画像の重心を計算して対象ワークを認識する【道具としての OpenCV 画像認識】

# 本記事でできること
元画像１
![moment_sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37698/84aee51a-9a56-a22e-6b4d-ddf240ab9ab7.png)


元画像１の重心を描画した画像
![Screenshot from 2024-03-10 16-37-29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37698/0cabed66-1d29-7d65-86a6-d48befc53d35.png)


元画像２
![moment_sample_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37698/371e81cd-d2f9-172b-a69f-cf1b3273b464.png)


元画像２の重心を描画した画像
![Screenshot from 2024-03-10 16-37-52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37698/b85ef5c2-4eab-3762-99e0-7c5448bdacde.png)



# 想定シーン
## ロボットをビジュアルフィードバック制御する
カメラを使ったロボットアームの制御方法を、ビジュアルフィードバック制御といいます。ここでは、ビジュアルフィードバック制御を使った自動化システムを開発する際に必要となる画像認識プログラムをまとめています。

## 画像の重心を計算して対象ワークを認識する
ロボットをビジュアルフィードバック制御で動かして、把持対象物体を吸着パッドなどで把持したり、チャックで中心線上をつかんだりする場合があります。このとき、認識した把持対象物体の重心位置を定める必要があります。本記事では画像内の物体に対して、画像重心を算出するプログラムを掲載します。
なお、重心といっても質量重心ではなく、画像内における重心位置なので、エンドエフェクタ（＝ロボットハンド）の把持力に対して物体が高重量である場合などについては注意が必要です。

# 実行環境
- OS：Ubuntu 22.04 LTS
- 言語：C++
- クルーボ：v5.1.0

## クルーボについて
ロボットアプリケーション開発には、株式会社チトセロボティクスのロボット制御ソフトウェア「クルーボ」を使用します。本記事のプログラムは、クルーボがインストールされた制御コンピュータ上で動作します。

- クルーボの製品サイト：https://chitose-robotics.com/

## プログラム抜粋
```c++
cv::Point2f get_moment_point(const cv::Mat& image) {
    cv::Mat image_gray = convert_grayscale(image);
    const cv::Moments image_moment = cv::moments(image_gray, false);
    const cv::Point2f moment_center = cv::Point2f(
            static_cast<float>(image_moment.m10 / image_moment.m00),
            static_cast<float>(image_moment.m01 / image_moment.m00));
    return moment_center;
}
```

## 全体プログラム
```c++
#include <iostream>
#include <opencv2/opencv.hpp>

cv::Mat load_image(const std::string file_path) {
    cv::Mat loaded_image = cv::imread(file_path, 1);
    if (loaded_image.empty()) {
        throw std::runtime_error("画像を正常に読み込めませんでした。");
    }
    return loaded_image;
}

cv::Mat convert_grayscale(const cv::Mat& image) {
    const int grayscale_channel_num = 1;
    if (image.channels() == grayscale_channel_num) {
        return image.clone();
    }
    cv::Mat gray_image;
    cvtColor(image, gray_image, cv::COLOR_BGR2GRAY);
    return gray_image;
}

cv::Mat convert_colorimage(const cv::Mat& image) {
    const int colorimage_channel_num = 3;
    if (image.channels() == colorimage_channel_num) {
        return image.clone();
    }
    cv::Mat color_image;
    cvtColor(image, color_image, cv::COLOR_GRAY2BGR);
    return color_image;
}

cv::Point2f get_moment_point(const cv::Mat& image) {
    cv::Mat image_gray = convert_grayscale(image);
    const cv::Moments image_moment = cv::moments(image_gray, false);
    const cv::Point2f moment_center = cv::Point2f(
            static_cast<float>(image_moment.m10 / image_moment.m00),
            static_cast<float>(image_moment.m01 / image_moment.m00));
    return moment_center;
}

cv::Mat draw_dot(const cv::Mat image, const cv::Point center, const float radius, const cv::Scalar color) {
    cv::Mat drawed_image = convert_colorimage(image);
    const int thickness = -1;
    cv::circle(drawed_image, center, static_cast<int>(radius), color, thickness, CV_MSA);
    return drawed_image;
}

int main(void) {
    std::string file_path = "../data/moment_sample.png";
    const cv::Mat loaded_image = load_image(file_path);

    const cv::Point2f moment_point = get_moment_point(loaded_image);
    const cv::Mat drawed_image = draw_dot(loaded_image, moment_point, 20, cv::Scalar(0, 0, 255));

    cv::imshow("window", drawed_image);
    cv::waitKey(0);
}
```

## おわりに
人手作業をロボットアームで自動化するために、カメラを使ったロボット制御＝ビジュアルフィードバック制御が大切です。
ロボット制御用の画像認識でも中身のひとつひとつはシンプルなので、要素に分解して解説していきたいと思います。