hantas's blog

ブログ移転しました→ http://blog.taniho.net/

ヤマネコでもわかるQtQuick(3)-QMLとC++を用いた描画枠の作成

要望があったので少し予定変更して進めます。 説明は後回しにして,コード例の紹介を優先します。

早速

新しく次のコードを準備します。

// main.cpp

#include <QApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include <QTextCodec>
#include <QQuickView>

#include "Drawing.h"

int main(int argc, char **argv){
    QApplication app(argc, argv);
    QTextCodec::setCodecForLocale(QTextCodec::codecForLocale());

    qmlRegisterType<Drawing>("DrawingArea", 1, 0, "Drawing");
    QQmlApplicationEngine engine("main.qml");

    return app.exec();
}
// main.qml

import QtQuick 2.0
import QtQuick.Controls 1.1
import DrawingArea 1.0

ApplicationWindow{
    id: applicationWindow1
    width: 800
    minimumWidth: 640
    height: 600
    minimumHeight: 480
    color: "white"
    title: qsTr("QtSample")
    visible: true

    Rectangle {
        y: 80
        width: 180; height: 150
        anchors.horizontalCenterOffset: 2
        anchors.horizontalCenter: parent.horizontalCenter
        color: "lime"
        radius: 5
        Label {
            x: 10; y: 10
            id: label
            font.pixelSize: 18
            text: qsTr("Label")
        }
        Button {
            id: button
            x:30; y:40
            width: 120
            text: "Button"
            onClicked:{
                drawingarea.setNum(combo1.currentIndex)
                drawingarea.setColor(combo2.currentText)
                drawingarea.update()
            }
        }
        ComboBox {
            id: combo1
            x:50; y:80
            width: 100
            model: ["A", "B", "C", "D", "E"]
        }
        ComboBox {
            id: combo2
            x:50; y:110
            width: 100
            model: ["red", "green", "blue", "yellow", "pink"]
        }
    }
    Rectangle {
        id: rect2
        y: 300
        width: 330; height: 250
        anchors.horizontalCenterOffset: 2
        anchors.horizontalCenter: parent.horizontalCenter
        color: "black"
        Drawing {
            id: drawingarea
            x: 5; y: 5
            width: 320; height: 240
        }
    }
}
// Drawing.h

#include <QQuickPaintedItem>
#include <QColor>
#include <QPen>
#include <QPainter>
#include <QRect>
#include <QFont>

class Drawing : public QQuickPaintedItem{
    Q_OBJECT
    Q_DISABLE_COPY(Drawing)

public:
    explicit Drawing(QQuickItem *parent = 0);

    Q_INVOKABLE void setNum(const int &num);

    Q_INVOKABLE void setColor(const QString &color);

    void paint(QPainter *painter);

private:
    QString m_num;
    QColor m_color;
};
// Drawing.cpp

#include "Drawing.h"

Drawing::Drawing(QQuickItem *parent) : QQuickPaintedItem(parent){
    m_color = QColor("red");
    m_num = "A";
}

void Drawing::setNum(const int &num){
    m_num = QString('A'+num);
}

void Drawing::setColor(const QString &color){
    m_color = QColor(color);
}

void Drawing::paint(QPainter *painter){
    QFont font = QFont();
    QPen pen(m_color, 3);
    QRect rect(0, 0, 100, 100);
    rect.moveTo(110, 70);
    font.setPointSize(30);
    painter->setFont(font);
    painter->setPen(pen);
    painter->setRenderHints(QPainter::Antialiasing, true);
    painter->fillRect(rect, "white");
    painter->drawText(150, 110, m_num);
}

.proファイルは前回と同様に作成してください。 その中に次のコードを追記します。

QT += qml quick widgets gui

SOURCES += main.cpp Drawing.cpp
HEADERS += Drawing.h

実行

これを実行すると,次のような画面が出てきます。 f:id:hantas:20151205025138p:plain

一応QMLとの対応付けをしておきます。

f:id:hantas:20151205025139p:plain

ComboBoxの内容をいじくり,ボタンを押すと表示されるテキストが変わりました。

概説

今回はQMLに新しい要素,"Drawing"を入れました。

お気づきかと思いますが,これはC++で書いたDrawingクラスのことです。

