« 2008年9月 | トップページ | 2008年11月 »

2008年10月27日 (月)

FLASHで「L.A.S.E.R Tag」みたいなやつのテスト

L.A.S.E.R Tag」みたいなのをFLASHで作りたいなーと思ってちょっとやってみました。

簡単に言うとカメラの映像の中から指定した色の部分を検出し、それと同じ座標に描画していけばいいのかなと。
プロジェクターで出力した部分をカメラで取得することで、実際にそこに描いてるかのように見せられる。

いくつか必要なものがあるけど、ちゃんとしたものが無いので以下のような感じでやりました。

<プロジェクタ>
あるけど出すの面倒なので、モニターで。。

<レーザーポインタ>
無いのでペンの先に赤いのをつけてそれで代用。

<カメラ>
安いWEBカメラで。もっといいやつなら動きもよくなるかと。
これだとフレームレート5~7とかなので。

ということでやってみたのがこれ。(要:FlashPlayer10)

やってみた映像

左上がカメラの映像。モニターを映してみてください。出力した映像(下の大きいエリア=描画エリア)が収まるようにします。
左上の映像の四隅の青い四角を移動させて描画エリアを指定。(映像を見てもらえれば分かるかな・・・)
真ん中の映像が描画エリアだけになります。
右上が描画エリア内の赤い部分の検出状況。
今回モニターでやってますが、モニターの描画エリア部分のとこで赤いものを動かすとそれが検出されて、その部分に描画されていきます。

処理の流れは以下のとおりです。

1.カメラの映像を取得
2.キャプチャーするエリア(描画エリア)だけ抜き出す
3.赤い部分を検出する
4.検出した部分の座標を使って描画

●1.カメラの映像を取得
これは特に説明する必要もない感じです。

●2.キャプチャーするエリア(描画するエリア)だけ抜き出す
プロジェクタで映し出した部分(今回はモニター)をカメラで撮るわけですが、余計な部分も入ってしまいます。
それに正面にカメラをおけるわけではないので、パースがついてしまいます。
これを必要な部分だけをパースがつかない状態にするために、カメラ固定後に描画エリアの四隅を指定するようにします。
今回のでは左上の映像の四隅に青い四角が置いてありますが、それを左上の映像内の描画エリアの四隅に移動させます。
そうすると上の真ん中の映像が描画エリアだけになります。

これをするためにFlashPlayer10からのdrawTrianglesを使いました。
(参考)
http://www.senocular.com/flash/tutorials/flash10drawingapi/

●3.赤い部分を検出する
こちらを参考にしました。
http://www.hatayan.org/weblog/archives/2005/11/30/123132.php

BitmapDataの赤のチャンネルから、青のチャンネル+緑のチャンネルを引いたものが赤い部分になります。

●4.検出した部分の座標を使って描画
これは単にマウスで描くのと同じで、マウス座標使うか検出したエリアの座標使うかの違いだけです。
描画方法はいろいろありますけど、とりあえず簡単なやつで。

精度とか置いといて一応動いた。実際プロジェクタとレーザーポインタでもっと遠い距離でやってみてどうかかな。
こういう仕組みを使ってなんかおもしろいことできればいいなーと思ってます。

