はじめに
OpenCVで画像から物体を検出する際、以下の作業を行うことがあります。
- HSV空間に変換してHueの要素から色を検出する
- あるしきい値を基準にバイナリ画像へ変換する
それらのしきい値を変えてはソフトウェアを再実行するのが手間に感じたのでインタラクティブなツールを作ってみようとしました。
以下のようなものが完成イメージです。
左側が元画像、真ん中がHSVに変換された画像、右側がHSVのHueをもとにマスクした画像です。
緑のKを認識したいときは、Hueの値は80~111の間になることがわかりました。
環境
以下の環境で動作を確認しています。
項目 | バージョン |
---|---|
Ubuntu | 22.04 |
Qt/Qt creator | Qt Creator 6.0.2 based on Qt 5.15.3 |
QtのUIを作成することができるQtCreatorは以下のコマンドでインストールできます。
sudo apt install qtcreator
QtCreatorの使い方
プロジェクトの作成
QtCreatorを使うのは初めてだったので簡単に使い方も説明していきます。
以下のコマンドで起動します。
qtcreator
左側の「プロジェクト」を選択し、ディレクトリ名等を入力して作成するプロジェクトの設定を行っていきます。
今回、ビルドタイプはCMakeを選択しました。
プロジェクトの設定が完了すると以下のような画面になります。
デフォルトでソースファイル、ヘッダファイル、ui用のファイルが生成されます。
生成されたファイル一覧
CMakeLists.txt
CMakeLists.txt.user
main.cpp
mainwindow.cpp
mainwindow.h
mainwindow.ui
Qtの開発(少なくとも今回は)では主に、 CMakeLists.txt
, mainwindow.cpp
. mainwindow.h
, mainwindow.ui
を編集します。
また、mainwindow.ui
はXML形式のファイルですが、専用のUI作成のツールから作成するので直接XML形式のファイルを編集することはほぼありません。
UIの作成
UIを編集していきます。
上記の画面の状態で左側ファイル一覧のmainwindow.ui
をダブルクリックするとUIの編集画面が立ち上がります。
この画面の左側から好きな要素をドラッグアンドドロップすることでUIの要素を追加できます。
また、スライドバーのような、ユーザーのインプットがある要素は設定するとコールバック関数のひな型を自動で生成してくれます。
その要素を選択した状態で右クリック=>「スロットへ移動」=>シグナルを選択で、選択された場合や、値が変わった場合などのタイミングで実行される関数のひな型を作成することができます。
slider_min
という名前のスライドバーでvalueChanged
を選択した場合は以下のようにヘッダファイルとソースファイルに追記されます。便利ですね。
private slots:
void on_slider_min_valueChanged(int value);
void MainWindow::on_slider_min_valueChanged(int value)
{
}
最終的にUIは以下のようにしました。
ソフトウェアの実行は右下の緑色の再生ボタンから実行できます。
プロジェクトへOpenCVの導入
OpenCVを使いたいので、CMakeLists.txtを編集しましょう。
cmake_minimum_required(VERSION 3.5)
project(interactive_image_processor VERSION 0.1 LANGUAGES CXX)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets REQUIRED)
# 追加
find_package(OpenCV REQUIRED)
# 追加
include_directories(
${OpenCV_INCLUDE_DIRS}
)
set(PROJECT_SOURCES
main.cpp
mainwindow.cpp
mainwindow.h
mainwindow.ui
)
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
qt_add_executable(interactive_image_processor
MANUAL_FINALIZATION
${PROJECT_SOURCES}
)
# Define target properties for Android with Qt 6 as:
# set_property(TARGET interactive_image_processor APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
# ${CMAKE_CURRENT_SOURCE_DIR}/android)
# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
else()
if(ANDROID)
add_library(interactive_image_processor SHARED
${PROJECT_SOURCES}
)
# Define properties for Android with Qt 5 after find_package() calls as:
# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
else()
add_executable(interactive_image_processor
${PROJECT_SOURCES}
)
endif()
endif()
# ${OpenCV_LIBRARIES}を追加
target_link_libraries(interactive_image_processor PRIVATE Qt${QT_VERSION_MAJOR}::Widgets ${OpenCV_LIBRARIES})
set_target_properties(interactive_image_processor PROPERTIES
MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
MACOSX_BUNDLE TRUE
WIN32_EXECUTABLE TRUE
)
if(QT_VERSION_MAJOR EQUAL 6)
qt_finalize_executable(interactive_image_processor)
endif()
ヘッダファイルにも以下を追加しておきます。
#include <opencv2/opencv.hpp>
ソースコード
最終的に、mainwindow.cpp
は以下のようになりました。
#include "mainwindow.h"
#include "./ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
original_image_ = cv::imread("PATH_TO_FILE");
int height = ui->image_label_1->height();
int big_height = original_image_.cols > original_image_.rows ? original_image_.cols : original_image_.rows;
double ratio = ((double)height / (double)big_height);
cv::resize(original_image_, original_image_, cv::Size(), ratio, ratio, cv::INTER_NEAREST);
ui->image_label_1->setPixmap(QPixmap::fromImage(QImage((unsigned char*) original_image_.data, original_image_.cols, original_image_.rows, original_image_.step, QImage::Format_BGR888)));
cv::cvtColor(original_image_, mat_, cv::COLOR_BGR2HSV_FULL);
ui->image_label_2->setPixmap(QPixmap::fromImage(QImage((unsigned char*) mat_.data, mat_.cols, mat_.rows, mat_.step, QImage::Format_RGB888)));
min_hue_ = 0;
max_hue_ = 255;
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_slider_min_valueChanged(int value)
{
min_hue_ = value;
mask_by_hue(mat_, mask_, min_hue_, max_hue_);
ui->image_label_3->setPixmap(QPixmap::fromImage(QImage((unsigned char*) mask_.data, mask_.cols, mask_.rows, mask_.step, QImage::Format_Indexed8)));
ui->label_hue_min_value->setText(QString::number(value));
}
void MainWindow::on_slider_max_valueChanged(int value)
{
max_hue_ = value;
mask_by_hue(mat_, mask_, min_hue_, max_hue_);
ui->image_label_3->setPixmap(QPixmap::fromImage(QImage((unsigned char*) mask_.data, mask_.cols, mask_.rows, mask_.step, QImage::Format_Indexed8)));
ui->label_hue_max_value->setText(QString::number(value));
}
void MainWindow::mask_by_hue(cv::Mat& hsv_img, cv::Mat& mask, int min_hue, int max_hue) {
cv::inRange(hsv_img, cv::Scalar(min_hue, 50, 0), cv::Scalar(max_hue, 255, 255), mask);
}
ヘッダファイルは以下です。
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <opencv2/opencv.hpp>
#include <QPixmap>
#include <QString>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_slider_min_valueChanged(int value);
void on_slider_max_valueChanged(int value);
private:
Ui::MainWindow *ui;
cv::Mat original_image_, mat_, mask_;
int min_hue_, max_hue_;
void mask_by_hue(cv::Mat& hsv_img, cv::Mat& mask, int min_hue, int max_hue);
};
#endif // MAINWINDOW_H
それぞれのスライドバーのコールバック関数内でマスク処理を呼び出して画像とその時のHueの値を更新しています。
void MainWindow::on_slider_min_valueChanged(int value)
{
min_hue_ = value;
mask_by_hue(mat_, mask_, min_hue_, max_hue_);
ui->image_label_3->setPixmap(QPixmap::fromImage(QImage((unsigned char*) mask_.data, mask_.cols, mask_.rows, mask_.step, QImage::Format_Indexed8)));
ui->label_hue_min_value->setText(QString::number(value));
}
void MainWindow::on_slider_max_valueChanged(int value)
{
max_hue_ = value;
mask_by_hue(mat_, mask_, min_hue_, max_hue_);
ui->image_label_3->setPixmap(QPixmap::fromImage(QImage((unsigned char*) mask_.data, mask_.cols, mask_.rows, mask_.step, QImage::Format_Indexed8)));
ui->label_hue_max_value->setText(QString::number(value));
}
おわりに
今回は画像処理のための簡単なツールを作ってみました。自分の手間をなくす用にとりあえず作ってみたものですが、今後もっと拡張していきたいです。
今のところ以下を検討しています。
- 画像/動画ファイルの読み込み
- 他の画像処理方法の追加
参考
OpenCV×Qtについて
QtCreatorの使い方について