読者です 読者をやめる 読者になる 読者になる

アールテクニカ地下ガレージ

アールテクニカ株式会社の製品開発・研究開発・日々の活動です

Qtによるマルチプラットフォーム開発【C++画像処理編】

Author

中村@アールテクニカ

アールテクニカの中村です。
Qtによるマルチプラットフォーム開発の3回目です。
前回はQMLによるGUIアプリの新規作成まで行ないました。
今回はそのプロジェクトを元にしてC++によるネイティブコードの威力を活かすべく簡単な画像処理アプリを作成してみましょう。

QMLとC++との連携方法

前回はGUIをQMLで記述したので、QMLとC++を連携させる必要があります。何をどう連携させるかによっていくつか方法がありますが、今回作成する画像処理アプリに必要な以下の2つの方法を解説します。

  • QMLからC++のクラスを参照する方法
  • QMLのImageエレメントの画像データをC++で作成する方法

QMLからC++のクラスを参照する方法

QMLから参照可能なクラスを作成するには、QObjectを継承する必要があります。
また、QMLから呼び出すメソッドには、Q_INVOKABLEというマクロをつける必要があります。

以下に、簡単なサンプルを提示します。

QObject派生クラス

MyClassは、引数countの数だけ'*'を返すgetText()というメソッドを持っています。

gist.github.com

gist.github.com

QMLとMyClassオブジェクトのバインディング

次に、MyClassのオブジェクトをQMLから参照できるようにバインディングします。
main.cppの以下の箇所でMyClassをバインディングします。
QMLからは、setContextPropertyの第一引数で指定した、"myclass"でMyClassオブジェクトを参照出来ます。

QQmlApplicationEngine engine;
MyClass myclass(NULL);
engine.rootContext()->setContextProperty("myclass", &myclass);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
QMLからMyClassのオブジェクトを参照する方法

ボタンが押されたら、myclassのgetText()メソッドに引数を渡して呼び出します。

main.qml

MainForm {
    id: mainform
    anchors.fill: parent
    button1.onClicked:messageDialog.show(qsTr(myclass.getText(5)))
  (略)
}

QMLのImageエレメントの画像データをC++で作成する方法

QMLのImageエレメントはHTMLのimgタグのようにsourceプロパティで画像のURLを指定しますが、このURLにはQQuickImageProviderの派生クラスをソースとするように指定も出来ます。

MyImageProviderクラスの作成

画像データが必要になった際にrequestImage()メソッドが呼ばれ、このメソッドが返したQImageオブジェクトがImageエレメントの画像データとして使用されます。

class MyImageProvider : public QQuickImageProvider
{
public:
    MyImageProvider() :QQuickImageProvider(QQuickImageProvider::Image){}

    QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize)
    {
        QImage image(requestedSize, QImage::Format::Format_RGBA8888);

        // ここでimageに対し処理を行う

        return image;
    }
};
QMLにImageProviderをセットする

以下のコードでQMLのGUIエディタでImageエレメントのsourceプロパティに"image://imageprovider/xxx"を設定します。
この設定によりMyImageProvider::requestImage()が返すQImageが画像データとして使用されます。xxxの部分はidでMyImageProviderを複数のImageエレメントのソースに設定した場合の識別用に使用します。

main.cppの以下の箇所に、engine.addImageProvider(…)でMyImageProviderを追加します。

QQmlApplicationEngine engine;
engine.addImageProvider(QLatin1String("imageprovider"), new MyImageProvider);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

画像フィルターアプリの作成

以上のQML - C++連携の仕組みを使用して画像フィルターアプリを作成してみます。
フィルター処理は、せっかくネイティブコードで実装するので比較的処理の重いブラー(ぼかし)フィルターとします。
主な仕様は以下の通りとします。

  • ブラーフィルター処理部分をC++で実装する
  • ブラー半径をテキストフィールドで入力する
  • 「フィルター実行」ボタンを押してフィルター処理を開始する
  • 画像はアプリ埋め込みの画像を使用する

Filterクラス

まず、QMLから参照可能かつQMLの画像ソースとなるようにQobjectとQQuickImageProviderを継承したFilterクラスを作成します。

setKernelRadius()メソッドでフィルターの半径をセットします。
requestImage()メソッドではソース画像を読み込みブラーフィルターをかけた画像を返します。
フィルター処理部分では、フィルター半径に応じてコンボリューション行列を作成します。
今回は行列に0か1のみの値を使用し、1が円形に並ぶ単純なブラー効果とします。

半径2(5x5行列)のコンボリューション行列
0 0 1 0 0
0 1 1 1 0
1 1 1 1 1
0 1 1 1 0
0 0 1 0 0

gist.github.com

gist.github.com

QML

次に、main.qmlとMainform.ui.qmlをそれぞれ以下の様に編集します。

gist.github.com

gist.github.com

上記QMLをGUIエディタで表示すると以下のようになります。
左側に元画像、右側にフィルター適用後の画像を表示します。
半径を入力し「フィルター実行」ボタンを押すと右側にフィルター処理された画像が表示されます。
f:id:artteknika_nakamura:20160513110434p:plain

main.cpp

次に、main.cppでFilterクラスのオブジェクトをnewし、QMLにバインディングします。
gist.github.com

画像データの準備

最後に、テスト用画像をプロジェクトに追加します。
今回は以下のサイトからダウンロードさせて頂いた画像を使用しています。
http://www.ess.ic.kanagawa-it.ac.jp/app_images_j.html
Parrots.bmpPNGに変換して、Parrots.pngとして使用しています。
画像サイズは256x256ピクセルです。

実行結果

ソースコードの編集が終わったら早速実行してみましょう。

Windows版の実行結果

起動直後

f:id:artteknika_nakamura:20160513113256p:plain:w400

フィルター半径=5で実行した結果

f:id:artteknika_nakamura:20160513113258p:plain:w400

フィルター半径=30で実行した結果

f:id:artteknika_nakamura:20160513113300p:plain:w400

Android版の実行結果

起動直後

f:id:artteknika_nakamura:20160513121000p:plain:h300

フィルター半径=5で実行した結果

f:id:artteknika_nakamura:20160513121004p:plain:h300

フィルター半径=30で実行した結果

f:id:artteknika_nakamura:20160513121009p:plain:h300

WindowsAndroidともにC++でコーディングしたフィルター処理化正常に動作しているのが確認できました。しかし、フィルター処理はさほど最適化していないためフィルター半径が大きくなるとかなり時間がかかってしまいます。
フィルター半径=30で処理時間を計測した結果が以下になります。

OS Windows 10 Android 4.4 (Nexus7 2013)
CPU Core i5 4670 3.4HHz Snapdragon 1.5GHz
処理時間 5.0秒 28.5秒

Androidではで30秒近くかかってしまうのでとても実用レベルとはいえません。実用レベルにするには、マルチスレッド化も含めた最適化が必須でしょう。

まとめ

Qtによるマルチプラットフォーム開発と題して3回にわたり記事を書いてきましたが、簡単なアプリを作成できましたのでここでいったん完結としたいと思います。
QMLを使えばそれほど苦労なくマルチプラットフォーム対応のGUIが作成できる反面、C++コーディングのメリットを活かすためにやはり最適化対応が必要になることがわかりました。
機会があれば、フィルター処理などの重い処理の最適化手法なども調べていきたいと思います。


Author

中村@アールテクニカ

アールテクニカのSE兼プログラマ。Web系のサーバサイド・フロントエンドからスマホ・PCのネイティブアプリまでソフトウェア開発全般が守備範囲。最近はハードウェア開発にも興味あり。

スポンサーリンク