特定の面積の領域を削除する・ノイズを除去する【道具としての OpenCV 画像認識】

# 本記事でできること
元画像
![binary_sample.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37698/6c0c03b6-db4e-1f10-0c8f-d7c3e83e8e2a.jpeg)


特定面積の領域以外を削除した画像（面積設定：30〜4000）
![Screenshot from 2024-03-10 09-40-35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37698/43eb8dc0-0db8-a901-aa70-b57604cff454.png)


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

## 特定の面積の領域を削除する・ノイズを除去する
ロボットをビジュアルフィードバック制御して、カメラ情報に基づいて自動化作業をさせようとするとき、対象物体の大きさが既知である場合には、画像上での対象物体の大きさから、対象物体か、そうでないかを判断するプログラムを制作することがあります。この場合、２値化された画像の中で、たとえば、想定した対象物体の大きさに対して、カメラに写っているものが大きすぎる場合に除去したり、細かなノイズのようなものを除去したり、「見たくないもの＝対象物体ではないもの」を除去することになります。本記事では、ピクセル数値表現での面積値を指定して領域を削除するプログラムを掲載します。

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

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

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

## プログラム抜粋
```c++
/**
 * 設定した面積値以外の領域を削除する
 * min_area 最小面積の指定値（ピクセル）
 * max_area 最大面積の指定値（ピクセル）
 */
cv::Mat remove_area(const cv::Mat& image, const int min_area, const int max_area) {
    cv::Mat image_gray = convert_grayscale(image);
    cv::Mat labeled_image;
    cv::Mat stats;
    cv::Mat centroids;
    int nLab = cv::connectedComponentsWithStats(image_gray, labeled_image, stats, centroids);

    std::vector<int> blackorwhite(nLab, 0);

    for (int i = 1; i < nLab; ++i) {
        int* param = stats.ptr<int>(i);
        if ((min_area < param[cv::ConnectedComponentsTypes::CC_STAT_AREA]) &&
            (param[cv::ConnectedComponentsTypes::CC_STAT_AREA] < max_area)) {
            blackorwhite[i] = 255;
        } else {
            blackorwhite[i] = 0;
        }
    }

    cv::Mat area_removed_image = image_gray.clone();
    for (int i = 0; i < area_removed_image.rows; i++) {
        for (int j = 0; j < area_removed_image.cols; j++) {
            for (int n = 1; n < nLab; n++) {
                area_removed_image.at<uchar>(i, j) = (uchar)blackorwhite[labeled_image.at<int>(i, j)];
            }
        }
    }

    return area_removed_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;
}

/**
 * 設定した面積値以外の領域を削除する
 * min_area 最小面積の指定値（ピクセル）
 * max_area 最大面積の指定値（ピクセル）
 */
cv::Mat remove_area(const cv::Mat& image, const int min_area, const int max_area) {
    cv::Mat image_gray = convert_grayscale(image);
    cv::Mat labeled_image;
    cv::Mat stats;
    cv::Mat centroids;
    int nLab = cv::connectedComponentsWithStats(image_gray, labeled_image, stats, centroids);

    std::vector<int> blackorwhite(nLab, 0);

    for (int i = 1; i < nLab; ++i) {
        int* param = stats.ptr<int>(i);
        if ((min_area < param[cv::ConnectedComponentsTypes::CC_STAT_AREA]) &&
            (param[cv::ConnectedComponentsTypes::CC_STAT_AREA] < max_area)) {
            blackorwhite[i] = 255;
        } else {
            blackorwhite[i] = 0;
        }
    }

    cv::Mat area_removed_image = image_gray.clone();
    for (int i = 0; i < area_removed_image.rows; i++) {
        for (int j = 0; j < area_removed_image.cols; j++) {
            for (int n = 1; n < nLab; n++) {
                area_removed_image.at<uchar>(i, j) = (uchar)blackorwhite[labeled_image.at<int>(i, j)];
            }
        }
    }

    return area_removed_image;
}

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

    const int min_area = 30;
    const int max_area = 4000;
    const cv::Mat area_removed_image = remove_area(loaded_image, min_area, max_area);

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

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