JSON + CanvasでVisual Code Reading×

OpenCVってどうやって顔を見つけているのか気になりませんか。気になりますよね。というわけで、ちょっとソースを眺めてみました×

その結果……よくわかりません!そうですよね。学者さん達の研究の結晶ですからね。ぱっと見て分かるわけがありません×
やっぱり論文とか本とか読まなきゃいけないのかな。英語のは結構ネットで読めるけど、日本語のは図書館に行かないとなかなか……×
よし。図書館に行く前に、もうちょっと頑張ってプログラムを読んでみましょう。答えはここにあるわけですから×

データを視る

プログラムを読むのは、実に簡単ですよね。文字で書いてあるわけですから。あと、プログラムの動作の流れも、デバッガでステップ実行すれば簡単に分かります×
難しいのは……そう、データですね。確かにデバッガを使えば変数の値ぐらいは見えますけど、今扱っているのは画像ですから、変数の値がちょっと見えたぐらいではクソの役にも立ちません×
ハッカーの人たちは、もっと強力なツールを使ってるのかな?dddとか?でも、yunocchiは難しいツールの使い方はわからないし、商用の製品を買うお金もありません×
こんなとき、yunocchiはWebブラウザを使います。Webブラウザは、HTMLレンダラ、SVGレンダラ、そしてCanvasといった「視覚化ツール」を取り揃えています。これを使わない手はありません×

OpenCVで顔の認識をする場合、cvHaarDetectObjects という関数を使います。この関数を眺めると、sumsqsum という2つの変数を使っていろいろ計算していることがわかります。どうやら、この2つの変数が入力画像と関係があるようです。――ええ、そうです。これらは入力画像そのものではないんですね。コードを眺めたところ、入力画像そのものは、img という変数に格納されているようです。まず、この事実を確認してみます×

printf("{\n");
printf("  \"name\"  : \"img\",\n");
printf("  \"width\" : %d,\n", img->width);
printf("  \"height\": %d,\n" , img->height);
printf("  \"step\"  : %d,\n" , img->step);
printf("  \"data\"  : ["); dumpData(img); puts("]");
printf("}\n\n");
fflush(stdout); 

printfデバッグ!この時代に!!とか気になる人は……まあ、適当なロギングツールを使ってください。それより、printfしている中身です。これはJSONです。JSONでデータを吐いておけば、Webブラウザに容易に読み込ませることが出来ます。printfしてJavascriptのソースに貼り付けるだけで、ブラックボックスからデータを取り出すことが出来ます。もうちょっと複雑な構造のデータであっても、JSONならprintfで充分組み立てられます×
dumpData という関数は、JSON出力のために勝手に作った関数です。この関数は、img->data.ptr が指すバッファの中身をカンマ区切りで出力します×

さて、これを実行すると以下のようなJSONになります。

{
  "name"  : "img",
  "width" : 439,
  "height": 599,
  "step"  : 440,
  "data"  : [255,254,255,255,254,255 /* …長いので省略 */ ]
}

width と height は、なんか妥当そうな値ですね。step というのは、バッファ中での1ラインあたりのバイト数です。これも問題なさそうです(今は、1byteのpaddingが入っている理由は放っておきます)問題はdataの中身です。これを並べると入力画像が再現されるはずです×
最新のWebブラウザ――たとえばFirefoxは、Canvasでラスタ画像を扱うことが出来ます。具体的に言うと、getImageData / putImageDataというAPIです。これらのAPIを使うと、Canvasの内容をラスタデータとして取得したり、ラスタデータを書き込んだりといったことができます。では、先ほどのJSONに本当に入力画像が含まれているか確認してみます×

/* source は、上記のJSONオブジェクトを保持しています */
var cv = document.getElementById('canvas');
var g = cv.getContext('2d');
var buf = g.getImageData(0, 0, source.width, source.height);

var len = source.width * 200;
var dest_pos = 0;
var k = 0;
for (var i = source.width;i < len;i++)
{
	k = source.data[i];
	buf.data[dest_pos++] = k;
	buf.data[dest_pos++] = k;
	buf.data[dest_pos++] = k;
	buf.data[dest_pos++] = 255;
}
g.putImageData(buf, 0, 0);

まず、getImageData で canvas の内容を取得します(もちろん真っ白ですが)。生のラスタデータは、返ってきたオブジェクトの data プロパティに配列として入っています。この配列は、R,G,B,A,R,G,B,A,R,G,B,A……というふうに、4つの要素で1つのピクセルを表しています。いま、入力画像はモノクロ8bitなので、R,G,Bに同じ値をセットして、Aには255を入れておきます。こうして書き換えたオブジェクトを putImageData で canvas に戻します×
さあ、どうでしょうか……

出ました! これは確かに又吉さんのポスターです。これで img に入力画像が入っていることは確定しました。それでは、sum と sqsum ですが…

X / _ / X < つづく