qmlRegisterType<Drawing>("DrawingArea", 1, 0, "Drawing");

このコードで,QMLの中からDrawingArea 1.0をインポート出来るようにします。 あとはQMLの中でDrawingを呼び出せば指定したエリアに表示することが出来ます。

(このようにQMLに配置したいクラスは,QObjectを継承したクラスである必要があります。またコード内に意味不明なマクロが入っていますが,次回以降と言うことにします。)

また,ボタンが押されたときの動作は次のように書いていました。

onClicked:{
    drawingarea.setNum(combo1.currentIndex)
    drawingarea.setColor(combo2.currentText)
    drawingarea.update()
}

今回の場合Drawingのidを"drawingarea"としていたので,Drawingのメソッドをこのように呼び出すことが出来ます。

void Drawing::paint(QPainter *painter){
    QFont font = QFont();
    QPen pen(m_color, 3);
    QRect rect(0, 0, 100, 100);
    rect.moveTo(110, 70);
    font.setPointSize(30);
    painter->setFont(font);
    painter->setPen(pen);
    painter->setRenderHints(QPainter::Antialiasing, true);
    painter->fillRect(rect, "white");
    painter->drawText(150, 110, m_num);
}

これがDrawingの描画本体部分です。 基本的にはQPainterに対して何らかの描画を行っていきます。 今回の場合,フォントと色を設定した後に与えられた文字を描画処理しています。 再描画したい場合はこの関数ではなく,update()を呼び出します。

次回はC++とQMLの別の連携法をまとめるのではないでしょうか(未定)

何回かはコードを掲載することに重点を置いてブログ更新します。

ヤマネコでもわかるQtQuick(2)-コンパイルと簡単なデザイン

前回の続きです。 開発環境は導入できたとして,とりあえず画面を表示させるところまで進めます。

まずは

何も考えずに次のファイルを準備してください。

// main.cpp

#include <QApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include <QTextCodec>
#include <QQuickView>

int main(int argc, char **argv){
    QApplication app(argc, argv);
    QTextCodec::setCodecForLocale(QTextCodec::codecForLocale());

    QQmlApplicationEngine engine("main.qml");
    
    return app.exec();
}
// main.qml

import QtQuick 2.0
import QtQuick.Controls 1.0

ApplicationWindow{
    id: applicationWindow1
    width: 800
    minimumWidth: 640
    height: 600
    minimumHeight: 480
    color: "white"
    title: qsTr("QtSample")
    visible: true

    Rectangle {
        x: 560; y: 180
        width: 180; height: 150
        anchors.horizontalCenterOffset: 2
        anchors.horizontalCenter: parent.horizontalCenter
        color: "black"
        radius: 5
        Label {
            x: 10; y: 10
            id: label
            font.pixelSize: 18
            text: qsTr("Label")
        }
        Button {
            id: button
            x:30; y:40
            width: 120
            text: "Button"
            onClicked: textarea.text += combo.currentText + "\n"
        }
        ComboBox {
            id: combo
            x:100; y:80
            width: 50
            model: [1, 2, 3, 4, 5]
        }
        TextArea {
            id: textarea
            text: "Text Area\n"
            x: 40
            y: 180
            anchors.horizontalCenterOffset: 2
            anchors.horizontalCenter: parent.horizontalCenter
        }
    }
}

そして同じディレクトリで次のコマンドを打ちます。

qmake -project

.proファイルが一つ生成されたと思うので,次の行を足します。

QT += qml quick widgets gui

これでよいでしょう。 続いて次のコマンドでビルドします。

qmake
make

補足:Windowsを使用した場合,QtCreatorから.proファイルを開き,そのままビルドが可能です。

さて,このプログラムを実行するとこんな感じのウインドウが開きました。 f:id:hantas:20151204010042p:plain そしてボタンをクリックするたびテキストボックスの中に,コンボボックスで選択した数字が追記されていきます。

今回は初めての例と言うことで簡単な物を用意しました。 ざっと補足していきます。

補足

main.cpp

  • QTextCodec::setCodecForLocale(QTextCodec::codecForLocale()); 文字コードを設定します。日本語の文字化け対策。
  • QQmlApplicationEngine engine("main.qml"); QMLのロードをします。

