Beautifl - Flash Gallery

Thumbnail : カメラの前で体を動かしたら、箱が動く・・・みたいな実験
カメラの前で体を動かしたら、箱が動く・・・みたいな実験
narutohyper 2010-03-04 MIT License

再生するにはFlash Playerが必要です。デスクトップのブラウザでご覧ください。

// forked from utabi's パターンマッチングによるカメラの移動方向推定
// パターンマッチングによるカメラの移動方向推定
//
// 映像を縮小し、全体をすこしずつ移動して直前のフレームと
// 重ねあわせ、その差を比較することで移動方向を推定。
// SSDという手法があるらしいが、それに近いのかも。
// CPU負荷とのたたかいなので、scaleSmall、search_count、threshold
// の組み合わせでよいところを設定。
// search_countを多くすると、速い動きにも対応できるが、
// それだけCPUに負担
//
// [注意]固定カメラで顔を動かしたりしても反応しません。
// 運動カメラの動きをとらえます。

package {
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Sprite;
	import flash.display.BlendMode;
	import flash.events.Event;
	import flash.filters.BlurFilter;
	import flash.filters.ColorMatrixFilter;
	import flash.filters.ConvolutionFilter;
	import flash.geom.Matrix;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.geom.ColorTransform;
	import flash.media.Camera;
	import flash.media.Video;
	import net.hires.debug.*;
	import flash.text.*;


	import alternativ5.engine3d.materials.FillMaterial;
	import alternativ5.engine3d.primitives.Box;
	import alternativ5.engine3d.events.MouseEvent3D
	import alternativ5.types.Point3D;

	import flash.display.Sprite;
	import flash.display.BlendMode;


	
	[SWF(frameRate="30")]
	Wonderfl.capture_delay( 20 );
	
	public class SimpleDemo extends Sprite {
		private var camera:Camera;
		private var video:Video;
		private var videoWidth:int = 465;
		private var videoHeight:int = 465;
		private var raw:BitmapData;
		private var rect:Rectangle;
		private var pt:Point = new Point(0,0);
		
		private var xp:int,yp:int;
		// Properties for Patch Search
		private var now:BitmapData;
		private var nowMedium:BitmapData;
		private var nowSmall:BitmapData;
		private var last:BitmapData;
		private var lastMedium:BitmapData;
		private var lastSmall:BitmapData;
		private var scaleSmall:Number = .12;
		private var scaleMedium:Number = .25;
		private var mSmall:Matrix = new Matrix();
		private var mMedium:Matrix = new Matrix();
		// 検索の順番
		// 7 8 1
		// 6 0 2
		// 5 4 3
		private var compareX:Array = [ 0, 1, 1, 1, 0, -1, -1, -1, 0];
		private var compareY:Array = [ 0, -1, 0, 1, 1, 1, 0, -1, -1];
		private var dNow:int = 0;
		private var dResult:Point = new Point(0,0);
		private var score:Number;
		private var scoreNow:Number;
		private var scoreLast:Number;
		private var count:int = 0;
		private var t:Number;
		private var ii:int;
		private var aVec:Vector.<uint>;
		private var bVec:Vector.<uint>;
		private var scoreIndiv:Number;
		private var scoreNowIndiv:uint;
		private var current:int;
		private var dif:int;

		//Sprite
		private var arrow:Sprite;
		
		public function SimpleDemo() {
			camera=Camera.getCamera();
			if (camera==null) {
			} else {
				start();
			}
		}

		private function start():void {
			camera.setMode(640, 480,30);
			video = new Video(videoWidth, videoHeight);
			video.attachCamera(camera);
			
			raw = new BitmapData(videoWidth,videoHeight);
			rect = raw.rect;
			
			//this.addChild(video);
			//this.addChild(new Bitmap(raw));
			
			arrow = new Sprite();
			
			arrow.graphics.beginFill(0xff0000);
			arrow.graphics.moveTo(0,-2);
			arrow.graphics.lineTo(40,-2);
			arrow.graphics.lineTo(40,-10);
			arrow.graphics.lineTo(63,0);
			arrow.graphics.lineTo(40,10);
			arrow.graphics.lineTo(40,2);
			arrow.graphics.lineTo(0,2);
			arrow.graphics.lineTo(0,-2);
			arrow.graphics.endFill();
			
			arrow.x = videoWidth/2;
			arrow.y = videoHeight/2- arrow.height/2;
			

			//
			mSmall.scale(scaleSmall,scaleSmall);
			mMedium.scale(scaleMedium,scaleMedium);
			
			now = new BitmapData(videoWidth,videoHeight,false);
			nowMedium = new BitmapData(videoWidth*scaleMedium, videoHeight*scaleMedium, false);
			nowSmall = new BitmapData(videoWidth*scaleSmall, videoHeight*scaleSmall, false);
			last = new BitmapData(videoWidth,videoHeight,false);
			lastMedium = new BitmapData(videoWidth*scaleMedium, videoHeight*scaleMedium, false);
			lastSmall = new BitmapData(videoWidth*scaleSmall, videoHeight*scaleSmall, false);
			

			// テンプレートを作成します
			var template:BasicTemplate = new BasicTemplate();
			addChild(template);

			//this.addChild(arrow);
			this.addChild(new Stats);
			
			// プリミティブを作成します
			var box:Box;
			box = new Box(600, 600, 600);
			box.cloneMaterialToAllSurfaces(new FillMaterial(0x0000FF,1,BlendMode.NORMAL,1,0x0000000));

			// 3Dシーンのルートに追加します
			template.scene.root.addChild(box);
			template.cameraContoller.lookAt(new Point3D());
			
			var dbg:TextField=new TextField()
			dbg.selectable=true;
			dbg.mouseEnabled=true;
			dbg.height=100
			var format:TextFormat=new TextFormat();
			format.color=0x666666
			format.size=12;
			format.font='_ゴシック';
			dbg.defaultTextFormat=format
			dbg.x=300
			addChild(dbg)

			// Event.ENTER_FRAME 時に実行されるレンダリングのイベントです。
			// レンダリング前に実行したい処理を記述します。
			template.onPreRender = function():void {

				// 立方体を回転させます (角度はラジアン)
				raw.lock();

				raw.draw(video);
				
				var detected:Object = detectDirection(raw);

				arrow.rotation = (Math.atan2(detected.point.x,detected.point.y)* -180 / Math.PI) +90;
				
								
				if(arrow.rotation>0&&arrow.rotation<=90){
					
				} else if(arrow.rotation==180){
					dbg.appendText(String(arrow.rotation)+"\n");						
					box.rotationY += 2 * Math.PI / 180;						
				} else if(arrow.rotation==0){
					dbg.appendText(String(arrow.rotation)+"\n");						
					box.rotationY -= 2 * Math.PI / 180;							
				} else {
					dbg.appendText(String(arrow.rotation)+"\n");						
				}										
				//arrow.scaleX = detected.distance/25;
				//arrow.scaleY = detected.distance/25;

			}

		}




		private function detectDirection(bd:BitmapData):Object {
			
			now.copyPixels(bd,rect,pt);
			nowMedium.draw(bd,mMedium);
			nowSmall.draw(bd,mSmall);

			var p:Point = getDirection(new Point(0,0),nowSmall,lastSmall, 8, 0.1);
			//var p2:Point = getDirection(new Point(p.x*scaleMedium/scaleSmall,p.y*scaleMedium/scaleSmall),nowMedium,lastMedium, 2, 0.05);
			var d:Number = Point.distance(pt, p)*(1/scaleSmall);

			//trace(p,Point.distance(pt, p)*(1/scaleSmall),Math.atan2(p.x,p.y));

			last.copyPixels(bd,last.rect,pt);
			lastMedium.copyPixels(nowMedium,lastMedium.rect,pt);
			lastSmall.copyPixels(nowSmall,lastSmall.rect,pt);
			
			return {point:p,distance:d};
		}
		private function getDirection(centerP:Point,a:BitmapData,b:BitmapData,search_count:int, threshold:Number):Point{
			dNow = 0;
			dResult = centerP;
			score = 50000000;
			scoreNow = 0;
			scoreLast = 0;
			count = 0;
			t = (a.width-1) * (a.height-1) *threshold;

			while(score > t){
				dNow = 0;
				
				for (ii = 0 ; ii < 9; ii++){
					
					scoreNow = getScore(a, b, compareX[ii] + dResult.x, compareY[ii] + dResult.y);
					
					if (scoreNow < score){
						//前回のものよりスコアが低いなら、そっちを優先
						dNow = ii;
						score = scoreNow;
					}
				};
				
				dResult = new Point(dResult.x + compareX[dNow],dResult.y+compareY[dNow]);
				
				count ++;
				if(count >= search_count){
					// search_countまでに条件が満たなかったら中断
					//trace("break");
					break;
				}
			}
			
			return dResult;
		}
		private function getScore(a:BitmapData,b:BitmapData,x:int,y:int):Number{
			//x,yずらした画像の差分をベクトルで計算し、合計スコアを返す
			
			aVec = a.getVector(a.rect);
			bVec = b.getVector(b.rect);
			scoreIndiv = 0;
			scoreNowIndiv = 0;
			current = 0;
			dif = x + y*a.width;
			
			for (yp = 1; yp < a.height-1 ; yp++){
				for (xp = 1; xp < a.width-1 ; xp++){
					current = yp*a.width + xp;
					if( 0 <= current+dif && current+dif < aVec.length){
						scoreNowIndiv = Math.abs(aVec[current] - bVec[current+dif]);
						if ((0x00000000 < scoreNowIndiv ) && (scoreNowIndiv < 0x00ffffff)){
							scoreIndiv += (scoreNowIndiv / 0xffffff);
						}
					}
				}
			}
			return  scoreIndiv;
		}

	}
}




