画像が同じ画像か比較する【道具としての OpenCV 画像認識】

# 本記事でできること
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37698/358d28d9-3052-8c5b-ee3e-89f9254da657.png)


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

## 画像が同じ画像か比較する
画像認識プログラムを制作していると、処理している画像が同じものかどうかを判定したい、という場合があります。たとえば、画像処理した結果が、目標となっている画像と一致しているかを確認する際や、同じ画像であれば処理をスキップして処理時間を削減したい場合などです。本記事では、２枚の画像を比較して、同じものかどうか（＝完全に同じものかどうか）を判定するプログラムを掲載します。

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

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

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

## プログラム抜粋
```c++
/* 画像を比較して一致しているかを判定します */
bool is_same_image(const cv::Mat& image1, const cv::Mat& image2) {
    if (image1.empty() || image2.empty()) {
        std::cout << "画像が読み込まれていません。" << std::endl;
        return false;
    }
    if (image1.cols != image2.cols || image1.rows != image2.rows || image1.dims != image2.dims ||
        image1.channels() != image2.channels()) {
        std::cout << "画像サイズが一致しません。" << std::endl;
        std::cout << "cols : " << image1.cols << ", " << image2.cols << std::endl;
        std::cout << "rows : " << image1.rows << ", " << image2.rows << std::endl;
        std::cout << "dims : " << image1.dims << ", " << image2.dims << std::endl;
        std::cout << "channels : " << image1.channels() << ", " << image2.channels() << std::endl;
        return false;
    }
    cv::Mat diff_image;
    cv::Mat gray_image1 = convert_grayscale(image1);
    cv::Mat gray_image2 = convert_grayscale(image2);
    cv::compare(gray_image1, gray_image2, diff_image, cv::CMP_NE);
    int pixel_non_zero = cv::countNonZero(diff_image);
    bool is_non_zero = pixel_non_zero == 0;
    if (!is_non_zero) {
        std::cout << "画像が一致しません。" << std::endl;
        std::cout << "pixel non zero : " << pixel_non_zero << std::endl;
        return false;
    }
    return true;
}
```

## 全体プログラム
```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;
}

/* 画像を比較して一致しているかを判定します */
bool is_same_image(const cv::Mat& image1, const cv::Mat& image2) {
    if (image1.empty() || image2.empty()) {
        std::cout << "画像が読み込まれていません。" << std::endl;
        return false;
    }
    if (image1.cols != image2.cols || image1.rows != image2.rows || image1.dims != image2.dims ||
        image1.channels() != image2.channels()) {
        std::cout << "画像サイズが一致しません。" << std::endl;
        std::cout << "cols : " << image1.cols << ", " << image2.cols << std::endl;
        std::cout << "rows : " << image1.rows << ", " << image2.rows << std::endl;
        std::cout << "dims : " << image1.dims << ", " << image2.dims << std::endl;
        std::cout << "channels : " << image1.channels() << ", " << image2.channels() << std::endl;
        return false;
    }
    cv::Mat diff_image;
    cv::Mat gray_image1 = convert_grayscale(image1);
    cv::Mat gray_image2 = convert_grayscale(image2);
    cv::compare(gray_image1, gray_image2, diff_image, cv::CMP_NE);
    int pixel_non_zero = cv::countNonZero(diff_image);
    bool is_non_zero = pixel_non_zero == 0;
    if (!is_non_zero) {
        std::cout << "画像が一致しません。" << std::endl;
        std::cout << "pixel non zero : " << pixel_non_zero << std::endl;
        return false;
    }
    return true;
}

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

    const bool is_loaded_image_same_image = is_same_image(loaded_image, loaded_image);
    std::cout << "Is same image : " << std::boolalpha << is_loaded_image_same_image << std::endl;
}
```

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