main.qml

  • onClicked: textarea.text += combo.currentText + "\n" ボタンがクリックされたときの動作です。 javascriptとしてスクリプトを書くことが出来ます。 今回の場合,id:textareaのtext属性に,コンボボックスの選択中テキストと改行文字を追記しています。 QMLで扱えるタイプは公式ドキュメントが参考になります。 All QML Types | Qt 5.5

とりあえず,QMLの一番シンプルな形を掲載しました。 次はもうちょっと別の例を紹介すると思います。

ヤマネコでもわかるQtQuick(1)-概説と導入

今作のロボットから開発環境をUbuntuに切り替えたため,C#で開発していた経路シミュレータがお役御免となりました。
しかしWindowsで旧作マウスをいじることも考えられるため,クロスプラットフォームで動作するシミュレータをつくろうと思います。
開発環境は,Ubuntu12.04LTS, gcc4.8.4,Qt5.2です。
Windows環境はWindows7 Pro 64bit,GNU Make 3.81,Qt5.1です(近いうちにバージョンそろえた方が良いですね)。
QtはGUIアプリケーションをクロスプラットフォーム開発できる有名なライブラリですが,今回は一般的に用いられているQtCreatorを使わずに開発しようと思います。
(ただし,Windowsのmakeを利用したコンパイルがなぜか出来なかったので,コンパイル時のみQtCreatorを使用しています。まあWindowsでQtCreator使わないメリットが特に無いような気がするので良いでしょう)

Qtについてはまだ勉強中なので間違いが見つかると思いますが,その際は指摘をいただけると幸いです。

Qtって何よ?

公式サイトによると

Create connected devices, UIs and applications that run anywhere on any device, on any operating system at any time.

だそうです。
これはマルチプラットフォームで動作するGUIアプリケーションを作るためのフレームワーク,かみ砕いて言えば,Qtを使えばWindowsでもMacでもLinuxでも,はたまたAndroidでも動作するソフトが作れるようになります。 しかも同じソースコードで!
かの有名なSkypeやGoogleEarthなどもQtを利用して作られているそうですので安心して使うことができそうです。

対象読者

  • GUIアプリケーションを作ってみたいが何を使ったら良いか迷っている人
  • Linuxで開発したい人
  • Qtは聞いたことがあるがQMLについて知りたい人

QMLとは

一言にQtと言っても,特にUI設計の作り方にはいくつかの方法があります。

  • フォームファイルでデザインする方法
    VisualStudioでGUI設計することをイメージすると伝わりやすいと思います。 マウスでぽちぽちと部品を配置して,画面の設計をする方法です。 QtCreatorを使う必要があります。

  • QMLでデザインする方法
    独自のQML形式で画面設計を行います。 QMLはCSSとよく似た書式ですが,拡張性に優れています。

今回はQtCreatorを使いたくなかったので,QMLを用いた方式で開発することにしました。 QMLを構成している,QtQuickというフレームワークを利用します。

ここでQMLの優れている点をまとめておきます。

  • 動的にロードするため,画面デザインの変更にはコンパイルが不要
  • C++と相互に連携して呼び出すことができる
  • テキストエディタがあれば編集することが出来る

導入方法

コンパイルするために,makeとgccが必要です。 (今回の開発ではC++を用います)
当然Qtも必要です。
あわせて,Makefileを生成してくれるqmakeもインストールしておきます。

Ubuntu

gcc,makeはすでにインストールされているものとします。

sudo apt-get update
sudo apt-get install qt5-default qt5-qmake

Windows

ダウンロード,インストールします。
Qt - Download
英語が読めない人は諦めましょう,と言いたいところですが,Google先生にでも頼って切り抜けてください。
正直なことを言うと,これくらいの英語が読めないとやっていけないです。 Qtのドキュメントはほとんどが英語です。頑張って慣れてください。

続いてmakeもダウンロード,インストールします。
Make for Windows

あとMinGWをインストールする必要があります。 MinGW - Minimalist GNU for Windows - Browse /Installer at SourceForge.net

次の三つのパスを通しておきます。

C:\Program Files (x86)\GnuWin32\bin
C:\Program Files (x86)\MinGW\bin
C:\Qt\Qt5.1.1\5.1.1\msvc2012_64\bin

拡張子