package {
	import flash.display.Shape;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.BitmapDataChannel;
	import flash.events.StatusEvent;
	import flash.filters.BlurFilter;
	import flash.geom.Matrix;
	import flash.geom.Point;
	import flash.geom.ColorTransform;
	import flash.geom.Rectangle;
	import flash.media.Camera;
	import flash.media.Video;
	import flash.display.BlendMode;
	public class LaserTag extends Sprite {
		private var cam:Camera;
		private var video:Video;
		private var bdCaptureArea:BitmapData;
		private var bdDetector:BitmapData;
		private var bdOutput:BitmapData;
		private var mt:Matrix;
		private var pt:Point;
		private var ct:ColorTransform
		private var box:Shape;
		
		private var cameraPoint1:Sprite;
		private var cameraPoint2:Sprite;
		private var cameraPoint3:Sprite;
		private var cameraPoint4:Sprite;
		
		private var tri1:Shape;
		private var mouseCursor:Shape;
		private var endX:Number = 0;
		private var endY:Number = 0;
		public function LaserTag() {
			addEventListener(Event.ADDED_TO_STAGE, added);
		}
		private function added(e:Event):void {
			removeEventListener(Event.ADDED_TO_STAGE, added);
			init();
		}
		private function init():void {
			//よく使うやつを作っとく。
			mt = new Matrix();
			pt = new Point();
			ct = new ColorTransform();
			
			//カメラ
			cam = Camera.getCamera();
			cam.setMode(320, 240, 30, false);
			video = new Video();
			video.attachCamera(cam);
			addChild(video);
			
			//カメラの動画内のキャプチャーする部分用
			bdCaptureArea = new BitmapData(320, 240, false, 0x000000);
			//↑の赤い部分だけを残したやつ
			bdDetector = new BitmapData(320, 240, false, 0x000000);
			//出力用
			bdOutput = new BitmapData(960, 720, false, 0x000000);
			
			//それぞれaddChild
			var bm1:Bitmap = new Bitmap(bdCaptureArea);
			addChild(bm1);
			bm1.x = 320;
			var bm2:Bitmap = new Bitmap(bdDetector);
			addChild(bm2);
			bm2.x = 640;
			var bm3:Bitmap = new Bitmap(bdOutput);
			addChild(bm3);
			bm3.x = 0;
			bm3.y = 240;
			
			//赤い部分の検出エリアを分かりやすくするためのBOX
			box = new Shape();
            box.graphics.lineStyle(1, 0x0000FF);
            box.graphics.drawRect(0, 0, 100, 100);
            addChild(box);
			
			//キャプチャー部分を長方形にする用
			tri1 = new Shape();
			tri1.x = 320;
            addChild(tri1);
			
			//描画用の青い丸
			mouseCursor = new Shape();
			mouseCursor.graphics.beginFill(0x000099, 0.8);
			mouseCursor.graphics.drawCircle( -10, -10, 20);
			mouseCursor.graphics.endFill();
			
			//キャプチャー範囲指定用の4つのポイントを作成
			setCameraPoints();
			
			//ENTER_FRAMEスタート
			addEventListener(Event.ENTER_FRAME, loop);
			
			//画面クリックで出力用BitmapDataを初期化
			stage.addEventListener(MouseEvent.MOUSE_DOWN, reflesh);
		}
		private function reflesh(e:MouseEvent):void {
			//黒く塗りつぶす
			bdOutput.draw(new BitmapData(960, 720, false, 0x000000));
		}
		
		private function loop(e:Event):void {
			//カメラの映像をBitmapDataにdraw
			var bdVideo:BitmapData = new BitmapData(320, 240);
			bdVideo.draw(video);
			
			//4つのcameraPointの範囲の部分を長方形にする
			tri1.graphics.clear();
			tri1.graphics.beginBitmapFill(bdVideo);
            tri1.graphics.lineStyle(1, 0x0000FF);
			tri1.graphics.drawTriangles(
				Vector.([0, 0,			//左上
								320, 0,			//右上
								0, 240,			//左下
								320, 240]),		//右下
				Vector.([0,1,2, 1,3,2]),
				Vector.([cameraPoint1.x/320, cameraPoint1.y/240,	//左上
								cameraPoint2.x/320, cameraPoint2.y/240,		//右上
								cameraPoint3.x/320, cameraPoint3.y/240,		//左下
								cameraPoint4.x/320, cameraPoint4.y/240]));	//右下
			tri1.graphics.endFill();
			
			//長方形にしたものをdraw
			bdCaptureArea.draw(tri1);
			
			//赤い部分を検知
			var bdBlue:BitmapData = new BitmapData(320, 240);
			var bdGreen:BitmapData = new BitmapData(320, 240);
			var bdRed:BitmapData = new BitmapData(320, 240);
			bdBlue.copyChannel(bdCaptureArea, bdCaptureArea.rect, pt, BitmapDataChannel.BLUE, BitmapDataChannel.RED);
			bdGreen.copyChannel(bdCaptureArea, bdCaptureArea.rect, pt, BitmapDataChannel.GREEN, BitmapDataChannel.RED);
			bdRed.copyChannel(bdCaptureArea, bdCaptureArea.rect, pt, BitmapDataChannel.RED, BitmapDataChannel.RED);
			bdBlue.draw(bdGreen, mt, ct, BlendMode.LIGHTEN);
			bdRed.draw(bdBlue, mt, ct, BlendMode.SUBTRACT);
			bdDetector.draw(bdRed);
			bdDetector.threshold(bdDetector, bdDetector.rect, pt, ">", 0xff220000, 0xffFF0000);
			
			//赤い部分の短形を取得
			var rect:Rectangle = bdDetector.getColorBoundsRect(0xffFF0000, 0xffFF0000, true);
			
			//赤い部分を囲むようにBOXを移動、変形
			box.width = rect.width;
			box.height = rect.height;
			box.x = rect.x + 640;
			box.y = rect.y;
			
			//検出されてる場合はその座標を設定
			if (rect.x > 0 && rect.y > 0) {
				endX = rect.x * 3;
				endY = rect.y * 3;
			}
			
			//検出された部分に青い丸が向かってく
			mouseCursor.x += (endX - mouseCursor.x) / 10;
			mouseCursor.y += (endY - mouseCursor.y) / 10;
			
			//青い丸を描画&毎回全体にブラーをかける
			bdOutput.applyFilter(bdOutput, bdOutput.rect, pt, new BlurFilter(2, 2, 3));
			var mtMouse:Matrix = new Matrix(1, 0, 0, 1, mouseCursor.x, mouseCursor.y);
			bdOutput.draw(mouseCursor, mtMouse);
		}
		
		private function setCameraPoints():void {
			//キャプチャー範囲指定用のCameraPointを設置
			cameraPoint1 = new CameraPoint();
			cameraPoint2 = new CameraPoint();
			cameraPoint3 = new CameraPoint();
			cameraPoint4 = new CameraPoint();
			
			addChild(cameraPoint1);
			addChild(cameraPoint2);
			addChild(cameraPoint3);
			addChild(cameraPoint4);
			
			cameraPoint2.x = 320;
			cameraPoint3.y = 240;
			cameraPoint4.x = 320;
			cameraPoint4.y = 240;
		}
	}
}
import flash.display.Sprite;
import flash.display.Shape;
import flash.events.Event;
import flash.events.MouseEvent;
class CameraPoint extends Sprite {
	
