ドットノイズ・ごましおノイズを除去する【道具としての OpenCV 画像認識】

# 本記事でできること
元画像
![binary_sample.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37698/cc2c2bfb-d52e-9123-6a6e-a04c1602e28e.jpeg)


ノイズ（＝小さな領域）を除去した画像
![Screenshot from 2024-03-10 09-47-15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37698/c574a7be-5d0e-55b6-0f6a-825f1ff4449e.png)


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

## ドットノイズ・ごましおノイズを除去する
画像認識をしている際に、画像内にノイズが出てしまうことがあります。ここでのノイズは、みたいもの＝対象物体以外の部分が、色だったり明るさだったりで画像処理フィルターで抽出されてしまっている状態です。細かなノイズが出ないように画像処理フィルターを調整してしまうと非常に固定化された状況でしか動かないものになってしまいます。ですので、細かなノイズは粒の領域面積（ピクセル値）で判定して消してしまうのが便利です。本記事ではノイズをさくっと消すためのプログラムを掲載します。

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

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

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

## プログラム抜粋
```c++
/**
 * ノイズとなり得る少領域を除去する
 * image 対象画像
 * noise_min_area 少領域の判定基準となる大きさ
 */
cv::Mat remove_dot_noise(const cv::Mat& image, const int noise_min_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 (param[cv::ConnectedComponentsTypes::CC_STAT_AREA] > noise_min_area) {
            blackorwhite[i] = 255;
        } else {
            blackorwhite[i] = 0;
        }
    }

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

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

/**
 * ノイズとなり得る少領域を除去する
 * image 対象画像
 * noise_min_area 少領域の判定基準となる大きさ
 */
cv::Mat remove_dot_noise(const cv::Mat& image, const int noise_min_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 (param[cv::ConnectedComponentsTypes::CC_STAT_AREA] > noise_min_area) {
            blackorwhite[i] = 255;
        } else {
            blackorwhite[i] = 0;
        }
    }

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

    return dot_noise_removed_image;
}

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

    const int noise_min_area = 400;
    const cv::Mat area_removed_image = remove_dot_noise(loaded_image, noise_min_area);

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

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