Windows Azure SDK 1.6 日本語版をインストールする

出遅れた…というか、気づかなかった。
「某ひと目Azureなんとか」(既に伏字になっていない)を直していると、SDK のバージョンが 1.4 から 1.5 から 1.6 に変わっているという…うーむ。発行時には、大丈夫なんでしょうか?

で、1.5 の時は、日本語版がなかなか出なくて困っていたのですが、1.6 は英語版が出てから1週間後に出ているのですね。これだと、次のバージョンでも安心かも。

Windows Azure SDK 1.6 日本語版
http://blogs.msdn.com/b/bluesky/archive/2011/11/24/windows-azure-sdk-1-6-jpn-release-and-windows-azure-guest-os-2-8-amp-1-16-minor-update.aspx

以前は、MSDN のダウンロードを探し回らなくては駄目だったのですが、今回は Web Platform Installer から検索するとすんなりいけそうです。「windows azure」で検索すると出てきます。

Visual Studio 2010 に「Webコンポーネントのインストール」というツールバーが追加されるので、これをクリックすると簡単にインストールできますね。

# NuGet のほうは、まだ試していないという…状態ですが。

カテゴリー: Azure | Windows Azure SDK 1.6 日本語版をインストールする はコメントを受け付けていません

色相のみでヒストグラムを計算表示してみる

大雑把に、HSV 変換をした後に色相(Hue)だけを取り出して、ヒストグラムにしてみる。
こうすると、対象物(この場合はゲームの駒)が持っている「色」という特徴量がわかる…ハズ。原子分析みたいなものかも。

#include "stdafx.h"
#include <iostream>
#include &quot;opencv/cv.h&quot;
#include &quot;opencv/highgui.h&quot;
using namespace std;