	public function CameraPoint() {
		addEventListener(Event.ADDED_TO_STAGE, added);
	}
	
	private function added(e:Event):void {
		removeEventListener(Event.ADDED_TO_STAGE, added);
		init();
	}
	
	private function init():void {
		var box:Shape = new Shape();
		box.graphics.beginFill(0x0000FF);
		box.graphics.lineStyle(1, 0x0000FF);
		box.graphics.drawRect(-10, -10, 20, 20);
		box.graphics.endFill();
		addChild(box);
		
		buttonMode = true;
		addEventListener(MouseEvent.MOUSE_DOWN, startMove);
	}
	
	private function startMove(e:MouseEvent):void {
		addEventListener(Event.ENTER_FRAME, loop);
		stage.addEventListener(MouseEvent.MOUSE_UP, stopMove);
	}
	private function stopMove(e:MouseEvent):void {
		removeEventListener(Event.ENTER_FRAME, loop);
		stage.removeEventListener(MouseEvent.MOUSE_UP, stopMove);
	}
	
	private function loop(e:Event):void {
		x = stage.mouseX;
		y = stage.mouseY;
	}
}

| | コメント (0) | トラックバック (0)

2008年10月17日 (金)

アルファをかけた時の色の違い

背景色と同じ色のオブジェクトを置くと、もちろん色が同じなのでどこに置いたか分からなくなります。
ではそのオブジェクトにアルファをかけるとどうなるか。
背景色と同じ色だからアルファをかけても見た目には変わらないように思えます。
でも実際は色味が微妙に変わってしまい、オブジェクトの境界線が見えてしまう場合があります。

