複数の画像を並べて表示する【道具としての OpenCV 画像認識】

# 本記事でできること
![Screenshot from 2024-03-10 11-54-20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37698/f800bcfd-c17d-33ac-c5d8-3ee7cfe5afb5.png)


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

## 複数の画像を並べて表示する
画像処理プログラムを制作するとき、フィルターやパラメータが異なる処理を施した画像と並べて表示して比較したい場合があります。本記事では複数の画像を並べてひとつの画像にして表示するプログラムを掲載します。

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

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

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

## プログラム抜粋
```c++
cv::Mat concat_images(const std::vector<std::vector<cv::Mat>> images) {
    cv::Mat concated_image;
    std::vector<cv::Mat> vertical_images;

    for (int i = 1; i < (int)images.size(); i++) {
        int first_row_index = 0;
        int number_of_elements_in_first_row = (int)images.at(first_row_index).size();
        int number_of_elements_in_target_row = (int)images.at(i).size();
        if (number_of_elements_in_first_row != number_of_elements_in_target_row) {
            std::cerr << "画像結合の縦横枚数が一致しません。" << std::endl;
            std::exit(EXIT_FAILURE);
        }
    }

    for (int i = 0; i < (int)images.size(); i++) {
        std::vector<cv::Mat> horizontal_images;
        cv::Mat horizontal_concated_image;
        for (int j = 0; j < (int)images.at(i).size(); j++) {
            cv::Mat resized_image;
            cv::resize(images[i][j], resized_image, images[0][0].size(), 0.0, 0.0, cv::INTER_NEAREST);
            horizontal_images.push_back(convert_colorimage(resized_image));
        }
        cv::hconcat(horizontal_images, horizontal_concated_image);
        vertical_images.push_back(horizontal_concated_image);
    }
    cv::vconcat(vertical_images, concated_image);
    return concated_image;
}
```

## 全体プログラム
```c++
#include <iostream>
#include <opencv2/opencv.hpp>
#include <vector>

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_colorimage(const cv::Mat& image) {
    const int colorimage_channel_num = 3;
    if (image.channels() == colorimage_channel_num) {
        return image.clone();
    }
    cv::Mat color_image;
    cvtColor(image, color_image, cv::COLOR_GRAY2BGR);
    return color_image;
}

cv::Mat concat_images(const std::vector<std::vector<cv::Mat>> images) {
    cv::Mat concated_image;
    std::vector<cv::Mat> vertical_images;

    for (int i = 1; i < (int)images.size(); i++) {
        int first_row_index = 0;
        int number_of_elements_in_first_row = (int)images.at(first_row_index).size();
        int number_of_elements_in_target_row = (int)images.at(i).size();
        if (number_of_elements_in_first_row != number_of_elements_in_target_row) {
            std::cerr << "画像結合の縦横枚数が一致しません。" << std::endl;
            std::exit(EXIT_FAILURE);
        }
    }

    for (int i = 0; i < (int)images.size(); i++) {
        std::vector<cv::Mat> horizontal_images;
        cv::Mat horizontal_concated_image;
        for (int j = 0; j < (int)images.at(i).size(); j++) {
            cv::Mat resized_image;
            cv::resize(images[i][j], resized_image, images[0][0].size(), 0.0, 0.0, cv::INTER_NEAREST);
            horizontal_images.push_back(convert_colorimage(resized_image));
        }
        cv::hconcat(horizontal_images, horizontal_concated_image);
        vertical_images.push_back(horizontal_concated_image);
    }
    cv::vconcat(vertical_images, concated_image);
    return concated_image;
}

int main(void) {
    std::string file_path_1 = "../data/fuji.jpg";
    const cv::Mat loaded_image_1 = load_image(file_path_1);
    std::string file_path_2 = "../data/concat_2.png";
    const cv::Mat loaded_image_2 = load_image(file_path_2);
    std::string file_path_3 = "../data/concat_3.png";
    const cv::Mat loaded_image_3 = load_image(file_path_3);
    std::string file_path_4 = "../data/concat_4.png";
    const cv::Mat loaded_image_4 = load_image(file_path_4);

    const std::vector<std::vector<cv::Mat>> images = {
            {loaded_image_1, loaded_image_2}, {loaded_image_3, loaded_image_4}};
    const cv::Mat concated_image = concat_images(images);

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


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