Twitter!

JavaScript+canvasで顔認識

実行結果

Face Detector
また意味の分からない物を作った。上のは顔認識のデモ。
下記のエントリーのアルゴリズムを移植して作った。
Face detection in pure PHP (without OpenCV) - Maurice Bloggue
HTML5のお陰でクライアントサイドで画像処理ができるし、上記の顔認識プログラムはピュアPHPでライブラリ使ってるわけじゃないからJavaScriptでもできた。
もちろんIEは無視。

移植するとき面倒だったのがJSとPHPの微妙な仕様の違い。
認識用のデータに誤差が出てるみたいだから、仕方なくMath.roundで丸めた。
ブラウザによって結果が変わることがある。特に複数人が写った画像の場合はそれが顕著になる。
顔認識用のJSは認識用のデーターを含んでいるから、140KBくらいある。
使い方は、FaceDetector.detect(img);で、imgにはnew Image()などで生成された画像オブジェクトを指定する。
返り値は{x:(X座標),y:(Y座標),w:(顔のサイズ)}の形式。
圧縮したものを、ここに公開してあるので自由にお使いください。
認識用データを除いたコードは以下に。

FaceDetector={
	detection_data:null,
	_extimg:function (img,size){
		var cvs=document.createElement("canvas");
		cvs.width=size.width;
		cvs.height=size.height;
		var g=cvs.getContext("2d");
		g.drawImage(img,0,0,size.width,size.height);

		var inp=g.getImageData(0,0,size.width,size.height);

		return {
			get:function (x,y){
				var base=(y*inp.width+x)*4;
				return {r:inp.data[base],g:inp.data[base+1],b:inp.data[base+2]};
			},
			width:inp.width,
			height:inp.height
		};
	},
	detect:function (img){

		function getimgstats(c)
		{
			var w=c.width;
			var h=c.height;
			var iis=compute_ii(c,w,h);

			return iis;
		}

		function compute_ii(c,w,h)
		{
			var iiw=w+1;
			var iih=h+1;
			var ii={};
			var ii2={};

			for(var i=0;i<iiw;i++)ii[i]=ii2[i]=0;

			for(var i=1;i<iiw;i++)
			{
				ii[i*iiw]=0;
				ii2[i*iiw]=0;
				var rowsum=0;
				var rowsum2=0;

				for(var j=1;j<iih;j++)
				{
					var rgb=c.get(j,i);

					var gray=(0.2989*rgb.r+0.587*rgb.g+0.114*rgb.b)>>0;
					rowsum+=gray;
					rowsum2+=gray*gray;

					var iiabove=(i-1)*iiw+j;
					var iithis=i*iiw+j;
					ii[iithis]=ii[iiabove]+rowsum;
					ii2[iithis]=ii2[iiabove]+rowsum2;
				}

			}

			return {ii:ii,ii2:ii2,w:w,h:h};
		}

		function detect_b2s(iis)
		{
			var sw=iis.w/20;
			var sh=iis.h/20;
			var startScale=(sh<sw)?sh:sw;
			var scaleUpdate=1/1.2;

			for(var scale=startScale;scale>1;scale*=scaleUpdate)
			{
				var w=(20*scale)>>0;
				var endx=iis.w-w-1;
				var endy=iis.h-w-1;

				var step=Math.max(scale,2)>>0;
				var invarea=1/(w*w);

				for(var y=0;y<endy;y+=step)
				{

					for(var x=0;x<endx;x+=step)
					{
						var passed=detect_sub(x,y,scale,iis,w,iis.w+1,invarea);

						if(passed)return {x:x,y:y,w:w};
					}

				}

			}

			return null;
		}

		function detect_sub(x,y,scale,iis,w,iiw,invarea)
		{
			var mean=(iis.ii[(y+w)*iiw+x+w]+iis.ii[y*iiw+x]-iis.ii[(y+w)*iiw+x]-iis.ii[y*iiw+x+w])*invarea;
			var vnorm=(iis.ii2[(y+w)*iiw+x+w]+iis.ii2[y*iiw+x]-iis.ii2[(y+x)*iiw+x]-iis.ii2[y*iiw+x+w])*invarea-mean*mean;
			vnorm=(vnorm>1)?Math.sqrt(vnorm):1;

			for(var i_stage in FaceDetector.detection_data)
			{
				var stage=FaceDetector.detection_data[i_stage];

				var trees=stage[0];
				var stageThresh=stage[1];
				var stageSum=0;

				for(var i_tree in trees)
				{
					var tree=trees[i_tree];
					var currentNode=tree[0];
					var treeSum=0;

					while(currentNode!=null)
					{
						var vals=currentNode[0];
						var nodeTresh=vals[0];
						var leftval=vals[1];
						var rightval=vals[2];
						var leftidx=vals[3];
						var rightidx=vals[4];
						var rects=currentNode[1];
						var rectSum=0;

						for(var i_rect in rects)
						{
							var s=scale;
							var rect=rects[i_rect];
							var rx=(rect[0]*s+x)>>0;
							var ry=(rect[1]*s+y)>>0;
							var rw=(rect[2]*s)>>0;
							var rh=(rect[3]*s)>>0;
							var wt=rect[4];
							var rSum=(iis.ii[(ry+rh)*iiw+rx+rw]+iis.ii[ry*iiw+rx]-iis.ii[(ry+rh)*iiw+rx]-iis.ii[ry*iiw+rx+rw])*wt;
							rectSum+=rSum;
						}

						rectSum*=invarea;
						currentNode=null;
						
						if(rectSum>=nodeTresh*vnorm)
						{
							if(rightidx==-1)treeSum=rightval;
							else currentNode=tree[rightidx];
						}else{
							if(leftidx==-1)treeSum=leftval;
							else currentNode=tree[leftidx];
						}

					}

					stageSum+=treeSum;
				}


				if(stageSum+0.08<stageThresh)return false;
			}

			return true;
		}

		var ratio=0;
		var size={width:img.width,height:img.height};
		var diff={width:320-size.width,height:240-size.height};

		if(diff.width>diff.height)
		{
			ratio=size.width/320;
		}else{
			ratio=size.height/240;
		}

		if(ratio!=0)
		{
			size.width/=ratio;
			size.height/=ratio;
		}

		var c=FaceDetector._extimg(img,size);
		var stats=getimgstats(c);
		var face=detect_b2s(stats);

		if(face==null)return null;

		if(ratio!=0)
		{
			face.x*=ratio;
			face.y*=ratio;
			face.w*=ratio;
		}

		return face;
	}
};

出来る人は高速化してねー!俺の技術力じゃ無理。
まぁ、これが何に使えるかって言う話だけどね。