forked from: 動体検知
まだまだ途中バージョン
* 作っている自分も良くわかってません。
* videoのサイズ大きいと負荷が高いのかPCのファンが高速回転汗
* 参考URL
* 動体検知:Flash8で動体検知
* http://faces.bascule.co.jp/motiondetection/
* bitmap操作関連:ActionScript 3.0 でラベリング (改)を勝手に添削
* http://void.heteml.jp/blog/archives/2007/10/as3_labeling.html
*
* 一応完成
* バスキュールの動体検知ほぼそのままです。
* 無駄に長い気がする・・・as3なのだからas2のころより短く書けるはず・・・ですが自分では出来ません。
// forked from fumix's 動体検知
// forked from fumix's forked from: 動体検知
// forked from ll_koba_ll's 動体検知
// write as3 code here..
/***
* まだまだ途中バージョン
* 作っている自分も良くわかってません。
* videoのサイズ大きいと負荷が高いのかPCのファンが高速回転汗
* 参考URL
* 動体検知:Flash8で動体検知
* http://faces.bascule.co.jp/motiondetection/
* bitmap操作関連:ActionScript 3.0 でラベリング (改)を勝手に添削
* http://void.heteml.jp/blog/archives/2007/10/as3_labeling.html
*
* 一応完成
* バスキュールの動体検知ほぼそのままです。
* 無駄に長い気がする・・・as3なのだからas2のころより短く書けるはず・・・ですが自分では出来ません。
***/
package
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.BlendMode;
import flash.display.Sprite;
import flash.events.Event;
import flash.filters.ConvolutionFilter;
import flash.geom.ColorTransform;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.media.Camera;
import flash.media.Video;
import flash.text.TextField;
[SWF(width = "465", height = "465", frameRate = "30",backgroundColor="0x000000")]
public class blend extends Sprite
{
private var camera:Camera;
private var video:Video;
private var now:BitmapData;
private var before:BitmapData;
private var bmd:BitmapData;
private var rect:Rectangle;
private var pt:Point;
private var motion:MotionDetector;
private var noiseReduction:ConvolutionFilter;
private var size:int = 465;
public function blend()
{
super();
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
/**
* 初期処理
* @param e
*
*/
private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
camera = Camera.getCamera();
//カメラあり
if(camera != null){
setUpCamera();
//カメラ無し
}else{
var txt:TextField = new TextField();
txt.text ='カメラ無し';
addChild(txt);
}
noiseReduction = new ConvolutionFilter(3, 3);
noiseReduction.bias = -(0x1000 + 0x100 * 6);
noiseReduction.matrix = [
1, 1, 1,
1, 16, 1,
1, 1, 1
];
bmd = new BitmapData(size,size/camera.width * camera.height,false,0x000000);
addChild(new Bitmap(bmd));
//動体検知クラス
motion = new MotionDetector();
addEventListener(Event.ENTER_FRAME,loop);
/* var btn:Sprite = new Sprite();
btn.graphics.beginFill(0xFFFFFF);
btn.graphics.drawRect(0,0,50,30);
btn.graphics.endFill();
btn.buttonMode = true;
btn.addEventListener(MouseEvent.CLICK,loop);
addChild(btn);
*/
now = new BitmapData(camera.width,camera.height,false);
before = new BitmapData(camera.width,camera.height,false);
rect = new Rectangle(0, 0, camera.width,camera.height);
pt = new Point;
}
/**
*カメラのセットアップ
*
*/
private function setUpCamera():void
{
camera.setMode(240,240,12);
video = new Video(camera.width,camera.height);
video.attachCamera(camera);
addChild(video);
}
private function loop(e:Event=null):void
{
now.draw(video);
now.draw(before,new Matrix(), new ColorTransform(),BlendMode.DIFFERENCE);
var ret:uint = now.threshold(now,rect,pt,">",0xff111111,0xffffffff);
//ノイズリダクション
var ret2 = now.applyFilter(now, rect, pt, noiseReduction);
before.draw(video);
var drawMap:Sprite = new Sprite();
var motionArray:Object = motion.Check(now);
for(var idx:int=0; idx<motionArray.w.length; idx++){
var tgt_rec:Rectangle = motionArray.w[idx].rect;
if(tgt_rec.width * tgt_rec.height > 500){
drawMap.graphics.beginFill(0xFF0000,0.5);
drawMap.graphics.drawRect(tgt_rec.x*2,tgt_rec.y*2,tgt_rec.width*2,tgt_rec.height*2);
drawMap.graphics.endFill();
}
}
/*
for(var idx=0; idx<motionArray.n.length; idx++){
var tgt_rec:Rectangle = motionArray.n[idx].rect;
drawMap.graphics.lineStyle(1,0x0000ff);
drawMap.graphics.drawRect(tgt_rec.x,tgt_rec.y,tgt_rec.width,tgt_rec.height);
}
*/
//trace(lno);
bmd.draw(video,new Matrix(2.0,0,0,2,0,0));
bmd.draw(drawMap);
}
}
}
import flash.display.BitmapData;
import flash.geom.Rectangle;
import flash.geom.Point;
class MotionDetector
{
private var _watchingRectArray:Array;
private var _nowRectArray:Array;
private var idCounter:int;
/**
*コンストラクタ
*
*/
public function MotionDetector()
{
_watchingRectArray = new Array();
idCounter = 0;
}
/**
* 動体の矩形の入った配列を返す
* @param bmd チェックしたい画像(フィルタリング済み)
* @return 矩形情報の配列
*
*/
public function Check(bmd:BitmapData):Object
{
var dst:BitmapData = bmd.clone();
var temp:BitmapData = new BitmapData(dst.width,1,false,0x000000);
var lno:int = 0;
var zero:Point = new Point();
_nowRectArray = new Array();
var _tempRectArray:Array = new Array();
//ブロック化
var rect:Rectangle = dst.getColorBoundsRect(0xffffff,0xffffff,true);
var area:Rectangle = new Rectangle(0,0,dst.width,1);
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 );
_nowRectArray.push(new ExRectangle(br));
}
//rectangleが重なってたら、同じものとみなして、数を絞る
for(var idx:int=0; idx<_nowRectArray.length; idx++){
var idx_rec:Rectangle = _nowRectArray[idx].rect;
for(var i:int=idx+1; i<_nowRectArray.length; i++){
var tgt_rec:Rectangle = _nowRectArray[i].rect;
if( idx_rec!=tgt_rec && idx_rec.intersects(tgt_rec) ){
_nowRectArray.push(new ExRectangle(idx_rec.union(tgt_rec)));
_nowRectArray.splice(Number(i), 1);
_nowRectArray.splice(Number(idx), 1);
idx --;
break;
}
}
}
//今回検出分とwatchしてる分の重なりを検証
for(var i:int=0; i<_nowRectArray.length; i++){
var rec_ref:ExRectangle = _nowRectArray[i];
if(rec_ref.rect.width*rec_ref.rect.height >= bmd.width*bmd.height*0.7){
//画面全体が変化している場合(カメラの自動露出調整、オートホワイトバランス調整など)は無視。
_nowRectArray.splice(Number(i), 1);
i--;
}else{
rec_ref.isects = new Array();
for(var j in _watchingRectArray){
var rec_watch:ExRectangle = _watchingRectArray[j];
if(rec_watch.rect.intersects(rec_ref.rect)){
rec_watch.isects.push(rec_ref);
rec_ref.isects.push(rec_watch);
}
}
}
}
//今回検出分がそれぞれ、いくつのwatch分に重なってるか
for(i=0; i<_nowRectArray.length; i++){
var rec_ref:ExRectangle = _nowRectArray[i];
var isectsNum:int = rec_ref.isects.length;
//1つもwatch中のrectとintersectしてない → 新規追加
if(isectsNum == 0){
if(rec_ref.rect.width*rec_ref.rect.height <= 50){
//結構小さい → 廃棄
//trace("tooSmall"+(rec_ref.width*rec_ref.height)+":"+(original_width*original_height*resolution_scale*_min_size_new_rect_rate));
_nowRectArray.splice(Number(i), 1);
i--;
}else{
//新規生成
rec_ref.event = "create";
rec_ref.life = 5;
rec_ref.id = ++idCounter;
_tempRectArray.push(rec_ref);
}
//複数のwatch中のrectとintersectしている → watch中のrectと最もたくさんintersectしているものに統合
}
else if(isectsNum >= 2){
var rect_integrate:ExRectangle = null;
for(var j=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(var j=0; j<isectsNum; j++){
if(rect_integrate != rec_ref.isects[j]){
var rec_disappear:ExRectangle = rec_ref.isects[j];
//rec_disappear.event = "integrate";
//rec_disappear.integarateTarget = rect_integrate;
//recs_disappeared.push(rec_disappear);
//recs_watchingから統合されたrectを削除
for(var k=0; k<_watchingRectArray.length; k++){
if(_watchingRectArray[k] == rec_disappear){
_watchingRectArray.splice(k, 1);
break;
}
}
}
}
}
}
//重なりデータを下に、新規watch候補を選定
for(var k=0; k<_watchingRectArray.length; k++){
var rec_watch:ExRectangle = _watchingRectArray[k];
var isectsNum = 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";
_tempRectArray.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 = 5;
rec_watch.isects[0].isects = new Array();
_tempRectArray.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 = 5;
_tempRectArray.push(rec_watch.isects[0]);
}
}
_watchingRectArray = new Array();
//候補から、判定
for(var i=0; i<_tempRectArray.length; i++){
var rec_item:ExRectangle = _tempRectArray[i];
var infratex = (rec_item.rect.width - 4 <= 4) ? 0 : 4;
var infratey = (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){
//trace("disappear : " + rec_item.id);
//rec_item.event = "disappear";
//recs_disappeared.push(rec_item);
}else{
_watchingRectArray.push(rec_item);
}
//移動、統合物
}else if(rec_item.event == "move"){
_watchingRectArray.push(rec_item);
//新規
}else if(rec_item.event == "create"){
_watchingRectArray.push(rec_item);
}
}
//trace("item:"+_watchingRectArray.length);
return {w:_watchingRectArray,n:_nowRectArray};
}
public function get watchingRectArray():Array
{
return _watchingRectArray;
}
public function get nowRectArray():Array
{
return _nowRectArray;
}
}
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;
}
}