import alternativ5.engine3d.controllers.CameraController;
import alternativ5.engine3d.core.Camera3D;
import alternativ5.engine3d.core.Object3D;
import alternativ5.engine3d.core.Scene3D;
import alternativ5.engine3d.display.View;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageQuality;
import flash.display.StageScaleMode;
import flash.events.Event;


/**
 * BasicTemplate for Alternativa3D
 * Alternativa3Dを扱いやすくするためのテンプレートです
 * @author Yasu
 */
class BasicTemplate extends Sprite{
	/**
	 * シーンインスタンスです。
	 */
	public var scene:Scene3D;
	/**
	 * ビューインスタンスです。
	 */
	public var view:View;
	/**
	 * カメラインスタンスです。
	 */
	public var camera:Camera3D;
	/**
	 * カメラコントローラーです。
	 */
	public var cameraContoller:CameraController;
	
	private var _viewWidth:int;
	private var _viewHeight:int;
	private var _scaleToStage:Boolean;

	/**
	 * 新しい BasicTemplate インスタンスを作成します。
	 * @param	viewWidth
	 * @param	viewHeight
	 * @param	scaleToStage
	 */
	public function BasicTemplate(viewWidth:int=640, viewHeight:int=480, scaleToStage:Boolean = true) {
		_viewWidth = viewWidth;
		_viewHeight = viewHeight;
		_scaleToStage = scaleToStage;
		
		// Creating scene
		scene = new Scene3D();
		scene.splitAnalysis = false; // not analysis for performance
		scene.root = new Object3D();
		
		// Adding camera
		camera = new Camera3D();
		camera.z = -1000;
		scene.root.addChild(camera);
		
		// camera contoller
		cameraContoller = new CameraController(this);
		cameraContoller.camera = camera;
		
		// set view
		view = new View();
		view.camera = camera;
		addChild(view);
		
		// stage
		if (stage) init();
		else addEventListener(Event.ADDED_TO_STAGE, init);
	}
	
