ジャグリングと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
/**
* Copyright fumix ( http://wonderfl.net/user/fumix )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/A1Hg
*/
/***
動画: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;
}
}