月別アーカイブ: 2015年2月

OpenCVのテンプレートマッチング

3DSのプチコン3号をどうにかしたいという目標を持ったので、3DSタッチパネルディスプレイに表示されるソフトウェアキーボードを、OpenCVを使って位置を認識させる、ということを試してみました。

まずはあらかじめ、キーボードの写真を撮っておきます。撮影にはNexus5の内蔵カメラを使いました。画像が若干傾いていたので、撮った写真をgimpで加工し、ほぼ長方形になるよう調整しました。

プチコンキーボード

この画像から、キーを一つ切り出します。とりあえず「1」のキーを切り取ってみました。

petit-kbd-1

キーボードの全体画像からこの「1」のキーを検出する方法として、テンプレートマッチングを行ってみます。OpenCVのサイトにそのものずばりのC++サンプルが掲載されています。自分はどこかのページで見たコードを参考に以下のようなものを作成しました。

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char **argv)
{
    cv::Mat search_img = cv::imread("../petitcom-kbd-fix.png", 1);
    cv::Mat tmp_img = cv::imread("../petit-kbd-1.png", 1);

    cv::Mat result_img;
    cv::matchTemplate(search_img, tmp_img, result_img, CV_TM_CCOEFF_NORMED);

    cv::Rect roi_rect(0, 0, tmp_img.cols, tmp_img.rows);
    cv::Point max_pt;
    double maxVal;
    cv::minMaxLoc(result_img, NULL, &maxVal, NULL, &max_pt);
    roi_rect.x = max_pt.x;
    roi_rect.y = max_pt.y;

    std::cout<< "(" << max_pt.x << "," << max_pt.y << ") score="
             << maxVal << std::endl;
    cv::rectangle(search_img, roi_rect, cv::Scalar(0, 0, 255, 3));

    std::cout << "rectangle: (" << roi_rect.x << ", " <<
        roi_rect.y << ") size (" << roi_rect.width << ", " <<
        roi_rect.height << ")" << std::endl;

    cv::imwrite("output.png", search_img);
    return 0;
}

search_imgを対象画像とし、tmp_imgにマッチする領域をtemplateMatch関数で算出します。アルゴリズムの選択には、特に何も考えずサンプルのまま(CV_TM_CCOEFF_NORMED/Collection coefficient)にしました。これにより、演算結果がresult_imgに代入されます。

その結果から、minMaxLoc関数を用いてresult_imgから値が最大となる位置を算出します。cv::Matは行列データ型であり、minMaxLocは行列内の最大値、最小値を持つ座標とその値を取得する関数です。今回の場合、必要なのは最大値の座標だけです。

サンプルプログラムではrectangle関数で該当する矩形領域に線を引いています。

自分はC++に不慣れなので、同じことをPythonでやってみようとしました。いろいろと試行錯誤の結果、以下のようなコードになりました。

import cv2

src = cv2.imread("../petitcom-kbd-fix.png", 1)
tmp = cv2.imread("../petit-kbd-1.png", 1)

res = cv2.matchTemplate(src, tmp, cv2.TM_CCOEFF_NORMED)

(minval, maxval, minloc, maxloc) = cv2.minMaxLoc(res)

print "({0}, {1}) score = {2}\n".format(maxloc[0], maxloc[1], maxval)

(h, w, d) = tmp.shape

rect_1 = (maxloc[0], maxloc[1])
rect_2 = (maxloc[0] + w, maxloc[1] + h)
print "({0}, {1}) score = {2}\n".format(maxloc[0], maxloc[1], maxval)
print "size ({0}, {1})\n".format(w, h)
cv2.rectangle(src, rect_1, rect_2, 0x00ff00)

cv2.imwrite("py-output.png", src)

rectangleに与えるべき引数がC++とはちょっと異なります。C++では矩形の起点と高さ+幅を与えるのに対し、Pythonでは2つの対角上の頂点の座標を指定します。最初この違いに気付かなくて、思うような結果にならず悩みました。

今回はスケールが同一の画像でのテンプレートマッチングだったので期待通りの結果を得ることができましたが、実際の利用をする場合には異なるサイズや、若干傾いた画像、他に余計なものが写りこんでいる画像などを相手にする必要があるので、もっといろいろな下処理などが必要になるものと思われますが、まずは第一歩を進めることができました。

壊れた仮想ディスクを扱う

立ち上げっぱなしのWindowsマシンが、気が付くとBSoDで落ちてしまいました。原因は追加したHDDにあるようで、Linuxで観測してみると不定期にSATAのエラーを吐きます。

再起動したら、HDD自体が認識されず困ったので、testdiskでパーティションテーブルの復元を試みました。幸いなことに、これでNTFSパーティションの復旧はできました。このtestdiskはSystemRescueCD辺りも含まれているので、普段から起動ディスクとして用意しておくと安心です。

