凹みTips

C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

Qt Quick 2 で使える OpenCV 用の QML 要素を作ってみた

はじめに

前回(QML から OpenCV で取得したカメラ画を表示してみた - 凹みTips)は、QQmlImageProvider を用いたアプローチで C++ 側で作成したピクセルデータを QML 側で扱う方法を紹介しました。今回は、qmlRegisterType によって登録できる QML 要素から扱う方法を紹介します。やる内容は前回と同じく、OpenCV で取得したカメラ画を表示します

出来るようになること

import QtQuick 2.0
import OpenCV 1.0

Rectangle {
    width: 1280
    height: 720
    Camera {
        anchors.fill: parent
    }
}

これでカメラ画が出るようになります。

概要

QQuickPaintedItem を継承したクラスを作成し、qmlRegisterType で登録します。これは QML の要素の基となる QQuickItem を継承したクラスで、描画を virtual なメンバ関数の paint に渡ってくる QPainter を通じて行うことが出来ます。

virtual void paint(QPainter *painter) = 0;

さっそく作ってみましょう。

コード

最小限のコードは以下になります。

camera.h
#include <QQuickPaintedItem>

class CameraItem : public QQuickPaintedItem
{
    Q_OBJECT
public:
    CameraItem(QQuickItem *parent = 0);
    void paint(QPainter *painter);
    
private:
    cv::VideoCapture camera_;
};
camera.cpp
CameraItem::CameraItem(QQuickItem *parent)
    : QQuickPaintedItem(parent), camera_(0)
{
}

void CameraItem::paint(QPainter *painter)
{
    // get camera input
    cv::Mat input_img;
    camera_ >> input_img;

    // BGR -> ARGB
    cv::cvtColor(input_img, input_img, CV_BGR2BGRA);
    std::vector<cv::Mat> bgra;
    cv::split(input_img, bgra);
    std::swap(bgra[0], bgra[3]);
    std::swap(bgra[1], bgra[2]);
    
    // cv::Mat から QImage へ変換、リサイズしてから描画
    QImage output_img(input_img.data, input_img.cols, input_img.rows, QImage::Format_ARGB32);
    output_img = output_img.scaled(width(), height());
    painter->drawImage(width()/2  - output_img.size().width()/2,
                       height()/2 - output_img.size().height()/2,
                       output_img);
}
main.cpp
#include <QtGui/QGuiApplication>
#include "qtquick2applicationviewer.h"
#include "camera.h"

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);
    QtQuick2ApplicationViewer viewer;
    
    // 作成したクラスを登録
    qmlRegisterType<CameraItem>("OpenCV", 1, 0, "Camera");
    
    viewer.setMainQmlFile(QStringLiteral("qml/LegoAnalyzer/main.qml"));
    viewer.showExpanded();

    return app.exec();
}

これで冒頭の QML でカメラ画が描画されます。ただ、ウィンドウを拡大縮小して paint を再度呼んでもらわないとカメラ画が更新されません(静止画になってしまいます)。再描画を行うには update を呼べば良いのですが、C++ 側でスレッド切ってやるのは面倒なので前回と同じく QML 側から呼んでみます。

import QtQuick 2.0
import OpenCV 1.0

Rectangle {
    width: 1280
    height: 720
    Camera {
        id: camera
        property int frameRate: 30
        anchors.fill: parent
    }
    Timer {
        interval: 1000 / camera.frameRate
        running: true
        repeat: true
        onTriggered:  {
            camera.update();
        }
    }
}

これで動画として表示されます。

おわりに

前回の方法では、別スレッドで動作する利点や URI をパラメタとしてもらえる利点はあるものの、カメラ自体の設定を変更したいとすると URI をパースしたり、QQmlImageProvider を継承したクラスを操作するクラスを別に作ってそのインターフェースを QML 側に公開する、という方法を取らなければならず面倒です。今回の方法では直接公開するクラスにモリモリ肉付け出来るので楽です。用途に応じて使い分けるのが良いと思います。

参考