修正(2013/09/16)
※ タイトルが内容に即していないため変更しました(旧: Qt Quick 2 で使える OpenCV 用の QML 要素を作ってみた)。
内容に即した記事は次回で書いています。
はじめに
OpenCV 始めました。C++ インターフェース使うと C++11 と併せてサクサク処理できてとても楽しいです。ただ、単体ではどうしても弱い UI 周りをどうしようかなと思い、広く使われている openFrameworks を使おうかとも考えたのですが、もっと気軽に扱いたく思い Qt Quick 2 の QML から使えるようにする方法を調べてみました。
概要
QQmlImageProvider を利用します。
4.x 系の時は QtDeclarativeImageProvider と呼ばれていたもの(らしい)です。これは QML から非同期スレッドでビットマップを扱うことの出来るインターフェースを提供してくれます。
Image { source: "image:/(任意の ImageProvider 名)/(識別子 e.g. hoge.png)" }
つまり、QQmlImageProvider を拡張したクラスを作り、これを QML のエンジンに登録すれば、OpenCV に限らず OpenGL 等も含めて QML から任意のビットマップを扱うことが出来るようになるわけです。
詳細
まず、QQmlImageProvider を継承したクラスを作成します。継承後のクラスの宣言は以下のようになります。
#ifndef OPENCV_VIDEO_H #define OPENCV_VIDEO_H #include <QQuickImageProvider> #include <QPixmap> #include <opencv2/opencv.hpp> class VideoImageProvider : public QQuickImageProvider { public: VideoImageProvider(); ~VideoImageProvider(); QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize); private: CvCapture* capture_; }; #endif // OPENCV_VIDEO_H
virtual なメンバ関数 requestPixmap はビットマップの要求時に呼ばれます。引数の id は先述した識別子、requestedSize は QML の Image 要素にて指定されたサイズ(だと思うのですが常に -1 がやってきて不明です)、size は元の画像サイズ(Image 要素でサイズ指定がないときに使用)となっています。
これを元に実装してみます。
#include "video_image.h" VideoImageProvider::VideoImageProvider() : QQuickImageProvider(QQuickImageProvider::Pixmap) { capture_ = cvCaptureFromCAM(0); } VideoImageProvider::~VideoImageProvider() { cvReleaseCapture(&capture_); } QPixmap VideoImageProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) { // get camera input cv::Mat img = cvQueryFrame(capture_); // resize *size = QSize(img.cols, img.rows); int width = requestedSize.width() > 0 ? requestedSize.width() : img.rows; int height = requestedSize.height() > 0 ? requestedSize.height() : img.cols; cv::Mat resized_img(width, height, img.type()); cv::resize(img, resized_img, resized_img.size(), cv::INTER_CUBIC); // BGR -> ARGB cv::cvtColor(resized_img, resized_img, CV_BGR2BGRA); std::vector<cv::Mat> bgra; cv::split(resized_img, bgra); std::swap(bgra[0], bgra[3]); std::swap(bgra[1], bgra[2]); QImage video_img(resized_img.data, resized_img.cols, resized_img.rows, QImage::Format_ARGB32); return QPixmap::fromImage(video_img); }
OpenCV で得られた画像データ cv::Mat を BGR から ARGB へ変換(BGR から ARGB へ直接変換できないので BGRA 変換後に並び替えしてます)、QImage を通じて QPixmap にして返しています。特に id は使わず無視していますがカメラの番号をあてるとかすると良いと思います。
そしてこれを main.cpp から読み取ります。
#include <QtGui/QGuiApplication> #include <QQmlEngine> #include "qtquick2applicationviewer.h" #include "video_image.h" int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QtQuick2ApplicationViewer viewer; QQmlEngine* engine = viewer.engine(); engine->addImageProvider(QLatin1String("videoCapture"), new VideoImageProvider); viewer.setMainQmlFile(QStringLiteral("qml/OpenCV_QML_Integration/main.qml")); viewer.showExpanded(); return app.exec(); }
ここで QQuickView から QQmlEngine を取得していることに注意して下さい。公式のサンプルコードは誤りです*1。
そして、これを利用したコードを QML で書きます。
import QtQuick 2.0 Rectangle { width: image.width height: image.height Image { id: image source: "image://videoCapture/hoge" } }
最後にリンクの設定を .pro ファイルに追加します。
LIBS += -lopencv_core -lopencv_highgui -lopencv_imgproc
さきほど、videoCapture という名前で Image Provider を定義したので、その名前を通じてアクセスします。ID は使わないので適当に hoge とかつけています。実行すると以下のようになります。
無事表示されました!
動画として表示する
例えば以下のように Timer を回せば動画っぽい表示になります。
import QtQuick 2.0 Rectangle { width: image.width height: image.height Image { id: image source: "image://videoCapture/hoge" } Timer { property int cnt: 0 interval: 32 running: true repeat: true onTriggered: { if (image.status === Image.Ready) { image.source = "image://videoCapture/" + cnt; ++cnt; } } } }
更新頻度(Timer の interval)を上げ過ぎると、白フレームが挟まってしまいました。どこかで処理落ちしているようです。。
特徴量を重畳してみる
SURF で特徴量を載っけた画像を出すとかも出来ます。QML の利点である UI の作りやすさの例も含めて、下記を追記してみました。
// Gray-scale Image cv::Mat gray_img; cv::cvtColor(resized_img, gray_img, CV_BGR2GRAY); // SURF std::vector<cv::KeyPoint> keypoints; cv::SurfFeatureDetector detector(4500); cv::Scalar color(100, 255, 50); detector.detect(gray_img, keypoints); for (const auto& point : keypoints) { cv::circle(resized_img, point.pt, 1, color, -1); cv::circle(resized_img, point.pt, point.size, color, 1, CV_AA); }
import QtQuick 2.0 Rectangle { width: 640 height: 360 Image { id: image anchors.fill: parent source: "image://videoCapture/hoge" } Timer { property int cnt: 0 interval: 32 running: true repeat: true onTriggered: { if (image.status === Image.Ready) { image.source = "image://videoCapture/" + cnt; ++cnt; } } } Rectangle { width: parent.width height: parent.height/5 anchors.bottom: parent.bottom color: '#aa000000' Text { anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter font.pointSize: 24 color: '#fff' text: "<b>SURF</b> による特徴量検出" } } }
LIBS += -lopencv_core -lopencv_highgui -lopencv_imgproc -lopencv_features2d
参考
- 画像処理 — OpenCV-CookBook
- OpenCV のサンプル
- QtQuick 5.0: Porting QML Applications to Qt 5 | Documentation | Qt Project
- Qt 5 からは QDeclarative* 系が QQml* 系に変化しています。
- QDeclarativeImageProvider Class Reference | Documentation | Qt Project
- QDeclarativeImageProvider のサンプル(QQuickImageProvider へ変化)
- QtQuick 5.0: QQuickImageProvider Class | Documentation | Qt Project
- サンプルが抜けてます。
- 抜けバグを回避したサンプル: Qt Documentation Snapshots
- その他サンプル: [Solved] Wanted: QQuickImageProvider examples | Qt Project forums | Qt Project
*1:qmlRegisterType みたいに勝手にやってくれるのかと思っていて、結構悩みました。