“「強いられているんだ!」を顔認識でやってみた”をFlashに移植してみた

強いられているんだ! ガンダムAGEの「強いられているんだ!(集中線)」を顔認識でやってみた ‐ ニコニコ動画(原宿) ↑の発想に感動したのでFlashに移植してみました。 ブラウザ上で手軽に強いられることができて便利です。

プログラム

Shiirare.swf

Flash Player required.

使い方

WebカメラがついてるPCなら、カメラの使用の許可を求められると思うので、許可をクリックするとWebカメラの映像から顔認識をして、強いられることができます。

使用したライブラリ

ActionScriptで顔認識のライブラリが公開されていたのですぐに実装できました。楽しいですね。

ソース

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="<a href="http://ns.adobe.com/mxml/2009" target="_blank" rel="noreferrer" style="cursor:help;display:inline !important;">http://ns.adobe.com/mxml/2009</a>"
			   xmlns:s="library://ns.adobe.com/flex/spark"
			   xmlns:mx="library://ns.adobe.com/flex/mx"
			   height="542" minWidth="640" minHeight="542" applicationComplete="init();">
	<fx:Script source="main.as">
	</fx:Script>
	<fx:Declarations>
		<!-- 非ビジュアルエレメント (サービス、値オブジェクトなど) をここに配置 -->
	</fx:Declarations>
	<s:VideoDisplay id="videoDisp" width="640" height="480"
					horizontalCenter="0" verticalCenter="-46"/>
	<s:HGroup width="384" height="52" horizontalAlign="center" horizontalCenter="0"
			  verticalCenter="247">
		<s:CheckBox label="字幕の表示" id="textVisible" selected="true"/>
		<s:Spacer width="76" height="10"/>
		<s:Button label="画面をキャプチャして保存" id="captureButton" click="captureButtonHandler();"/>

	</s:HGroup>
</s:Application>
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.MovieClip;
import flash.display.Sprite;
import flash.events.StatusEvent;
import flash.events.TimerEvent;
import flash.geom.Matrix;
import flash.geom.Rectangle;
import flash.media.Camera;
import flash.media.Video;
import flash.net.FileReference;
import flash.utils.ByteArray;
import flash.utils.Timer;
import frocessing.display.F5MovieClip2D;
import jp.maaash.ObjectDetection.ObjectDetector;
import jp.maaash.ObjectDetection.ObjectDetectorEvent;
import jp.maaash.ObjectDetection.ObjectDetectorOptions;
import mx.graphics.codec.PNGEncoder;
private const BMP_WIDTH:int = 320;
private const BMP_HEIGHT:int = 240;
private var video:Video;
private var bitmapData:BitmapData;
private var detector:ObjectDetector;
private var options:ObjectDetectorOptions;
private var faceRectContainer:Sprite;
private var bmpTarget:Bitmap;
private var camera:Camera;
private var scaleW:Number;
private var scaleH:Number;
private var shuchuLine:F5MovieClip2D;
[Embed(source = "text.swf")]
private var myText:Class;
private var myTextMC:MovieClip;

private function init():void
{
	initUI();
	initDetector();
	//予め bmpTargetの大きさ/videoDispの大きさを計算
	scaleW = BMP_WIDTH / videoDisp.width;
	scaleH = BMP_HEIGHT / videoDisp.height;
}

private function initUI():void
{
	//カメラの準備
	camera = Camera.getCamera();
	camera.setMode(640, 480, 15);
	camera.addEventListener(StatusEvent.STATUS, statusHandler);
	video = new Video(videoDisp.width, videoDisp.height);
	video.attachCamera(camera);
	videoDisp.addChild(video); //無編集
	//集中線描画用コンテナ
	faceRectContainer = new Sprite;
	videoDisp.addChild(faceRectContainer);
	//集中線
	shuchuLine = new F5MovieClip2D(false);
	faceRectContainer.addChild(shuchuLine);
	//MC用意
	myTextMC = new myText();
	myTextMC.x = (videoDisp.width - myTextMC.width) / 2;
	myTextMC.y = videoDisp.height - myTextMC.height * 1.5;
	myTextMC.visible = false;
	videoDisp.addChild(myTextMC);
}

