/*** 動画:http://twitvid.com/DF27A 画像:http://www.flickr.com/photos/tensafefrogs/3584090438/in/set-72157619058523389/ 参考:フィルタリング処理 forked from: 動体検知 + 肌色認識 | wonderfl build flash online http://wonderfl.net/code/a95ff369b20c756ee8a612aaa620c7b028229624 ***/ package { import flash.display.*; import flash.events.*; import flash.geom.Point; import flash.geom.Rectangle; import flash.media.*; import flash.net.*; //動画ストリーミングの再生 [SWF(width=465, height=465, backgroundColor=0xFFFFFF)] public class juggling extends Sprite { //変数 private var nc:NetConnection; private var ns:NetStream; private var motion : MotionDetector; private var _bmdCurrent : BitmapData; private var video : Video; private var hige : BitmapData; private const VIDEO_URL:String = 'http://www.planet-ape.net/wonderfl/video_new.flv'; private const IMAGE_URL:String = 'http://www.planet-ape.net/wonderfl/hige.png'; //コンストラクタ public function juggling() { //画像読み込み var imgLoader:Loader = new Loader(); imgLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, loadComplete); imgLoader.load(new URLRequest(IMAGE_URL)); //コネクションの生成 nc=new NetConnection(); nc.addEventListener(NetStatusEvent.NET_STATUS,onNetStatus); nc.addEventListener(SecurityErrorEvent.SECURITY_ERROR,onSecurityError); nc.connect(null); } private function loadComplete(event : Event) : void { event.target.removeEventListener(Event.COMPLETE, loadComplete); //取得した画像を入れる hige = new BitmapData (event.target.loader.content.bitmapData.width, event.target.loader.content.bitmapData.height, true); //画像のマスクを有効に hige.copyPixels(event.target.loader.content.bitmapData,event.target.loader.content.bitmapData.rect , new Point(0,0),event.target.loader.content.bitmapData); } //通信状態イベントの処理 private function onNetStatus(evt:NetStatusEvent):void { //成功 if (evt.info.code=="NetConnection.Connect.Success") { //ストリームの生成 ns=new NetStream(nc); ns.checkPolicyFile = true; ns.addEventListener(NetStatusEvent.NET_STATUS,onNetStatus); ns.addEventListener(AsyncErrorEvent.ASYNC_ERROR,onAsyncError); //メタデータの取得 var obj:Object=new Object(); obj.onMetaData=onMetaData; ns.client=obj; //ビデオの生成 video=new Video(360,480); //addChild(video); video.attachNetStream(ns); //ビデオの再生 ns.play(VIDEO_URL); //動体検知クラス motion = new MotionDetector(); motion.startDetect(video); //取得した画像を切り取って_bmdCurrentに入れる _bmdCurrent = new BitmapData (465, 465, true, 0xFFFFFF); addChild(new Bitmap(_bmdCurrent)); addEventListener(Event.ENTER_FRAME,onEnterFrameHandler); //速度とかメモリとかチェック(重要じゃないので無視無視) //addChild(new Stats()); } //停止 else if (evt.info.code=="NetStream.Play.Stop") { trace("Stop"); } //エラー else if (evt.info.level=="error") { trace("Error"); } } private function onEnterFrameHandler(event : Event=null) : void { //検知した動体の配列 var mousePointArray:Vector.<ExRectangle> = motion.watchingRectArray; //動体描画用のスプライト用意 var rectSprite:Sprite = new Sprite(); //動体の数だけループ for(var idx:int=0; idx<mousePointArray.length; idx++){ var tgt_rec:Rectangle = mousePointArray[idx].rect; //動体を某画像に var sp:Sprite = new Sprite(); sp.addChild(new Bitmap(hige)); //動体の大きさに合わせて画像を拡大縮小(縦横比は維持) sp.x = tgt_rec.x; sp.y = tgt_rec.y; var w:Number = tgt_rec.width; var h:Number = sp.height * w / sp.width; sp.width = w*1.2; sp.height = h*1.2; rectSprite.addChild(sp); } //動画を描画 _bmdCurrent.draw (video); //デバッグ用:それぞれを有効にするとフィルタリングされた動画になります //_bmdCurrent.draw(motion._copy); //_bmdCurrent.draw(motion._now); //動体画像を描画 _bmdCurrent.draw(rectSprite); } //メタデータ取得イベントの処理 private function onMetaData(info:Object):void { } //セキュリティーエラーイベントの処理 private function onSecurityError(evt:SecurityErrorEvent):void { trace("SecurityError"); } //同期エラーイベントの処理 private function onAsyncError(evt:AsyncErrorEvent):void { trace("AsyncErrorEvent"); } } } import flash.display.BitmapData; import flash.filters.ColorMatrixFilter; import flash.filters.ConvolutionFilter; import flash.geom.Point; import flash.geom.Rectangle; import flash.media.Video; import flash.utils.setInterval; /** * 動体検知クラス * @author fumix * これがメイン: */ class MotionDetector { private var _watchingRectArray:Vector.<ExRectangle>; private var _nowRectArray:Vector.<ExRectangle>; private var idCounter:int; private var video:Video; private var now:BitmapData; private var copy:BitmapData; private var rect:Rectangle; private var pt:Point; private var noiseReduction:ConvolutionFilter; private var grayScale:ColorMatrixFilter; private var skin:ColorMatrixFilter; /** *コンストラクタ * */ public function MotionDetector() { _watchingRectArray = new Vector.<ExRectangle>; _nowRectArray = new Vector.<ExRectangle>; idCounter = 0; //ノイズリダクション noiseReduction = new ConvolutionFilter(3, 3); noiseReduction.bias = -(0x1000 + 0x100 * 6); noiseReduction.matrix = [ 1, 1, 1, 1, 16, 1, 1, 1, 1 ]; grayScale = new ColorMatrixFilter([ 0.3, 0.59, 0.11, 0, 0, 0.3, 0.59, 0.11, 0, 0, 0.3, 0.59, 0.11, 0, 0, 0, 0, 0, 1, 0 ]); skin = new ColorMatrixFilter([ 0, 0, 0, 0, 0, -0.43, -0.85, 1.28, 0, 198.4, 1.28, -1.07, -0.21, 0, 108.8, 0, 0, 0, 1, 0 ]); } /** * 動体検知スタート * @param img 検知したい動画 * @return * */ public function startDetect(img:Video):void { video = img; now = new BitmapData(video.width,video.height,false); copy = new BitmapData(video.width,video.height,false); rect = new Rectangle(0, 0, video.width,video.height); pt = new Point; //30msで検知 setInterval(Check,30); } /** * 動体検知 * */ private function Check():void { //一時配列のリセット var _tempRectArray:Array = new Array(); //bitmapの処理 now.draw(video); copy = now.clone(); //オレンジの玉を強調させるフィルタリング copy.applyFilter(now, rect, pt, skin); //グレイスケール now.applyFilter(now, rect, pt, grayScale); //閾値による色の置き換えでオレンジの玉部分のみを抽出する now.threshold(now, rect, pt, ">", 0xff111111, 0xffffffff); now.threshold(now, rect, pt, "!=", 0xffffffff, 0xff000000); now.threshold(copy, rect, pt, "!=", 0x0060ff, 0xff000000, 0x00c0c0); //ノイズリダクション now.applyFilter(now, rect, pt, noiseReduction); //ブロック化 _nowRectArray = blocked(now); //今回検出分がそれぞれ、いくつのwatch分に重なってるか //複数のwatch中のrectとintersectしている → watch中のrectと最もたくさんintersectしているものに統合 checkOverlapRectNumber(_watchingRectArray,_nowRectArray,_tempRectArray); //重なりデータを下に、新規watch候補を選定 checkNewWatchArray(_watchingRectArray,_tempRectArray); //候補から、判定 _watchingRectArray = setNewWatchArray(_tempRectArray); } /** * ブロック化 * @param dst 対象となるビットマップ * @return ブロック化した配列 * */ private function blocked(dst:BitmapData):Vector.<ExRectangle> { var rect:Rectangle = dst.getColorBoundsRect(0xffffff,0xffffff,true); var area:Rectangle = new Rectangle(0,0,dst.width,1); var temp:BitmapData = new BitmapData(dst.width,1,false,0x000000); var zero:Point = new Point(); var tempArray:Vector.<ExRectangle> = new Vector.<ExRectangle>; while( !rect.isEmpty()){ area.y = rect.top; temp.copyPixels(dst,area,zero); rect = temp.getColorBoundsRect(0xffffff,0xffffff,true); dst.floodFill(rect.x,area.y,0xff00ff); var br:Rectangle = dst.getColorBoundsRect(0xFFFFFF, 0xFF00FF); br.inflate(4,4); dst.fillRect(br, 0x0000ff); rect = dst.getColorBoundsRect( 0xffffff, 0xffffff, true ); tempArray.push(new ExRectangle(br)); } return tempArray; } /** * 今回検出分がそれぞれ、いくつのwatch分に重なってるか * @param watchArray watchしている矩形の配列 * @param nowArray 今回の矩形の配列 * @param tempArray watch候補の矩形の配列 * */ private function checkOverlapRectNumber(watchArray:Vector.<ExRectangle>,nowArray:Vector.<ExRectangle>,tempArray:Array):void { for(var i:int=0; i<nowArray.length; i++){ var rec_ref:ExRectangle = nowArray[i]; var isectsNum:int = rec_ref.isects.length; //1つもwatch中のrectとintersectしてない → 新規追加 if(isectsNum == 0){ if(rec_ref.rect.width*rec_ref.rect.height <= 1024*0.7){ //結構小さい → 廃棄 _nowRectArray.splice(Number(i), 1); i--; }else{ //新規生成 rec_ref.event = "create"; rec_ref.life = 1; rec_ref.id = ++idCounter; tempArray.push(rec_ref); } //複数のwatch中のrectとintersectしている → watch中のrectと最もたくさんintersectしているものに統合 } else if(isectsNum >= 2){ var rect_integrate:ExRectangle = null; for(var j:int=0; j<isectsNum-1; j++){ var rec_now:ExRectangle = rec_ref.isects[j]; var rec_next:ExRectangle = rec_ref.isects[j+1]; rect_integrate = (rec_now.isects.length >= rec_next.isects.length) ? rec_now : rec_next; } for(j=0; j<isectsNum; j++){ if(rect_integrate != rec_ref.isects[j]){ var rec_disappear:ExRectangle = rec_ref.isects[j]; for(var k:int=0; k<watchArray.length; k++){ if(watchArray[k] == rec_disappear){ watchArray.splice(k, 1); break; } } } } } } } /** * 重なりデータを下に、新規watch候補を選定 * @param watchArray watchしている矩形の配列 * @param nowArray 今回の矩形の配列 * @param tempArray watch候補の矩形の配列 * */ private function checkNewWatchArray(watchArray:Vector.<ExRectangle>,tempArray:Array):void { //重なりデータを下に、新規watch候補を選定 for(var k:int=0; k<watchArray.length; k++){ var rec_watch:ExRectangle = watchArray[k]; var isectsNum:int = rec_watch.isects.length; if(isectsNum == 0){ //0個の場合 → LIFE分継続 //trace("watch_未intersect: id:" + rec_watch.id + " life:"+ rec_watch.life + " event: " + rec_watch.event); rec_watch.life -- ; rec_watch.event = "stay"; tempArray.push(rec_watch); }else if(isectsNum == 1){ //一つの場合 → 維持 //trace("watch_1つintersect: " + rec_watch.id + " life:" + rec_watch.life); rec_watch.isects[0].id = rec_watch.id; rec_watch.isects[0].event = "move"; rec_watch.isects[0].life = 1; rec_watch.isects[0].isects = new Array(); tempArray.push(rec_watch.isects[0]); }else{ //複数個の場合 → union //それぞれの距離を測ったほうがいいかも //あるいは、一つのrectの面積の上限を決めるとか //(2つのrectが一旦unionされると以降つながり続ける→すれ違った人が、ずっと同じ人間だと認識されてしまうだろう) //計算面倒だなあ //trace("watch_複数にintersect: id:" + rec_watch.id + " 個数:" + rec_watch.isects.length + " life:" + rec_watch.life); while(rec_watch.isects.length > 1){ rec_watch.isects[0].rect = rec_watch.isects[0].rect.union(rec_watch.isects[1].rect); rec_watch.isects.splice(1, 1); } rec_watch.isects[0].id = rec_watch.id; rec_watch.isects[0].event = "move"; rec_watch.isects[0].life = 1; rec_watch.isects[0].isects = new Array(); tempArray.push(rec_watch.isects[0]); } } } /** * 新規watchデータ生成 * @param tempArray watch候補の矩形の配列 * @return 生成されたwatchデータ * */ private function setNewWatchArray(tempArray:Array):Vector.<ExRectangle> { //候補から、判定 var watchArray:Vector.<ExRectangle> = new Vector.<ExRectangle>; var tempLength:int = tempArray.length; for(var i:int=0; i<tempLength; i++){ var rec_item:ExRectangle = tempArray[i]; var infratex:int = (rec_item.rect.width - 4 <= 4) ? 0 : 4; var infratey:int = (rec_item.rect.height - 4 <= 4) ? 0 : 4; rec_item.rect.inflate(-infratex, -infratey); //誰ともintersectしてない寿命切れのrectを削除 if(rec_item.event == "stay"){ if(rec_item.life <= 0){ }else{ watchArray.push(rec_item); } //移動、統合物 }else if(rec_item.event == "move"){ watchArray.push(rec_item); //新規 }else if(rec_item.event == "create"){ watchArray.push(rec_item); } } return watchArray; } public function get nowRectArray():Vector.<ExRectangle> { return _nowRectArray; } public function get watchingRectArray():Vector.<ExRectangle> { return _watchingRectArray; } public function get _now() : BitmapData { return now; } public function get _copy() : BitmapData { return copy; } } class ExRectangle { public var rect:Rectangle; public var isects:Array; public var event:String; public var life:int; public var id:int; public function ExRectangle(r:Rectangle) { isects = new Array(); rect = r; event = ''; life = 0; id = 0; } } ジャグリングとFlash