QMLを用いた開発の際,どのようなファイルが必要なのかまとめておきます。

  • .cpp .h
    C++ソースコードとヘッダです。
  • .qml
    QMLファイルです。 デザインを定義します。
  • .pro
    qmake用のファイルです。 Makefileを生成するためのルールを書きます。
  • Makefile
    qmakeにより自動生成します。

計画

  1. 導入
  2. コンパイルとQML
  3. QMLとドキュメントの使い方
  4. QML要素の動的変化
  5. QMLからC++を呼び出す
  6. C++からQMLを呼び出す

以下未定
次回はコンパイルの仕方とQMLの基礎をまとめます。

壁情報の記録

マイクロマウスは壁情報を記録しながら走行し,その壁情報から最短経路を導出する競技なわけですが,さてこの壁情報はどうやって記録すべきなのでしょうか。

少なくともサークル内では次に紹介されている記録方法が主流なようです。
座標系・迷路表現の定義 | マイクロマウス委員会 関西支部

要約するならば,

迷路の各マスごとに次の8ビットの情報を持っている
・マスに隣接する壁の有無(4方向4ビット)
・マスに隣接する壁の到達情報(4方向4ビット)

といったところでしょうか。
したがってクラシックサイズの迷路を格納する場合,
8ビット × 縦16マス × 横16マス = 2048ビット(256バイト)
ものメモリ領域を食いつぶします。

今回は今作で採用している壁情報の記録法を簡単に紹介しておきます。
(そんな大げさなことじゃないですが,この方法をサークル内に普及させることが必要に感じたのでまとめています)
考え方は単純で,

外周の壁を除いた各壁1枚ずつに1ビットを割り当て,存在情報を記録する
各マスに1ビットを割り当て,到達情報をマスごとに記録する

といった感じです。
この格納法の場合,
|壁が1列に16枚 × 15列 + ー壁が1列に16枚 × 15列 + 縦16マス × 横16マス = 736ビット(92バイト)
のメモリ領域で話が済んでしまいます。
(前述した方法のおよそ35%の使用量)

具体的には次のクラスを作成しました。
(実際の実装は少し異なっています)

class Map{
private:
	/**
	 * @brief 一番上の壁がMSB,一番下の壁がLSB,左から順
	 */
	unsigned short column[15];

	/**
	 * @brief 一番左の壁がMSB,一番右の壁がLSB,下から順
	 */
	unsigned short row[15];

	/**
	 * @brief 一番左のマスがMSB,一番右のマスがLSB,下から順
	 */
	unsigned short reached[16];

	/**
	 * @brief マウスから見たWalldataを絶対方向に変換します
	 * @param wall 変換元の壁情報
	 * @param ang マウスの向いている方向
	 * @return 変換後のWalldataを返します
	 */
	Walldata rotateWallToAbsolute(Walldata wall, EMouseAngle ang);

	/**
	 * @brief 絶対方向のWalldataをマウスから見た方向に変換します
	 * @param wall 変換元の壁情報
	 * @param ang 変換したい方向
	 * @return 変換後のWalldataを返します
	 */
	Walldata rotateWallToRelative(Walldata wall, EMouseAngle ang);

public:
	Map();

	/**
	 * @brief 迷路データを初期化する
	 */
	void resetMap();

	/**
	 * @brief 到達マップを初期化する
	 */
	void resetReachedMap();

	/**
	 * @brief 壁を追加します
	 * @param x 壁を追加する区画のx座標
	 * @param y 壁を追加する区画のy座標
	 * @param angle 今自分が向いている絶対方向
	 * @param wall 今見えている壁情報
	 */
	void addWall(int x, int y, EMouseAngle angle, Walldata wall);

	/**
	 * @brief 壁を設定します
	 * @param x 壁を追加する区画のx座標
	 * @param y 壁を追加する区画のy座標
	 * @param angle 今自分が向いている絶対方向
	 * @param wall 今見えている壁情報
	 */
	void setWall(int x, int y, EMouseAngle angle, Walldata wall);

	/**
	 * @brief 壁を追加します。絶対方向でのみ指定が可能です。
	 * @param x 壁を追加する区画のx座標
	 * @param y 壁を追加する区画のy座標
	 * @param angle 今自分が向いている絶対方向
	 */
	void addSingleWall(int x, int y, EMouseAngle angle);

