forked from: forked from: ジャグリングとFlash

by Mae_ITR
♥0 | Line 292 | Modified 2010-02-22 11:57:53 | MIT License
play

ActionScript3 source code

/**
 * Copyright Mae_ITR ( http://wonderfl.net/user/Mae_ITR )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/4WM1
 */

// forked from ninehundred's forked from: ジャグリングとFlash
// forked from fumix's ジャグリングとFlash
/***
動画: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;
	}	
}