しかしこれだけでは不完全で、NTFSパーティション自体がdirtyになっていたので、回復コンソールからchkdsk /fを実施することでファイルシステムレベルの修正できました。

しかし不幸はこれだけでは終わりませんでした。対象HDD内においていた仮想マシンが起動しなくなり、grubのプロンプトに落ちるという状況になりました。Debianのインストーラーrescueモードで見ると、そもそもext2/3としてのsignatureまで壊れてしまっているようでした。

使っていた仮想マシンのディスク形式はVMware Playerからimportしてきたvmdk形式だったので、いったんqemu-imgでraw形式に変換し、kpartxでパーティションを認識、fsckを実施しました。

# qemu-img convert -f vmdk -O raw /path/to/disk.vmdk /output/disk.img
# kpartx -av /output/disk.img
# fsck.ext3 -y /dev/mapper/loop1p1

大量のエラーが発生し、lost+foundにかなりのデータがおいやられたものの、なんとかfsckは完走しました。/homeごとなくなってしまいましたが…

幸い、以前変換したときの元のイメージも残っていたので、現在はそちらをraw形式に変換してデータの復元を試みています。重要なデータはlost+foundの方を参照することにします。

安価なBluetoothヘッドフォン

ずいぶん前にAmazonで購入したBluetoothヘッドホンを長らく使っていました。MM-BTSH24と同等品だったと思うのですが、当時2500円ぐらいで購入した記憶があります。

メーカー純正品は結構高価なのですが、同等品が当時と同じぐらいの値段で売られているようです。

長く使っていたせいか、耳あて部分の片方が若干痛んできています。先週末にあきばお~でBSHSBE19(白)が税別1999円で販売されていたので、この機会に買ってみました。Amazonでも3000円程度で購入できるようです。

違いはいろいろありますが、どちらにも一長一短があります。BSHSBE19は新しい製品なので、以下のポイントが個人的に良いと思っています。

  • Bluetooth 4.0対応
  • マルチポイント(2デバイス対応)
  • ノイズキャンセリング搭載
  • microUSBによる給電

一方MM-BTSH24の方がよかったなあ、と思う点は以下です。

クリップ型

個人的に耳は小さい方なので、カナル型よりもクリップ型である点は好みでした。必然的にノイズキャンセリング機能は載せられなくなってしまいますが、自分の用途だと主にジムで運動しながらというシチュエーションが多いので、むしろ環境音も聞こえた方が都合が良いです。

操作ボタンが多い

物理的サイズが大きい分、搭載されている操作ボタンが電源・再生ボタン、ボリュームボタンx2、選曲ボタンx2と多くあります。一方BSHSBE19は電源(スライドスイッチ)、マルチファンクションボタン、ボリュームボタンx2となっています。ボリュームボタンを長押しすると選曲ができるのですが、選曲を含めたあらゆる機能に音声ガイドがついているため、何か操作するたびに喋るのがちょっと煩わしいところです。

MM-BTSH24も使えなくなったわけではないので、当面は両方を使い分けることになりそうです。

VMware PlayerからVirtualBoxへの移行

最近のスラッシュドットの記事から、VMware Playerのライセンスにおける「非商用の解釈がかわった」という記事(ライセンス解釈が変わったようです。)を目にし、この機会にVirtualBoxへの移行を実施しました。

移行対象は違いますが、既にやってみた人の記事(VMware Fusion 5からVirtualBox 4.3への移行)があったので、作業自体はそれほど難しくはありませんでした。ただ、この記事にもあるようにストレージをSCSIからIDEに変更する、という作業はVMware Playerでも同様に必要でした。

自分の場合、もう一点「ホストオンリーネットワークもそのまま移行したい」という希望があったので、そのために若干の作業が発生しました。参考にしたのは「VirtualBoxのネットワーク設定とCentOS6.5のインストール」です。こちらの記事はMacOSXのようですが、Windowsでも行うことはあまり変わりません。

VirtualBox マネージャーからメニューの「ファイル」「環境設定」を選択し、ダイアログを開きます。さらに「ネットワーク」「ホストオンリーネットワーク」を選択し、ネットワークを追加します。ネットワークレンジやネットマスクを、これまでVMware Playerで使っていたものと同じになるよう設定します。

ここで一点注意があります。VMware Playerが入ったままだと、VMwareの仮想ネットワークインターフェースが設定として保持されたままになるので、vmnetcfg.exeで設定を変更するか、VMware Playerをアンインストールする必要があります。そうしないと、同一のネットワーク設定が存在しているとWindows側に認識され、リンクローカルなネットワークが強制的に割り当てられてしまいます。

あとはOVF化した仮想マシンをVirtualBoxからインポートし、ネットワーク設定を先ほど作成したホストオンリーネットワークに指定して、実際に通信ができれば完了です。

NATネットワークの移行も、ほぼ同様の手順で実行し、Window側のネットワーク共有を使えばできると思います。