	/**
	 * @brief 壁を設定します。絶対方向でのみ指定が可能です。
	 * @param x 壁を追加する区画のx座標
	 * @param y 壁を追加する区画のy座標
	 * @param angle 今自分が向いている絶対方向
	 * @param wall 今見えている壁情報
	 */
	void setSingleWall(int x, int y, EMouseAngle angle, int wall);

	/**
	 * @brief 絶対方向から見て壁があるか確認します
	 * @param x 壁を追加する区画のx座標
	 * @param y 壁を追加する区画のy座標
	 * @param ang 今自分が見ている絶対方向
	 * @return 壁が存在したら1,なかったら0を返します
	 */
	int isExistWall(int x, int y, EMouseAngle ang);

	/**
	 * @brief 到達マップを設定します
	 * @param x 到達設定する区画のx座標
	 * @param y 到達設定する区画のy座標
	 */
	void setReached(int, int);

	/**
	 * @brief 到達したか確認します
	 * @param x 到達確認する区画のx座標
	 * @param y 到達確認する区画のy座標
	 * @return 到達していたら1,していなかったら0を返します
	 */
	int hasReached(int, int);

	/**
	 * @brief =演算子のオーバーロード。Mapクラスを代入します
	 */
	Map& operator= (Map tmp);

	~Map();
};

Walldata,EMouseAngle型は触れていませんが,適宜置き換えてください。
概ね上記の通りに実装すれば,それなりに使えると思います。

一応サークルの人(主に初めてプログラムを書く人)向けに補足を入れておきます。

  • 今回作成したメンバ変数(グローバル変数として宣言したかもしれません)は,用意したメンバ関数(関数)以外からアクセスすべきではありません。とりわけ,今回のメンバ変数は直感的に操作できなさそうです。直感的に扱えないような変数を直感的に扱うために,メンバ関数を作成しました。したがって迷路情報を書き換えるときは必ずこのメンバ関数を利用することになります。
  • このMapクラスは迷路情報の格納と操作のみを任されたクラスです。それぞれのクラス・変数・関数の担う範囲をよく考えてコードを書いてください。例えば,マウスのセンサ情報をこのクラス内で扱う必要は一切ありません。
  • この設計では,迷路の外周壁データは用意していません。従って,外周壁を読み書きするように求められた場合は例外処理を書く必要があります。配列の範囲外アクセス,ダメ,絶対
  • 実際に使用する際はaddWall関数を呼び出しています。しかし,3枚の壁情報を同時に格納する良い方法が思いつかなかったので,内部的にaddSingleWall関数を3回呼び出しています。

他の人がどうやって迷路情報を保存しているのか全然知らないので,意見などお待ちしています。

GooglePlayMusic->Supersonic

またまた,有名なクラウドサービスから自前クラウドサービス(?)に乗り換えるというお話です。
といっても今回は乗り換えではありません(GooglePlayMusicは関係ない)。
旅行の移動中や部室での作業中などに音楽をかけたいと思うようになったのですが,携帯はiPhone16GBモデルで,今持ってる音楽プレイヤーはBluetooth2.0しか使えないことが判明しました。
そこで最近流行のGooglePlayMusicっぽいサービスを自前で用意しようと思い立ちました。

今回使用したのは"Supersonic"なるソフトウェアです。
こいつが音楽データの管理・ストリーミング配信等を行ってくれます。
配信を聞くために,パソコンからはブラウザ,iPhoneからはiSub Music Streamerを使いました。

iSub Music Streamer

iSub Music Streamer

  • Benjamin Baron
  • ミュージック
  • ¥600
600円のフルバージョンと200円のライトバージョンがありますが,プレイリストを使用したいと思ったのでフルバージョンを購入しました。
特に不便もなく利用できそうです。

環境

いつもどおりのUbuntuServer12.04 LTSサーバーにインストールしました。
本来であれば,
Mach5/supersonic · GitHub
このリポジトリをクローンして自前でパッケージをビルドすべきなのでしょうが,どうしてもコンパイルが通らなかったので
(プロジェクト外のファイルに対して"Not a v4.0.0 POM"というエラーが出ました),
ビルド済みのパッケージを使用しました。
Downloads · Mach5/supersonic · GitHub

