Motion(Direction) Detector
カメラ画像からモーションの方向を認識する部分
* わかりやすく矢印が出るようにしてみた。
*
* Webカメラの使用を許可してください☆
* ARとまではいかなくても2DのWebカメラ画像でももっと遊べるんじゃないかと思って作ってみました。
*
* カメラの前で手などを振ってください。振られた方向を矢印で表示します。
* 少し大げさに振るほうが確実ではありますが手で扇ぐような動作でもいけるかも?
* 精度はイマイチのような。
*
*
/**
* Copyright takishiki ( http://wonderfl.net/user/takishiki )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/lnNC
*/
// forked from takishiki's WebCamera+MRSS
// forked from Event's Simple MediaRSS Viewer
/*
*
* カメラ画像からモーションの方向を認識する部分
* わかりやすく矢印が出るようにしてみた。
*
* Webカメラの使用を許可してください☆
* ARとまではいかなくても2DのWebカメラ画像でももっと遊べるんじゃないかと思って作ってみました。
*
* カメラの前で手などを振ってください。振られた方向を矢印で表示します。
* 少し大げさに振るほうが確実ではありますが手で扇ぐような動作でもいけるかも?
* 精度はイマイチのような。
*
*
*/
package
{
import caurina.transitions.Tweener;
import caurina.transitions.Equations;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Graphics;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.display.Loader;
import flash.display.BlendMode;
import flash.events.Event;
import flash.events.MouseEvent;
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;
import flash.text.TextFieldAutoSize;
import flash.filters.ColorMatrixFilter;
// SWFメタデータタグ
[SWF(width = "465", height = "465", frameRate = "30", backgroundColor = "0x000000")]
public class Main extends Sprite
{
private const DIV: int = 10; // 分割数
private var _camera :Camera;
private var _video :Video;
private var _pastBmpd :BitmapData;
private var _nowBmpd :BitmapData;
private var _blackBmpd :BitmapData;
private var _bmpd2 :BitmapData;
private var _bmpd :BitmapData;
private var _bmp :Bitmap;
private var _sp :Sprite;
private var _cameraSp :Sprite;
private var _cameraBmp :Bitmap;
private var _divBmpd :BitmapData;
private var _map :Array;
private var _changeIndex:Array;
private var _changeFlag :Boolean;
private var _dir:int;
private var _arrowSp:Sprite;
// constructor
public function Main():void {
stage.frameRate = 30;
// カメラ
_camera = Camera.getCamera();
_camera.setMode(480, 320, _camera.fps);
if (_camera == null) {
return;
}
_cameraSp = new Sprite();
this.addChild(_cameraSp);
_sp = new Sprite();
_sp.blendMode = BlendMode.ADD;
_sp.alpha = 0.5;
this.addChild(_sp);
_arrowSp = new Sprite();
_arrowSp.alpha = 1.0;
_arrowSp.visible = false;
_arrowSp.x = stage.stageWidth / 2;
_arrowSp.y = stage.stageHeight / 2;
var g:Graphics;
g = _arrowSp.graphics;
g.lineStyle(2, 0x990000);
g.beginFill(0xFF0000);
g.moveTo(0, -100);
g.lineTo(100, 0);
g.lineTo(50, 0);
g.lineTo(50, 100);
g.lineTo(-50, 100);
g.lineTo(-50, 0);
g.lineTo(-100, 0);
g.lineTo(0, -100);
g.endFill();
this.addChild(_arrowSp);
// ビデオ
_video = new Video(_camera.width, _camera.height);
_video.attachCamera(_camera);
_video.x = _video.y = 0;
_bmpd = new BitmapData(_video.width, _video.height, false, 0x000000);
_bmp = new Bitmap(_bmpd);
_bmp.width = stage.stageWidth;
_bmp.height = stage.stageHeight;
_sp.addChild(_bmp);
_cameraBmp = new Bitmap(_bmpd);
_cameraBmp.width = stage.stageWidth;
_cameraBmp.height = stage.stageHeight;
_cameraSp.addChild(_cameraBmp);
_bmpd2 = new BitmapData(_video.width, _video.height, false, 0x000000);
_blackBmpd = new BitmapData(_video.width, _video.height, false, 0x000000);
_pastBmpd = new BitmapData(_video.width, _video.height, false, 0x000000);
_nowBmpd = new BitmapData(_video.width, _video.height, false, 0x000000);
_pastBmpd.draw(_video);
_nowBmpd.draw(_video);
_divBmpd = new BitmapData(_video.width / DIV, _video.height / DIV, false, 0xFF0000);
var i:int;
var j:int;
_changeFlag = false;
_changeIndex = [];
_changeIndex[0] = [0, 0, 0, 0];
_changeIndex[1] = [0, 0, 0, 0];
_map = [];
for (i = 0; i < DIV; i++) {
_map[i] = [];
for (j = 0; j < DIV; j++) {
_map[i][j] = 0;
}
}
this.addEventListener(Event.ENTER_FRAME, onEnterFrame);
Tweener.init();
}
// フレーム処理
private function onEnterFrame(event:Event):void {
detectMotionDirection();
}
// 動きから方向を検出
private function detectMotionDirection():void {
_bmpd.draw(_video);
_bmpd2.lock();
_nowBmpd.draw(_video);
// 前回との差分(変化領域)を取得
_nowBmpd.draw(_pastBmpd, new Matrix(), new ColorTransform(), BlendMode.DIFFERENCE);
//_nowBmpd = _nowBmpd.compare(_pastBmpd) as BitmapData;
// 黒一色(フレームレートの関係で前回と同じコマを取得してしまった場合)はスルー
if (_nowBmpd.compare(_blackBmpd) == 0) return;
_pastBmpd.copyPixels(_bmpd, _bmpd.rect, new Point());
_bmpd2.draw(_nowBmpd);
// グレースケール化
grayscale_filter(_bmpd2);
// 二値化
var mask:uint = 0x00FFFFFF;
var threshold:uint = 0xFF222222;
_bmpd2.threshold(_bmpd2, _bmpd2.rect, new Point(0, 0), ">=", threshold, 0xFFFFFFFF, mask, true);
_bmpd2.threshold(_bmpd2, _bmpd2.rect, new Point(0, 0), "<", threshold, 0xFF000000, mask, true);
// 10×10領域に分割して評価
var hist:Vector.<Vector.<Number>>;
var i:int;
var j:int;
var rect:Rectangle;
var pxMax:int = _divBmpd.width * _divBmpd.height;
var color:int;
var id:int;
var cnt:int = 0;
for (i = 0; i < DIV; i++) {
for (j = 0; j < DIV; j++) {
rect = new Rectangle(_bmpd2.width / DIV * j, _bmpd2.height / DIV * i, _bmpd2.width / DIV, _bmpd2.height / DIV);
_divBmpd.copyPixels(_bmpd2, rect, new Point());
hist = _divBmpd.histogram();
if (hist[0][255] / pxMax < 0.5) {
color = 0;
cnt++;
}else {
color = 1;
}
if (_map[i][j] != color) {
_map[i][j] = 1;
}else {
_map[i][j] = 0;
}
_bmpd2.fillRect(rect, 0x00FF00 * _map[i][j]);
}
}
if (cnt == DIV * DIV) {
// 変化なし
if (_changeFlag) {
for (i = 0; i < 4; i++) {
_changeIndex[1][i] = searchChangeEdge(i);
}
var w:int = Math.abs(_changeIndex[1][2] - _changeIndex[1][3]);
var h:int = Math.abs(_changeIndex[1][0] - _changeIndex[1][1]);
// 長さが5マス以上の場合、上下方向、左右方向の判定
if (Math.max(w, h) >= 5) {
if (w >= h) {
// 左右
if (Math.abs(_changeIndex[1][2] - _changeIndex[0][2]) >= Math.abs(_changeIndex[1][3] - _changeIndex[0][3])) {
// 左端のほうが変化が大きい=左方向への動き
appearArrow(2);
}else {
appearArrow(3);
}
}else {
// 上下
if (Math.abs(_changeIndex[1][0] - _changeIndex[0][0]) >= Math.abs(_changeIndex[1][1] - _changeIndex[0][1])) {
// 上端のほうが変化が大きい=上方向への動き
appearArrow(0);
}else {
appearArrow(1);
}
}
}
}
_changeFlag = false;
for (i = 0; i < DIV; i++) {
for (j = 0; j < DIV; j++) {
_map[i][j] = 0;
}
}
}else {
// 変化あり
if (_changeFlag == false) {
_changeFlag = true;
for (i = 0; i < 4; i++) {
_changeIndex[0][i] = searchChangeEdge(i);
}
}
}
_bmpd2.unlock();
_bmp.bitmapData = _bmpd2;
}
//
private function appearArrow(dir:int):void{
switch(dir){
case 0:
_arrowSp.rotation = 0;
break;
case 1:
_arrowSp.rotation = 180;
break;
case 2:
_arrowSp.rotation = 270;
break;
case 3:
_arrowSp.rotation = 90;
break;
}
_arrowSp.alpha = 1.0;
_arrowSp.visible = true;
_arrowSp.scaleX = 0.0;
_arrowSp.scaleY = 0.0;
Tweener.addTween(_arrowSp, {
scaleX: 2.0,
scaleY: 2.0,
alpha: 0.0,
time: 1.0,
transition: Equations.easeInOutCubic,//easeOutBack,//easeInOutCubic,
onComplete: function ():void {
_arrowSp.visible = false;
}
});
}
// 変化領域を取得
private function searchChangeEdge(dir:int):int {
var i:int;
var j:int;
var val:int = 0;
// 上下左右の順
switch(dir) {
case 0 :
for (i = 0; i < DIV; i++) {
if (_map[i].indexOf(1) != -1) {
val = i;
return val;
}
}
break;
case 1 :
for (i = 0; i < DIV; i++) {
if (_map[DIV - i - 1].indexOf(1) != -1) {
val = DIV - i - 1;
return val;
}
}
break;
case 2 :
for (i = 0; i < DIV; i++) {
for (j = 0; j < DIV; j++) {
if (_map[j][i] == 1) {
val = i;
return val;
}
}
}
break;
case 3 :
for (i = 0; i < DIV; i++) {
for (j = 0; j < DIV; j++) {
if (_map[j][DIV - i - 1] == 1) {
val = DIV - i - 1;
return val;
}
}
}
break;
}
return val;
}
// グレースケール化
public function grayscale_filter(bmpd:BitmapData):void {
var rAmp:Number;
var gAmp:Number;
var bAmp:Number;
//rAmp = gAmp = bAmp = 1 / 3;
rAmp = 0.299;
gAmp = 0.587;
bAmp = 0.114;
var cmf:ColorMatrixFilter = new ColorMatrixFilter([
rAmp, gAmp, bAmp, 0, 0,
rAmp, gAmp, bAmp, 0, 0,
rAmp, gAmp, bAmp, 0, 0,
0, 0, 0, 1, 0
]);
bmpd.applyFilter(bmpd, bmpd.rect, new Point(0, 0), cmf);
}
}
}