private function drawShuchuLine(x:int, y:int, w:int, h:int):void
{
	shuchuLine.stroke(0xFFFFFF, shuchuLine.random(0.7, 0.3));
	const MAX_RADIUS:Number = 2000;
	var degree:Number = 0;
	var i:Number = shuchuLine.random(30);
	while (degree < 360) {
		var min_radius:Number = shuchuLine.random(500, (w + h) * 0.52);
		shuchuLine.line(MAX_RADIUS * Math.cos(degree) + x, MAX_RADIUS * Math.sin(degree) + y, min_radius * Math.cos(degree) + x, min_radius * Math.sin(degree) + y);
		degree += (Math.sin(i * 3) + 1) / 4;
		i++;
	}
}

private function statusHandler(event:StatusEvent):void
{
	switch (event.code) {
		case "Camera.Unmuted":
			trace("User clicked Accept.");
			var timer:Timer = new Timer(100, 0);
			timer.addEventListener(TimerEvent.TIMER, function():void
			{
				startDetection();
			});
			timer.start();
			break;
	}
}

private function initDetector():void
{
	detector = new ObjectDetector;
	detector.options = getDetectorOptions();
	detector.loadHaarCascades("face.zip");
	detector.addEventListener(ObjectDetectorEvent.DETECTION_COMPLETE, function(e:ObjectDetectorEvent):void
	{
		trace("[ObjectDetectorEvent.COMPLETE]");
		if (e.rects) {
			shuchuLine.clear();
			myTextMC.visible = false;
			e.rects.forEach(function(r:Rectangle, idx:int, arr:Array):void
			{
				if (textVisible.selected) {
					myTextMC.visible = true;
				}
				drawShuchuLine(r.x / scaleW + r.width / scaleW / 2, r.width / scaleW + r.height / scaleH / 2.5, r.width / scaleW, r.height / scaleH);
				//頭の部分を考慮して中心を上にずらす
			});
		}
	});
//	var events:Array = [ObjectDetectorEvent.DETECTION_START, ObjectDetectorEvent.HAARCASCADES_LOAD_COMPLETE, ObjectDetectorEvent.HAARCASCADES_LOADING];
//	events.forEach(function(t:String, idx:int, arr:Array):void
//	{
//		detector.addEventListener(t, function(e:ObjectDetectorEvent):void
//		{
//			trace("ntime: " + (new Date) + " " + e.type);
//		});
//	});
}

private function startDetection():void
{
	trace("[startDetection]");
	//memo: bmpTargetがでかいと処理が重くなるので縮小して処理
	var scaleMatrix:Matrix = new Matrix();
	scaleMatrix.scale(scaleW, scaleH);
	bmpTarget = new Bitmap(new BitmapData(BMP_WIDTH, BMP_HEIGHT, false))
	bmpTarget.bitmapData.draw(videoDisp, scaleMatrix);
	detector.detect(bmpTarget);
}

private function getDetectorOptions():ObjectDetectorOptions
{
	options = new ObjectDetectorOptions;
	options.min_size = 50;
	options.startx = ObjectDetectorOptions.INVALID_POS;
	options.starty = ObjectDetectorOptions.INVALID_POS;
	options.endx = ObjectDetectorOptions.INVALID_POS;
	options.endy = ObjectDetectorOptions.INVALID_POS;
	return options;
}

private function captureButtonHandler():void
{
	var canvas:BitmapData = new BitmapData(videoDisp.width, videoDisp.height, false);
	canvas.draw(videoDisp);
	var ba:ByteArray = new PNGEncoder().encode(canvas);
	new FileReference().save(ba, "Shiirare.png");
}