int main(int argc, char* argv[])
{
	char *fname = argv[1];
	cv::Mat image = cv::imread( fname );
	cv::Mat imageHsv;

	cv::cvtColor( image, imageHsv, CV_RGB2HSV );
	for ( int y=0; y<imageHsv.rows; ++y ) {
		for ( int x=0; x<imageHsv.cols; ++x ) {
			cv::Vec3b &v = imageHsv.at<cv::Vec3b>(y,x);
			v[0] = v[0]; // H
			if ( v[1] < 64 || v[2] < 64 ) v[0] = 300; // 閾値以下は範囲外とする
			v[1] = ( v[1] < 64 )? 0: 255; // S 閾値以下は白とみなす
			v[2] = ( v[2] < 64 )? 0: 255; // V 閾値以下は黒とみなす
		}
	}

	// ヒストグラム用
	const int ch_width = 180, ch_height=200;
	cv::Mat imageHist( cv::Size( ch_width, ch_height), CV_8UC3, cv::Scalar::all(255));
	cv::cvtColor( imageHist, imageHist, CV_RGB2HSV );
	for ( int y=0; y<imageHist.rows; ++y ) {
		for ( int x=0; x<imageHist.cols; ++x ) {
			cv::Vec3b &v = imageHist.at<cv::Vec3b>(y,x);
			v[0] = x;		// H
			v[1] = 100;		// S
			v[2] = 255;		// V
		}
	}
	cv::cvtColor( imageHist, imageHist, CV_HSV2RGB );

	cv::Mat hist;
	const int hdims[] = {180}; // 次元毎のヒストグラムサイズ
	const float hranges[] = {0,180};
	const float* ranges[] = {hranges}; // 次元毎のビンの下限上限
	const int chs[] = {0};
	double max_val = .0;
	cv::calcHist(&imageHsv, 1, chs, cv::Mat(), hist, 1, hdims, ranges);
	// 最大値の計算
	cv::minMaxLoc(hist, 0, &max_val);
	cv::Scalar color = cv::Scalar::all(100);
	hist = hist * (max_val? ch_height/max_val:0.);
	for(int j=0; j<hdims[0]; ++j) {
		int bin_w = cv::saturate_cast<int>((double)ch_width/hdims[0]);
		cv::rectangle(imageHist,
			cv::Point(j*bin_w, imageHist.rows),
			cv::Point((j+1)*bin_w, imageHist.rows-cv::saturate_cast<int>(hist.at<float>(j))),
			color, -1);
	}
	cv::cvtColor( imageHsv, imageHsv, CV_HSV2RGB );

	cv::namedWindow(&quot;original&quot;, CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
	cv::namedWindow(&quot;result&quot;, CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
	cv::namedWindow(&quot;hist&quot;, CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
	cv::imshow(&quot;original&quot;,image);
	cv::imshow(&quot;result&quot;,imageHsv);
	cv::imshow(&quot;hist&quot;,imageHist);
	cv::waitKey(0);

	return 0;
}




 

背景がオレンジ色なので、それにヒストグラムが引っ張られてしまうのは後で検討するとして、色相が180の幅のままだと分解能が細かく過ぎてピークがとんがり過ぎるかなと。

試しに実画像を調べてみると、



のように広がりを持つので、相関係数でマッチさせるか、もっと単純に色を積算して max だけを取り出すか。色の検出自体は、おおまかで良い(白黒+6色ぐらい)ので、このぐらいであれば、どれだけ検出箇所が多くてもあっという間に終わるハズ。

カテゴリー: C++, OpenCV | 色相のみでヒストグラムを計算表示してみる はコメントを受け付けていません

OpenCV で機械学習を試して…みた

OpenCV で機械学習を試してみる…とまだ終わらず | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/2537

を、「-m 500」に設定して4時間程動かすと XML ファイルが出来上がりました。

検出用のプログラムをざっと作って、

#include &quot;stdafx.h&quot;
#include <iostream>
#include &quot;opencv/cv.h&quot;
#include &quot;opencv/highgui.h&quot;
using namespace std;

int main(int argc, char* argv[])
{
	/* 画像のロード */
	char *fname = argv[1];
	cv::Mat image = cv::imread( fname );
	/* 検出器のロード */
	// char *cascadeName = &quot;C:\\OpenCV2.3\\data\\haarcascades\\haarcascade_frontalface_default.xml&quot;;
	char *cascadeName = &quot;D:\\work\\OpenCV\\src\\PiyoDetect\\PiyoML\\data\\koma01.xml&quot;;
	cv::CascadeClassifier cascade;
	cascade.load(cascadeName);

	/* 検出 */
	vector<cv::Rect> komas;
	cascade.detectMultiScale( image, komas );

	/* 検出領域の描画 */
	for ( vector<cv::Rect>::iterator it=komas.begin(); it!=komas.end(); ++it )
	{
		cv::rectangle( image, cv::Rect(
			it->x, it->y, it->width, it->height ), cv::Scalar(0,255,0));
	}
	cout << &quot;count: &quot; << komas.size() << endl;
	/* 画像の表示 */
	cv::namedWindow(&quot;result&quot;, CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
	cv::imshow(&quot;result&quot;, image );
	cv::waitKey(0);
	return 0;
}

を動かしてみた結果が、次の図です。

全然あっていないじゃんッ!!! ってな状態です。どうも正解画像のサイズが大きすぎた(検出対象は30×30程度なのに、正解画像が45×45なのがちょっと間違い?)らしくって、検出先の画像は倍のサイズにして検出させました。

OpenCV に入っている haarcascade_frontalface_default.xml を使うと、顔検出ができるのでプログラムのほうは大丈夫らしい。

ちなみに、ver.2.3 のほうを使うと、実行時に cv::CascadeClassifier あたりで落ちるので、何故か ver.2.3.1 を使っています。実行時のエラーなので、DLL のビルド間違いとか(リビルドしていないし)かもしれません。

OpenCVによるアニメ顔検出ならlbpcascade_animeface.xml – デー
http://d.hatena.ne.jp/ultraist/20110718/1310965532
anime.udp.jp
http://anime.udp.jp/

探してみると、アニメ絵の顔検出もあって、結構な率で検出できているようです。正解画像を集めて特徴量を抽出してとプラス諸々をやっているそうです。動画のほうの顔検出も結構なスピードで動くので、実行時のスピードは十分ではないかなと。
色特徴によりキャラクター分類なんてのは、他での応用も大きいと思います<エラそうに。

で、自分の実験に話を戻すと、アクションパズルゲームの駒のようなものは、

  • そもそも、顔や人型のような「特徴」はない。
  • そもそも、ゲームの盤上から、判別すべき「特徴」が浮き出ている。

という特質があります。
最初の「特徴」のほうは、顔とか人型とかの「モデル」ですね。目があるとか口があるとか、それぞれの顔には必要な要素があって、それの要素が個別には少しずつ違う(画像によっては、半分だけとか)ので、これを多数の正解画像(顔の画像)から、共通部分を見つけるというスタイル。
2番目の「特徴」の方は、ゲーム盤面(背景画像)に埋もれないような特徴(色とか輝度とか形)とかが「駒」自体にはあるはずです。でないと、背景に埋もれてしまうし、それは背景ともいえる。背景差分はその手法のひとつで、背景はだいたい止まっているという前提のもとに、動いているもの(オブジェクト)を検出する方法。

で、ゲームの駒というのは、

  • ひょっとすると、盤面は動いていないかもしれない。
  • ひょっとすると、駒も盤面の動いていないかもしれない。
  • でも、明らかに盤面の上に駒がある(将棋の駒とか碁石とかもそう)。

なわけで、もっと大雑把に「特徴」を見極めるのでもよいかな、と昨晩考えていました。
Haar-Like のように細かく画像を調べるのは大袈裟で、もっと大雑把に、

  • HSV 画像の色相だけを取り出して、大まかに白黒+6色で特徴量とみなすとか、
  • 輝度のヒストグラムを取って、明るい駒と暗い駒を判別するとか、
  • 丸っこいとか、四角っぽいとか、ひし形っぽいとか、おおまかな形状で判別するとか、

のような特徴量だけでも十分なのかなと。どうも、駒の特徴量をそのままコーナー検出などに当てはめてしまうと、過学習っぽい気もするし。検出するときにカスケードのルートがひとつしかない(複数のカスケードを使えばそれでいいのでしょうが)のがちょっと気になっています。ひとつの検出器(検出ルール?)を用いるよりも、いくつかの検出器を併用して相互補完するのが誤検出に対しては強いのかなぁと。まだ妄想段階にすぎませんが。

カテゴリー: OpenCV | OpenCV で機械学習を試して…みた はコメントを受け付けていません

OpenCV で機械学習を試してみる…とまだ終わらず

テンプレートマッチングで画像から取り出すには無理がありそうなので、やはり、独自なオブジェクト検出器を作ってみないとだめか、と思い、

OpenCVで学ぶ画像認識:第4回 オブジェクト検出器の作成方法|gihyo.jp … 技術評論社
http://gihyo.jp/dev/feature/01/opencv/0004

を読んで、試しに、AdaBoost を使ってカスケードを作ろうと思ったのだが、いやあ、ちょっと時間が掛かりすぎる。

■学習用の正解ファイルの作成

C:\OpenCV2.3\build\bin\opencv_createsamples.exe ^
 -img images\koma01.png ^
 -vec koma01.vec ^
 -num 1000 ^
 -bg NG.txt ^
 -w 45 -h 45 ^
 -show

■オブジェクトの学習

C:\OpenCV2.3\build\bin\opencv_haartraining.exe ^
 -data koma01 ^
 -vec  koma01.vec ^
 -bg NG.txt ^
 -npos 1000 ^
 -nneg 467 ^
 -w 45 -h 45 ^
 -mem 200 ^

な感じで動かしていますが、4時間ほど経っても終わらず。

駒を検出したいので、今回の場合は正解画像は1つ(ゲーム中に出てくる画像)になるのですが、光の関係や画面を映す関係からいくつかの正解画像を用意します。そのあたりは、opencv_createsamples を使って、1000 枚の画像に水増しします。ファイルは別々にできずに、koma01.vec のように1つのファイルにまとめられます。

最初は、駒の画像が 90×90 だったので、そのまま指定したのですが、あえなくメモリーオーバーしてしまい opencv_haartraining がダウン。サイズを 45×45 にすると動くようになったので、対象画像はそこそこ小さいサイズにしないと駄目なのかも。

不正解の画像をどのように集めるのか?とも思ったのですが、マッチングしなければ何でもいいわけで、

Caltech101
http://www.vision.caltech.edu/Image_Datasets/Caltech101/Caltech101.html

にある適当なフォルダ(BACKGROUND_Google というランダムっぽい画像フォルダがある)を使っています。

うまく実行できると、下記のように、(多分)テンプレート画像を切り替えながら、閾値を切り替えながら学習し始めるわけですが…

D:\work\OpenCV\src\PiyoDetect\PiyoML\data>opencv_haartraining.exe  -d
xml  -vec  koma01.vec  -bg NG.txt  -npos 1000  -nneg 467  -w 45 -h 45

Data dir name: koma01.xml
Vec file name: koma01.vec
BG  file name: NG.txt, is a vecfile: no
Num pos: 1000
Num neg: 467
Num stages: 14
Num splits: 1 (stump as weak classifier)
Mem: 200 MB
Symmetric: TRUE
Min hit rate: 0.995000
Max false alarm rate: 0.500000
Weight trimming: 0.950000
Equal weights: FALSE
Mode: BASIC
Width: 45
Height: 45
Applied boosting algorithm: GAB
Error (valid only for Discrete and Real AdaBoost): misclass
Max number of splits in tree cascade: 0
Min number of positive samples per cluster: 500
Required leaf false alarm rate: 6.10352e-005

Tree Classifier
Stage
+---+
|  0|
+---+

Number of features used : 1007032

Parent node: NULL

*** 1 cluster ***
POS: 1000 1000 1.000000
NEG: 467 1
BACKGROUND PROCESSING TIME: 0.22
Precalculation time: 0.05
+----+----+-+---------+---------+---------+---------+
|  N |%SMP|F|  ST.THR |    HR   |    FA   | EXP. ERR|
+----+----+-+---------+---------+---------+---------+
|   1|100%|-|-0.763750| 1.000000| 1.000000| 0.123381|
+----+----+-+---------+---------+---------+---------+
|   2|100%|+|-0.660565| 0.999000| 0.837259| 0.123381|
+----+----+-+---------+---------+---------+---------+
|   3| 94%|-|-1.246627| 0.996000| 0.490364| 0.104294|
+----+----+-+---------+---------+---------+---------+
Stage training time: 889.36
Number of used features: 3

Parent node: NULL
Chosen number of splits: 0

Total number of splits: 0

Tree Classifier
Stage
+---+
|  0|
+---+

   0

Parent node: 0

*** 1 cluster ***
POS: 996 1000 0.996000
NEG: 465 0.524831
BACKGROUND PROCESSING TIME: 0.03
Precalculation time: 0.05
+----+----+-+---------+---------+---------+---------+
|  N |%SMP|F|  ST.THR |    HR   |    FA   | EXP. ERR|
+----+----+-+---------+---------+---------+---------+
|   1|100%|-|-0.635385| 1.000000| 1.000000| 0.182752|
+----+----+-+---------+---------+---------+---------+
|   2|100%|+|-0.333012| 0.996988| 0.544086| 0.149213|
+----+----+-+---------+---------+---------+---------+
|   3|100%|-|-0.999921| 0.996988| 0.544086| 0.082820|
+----+----+-+---------+---------+---------+---------+
|   4|100%|+|-0.692312| 0.996988| 0.389247| 0.063655|
+----+----+-+---------+---------+---------+---------+
Stage training time: 1205.64
Number of used features: 4

Parent node: 0
Chosen number of splits: 0

Total number of splits: 0

Tree Classifier
Stage
+---+---+
|  0|  1|
+---+---+

   0---1

Parent node: 1

*** 1 cluster ***
POS: 993 1000 0.993000
NEG: 463 0.230923
BACKGROUND PROCESSING TIME: 0.13
Precalculation time: 0.05
+----+----+-+---------+---------+---------+---------+
|  N |%SMP|F|  ST.THR |    HR   |    FA   | EXP. ERR|
+----+----+-+---------+---------+---------+---------+
|   1|100%|-|-0.912574| 1.000000| 1.000000| 0.160027|
+----+----+-+---------+---------+---------+---------+
|   2|100%|+|-1.372380| 1.000000| 1.000000| 0.298077|
+----+----+-+---------+---------+---------+---------+
|   3|100%|-|-1.029186| 1.000000| 1.000000| 0.073489|
+----+----+-+---------+---------+---------+---------+
|   4|100%|+|-1.168364| 0.996979| 0.796976| 0.073489|
+----+----+-+---------+---------+---------+---------+
|   5| 98%|-|-0.856654| 0.996979| 0.455724| 0.049451|
+----+----+-+---------+---------+---------+---------+
Stage training time: 1487.86
Number of used features: 5

Parent node: 1
Chosen number of splits: 0

Total number of splits: 0

Tree Classifier
Stage
+---+---+---+
|  0|  1|  2|
+---+---+---+

   0---1---2

Parent node: 2

*** 1 cluster ***
POS: 990 1000 0.990000
NEG: 462 0.1232
BACKGROUND PROCESSING TIME: 0.53
Precalculation time: 0.05
+----+----+-+---------+---------+---------+---------+
|  N |%SMP|F|  ST.THR |    HR   |    FA   | EXP. ERR|
+----+----+-+---------+---------+---------+---------+
|   1|100%|-|-0.606609| 1.000000| 1.000000| 0.199036|
+----+----+-+---------+---------+---------+---------+
|   2|100%|+|-0.848618| 1.000000| 1.000000| 0.199036|
+----+----+-+---------+---------+---------+---------+
|   3|100%|-|-0.527684| 0.996970| 0.614719| 0.152204|
+----+----+-+---------+---------+---------+---------+
|   4|100%|+|-0.781167| 0.996970| 0.623377| 0.154959|
+----+----+-+---------+---------+---------+---------+
|   5| 79%|-|-1.579804| 0.997980| 0.632035| 0.112948|

そもそもの目的が、ゲーム画面から既知の駒(時には未知の駒?)を見つけ出したいわけで、顔認識ほど正確でなくてもよいし、ある程度の場所を確定してから再マッチさせるという方式がやっぱりよさそうか、と思い直している次第です。

OpenCVで学ぶ画像認識:第3回 オブジェクト検出してみよう|gihyo.jp … 技術評論社
http://gihyo.jp/dev/feature/01/opencv/0003?page=2

にあるように、何もないところから特定のものを見つける(検出する率が低い)場合には、このような学習がよいのかもしれませんが、あらかじめ駒があるとわかっているゲームの盤上から探すには、もっと前処理をして絞り込んでもよいかなと。オブジェクト検出器の中で Ture/False の判断をするよりも、その前処理として Ture/False を大雑把に判断するロジックを挟む必要がありそうです。いちいち、駒の形状が変わるたびに、学習をさせるわけにはいかないし、学習自体にそれほど時間を掛ける意味はなさそうだし。

なので、決定木あたりかクラスタリングを使って、駒のツリー(今回は8種類程度だけど)を作るのがよいのでしょう。

のように、色味で分割で大雑把に分割ができるのは明らかなので、なんらかの特徴量を元に背景画像から抜き出した後に、相互に駒を判断するために再び特徴量を使うという2段階になるのでしょう。

そんな訳でオライリーの OpenCV を再読。

カテゴリー: 開発, OpenCV | OpenCV で機械学習を試してみる…とまだ終わらず はコメントを受け付けていません

データ指向設計でプロジェクトを成功に導けるのか?

思考を廻しておくために、ちょっと続きを

オブジェクト指向設計とプロジェクトの成功とは無関係なのか? | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/2527

「○○設計」や「○○手法」を使う場合、因果関係は薄いよからスタートすると、「じゃあ、何を使えば成功率は高くなるのか?」ってな話になります。まぁ、直接的な利益追求の話でいえば、直近のプロジェクトが云々ということになり、では先行きの学習のほうは?ということなりますが、まぁ、直近のプロジェクトを優先してみるという仮定で。

明確な形になっているかどうかは別として、要求定義→設計→実装→試験な、感じでソフトウェア開発工程が流れていくのは確かなことで、これがサイクル的に極めて早いか(アジャイル的に)、遅いか(ウォーターフォール的に)の違いはあるものの、程よく分けておくと議論がしやすくなります。無勝手流の場合は別として。

その中で、設計をする会社・人と実装をする会社・人が別のことが多い訳で、Web系以外はなんとなくわかれているのですね、これが。この分け方の理由としては、

  • 新人だから、設計できないので、実装から始める。
  • 外注だから、お客と話せないので、実装からスタートする。
  • 金銭上の関係で、実装となる。
  • 業務理解に乏しいので、実装側となる。

というような「設計」はできないけど「実装」はできる、という現実があります。

そんな場合、設計も実装も混在した形でアジャイル式に設計そして実装をやると、混乱が生まれる、と言いますか先に書いた記事のようにマイナス要因が多くなるので却下します。
できることならば、「設計」から「実装」への向きが変わらないほうが、スムースでマイナス要因が少ないわけです。

となれば、一方向になるように設計をする(画面設計も含めて)をするのがベターな話で、思考錯誤的にオブジェクト指向設計に落とし込んだり、便利機能を盛り込んであれこれとライブラリ化したものを組み合わせてしまうよりも、そりゃあ、データベースの構造をきっちりと作りこんだ後に、CRUD に従ってデータベースアクセスをきっちりする画面、関数構造を作っていけば、おおむね一応方向になります…ってのが、私の考えです。

「おおむね」と言うのは、関数として CRUD を揃えることは可能なのですが、あれこれと便利画面を用意するとこのあたり破綻します。あれこれできる GUI 画面は一見お客に便利なように見えますが、その分、実装側に負担が大きくなるために、この「一方向」の流れが崩されてしまいます。一方向の流れが崩れると例外が発生するので、要件→設計→実装の流れに齟齬が生まれはじめ、混沌としたやり取りが発生します。そこから、不具合なり見落としがでてきて、工数に時間が掛かったり試験による品質が悪くなったりしてしまいます。

なので、先の記事のようにマイナス要因を排除するという方針に従うと、あれとして便利画面を「入れません」ってのがデータ指向による設計の基本です。決まりきった方法でしかアクセスしないという方針を、ユーザーが使う画面に押し付けるという流れですね。

いや、そりゃちょっと昨今のソフトウェアでそれはないでしょ、ってことになるので、全部をデータ指向で突き進む訳にもいかないのです。

ならば、部分的にオブジェクト指向を導入してもよいですね、ってのが最近の結論です。全体的にオブジェクト指向の設計を入れたほうが隠蔽化とか汎用性とかが良いのですが、汎用性についてはデータ指向も負けていないわけで、コーディングの手順(手続き)や画面操作は多くなるものの正確にできるという点では、プログラミング(あるいは設計時)の能力に依存しないのです(実行時のパフォーマンス云々はちょっと問題ありかもしれませんが)。

あくまで部分的に最適なものを適用する。勿論、部分的な適用であるから最適化には限界があります。すぐに、天井があって、コーディング速度や開発効率などの上限にぶつかります。このあたりが、直近のプロジェクトの利益率を最大化する、という仮定の上にある話で、この「直近の」を外さない限り、このあたりの話は堂々巡りになってしまうのかなと。逆に「直近の」を外した場合はどうなるかというと、単一プロジェクトから複数プロジェクトの話になるので、もう少し選択肢が広がります、ってな話です。

カテゴリー: 設計, プロジェクト管理 | データ指向設計でプロジェクトを成功に導けるのか? はコメントを受け付けていません

オブジェクト指向設計とプロジェクトの成功とは無関係なのか?

ちょっとリハビリテーションがてら徒然に。

結論から言うと、オブジェクト指向設計を導入したからといって、プロジェクトが成功するとは限らず、逆にプロジェクトが失敗したからといって、オブジェクト指向設計を導入しなかったから、というには言えません。当たり前ですが…因果関係はそれほど強くない。

「○○設計」なり「○○手法」というものがあって、それを導入したからと言って必ず成功するとは限らない。言えるのは、成功する確率が上がるということしか言えない。逆パターンもあれば、導入すると失敗するとは言えないけれども、失敗する確率が上がる(成功する確率が下がる)パターンもある。これは、何を言うのかというと、「○○手法」を導入する場面、場所(人員も含む)、規模など諸々の条件があって、それが有効に働く場合と、働かない場合、むしろマイナスに働いてしまう場合もある、ということ。

が、かといって「○○設計」なり「○○手法」なりを導入しなくても良いかと云えば、そうでもなく、それは導入するということに対して、現時点のプロジェクトの成功だけはなく、次のプロジェクトの成功確率を上げるなり、他社との競争力を上げるなり、個人的なプログラミング能力をあげるなり、その場の流行であったり(これはモチベーションに関わる問題であたり)するわけで、単純にプロジェクトの成功/失敗だけによる結果だけで見るのは早計であるのです。

さて、長い前置きはそれとして、現実問題として「オブジェクト指向」を導入したほうがよいのか、あるいはオブジェクト指向的なアプローチを利用したほうがよいのか、という問題に話を始めると、

  • 「オブジェクト指向」を導入するならば、良いアプローチをせよ

ということに尽きる。逆に言えば、

  • 「良いアプローチ」ができそうでなければ、そのプロジェクトに「オブジェクト指向」を導入すると失敗する確率が高くなる

ということになる。

プロジェクトマネージメント的な言い方をすると、大きなプラスを求めるのではななく、大きなマイナス(あるいは小さなマイナス)を早期に発見・対処することによって、マイナス分を減らして、全体としてプラスに傾くようにするやり方が妥当である、ということになる。

具体的に云えば、

  • 関数的なアプローチしかできない人員の場合には、「オブジェクト指向」アプローチを避ける。
  • データ指向優先で、システム構成が決まっている場合は、「オブジェクト指向」を避ける。
  • システム設計ができていない場合は、「オブジェクト指向」を避ける。
  • 共通化部分が極端に少ない場合は、「オブジェクト指向」を避ける。

という判断があって、この「避けるべき条件」が積み重なるイコール、先行きプロジェクトが危うくなる条件がある、勝ってしまう場合には、オブジェクト指向を避けて、もともとある手続き型なアプローチや、データ指向型の設計を優先させる。

ちなみに、.NET で Windows フォームを使ってアプリケーションを作る場合、最低限 UI 部分はオブジェクト指向的、かつクラスライブラリを使う部分はオブジェクト指向的になるのだが、実際にプログラマがコーディングしなければいけない手続き部分は、さほどオブジェクト指向的にはならず、VB6 の頃からある手続き型のコーディング(関数型のコーディング)になる。

逆に「良いアプローチ」がどのようなポイントを示すのかというと、

  • システム設計段階で、オブジェクト指向的なアプローチがある。
  • NUnit などの自動化のテストケースを積極的に利用する用意がある。
  • オブジェクト指向のコード粒度や複雑度の話が理解でき、実行できる。

というものがある。

要件定義からシステム設計に落とすとき(システム設計というのは、ユーザーの要件をどのようにコンピューターが実行するのか、という物理設計を示している)、ここで手続きの流れのまま設計から実装まで突き進んでしまうのか、一度、共通的な手順、今後の拡張、クラスの強度などを考え直すうえで、システム構造をクラスとして考え直す、という流ればあれば、オブジェクト指向的なアプローチを導入する価値はある。

このあたり、要件定義→設計という流れの中で、これがウォーターフォール的に進むのか、イテレーション的に進むのか、アジャイル的に進むのか、というプロジェクトの進行の問題もある。数年前、このあたりを「アーキテクト」という言い方をしていたような気もするのだが、それはバズワードだったのか?、はここでは問わない。

ひとつ言えるのは、完璧な設計は存在しないし、完璧にオブジェクト指向を進めることは不可能であるので(完璧に近づくことが可能だにしても)、何らかの形で「後戻り」なり「再設計」が必要になる。ならば、現実的に考えて、「再設計あり」でプロジェクトを進めるのも大切であろうということ。逆に、再設計されそうな部分を「予知」しておくことも大切ということ。
このあたりは、職人的な勘というか、動物的な勘というか、そういう匂いがある場所を監視しておく(常に気にしておく)こととなる。体系的に学ぶことも可能だと思うのだが…それは別の機会にでも。

NUnit などの自動化のテストケースを導入する場合、手続き型の関数アプローチでも可能ではあるのだが、ものが大きくなれば、オブジェクト指向的なアプローチでクラスを分けたほうがやりやすくなる。と言うのも、自動化をする場合、対象のメソッドを動かすための前提条件を設定することになる。この前提条件が多ければ多いほど、テストケースを作るのは大変になる。なので、どのデータを設定したあとに、どのメソッドを動かすのか、という範囲が狭まればテストケースを作るのが楽になり、結果的にコーディングをしている時間が短くなり、プロジェクトに時間の余裕が出てくる。ゆえに、プロジェクトの成功確率は高くなる。

逆に言えば、オブジェクト指向、特に隠蔽性なり疎結合の話が理解できていないと、クラス構造に分けたとしてもクラス同士の連携が多くなりすぎて、自動化のテストケースのメリットは薄れてしまう。テストケースが、テストデータに密接に繋がってしまう故に、使い捨てのデータ(テストデータのためのデータ準備)が大変になってしまう。

で、まぁ、今回の業務的なアプローチ(プログラミングを行うという意味で)としては、大きな失敗を避けることはできたものの、いまいち「効果」が実感できるところまでいかなかった、というのが不満ではあります。この手の新規アプローチの導入に関しては、「失敗を避ける」というのが第一であって、実は「成功させる」というのは二の次なんですね。導入の説明としては「成功」へ導くわけですが、実はプロジェクトが失敗してしまうと、二度とそのアプローチに手がでなくなるという大きなマイナスがあるので、なんらかの形で「失敗させない」ことが優先事項になります。
なので、結果的に言えば、成功する失敗するの要因は、オブジェクト指向の導入や、NUnit の導入によるものではなくて、「失敗しない要因を揃えていく」あるいは「失敗しそうなアプローチを避ける(避けさせる)」というのが、今回の仕事だったりして。

カテゴリー: 設計, プロジェクト管理 | オブジェクト指向設計とプロジェクトの成功とは無関係なのか? はコメントを受け付けていません

相関係数を計算を自作する(完成)

マスク画像を使えるように、テンプレートマッチの関数を自作します。
templmatch.cpp 内の元ネタを使っても良かったのですが、相加平均を計算するところが、1/w*h で固定値になっているので、マスク画像を渡すと大幅に書き換えになってしまう…というのと、行列全体をフーリエ演算対象にしてしまっているので、そもそもマスク画像を渡すことができない。ってのが自作の理由です。

相関係数を計算を自作する(途中) | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/2509

前回は、RGB チャンネルを平坦に加算していたので誤検出多くなっていましたが、元の関数と同じようにチャンネル毎に相加平均を計算するという方式に変えました。すると、cv::matchTemplate と同じ結果が得られています(厳密にチェックした訳ではないので、違いはでてくるかも)。

このあたり、CV_TM_CCOEFF_NORMED を指定したときの検証用の関数を作る必要がありますね…先は長そう。

// 実画像の位置(dx,dy)に対して、相関係数を計算する
double crossCorr( const cv::Mat& img, const cv::Mat& templ, const cv::Mat& mask, int dx, int dy )
{
	int cnt = 0;
	// 対象ドット数を取得する
	for ( int y=0; y<templ.rows; ++y ) {
		for ( int x=0; x<templ.cols; ++x ) {
			if ( mask.at<char>(y,x) == 0 )
				continue;
			cnt++;
		}
	}

	// 分子(numerator)
	double numerator = 0.0;
	// 分母(denominator)
	double denominator_templ = 0.0;
	double denominator_img   = 0.0;

	// チャンネル毎(RGB)に計算する
	for ( int k=0; k<3; ++k ) {
		double templMean = 0.0;	// テンプレートの相加平均
		double imgMean = 0.0;	// 実画像の相加平均

		for ( int y=0; y<templ.rows; ++y ) {
			for ( int x=0; x<templ.cols; ++x ) {
				if ( mask.at<char>(y,x) == 0 )
					continue;

				cv::Vec3b v1 = templ.at<cv::Vec3b>(y,x);
				cv::Vec3b v2 = img.at<cv::Vec3b>(y+dy,x+dx);
				templMean += v1[k];
				imgMean += v2[k];
			}
		}
		templMean /= (double)cnt;
		imgMean   /= (double)cnt;

		for ( int y=0; y<templ.rows; ++y ) {
			for ( int x=0; x<templ.cols; ++x ) {
				if ( mask.at<char>(y,x) == 0 )
					continue;
				cv::Vec3b v1 = templ.at<cv::Vec3b>(y,x);
				cv::Vec3b v2 = img.at<cv::Vec3b>(y+dy,x+dx);
				numerator += (v1[k] - templMean)*(v2[k] - imgMean);
				denominator_templ += (v1[k] - templMean)*(v1[k] - templMean);
				denominator_img += (v2[k] - imgMean)*(v2[k] - imgMean);
			}
		}
	}
	// 分母を計算する
	double denominator = sqrt(denominator_templ)*sqrt(denominator_img);
	// 相関係数を計算する
	double corr = numerator/denominator;

	return corr;
}

// 実画像のすべてに対して相関係数を計算する
void MatchTemplate( const cv::Mat& img, const cv::Mat& templ, cv::Mat& result, const cv::Mat& mask )
{
	cv::Size corrsize(img.cols-templ.cols, img.rows-templ.rows);
	result.create(corrsize, CV_64F);

	for ( int y=0; y<corrsize.height; ++y ) {
		for ( int x=0; x<corrsize.width; ++x ) {
			double corr = crossCorr( img, templ, mask,x,y );
			result.at<double>(y,x) = corr ;
		}
	}
}

テンプレートマッチング法を使うときに、OpenCV の cv::matchTemplate では矩形に縛られるので、マッチングしたい画像の背景にあるものに影響を受けてしまう…と考えるわけで、教師画像を

のように、背景を抜くためのマスク画像を作ればよいのではないか?という想定です。

で、実際に実験をしてみると、

■マスク画像を使わない場合

■マスク画像を使う場合

マスク画像を使わない場合は、いちごの駒を誤検出していますが、マスク画像を使った場合はみかんの駒だけを検出している…というか、左下の白い領域をぼろぼろと誤検出していますね orz 。
おそらく、マッチングの評価関数に相関係数を使っているので、この白い領域の色ムラがちょうどみかんの教師画像の色ムラとあっているのではないかとと想像できます(相加平均からの差になるので、実画像の色のムラがたまたまマッチングしていると、こういう具合になってしまうかと)。これは、RGB 値の相関係数を使うときの欠点ではなかと。

あと、興味深いマッチングの仕方として、

のように、上記のような教師画像の色がうまく背景の画像にマッチングしてしまっている例があります。みかんの色がオレンジなので、まんなかにオレンジがある領域をさがしてしまうためです。これはマスク画像を使ったときの欠点ですね。

同じ画像でマスクを使わないと次の図になります。

右上のみかんの駒だけが検出されます。これは背景画像をマッチングさせているからです。

マスク画像を使いたいのは、次のように背景が切り替わった場合にも対応させたいためです。

背景差分を取って背景画像のほうを切り取るのもひとつの方法なのですが、検索対象となる教師画像のほうの背景画像を切り取ってしまうほうがより有効ではないかなと。
まあ、この図の場合、みかんだけでなくキウイ(かな?)もマッチングしてしまっている訳で、これも調節に必要ありですが。

カテゴリー: 開発, C++, OpenCV | 3件のコメント

相関係数を計算を自作する(途中)

結局、自作してみる… templmatch.cpp 内にある crossCorr 関数で相関平均を計算しているのだが、フーリエ解析を使っているためか値が違う。

後でコードを見直そう。

// 実画像の位置(dx,dy)に対して、相関係数を計算する
double crossCorr( const cv::Mat& img, const cv::Mat& templ, const cv::Mat& mask, int dx, int dy )
{
	int cnt = 0;

	double templMean = 0.0;	// テンプレートの相加平均
	double imgMean   = 0.0;	// 実画像の相加平均 
	
	for ( int y=0; y<templ.rows; ++y ) {
		for ( int x=0; x<templ.cols; ++x ) {
			if ( mask.at<char>(y,x) == 0 ) 
				continue;
			cv::Vec3b v1 = templ.at<cv::Vec3b>(y,x);
			cv::Vec3b v2 = img.at<cv::Vec3b>(y+dy,x+dx);
			templMean += v1[0] + v1[1] + v1[2];
			imgMean += v2[0] + v2[1] + v2[2];
			cnt += 3;
		}
	}
	templMean = templMean/(double)cnt;
	imgMean = imgMean/(double)cnt;

	// 分子(numerator)
	double numerator = 0.0;
	for ( int y=0; y<templ.rows; ++y ) {
		for ( int x=0; x<templ.cols; ++x ) {
			if ( mask.at<char>(y,x) == 0 ) 
				continue;

			cv::Vec3b v1 = templ.at<cv::Vec3b>(y,x);
			cv::Vec3b v2 = img.at<cv::Vec3b>(y+dy,x+dx);
			numerator += (v1[0] - templMean)*(v2[0] - imgMean);
			numerator += (v1[1] - templMean)*(v2[1] - imgMean);
			numerator += (v1[2] - templMean)*(v2[2] - imgMean);
		}
	}
	// 分母(denominator)
	double denominator_templ = 0.0;
	double denominator_img   = 0.0;
	for ( int y=0; y<templ.rows; ++y ) {
		for ( int x=0; x<templ.cols; ++x ) {
			if ( mask.at<char>(y,x) == 0 ) 
				continue;
			cv::Vec3b v1 = templ.at<cv::Vec3b>(y,x);
			cv::Vec3b v2 = img.at<cv::Vec3b>(y+dy,x+dx);

			denominator_templ += (v1[0] - templMean)*(v1[0] - templMean);
			denominator_templ += (v1[1] - templMean)*(v1[1] - templMean);
			denominator_templ += (v1[2] - templMean)*(v1[2] - templMean);
			denominator_img += (v2[0] - imgMean)*(v2[0] - imgMean);
			denominator_img += (v2[1] - imgMean)*(v2[1] - imgMean);
			denominator_img += (v2[2] - imgMean)*(v2[2] - imgMean);
		}
	}
	double denominator = sqrt(denominator_templ)*sqrt(denominator_img);

	return numerator/denominator;
}

// 実画像のすべてに対して相関係数を計算する
void MatchTemplate( const cv::Mat& img, const cv::Mat& templ, cv::Mat& result, const cv::Mat& mask )
{
	cv::Size corrsize(img.cols-templ.cols, img.rows-templ.rows);
	result.create(corrsize, CV_64F); 

	for ( int y=0; y<corrsize.height; ++y ) {
		for ( int x=0; x<corrsize.width; ++x ) {
			double corr = crossCorr( img, templ, mask,x,y );
			result.at<double>(y,x) = corr ;
		}
	}
}

ちなみに、Mat:at<> を使うとデバッグ実行ではひどく遅いのだが、Release ビルドをすると結構速い。
おそらく、mat.hpp に定義されている

template<typename _Tp> inline _Tp& Mat::at(int i0, int i1)
{
    CV_DbgAssert( dims <= 2 && data && (unsigned)i0 < (unsigned)size.p[0] &&
        (unsigned)(i1*DataType<_Tp>::channels) < (unsigned)(size.p[1]*channels()) &&
        CV_ELEM_SIZE1(DataType<_Tp>::depth) == elemSize1());
    return ((_Tp*)(data + step.p[0]*i0))[i1];
}

で設定されている CV_DbgAssert の関数が遅いわけで、これはリリースモードでは外れるようになっている。
この部分を、CV_DbgAssert を使わないように展開してやれば、早くなるのではないかなぁと。

カテゴリー: 開発, 雑談, C++, OpenCV | 相関係数を計算を自作する(途中) はコメントを受け付けていません

特定点でテンプレートマッチングを行うための考察

テンプレートマッチング法を使うと、教師画像を実画像から簡単に検索できるのだが、相関係数(CV_TM_CCOEFF_NORMED)を使って探索をすると結構遅い…というか、これが目で駒を追うやり方なのかどうかが疑問だったりします。

なので、以下は、ちょっと考察。

なにも、教師画像のすべてをマッチングさせる必要はなくて、教師画像の特徴を掴んで、実画像とマッチングさせればよいのです。これを特徴量という言い方をするのですが、この特徴量の定義として考えられるのが、

  • 教師画像の、色で検出する。
  • 教師画像の、形で検出する。
  • 教師画像の、白黒画像で検出する。
  • 教師画像の、角で検出する。

というやり方があります。

画像処理 (3) 顕著性マップ
http://fussy.web.fc2.com/algo/algo12-3.htm

をつらつらと読んで考えたのが、教師画像での顕著性のある場所が、実画像のマッチングする枠と比較してどうなのか?ということで、教師画像の特徴を掴むという行為は、実は、「色」だったり「形」だったり、「白黒」画像の「白」の位置だったり、「角」っこであったりするわけです。そう、「あったり」する訳なので、実は、それぞれの場面によって異なるのが普通ではないかと。

そうなると、教師画像を1枚だけ見て、どこに特徴があるのか?という行為/計算では足りなくて、実は、

  • 複数の教師画像から、それぞれの相違のある箇所(=特徴量)を見出す。
  • 教師画像と実画像をいくつか比較して、正解しやすい特徴量を学習する。

というパターンになります。

で、このどちらのパターンであっても、特徴量を検出する場合には、色/形状/輝度/角などの検出を決め打ちするのではなくて、その場面によって(実画像からどのような教師画像を見つけるのかなど)、特徴量の検出パターンをどれかに決めるという手順が必要ではないかと。

例えば、今、手元で実験している3マッチのアクションパズルは、果物を使っているので形状でも認識できそうですが、ちょっと前のバージョンでは形状が全く同じなので、でしか判別できません。


また、二角取りのように麻雀パイを使う場合には、マッチングの分類を「萬子」「索子」「筒子」「その他」に分けて検出しないとスピードが上がりません。と、言いますかマッチングパズルをしているときに、そういう見方を私はしています。

なので、画像をマッチングで検出する場合には、

  1. 教師画像の特徴を検出する(色、形、輝度、角など)。
  2. 教師画像の特徴点を決定する(背景画像の除去、不必要なノイズは検出させない)
  3. 教師画像同士で、1,2 が妥当であるか検証する

という前処理があった後

  1. 初回は、実画像の全箇所をテンプレートマッチする?
  2. あるいは、ランダムにマッチング箇所を決定する?

    あるいは、顕著性マップなどから、注目点と特定する?

  3. 次回以降は、前回の箇所から探索をする。
  4. 見つからない場合は、4 に戻る。

  5. 検出箇所を、教師画像と再検証させる。

という探索が必要になるのかなと。

実画像(検索の対象画像)をランダムに検索しようとするのは、目的が動画(リアルタイム)からのピックアップにあるので、実画像からの駒の検出は、計算時間を 30fps の間に収めたいわけです。時間にすると 33 msec というところですね。
この短い間に計算を終えるためには、あらかじめ、検出すべき特徴を頭に叩き込んでおいて実画像から素早くピックアップしなければいけません。例えるならば、サッカー選手が首を振った瞬間に、チームカラーで味方と敵の選手の位置を把握するようなものです。なので、あらかじめ教師画像から特徴量を決めておき、検出する箇所も数か所から数十か所に絞り込んでおいて、実画像とマッチングさせます。この場合、誤検出は出るでしょうが、それは 6 の再検出あるいは、特徴自体を数種類で取り込むことで検出率を上げるようにします(検出率を上げるという確率の方法を取り、100% の完全性は求めないところがミソです)。

で、そんなこんなで、特徴箇所だけでマッチングをするためには、cv::matchTemplate 関数にマスク値か探索位置を渡す必要があるので、コードを改造するしかないのかなぁ、と思ってソースを追っていたところ。

templmatch.cpp の 235 行目から cv::matchTemplate 関数の記述があって、sum 部分は 269 行目から、2 次元の繰り返し部分は、318 行目からになっているので、それなりに改造できそうかなと。

    for( i = 0; i < result.rows; i++ )
    {
        float* rrow = (float*)(result.data + i*result.step);
        int idx = i * sumstep;
        int idx2 = i * sqstep;

        for( j = 0; j < result.cols; j++, idx += cn, idx2 += cn )
        {
			// ここで mask を渡しておいて、
			// if ( mask(i,j) == 0 ) continue ;
			// のように skip させれば ok ?
			// 加えて、sum を計算しているところも mask を見るように修正?

            double num = rrow[j], t;
            double wndMean2 = 0, wndSum2 = 0;

            if( numType == 1 )
            {
				// CV_TM_CCOEFF CV_TM_CCOEFF_NORMED のループ
                for( k = 0; k < cn; k++ )
                {
                    t = p0[idx+k] - p1[idx+k] - p2[idx+k] + p3[idx+k];
                    wndMean2 += CV_SQR(t);
                    num -= t*templMean[k];
                }

                wndMean2 *= invArea;
            }

            if( isNormed || numType == 2 )
            {
                for( k = 0; k < cn; k++ )
                {
                    t = q0[idx2+k] - q1[idx2+k] - q2[idx2+k] + q3[idx2+k];
                    wndSum2 += t;
                }

                if( numType == 2 )
                    num = wndSum2 - 2*num + templSum2;
            }

            if( isNormed )
            {
				// CV_TM_CCOEFF_NORMED の箇所
                t = sqrt(MAX(wndSum2 - wndMean2,0))*templNorm;
                if( fabs(num) < t )
                    num /= t;
                else if( fabs(num) < t*1.125 )
                    num = num > 0 ? 1 : -1;
                else
                    num = method != CV_TM_SQDIFF_NORMED ? 0 : 1;
            }

            rrow[j] = (float)num;
        }
    }

これを流用してマスク画像を渡す方式にするのか、x,y 座標を含めた 1 次元配列を渡すのがよいのか、思案中。
2 次元の mask を取るほうが楽みたいだが。

カテゴリー: 開発, 雑談, OpenCV | 特定点でテンプレートマッチングを行うための考察 はコメントを受け付けていません

jsMath を使う

試しに jsMath で数式を書いてみる。

\int^1_\kappa
\left[\bigl(1-w^2\bigr)\bigl(\kappa^2-w^2\bigr)\right]^{-1/2} dw
= \frac{4}{\left(1+\sqrt{\kappa}\,\right)^2} K
\left(\left(\frac{1-\sqrt{\kappa}}{1+\sqrt{\kappa}}\right)^{\!\!2}\right)

jsMath: Web ページで数式を表示する一つの方法
http://math3.ifdef.jp/jsmath/welcome.htm

あたりを参考に

相関係数とか、

\sum_{i=1}^n \left( x_i – \overline{x} \right) \left( y_i – \overline{y} \right)
\over{
\sqrt{ \sum^n_{i=1} \left( x_i – \overline{x} \right)^2 }
\sqrt{ \sum^n_{i=1} \left( y_i – \overline{y} \right)^2 }
}

ソースは、数式をダブルクリックすると見れます。

カテゴリー: 雑談 | 1件のコメント