	/**
	 * 初期化されたときに実行されるイベントです。
	 * 初期化時に実行したい処理をオーバーライドして記述します。
	 */
	protected function atInit():void {}
	
	/**
	 * 初期化されたときに実行されるイベントです。
	 * 初期化時に実行したい処理を記述します。
	 */
	private var _onInit:Function = function():void { };
	public function get onInit():Function { return _onInit; }
	public function set onInit(value:Function):void {
		_onInit = value;
	}
	
	/**
	 * Event.ENTER_FRAME 時に実行されるレンダリングのイベントです。
	 * レンダリング前に実行したい処理をオーバーライドして記述します。
	 */
	protected function atPreRender():void {}
	
	/**
	 * Event.ENTER_FRAME 時に実行されるレンダリングのイベントです。
	 * レンダリング前に実行したい処理を記述します。
	 */
	private var _onPreRender:Function = function():void{};
	public function get onPreRender():Function { return _onPreRender; }
	public function set onPreRender(value:Function):void {
		_onPreRender = value;
	}
	
	/**
	 * Event.ENTER_FRAME 時に実行されるレンダリングのイベントです。
	 * レンダリング後に実行したい処理をオーバーライドして記述します。
	 */
	protected function atPostRender():void {
	}
	
	/**
	 * Event.ENTER_FRAME 時に実行されるレンダリングのイベントです。
	 * レンダリング後に実行したい処理を記述します。
	 */
	protected var _onPostRender:Function = function():void{};
	public function get onPostRender():Function { return _onPostRender; }
	public function set onPostRender(value:Function):void {
		_onPostRender = value;
	}
	
	/**
	 * レンダリングを開始します。
	 */
	public function startRendering():void {
		addEventListener(Event.ENTER_FRAME, onRenderTick);
	}
	/**
	 * レンダリングを停止します。
	 */
	public function stopRendering():void {
		removeEventListener(Event.ENTER_FRAME, onRenderTick);
	}
	
	/**
	 * シングルレンダリング(レンダリングを一回だけ)を実行します。
	 */
	public function singleRender():void {
		onRenderTick();
	}
	
	private function init(e:Event = null):void {
		stage.scaleMode = StageScaleMode.NO_SCALE;
		stage.align = StageAlign.TOP_LEFT;
		stage.quality = StageQuality.HIGH;

		// resize
		stage.addEventListener(Event.RESIZE, onResize);
		onResize(null);
		
		// render
		startRendering();
		
		atInit();
		_onInit();
		
	}
	
	private function onRenderTick(e:Event = null):void {
		atPostRender();
		_onPostRender();
		scene.calculate();
		atPreRender();
		_onPreRender();
	}
	
	private function onResize(event:Event = null):void {
		if (_scaleToStage) {
			view.width = stage.stageWidth;
			view.height = stage.stageHeight;
		}else {
			view.width = _viewWidth;
			view.height = _viewHeight;
		}
	}
}