cd /path/you/want/to/download
sudo apt-get update
sudo apt-get install lame ffmpeg
sudo apt-get install default-jre-headless
sudo dpkg -i supersonic-4.7.beta1.deb

インストールと同時に自動起動の設定もされるようです。

設定

ポートをあけてローカル環境からアクセスできるようにします。
デフォルト設定では4040番を使うそうなので,そのままにしておきます。

sudo ufw allow 4040

デフォルト状態ではセキュリティ上あまりよろしくないので,権限周りの変更をします。

/etc/default/supersonic

SUBSONIC_ARGS="--port=4040 --https-port=4443 --max-memory=100"
SUBSONIC_USER=supersonic

新規ユーザーを作成し,音楽データの保管先の所有権を変更します。

sudo useradd supersonic
cd /var
sudo mkdir music
sudo mkdir playlists
sudo chown supersonic:supersonic music
sudo chown supersonic:supersonic playlists
sudo service supersonic restart

ブラウザからの設定

これでとりあえずの準備はできたはずなので,早速4040ポートにアクセスします。
f:id:hantas:20151027140721p:plain
初期ID,パスワードはadmin, adminなのでログインし,表示されている3項目の設定変更を行います。
実際に使うことを考え,ユーザーアカウントも作成しておきます。
f:id:hantas:20151027140725p:plain
実はここにありがたい項目があり,ユーザーごとに最大ビットレートを変更することができます。
僕は携帯回線用と無線LAN用の2種類のアカウントを作成しています。

設定ができたらFTPなどを利用して音楽データを転送します。
僕は/var/music/以下にアーティスト名のディレクトリを作り,その下にアルバム名のディレクトリを作成しています。
データ転送後にはブラウザから音楽データのスキャンを実行する必要があります。
f:id:hantas:20151027140723p:plain

学内からのアクセス

本筋から少し離れるのですが,ここで少し苦戦しました。
携帯回線を使用する場合は構わないのですが,学内LANから4040ポートにアクセスできないようになっているので,リバースプロキシを利用して回避します。
リバースプロキシが何かは自分で調べてみてください。
すでにApache2を立ち上げているので,追加設定を行います。

/etc/apache2/sites-available/000-default.conf に追記

<VirtualHost *:80>
    ServerName music.hogehoge
    ProxyPass / http://localhost:4040/
    ProxyPassReverse / http://localhost:4040/
</VirtualHost>
sudo a2enmod proxy
sudo a2enmod proxy_http
sudo service apache2 restart

合わせてDNSサーバーにもmusic.hogehogeを通しておき,アクセスできるようにしておきます。

以上の設定で"http://music.hogehoge:80/"または"http://music.hogehoge/"でSupersonicにアクセスできるようになりました。
f:id:hantas:20151027140728p:plain

今回ストリーミングサーバとしていくつかの環境を候補に上げました。
その中でも,良い比較記事を書かれているブログがあったので紹介しておきます。
ミュージックストリーミングWebサーバのAmpacheとSubsonicを比較してみた: boKUma


さてさて,大会前なのになんでこんなことやってるんだろうなあ。
頑張ってマウス作ります。

写真から迷路データの取得

題のとおりです。
マイクロマウスの迷路シミュレータ用にいちいち手打ちで迷路を入れるのが面倒だったので,写真を読み込ませるだけで迷路データを吐き出すというソフトウェアに挑戦してみました。
今回使用した環境は,gcc4.8.4,OpenCV2.4.8です。

今回のプログラムでは次の順に処理を行う必要がありました。
「斜めから取った写真を上から見た迷路に変換(ホモグラフィー変換)」→「扱いやすいように色空間を変換(RGBからHSV)」→「壁の上色である赤のみ抽出」→「迷路の認識」

ホモグラフィー変換

OpenCVに関数が用意されています。
・getPerspectiveTransform //変換行列の作成
・warpPerspective //画像に行列を適用
変換前の座標と変換後の座標を任意に指定することで,ホモグラフィー変換を行うことが出来ました。

 Mat_<Vec3b> img1 = imread(src_image);  //画像ファイルの読み込み
 Mat homography_matrix = getPerspectiveTransform(src_pt, dst_pt);
 warpPerspective(img1, img2, homography_matrix, img1.size());
 imwrite(dst_image, img2);  //画像ファイルの書き出し