世間的には当たり前のことかもしれないけど、個人的にはさっき気付いた。

どういう時に変わってしまうのか軽く検証してみました。

・明るい色の場合は大丈夫
・暗い色の時、変わってしまう場合がある
・RGBの各値が偶数だと大丈夫で奇数だとだめっぽい
・RGBの各値が80以上のところは大丈夫っぽい

全部調べたわけでもなく、原因も分からないので正確じゃないかもしれないけど
RGBの各値で、80以下で奇数の部分が無いようにすればいいっぽい。

↓テストしてみたやつ。

左側の背景色が「#130900」、右側が「#140800」となってます。
左右の数値の上に背景色と同じ色の四角いオブジェクトが置いてあります。アルファを50%にしてます。
左側はよく見ると四角の境界線が見えます。リンク先のswfのほうが分かりやすいかも。
左側は「#130900」でRとGが奇数です。
右側はRとGを偶数にして「#140800」としたものです。こうすると境界線が見えなくなります。
少なくとも僕の環境では。
環境によってはこれでも見えちゃうのかな。。

<追記:2008/10/20>
上記の話はアルファが50%の時だけっぽい。。
アルファ50以下、50以上で値が変わってくる。うーん。。。

| | コメント (0) | トラックバック (0)

2008年10月16日 (木)

Google Maps API for FLASHがいつの間にかバージョン変わってた。。

Google Maps API for FLASHが何も変えてないはずなのにエラーが出るようになったのでサイトを見てみたらいつの間にかバージョンが1.7に・・・さっきまで1.3使ってた。たまにチェックしてたんだけどなあ。
しかも前まではflexしか対応してなかったと思うけど、FLASH CS3でも使用可能に。
Flexで作ってたけどFLASHに移行しよう。慣れてるからかFLASHのほうがやっぱりやりやすい。

Flexのほうでswcを1.3から1.7に変更してそのままパブリッシュしたら再生中にエラーが出たけど、同じソースでFLASH CS3で1.7のを使ってやったら特に問題無し。
原因は探らない。。

| | コメント (0) | トラックバック (0)

2008年10月14日 (火)

デザイン変更

前のは横幅が狭くてスクリプト載せるとすごい見にくかったので変更しました。
デザインできないのでココログのリッチテンプレートを使ってるけど、これは幅が決まってるので強制的にHTML内にCSS埋め込んで幅を広げました。
デザインできるようになりたい。。CSSも。。

そういえば「Mashup Awards 4」に応募しましたよ。「週末は箱根に行こう。」です。
別に受賞しようとかそういう気は全然無く、参加することに意義がある的な感じで。
他の作品はあんまり見てないけど、作り込んでる方もいらっしゃいますね。

週末は箱根に行こう。」はバージョンアップのためにいろいろ検討中です。
元々はFLASHの勉強のために始めたけど、もっとユーザーのことも考えつつサイトとして成り立つようにしたいなあ。

| | コメント (0) | トラックバック (0)

2008年10月10日 (金)

FlashPlayer10 読み込んだ音にエフェクトをかける

さっきのやつで、読み込んだMP3に対してエフェクトをかけるのは下記のとおり。
「music.mp3」という名前のMP3を用意してswfと同じとこにおいてください。

ブログのデザイン変えようかな。。横幅広げないと見にくい。

