円形のマスク画像を生成する【道具としての OpenCV 画像認識】

# 本記事でできること
元画像
![Screenshot from 2024-03-10 14-00-54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37698/872de88c-711f-68e9-9b75-ebbea8ddd19a.png)


マスク画像
![Screenshot from 2024-03-10 14-01-03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37698/7f3a6b9d-3fe6-5fba-7234-bfdf89f149bd.png)


マスクをかけたあとの画像
![Screenshot from 2024-03-10 14-01-15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37698/08aed931-671a-84dc-d496-14ab386b3f16.png)


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

## 円形のマスク画像を生成する
カメラ画像に基づくオブジェクトトラッキング（物体追跡）を実装することがあります。たとえば、空間内を自由移動する浮遊物体の追跡や、ベルトコンベアで移動する恥対象物体の追跡などです。このとき、似たような追跡対象物体が画像内に複数あった場合、「目移り（ワープ）」が発生してしまって、正しくトラッキングができなくなるケースがあります。

この対処として、前フレームでの対象物体の画像内位置を中心に一定距離の探索範囲を設定して、それより遠くの場所にある物体は追跡しない、と設定することで、目移りをなくそうという実装があります。この一定エリアのみ注視する、という手法をROI（Region of Interest）を設定する、といいます。

本記事では、円形のROIを設定してマスク画像を作成し、ROI以外の場所を黒くして情報を消してしまうプログラムを掲載します。

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

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

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

## プログラム抜粋
```c++
cv::Mat get_mask_image_with_circle_roi(const cv::Size image_size, const cv::Point roi_center, const int roi_radius) {
    cv::Mat mask_image = cv::Mat::zeros(image_size.height, image_size.width, CV_8UC3);
    cv::circle(mask_image, roi_center, roi_radius, cv::Scalar(255, 255, 255), -1, CV_MSA);
    return mask_image;
}
```

## 全体プログラム
```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 get_mask_image_with_circle_roi(const cv::Size image_size, const cv::Point roi_center, const int roi_radius) {
    cv::Mat mask_image = cv::Mat::zeros(image_size.height, image_size.width, CV_8UC3);
    cv::circle(mask_image, roi_center, roi_radius, cv::Scalar(255, 255, 255), -1, CV_MSA);
    return mask_image;
}

cv::Mat set_mask_on_image(const cv::Mat& target_image, const cv::Mat& mask_image) {
    cv::Mat masked_image;
    if (target_image.channels() == 1) {
        cv::Mat mask_image_gray = convert_grayscale(mask_image);
        target_image.copyTo(masked_image, mask_image_gray);
    } else {
        target_image.copyTo(masked_image, mask_image);
    }
    return masked_image;
}

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

    const cv::Point mask_point(300, 180);
    const int mask_roi_radius = 100;
    const cv::Mat mask_image = get_mask_image_with_circle_roi(loaded_image.size(), mask_point, mask_roi_radius);
    const cv::Mat masked_image = set_mask_on_image(loaded_image, mask_image);

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

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