色空間の変換

・cvtColor //色空間の変換

cvtColor(*src, colorImage, code);

おしまい。

赤色の抽出

汚いソースコードのみ掲載します。

void colorExtraction(Mat *src, Mat *dst, int code,
					 int ch1Lower, int ch1Upper,
					 int ch2Lower, int ch2Upper,
					 int ch3Lower, int ch3Upper
	){
	Mat colorImage;
	int lower[3];
	int upper[3];

	Mat lut = Mat(256, 1, CV_8UC3);
	cvtColor(*src, colorImage, code);

	lower[0] = ch1Lower;
	lower[1] = ch2Lower;
	lower[2] = ch3Lower;
	upper[0] = ch1Upper;
	upper[1] = ch2Upper;
	upper[2] = ch3Upper;

	Mat tmp = Mat(src->rows, src->cols, CV_8UC3);
	uchar hsv[3];
	for (int i=0; i<src->cols; i++) {
		for (int j=0; j<src->rows; j++) {
			hsv[0] = colorImage.at<Vec3b>(j, i)[0];
			hsv[1] = colorImage.at<Vec3b>(j, i)[1];
			hsv[2] = colorImage.at<Vec3b>(j, i)[2];
			if (lower[0] <= upper[0]) {
				if(((lower[0] <= hsv[0]) && (hsv[0] <= upper[0]))
				   && ((lower[1] <= hsv[1]) && (hsv[1] <= upper[1]))
				   && ((lower[2] <= hsv[2]) && (hsv[2] <= upper[2]))){
					tmp.at<uchar>(j, i*3) = 255;
					tmp.at<uchar>(j, i*3+1) = 255;
					tmp.at<uchar>(j, i*3+2) = 255;
				} else {
					tmp.at<uchar>(j, i*3) = 0;
					tmp.at<uchar>(j, i*3+1) = 0;
					tmp.at<uchar>(j, i*3+2) = 0;
				}
			} else {
				if(((lower[0] <= hsv[0]) || (hsv[0] <= upper[0]))
				   && ((lower[1] <= hsv[1]) && (hsv[1] <= upper[1]))
				   && ((lower[2] <= hsv[2]) && (hsv[2] <= upper[2]))){
					tmp.at<uchar>(j, i*3) = 255;
					tmp.at<uchar>(j, i*3+1) = 255;
					tmp.at<uchar>(j, i*3+2) = 255;
				} else {
					tmp.at<uchar>(j, i*3) = 0;
					tmp.at<uchar>(j, i*3+1) = 0;
					tmp.at<uchar>(j, i*3+2) = 0;
				}
			}
		}
	}
	*dst = tmp;
	return;
}

出力画像等

↓の画像を
f:id:hantas:20151009152756j:plain

↓になおして
f:id:hantas:20151009152800j:plain

↓のマスク画像にして
f:id:hantas:20151009152830j:plain

↓の迷路を出力させました

+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|       |                                                       |
+   +   +   +---+---+---+---+---+---+---+---+---+---+---+---+   +
|   |   |       |                                           |   |
+   +   +   +---+   +---+---+---+---+---+---+---+---+---+   +   +
|   |   |   |           |                               |       |
+   +   +   +---+   +---+   +---+---+---+---+---+---+   +---+---+
|   |   |   |           |   |                       |       |   |
+   +   +   +---+   +---+   +   +---+---+---+---+   +---+   +   +
|   |   |   |           |   |           |           |   |       |
+   +   +   +---+   +---+   +---+---+   +---+   +   +   +   +   +
|   |   |   |           |   |   |       |       |   |       |   |
+   +   +   +   +---+---+   +   +   +---+   +   +---+   +---+   +
|   |   |   |                       |       |   |               |
+   +   +   +---+---+---+   +---+---+   +   +---+   +---+   +---+
|   |   |   |       |       |           |   |                   |
+   +   +   +   +   +---+   +   +   +   +---+   +---+---+   +---+
|   |   |   |   |   |       |       |   |           |   |       |
+   +   +   +   +   +   +---+   +---+---+   +---+---+   +---+   +
|   |   |   |   |       |           |           |           |   |
+   +   +   +   +---+---+   +---+---+   +---+---+   +---+   +   +
|   |   |   |   |   |           |           |           |   |   |
+   +   +   +   +   +   +---+---+   +---+---+   +   +---+   +   +
|   |   |   |   |           |           |       |       |   |   |
+   +   +   +   +   +---+---+   +---+---+   +---+---+   +   +   +
|   |   |   |           |           |           |       |   |   |
+   +   +   +   +---+---+   +---+---+   +---+   +   +   +   +   +
|   |   |   |   |   |           |           |       |   |   |   |
+   +   +   +---+   +---+   +---+   +   +   +   +   +   +   +   +
|   |   |   |           |           |   |       |       |   |   |
+   +   +   +   +---+   +---+---+---+---+---+---+---+   +   +   +
|   |               |                                   |       |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