package {
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.media.Sound;
	import flash.utils.ByteArray;
	import flash.net.URLRequest;
	import flash.events.SampleDataEvent;
	import org.sazameki.audio.engine.ssGenerator.SsAudioSetting;
	import org.sazameki.audio.processor.effects.Distortion;
	import org.sazameki.audio.core.Sample;
	public class Main6 extends Sprite {
		private const BUFFER_LENGTH:Number = 8192;
		private var dynamicSound:Sound;
		private var dist:Distortion;
		private var distNo:int = 0;
		private var ssaSetting:SsAudioSetting;
		private var mp3sound:Sound;
		private var samples:ByteArray;
		
		public function Main6():void {
			if (stage) {
				init();
			} else {
				addEventListener(Event.ADDED_TO_STAGE, init);
			}
		}
		
		private function init(e:Event = null):void {
			removeEventListener(Event.ADDED_TO_STAGE, init);
			//チャンネル数やサンプルレートなどの設定。通常は引数無しでOK?
			ssaSetting = new SsAudioSetting();
			//Distortionインスタンスの作成
			dist = new Distortion();
			//Distortionの設定
			//第一引数はさっきの設定(SsAudioSetting)、第二引数にgain値を渡す(配列で)
			//(Distortionはパラメータ1つだけど、他のReverbとかでパラメータが複数ある関係で配列になってるかと)
			dist.initialize(ssaSetting, [0.0]);
			
			//Soundオブジェクトの作成
			dynamicSound = new Sound();
			
			//MP3の読み込み
			mp3sound = new Sound();
			mp3sound.addEventListener(Event.COMPLETE, loadComplete);
			mp3sound.load(new URLRequest("music.mp3"));
			
			//MP3の波形データを入れる用のByteArray
			samples = new ByteArray();
			
			//Distortionのgain値の切り替え用マウスイベント
			stage.addEventListener(MouseEvent.MOUSE_DOWN, changeDist);
		}
		
		private function loadComplete(event:Event = null):void {
			//MP3が読み込み終わったら
			
			//sampleDataイベントを設定
			dynamicSound.addEventListener("sampleData", sampleData);
			//再生
			dynamicSound.play();

		}
		
		private function changeDist(e:MouseEvent):void {
			//マウスクリックの度にgain値が0.0→0.3→0.6→0.0・・・と変わっていきます
			distNo++;
			if (distNo > 2) {
				distNo = 0;
			}
			//gain値を変えるためにinitializeで再設定。こんなやり方でいいのかな??
			//Distortion.asにgainのsetterを入れてしまうという手も。。
			if (distNo == 1) {
				dist.initialize(ssaSetting, [0.3]);
			} else if (distNo == 2) {
				dist.initialize(ssaSetting, [0.6]);
			} else {
				dist.initialize(ssaSetting, [0.0]);
			}
		}
		
		private function sampleData(event:SampleDataEvent):void {
			samples.position = 0;
			//MP3から波形を取得
			var len:Number = mp3sound.extract(samples,BUFFER_LENGTH);
			if (len < BUFFER_LENGTH) {
				len += mp3sound.extract(samples, BUFFER_LENGTH - len, 0);
			}
			samples.position = 0;
			
			var sss:Array = [];
			var c:int;
			for (c = 0; c < len; c++) {
				//MP3の波形を取得
				var left:Number = samples.readFloat();
				var right:Number = samples.readFloat();
				//Sampleインスタンスの作成
				var sam:Sample = new Sample();
				//左右のチャンネルに値を設定
				sam.left = left;
				sam.right = right;
				//配列にSampleを入れていく
				sss.push(sam);
			}
			
			//Sampleの入った配列を渡してDistortionをかける
			dist.processAudio(sss);
			
			//波形を書き込む
			for (c = 0; c < BUFFER_LENGTH; c++) {
				event.data.writeFloat(sss[c].left / 5);
				event.data.writeFloat(sss[c].right / 5);
			}
		}
	}
}



| | コメント (0) | トラックバック (0)

FlashPlayer10 音にエフェクトをかける

音でいろいろ遊びたい・・・と、いつも思ってるんだけど難しい。。
player10で波形をいじれるようになったということで時々試してるけどなかなか進まず。

動的な音の生成方法というのはなんとなく分かったけども、それをどうやったら音を変化させることができるのか、というのが全然分からん。

そんな時に↓こちらを見つけて、便利なライブラリもあったのでちょっと使ってみました。
http://www.adobe.com/jp/devnet/flash/articles/flp10_sound.html

とりあえずやってみたいのは音にエフェクトをかけること。delayとかreverbとか。

上記の中で見つけたライブラリ「sazameki」の中を見てみると「org.sazameki.audio.processor.effects」以下にそれっぽいクラスが!
このライブラリの基本的な使い方も分からんのだけど、エフェクトのクラスを使いたいがために逆にたどっていってなんとか使うことができました。できた気がするだけかもしれないけども。。

