画像の白部分を膨張・縮小させる【道具としての OpenCV 画像認識】

# 本記事でできること
元画像
![Screenshot from 2024-03-06 21-20-31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37698/f2b9a787-d13e-d0f3-5ddf-6218949fa704.png)


膨張させた画像
![Screenshot from 2024-03-06 21-20-40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37698/c724e747-09e1-b92a-db23-e0888a2d7898.png)


縮小させた画像
![Screenshot from 2024-03-06 21-20-49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37698/06e6addd-53dd-5a51-8f44-16244edd9761.png)

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

## 画像の白部分を膨張・縮小させる
２値化処理や、画像の色抽出などをしたあとに、「欠け」が見つかる場合や、影などによって明るさが低下して２値化で抽出できなかった場合などに、白い部分を膨張させたいシーンがあります。それとは反対に、照明が原因で対象物体の周囲も明るく撮影されてしまった際などに、白い部分を縮小させたいというケースもあります。ここでは、画像の白部分を膨張・縮小させるプログラムを掲載します。
なお、膨張と縮小によって、欠けをなくしたり、黒い領域を埋めたりする方法は、モルフォロジー処理をかけるプログラムとして掲載しています。

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

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

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

## プログラム抜粋
```c++
/* 膨張処理をかける（白を増やす） */
cv::Mat dilate_image(const cv::Mat& image, const int number_of_times) {
    cv::Mat gray_image = convert_grayscale(image);
    cv::Mat result_image;
    cv::Mat element = cv::Mat::ones(3, 3, CV_8UC1);
    cv::dilate(gray_image, result_image, element, cv::Point(-1, -1), number_of_times);
    return result_image;
}

/* 収縮処理をかける（白を減らす） */
cv::Mat erode_image(const cv::Mat& image, const int number_of_times) {
    cv::Mat gray_image = convert_grayscale(image);
    cv::Mat result_image;
    cv::Mat element = cv::Mat::ones(3, 3, CV_8UC1);
    cv::erode(gray_image, result_image, element, cv::Point(-1, -1), number_of_times);
    return result_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 dilate_image(const cv::Mat& image, const int number_of_times) {
    cv::Mat gray_image = convert_grayscale(image);
    cv::Mat result_image;
    cv::Mat element = cv::Mat::ones(3, 3, CV_8UC1);
    cv::dilate(gray_image, result_image, element, cv::Point(-1, -1), number_of_times);
    return result_image;
}

/* 収縮処理をかける（白を減らす） */
cv::Mat erode_image(const cv::Mat& image, const int number_of_times) {
    cv::Mat gray_image = convert_grayscale(image);
    cv::Mat result_image;
    cv::Mat element = cv::Mat::ones(3, 3, CV_8UC1);
    cv::erode(gray_image, result_image, element, cv::Point(-1, -1), number_of_times);
    return result_image;
}

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

    const int dilate_times = 10;
    const cv::Mat dilated_image = dilate_image(loaded_image, dilate_times);
    const int erode_times = 10;
    const cv::Mat eroded_image = erode_image(loaded_image, erode_times);

    cv::imshow("loaded_image", loaded_image);
    cv::waitKey(0);
    cv::imshow("dilated_image", dilated_image);
    cv::waitKey(0);
    cv::imshow("eroded_image", eroded_image);
    cv::waitKey(0);
}
```

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