また書きたくなったら追記します(レポートと東北大会と冬コミつらい)

○参考
OpenCV 2.xでマウスコールバックなGUI - minus9d's diary
C++(OpenCV2.x)でのHomographyTransformation : とある大学院生のプログラミング
OpenCVでHSV変換して色抽出 - Qiita

ドメインを取得

初めてドメインを取得してみたので記録を残します。

今回はVALUE DOMAINでドメインを取得しました。
(広告でよく見る「お名前.com」と同じGMOが経営してるんですね。向こうと何が違うんでしょうか……)
VALUE-DOMAIN バリュードメイン

今回取得したのは".net"ドメイン
初年度は680円、次年度以降も1280円で更新できるそうなので、これならばと思い取得しました。
DNSの簡単な設定と、サーバーの設定内容をまとめます。

VALUE DOMAINにはそこそこ使い勝手の良いコントロール画面があるので、そこからはじめの設定をしていきます。
とりあえず、DNSレコードと、DDNSパスワードの設定をしておけば問題はないと思います。

DDNS(Dynamic DNS)…個人宅で固定IPアドレスを取るのは財布にやさしくないということで、逐一IPアドレスDNSサーバーに送信し、動的IPアドレスドメインに紐付けることができます。今回はこれを利用しています。

DNSレコードの設定

VALUE DOMAINの場合、DDNSに対応したネームサーバは限られています。
まずはネームサーバを指定しました。
(今回の場合、NS1-5.VALUE-DOMAIN.COMを利用しています)

続いて、設定フィールドをいろいろと編集します。
とてもとても親切なことに、設定画面に懇切丁寧な説明書きがあるのでここでは詳説を省略します。
僕の場合、Webサーバー、クラウドサーバ、メールサーバに利用する予定があるので、次のとおり設定しました。

a @ IPアドレス
a cloud IPアドレス
a www IPアドレス
mx mail.ほげほげ. 10
a mail IPアドレス

DDNSパスワードの設定

DDNSの更新通知はHTTPリクエストにより送信します。
VALUE-DOMAIN バリュードメイン
こちらのページに書かれている通りのリクエストをすると、先ほど設定したDNSレコードが変更されるというわけです。

一つ罠があって(まあ考えたら当たり前なのですが)、ダイナミックDNS設定情報の設定画面からDDNS用のパスワードを設定する必要があります。
これを任意のパスワードに設定後、ブラウザからリクエストを送信すると、レスポンスコード:0 が帰ってきました。
確認した後、サーバーの方の設定を行っていきます。

もうひとつ、僕の環境ではホスト名指定に"*(全ホスト)"を設定しても反映されませんでした。
原因はわかりませんが、www、cloud、@、mailのそれぞれに対してリクエストを送信することにしました。

サーバーの設定

サーバーからはwgetを使ってリクエストを送ります。

wget -O /dev/null http://なんやらかんやら

今回の場合、定期的にこのリクエストを送信したいので、cron用のスクリプトを作りました。

#!/bin/sh
wget -O /dev/null http://なんやらかんやら > /dev/null 2>&1
wget -O /dev/null http://なんとかかんとか > /dev/null 2>&1

cronに1時間毎に設定し、無事稼働し始めたようです。
crontab -e でcronの設定、
crontab -l でcronの確認、
/var/log/syslog にログが記録されています。

さてさて、ホームページを作っていきましょうか……