一応、下記のように書いたらDistortionをかけることができました。
画面には何も表示されません。音が鳴るはずです。マウスクリックでDistortionのgain値が変わります。

ちなみにDistortion.as内の「processAudio()」の関数にDistortionをかけるための計算が書いてあるので、そこだけ持ってきてもできそうな感じがしないでもないです。

同様にReverbもやってみましたが、処理がすごく重かったです。パラメータがおかしかったのかも!?
DelayもReverb、Distortionとは少しやり方違ってましたが同じ感じでできました。

まずは波形の意味をちゃんと理解したい。

※Flash Player10対応です。

package {
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.media.Sound;
	import flash.utils.ByteArray;
	import flash.events.SampleDataEvent;
	import org.sazameki.audio.engine.ssGenerator.SsAudioSetting;
	import org.sazameki.audio.processor.effects.Distortion;
	import org.sazameki.audio.core.Sample;
	public class Main5 extends Sprite {
		private const BUFFER_LENGTH:Number = 8192;
		private var dynamicSound:Sound;
		private var dist:Distortion;
		private var distNo:int = 0;
		private var ssaSetting:SsAudioSetting;
		
		public function Main5():void {
			if (stage) {
				init();
			} else {
				addEventListener(Event.ADDED_TO_STAGE, init);
			}
		}
		
		private function init(e:Event = null):void {
			removeEventListener(Event.ADDED_TO_STAGE, init);
			//チャンネル数やサンプルレートなどの設定。通常は引数無しでOK?
			ssaSetting = new SsAudioSetting();
			//Distortionインスタンスの作成
			dist = new Distortion();
			//Distortionの設定
			//第一引数はさっきの設定(SsAudioSetting)、第二引数にgain値を渡す(配列で)
			//(Distortionはパラメータ1つだけど、他のReverbとかでパラメータが複数ある関係で配列になってるかと)
			dist.initialize(ssaSetting, [0.0]);
			
			//Soundオブジェクトの作成
			dynamicSound = new Sound();
			//sampleDataイベントを設定
			dynamicSound.addEventListener("sampleData", sampleData);
			//再生
			dynamicSound.play();
			
			//Distortionのgain値の切り替え用マウスイベント
			stage.addEventListener(MouseEvent.MOUSE_DOWN, changeDist);
		}
		private function changeDist(e:MouseEvent):void {
			//マウスクリックの度にgain値が0.0→0.3→0.6→0.0・・・と変わっていきます
			distNo++;
			if (distNo > 2) {
				distNo = 0;
			}
			//gain値を変えるためにinitializeで再設定。こんなやり方でいいのかな??
			//Distortion.asにgainのsetterを入れてしまうという手も。。
			if (distNo == 1) {
				dist.initialize(ssaSetting, [0.3]);
			} else if (distNo == 2) {
				dist.initialize(ssaSetting, [0.6]);
			} else {
				dist.initialize(ssaSetting, [0.0]);
			}
		}
		private function sampleData(event:SampleDataEvent):void {
			var sss:Array = [];
			var c:int;
			for (c = 0; c < BUFFER_LENGTH; c++) {
				//ピーっていう音を生成
				//この代わりにMP3とかの音から波形を取得するのも可能
				var rad:Number = Number(c + event.position)/Math.PI/2;
				var amp:Number = Math.sin(rad) / 4;
				//Sampleインスタンスの作成
				var sam:Sample = new Sample();
				//左右のチャンネルに値を設定
				sam.left = amp;
				sam.right = amp;
				//配列にSampleを入れていく
				sss.push(sam);
			}
			
			//Sampleの入った配列を渡してDistortionをかける
			dist.processAudio(sss);
			
			//波形を書き込む
			for (c = 0; c < BUFFER_LENGTH; c++) {
				event.data.writeFloat(sss[c].left / 5);	//音がでかいので5で割って小さくしてます
				event.data.writeFloat(sss[c].right / 5);
			}
		}
	}
}


| | コメント (2) | トラックバック (0)

« 2008年9月 | トップページ | 2008年11月 »