JSON + CanvasでVisual Code Reading×
OpenCVってどうやって顔を見つけているのか気になりませんか。気になりますよね。というわけで、ちょっとソースを眺めてみました×
その結果……よくわかりません!そうですよね。学者さん達の研究の結晶ですからね。ぱっと見て分かるわけがありません×
やっぱり論文とか本とか読まなきゃいけないのかな。英語のは結構ネットで読めるけど、日本語のは図書館に行かないとなかなか……×
よし。図書館に行く前に、もうちょっと頑張ってプログラムを読んでみましょう。答えはここにあるわけですから×
データを視る
プログラムを読むのは、実に簡単ですよね。文字で書いてあるわけですから。あと、プログラムの動作の流れも、デバッガでステップ実行すれば簡単に分かります×
難しいのは……そう、データですね。確かにデバッガを使えば変数の値ぐらいは見えますけど、今扱っているのは画像ですから、変数の値がちょっと見えたぐらいではクソの役にも立ちません×
ハッカーの人たちは、もっと強力なツールを使ってるのかな?dddとか?でも、yunocchiは難しいツールの使い方はわからないし、商用の製品を買うお金もありません×
こんなとき、yunocchiはWebブラウザを使います。Webブラウザは、HTMLレンダラ、SVGレンダラ、そしてCanvasといった「視覚化ツール」を取り揃えています。これを使わない手はありません×
OpenCVで顔の認識をする場合、cvHaarDetectObjects という関数を使います。この関数を眺めると、sum と sqsum という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 < つづく