// 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; } } } カメラの前で体を動かしたら、箱が動く・・・みたいな実験