画像にモルフォロジー処理をかける【道具としての OpenCV 画像認識】

# 本記事でできること
元画像
![binary_sample.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37698/ff387c0a-5fd6-3d7b-2d79-2e19b1b4ccec.jpeg)


モルフォロジー（オープン）した画像
![Screenshot from 2024-03-06 22-22-35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37698/89d9d55e-b14d-7dce-adff-a4e823a8b403.png)


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

## 画像にモルフォロジー処理をかける
画像内から色や明るさで対象物体の形状を抽出したあと、輪郭部分のマスクを作成したい場合や、ノイズを消し込みたい場合、欠けを埋めたい場合など、膨張と縮小を複数回繰り返したくなることがあります。モルフォロジー処理では、いくつかの規定のやりかたがありますので、本記事では、以下の5つの手法についてのプログラムを掲載します。

1. cv::MORPH_OPEN
    オープニング：収縮したあとに膨張させます。ごま塩ノイズのような細かなノイズの削除に役立ちます
1. cv::MORPH_CLOSE
    クロージング：膨張させたあとに収縮させます。白領域の中の黒い欠けを埋めるのに役立ちます
1. cv::MORPH_GRADIENT
    モルフォロジー勾配：膨張させた画像と収縮させた画像の差分を出します。境界領域のマスク画像として使えます
1. cv::MORPH_TOPHAT
    元画像と、オープニングさせた画像の差分を出します。どこが白くなったのかわかります
1. cv::MORPH_BLACKHAT
    元画像と、クロージングさせた画像の差分を出します。どこが黒くなったのかわかります

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

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

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

## プログラム抜粋
```c++
cv::Mat morphology(const cv::Mat& image, const int morphology_mode, const int number_of_times) {
    cv::Mat result_image;
    int op = -1;
    if (morphology_mode == 0) {
        op = cv::MORPH_OPEN;
    } else if (morphology_mode == 1) {
        op = cv::MORPH_CLOSE;
    } else if (morphology_mode == 2) {
        op = cv::MORPH_GRADIENT;
    } else if (morphology_mode == 3) {
        op = cv::MORPH_TOPHAT;
    } else if (morphology_mode == 4) {
        op = cv::MORPH_BLACKHAT;
    }

    cv::morphologyEx(
            image,
            result_image,
            op,  // モルフォロジー演算の種類
            cv::Mat(),
            cv::Point(-1, -1),
            number_of_times,  // 収縮と膨張が適用される回数．
            cv::BORDER_CONSTANT,
            cv::morphologyDefaultBorderValue());

    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 morphology(const cv::Mat& image, const int morphology_mode, const int number_of_times) {
    cv::Mat result_image;
    int op = -1;
    if (morphology_mode == 0) {
        op = cv::MORPH_OPEN;
    } else if (morphology_mode == 1) {
        op = cv::MORPH_CLOSE;
    } else if (morphology_mode == 2) {
        op = cv::MORPH_GRADIENT;
    } else if (morphology_mode == 3) {
        op = cv::MORPH_TOPHAT;
    } else if (morphology_mode == 4) {
        op = cv::MORPH_BLACKHAT;
    }

    cv::morphologyEx(
            image,
            result_image,
            op,  // モルフォロジー演算の種類
            cv::Mat(),
            cv::Point(-1, -1),
            number_of_times,  // 収縮と膨張が適用される回数．
            cv::BORDER_CONSTANT,
            cv::morphologyDefaultBorderValue());

    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 times = 10;
    const cv::Mat morphology_image = morphology(loaded_image, cv::MORPH_OPEN, times);

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

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