画像に文字を足す【道具としての OpenCV 画像認識】

# 本記事でできること
![Screenshot from 2024-03-10 12-07-09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37698/9a7675cd-8c2f-7631-03c5-d51cc7db3105.png)


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

## 画像に文字を足す
画像認識のプログラムを制作するとき、パラメータを変えてフィルターをかけた画像を並べて比較するとき、パラメータがいくつだったかを画像に足して表示したいという場合があります。本記事では、画像の下部に文字・テキストを足すプログラムを掲載します。

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

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

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

## プログラム抜粋
```c++
cv::Mat set_reference_text(const cv::Mat& image, const std::string message) {
    cv::Mat image_with_message;
    int font_style = cv::FONT_HERSHEY_SIMPLEX;
    double font_scale = 1.0;
    int bar_color = 255;
    int font_thickness = 1;
    int bar_height = 60;

    cv::Mat message_bar = cv::Mat::ones(bar_height, image.cols, CV_8UC1) * bar_color;
    cv::Size text_size = cv::getTextSize(message, font_style, font_scale, font_thickness, 0);
    cv::Point message_start_point((int)(message_bar.cols - text_size.width) / 2, (int)(message_bar.rows * 0.6));
    cv::putText(message_bar, message, message_start_point, font_style, font_scale, cv::Scalar(0, 0, 0), font_thickness);

    std::vector<cv::Mat> concat_images{convert_colorimage(image), convert_colorimage(message_bar)};
    cv::vconcat(concat_images, image_with_message);
    return image_with_message;
}
```

## 全体プログラム
```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_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 set_reference_text(const cv::Mat& image, const std::string message) {
    cv::Mat image_with_message;
    int font_style = cv::FONT_HERSHEY_SIMPLEX;
    double font_scale = 1.0;
    int bar_color = 255;
    int font_thickness = 1;
    int bar_height = 60;

    cv::Mat message_bar = cv::Mat::ones(bar_height, image.cols, CV_8UC1) * bar_color;
    cv::Size text_size = cv::getTextSize(message, font_style, font_scale, font_thickness, 0);
    cv::Point message_start_point((int)(message_bar.cols - text_size.width) / 2, (int)(message_bar.rows * 0.6));
    cv::putText(message_bar, message, message_start_point, font_style, font_scale, cv::Scalar(0, 0, 0), font_thickness);

    std::vector<cv::Mat> concat_images{convert_colorimage(image), convert_colorimage(message_bar)};
    cv::vconcat(concat_images, image_with_message);
    return image_with_message;
}

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

    const std::string message = "FUJISAN";
    const cv::Mat image_with_text = set_reference_text(loaded_image, message);

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

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