PixelBenderで輪郭抽出&輪郭に雪を積もらせる
最近カメラで取得した映像を使って遊んでます。
映像の中の人とかに雪が積もるといいなーと思ってやってみました。
境界線が分かればなんとかなるので、↓こちらを参考にやってみました。
http://web.sfc.keio.ac.jp/~shokai/archives/2007/05/proce55ing-webcam-edge-detect.html
●まずはActionScriptで
元ソースはProcessingなので、ASに書き直す必要があります。
そのままいけないので試行錯誤した結果が下記のような感じです。
(※省略してます)
考え方としては、まず画像全体の色を4段階にして処理をしやすくし、それから境界線を調べていくようです。
//RGBの各チャンネルを3つのBitmapDataのBLUEチャンネルにコピー
bd1Red.copyChannel(bd1,bd1.rect,pt,BitmapDataChannnel.RED,BitmapDataChannnel.BLUE);
bd1Blue.copyChannel(bd1,bd1.rect,pt,BitmapDataChannnel.GREEN,BitmapDataChannnel.BLUE);
bd1Green.copyChannel(bd1,bd1.rect,pt,BitmapDataChannnel.BLUE,BitmapDataChannnel.BLUE);
//4段階に量子化
//各座標の色を取得し、0x00~0xFFまでの値にし、その中でさらに4段階化する。
//0x00~0xFFまでの値にしやすいように全部BLUEチャンネルにコピー
//その状態でgetPixelしたら0x00~0xFF(0x000000~0x0000FF)の範囲で取得できるかと思いきや・・・
//他のチャンネルも同じ値になるみたい。
//仕方なくgetPixelしてから0xFFFF00で割った余りを使って0x00~0xFFの範囲にする
//一度64で割ってから小数点以下を切り捨てて、また64倍することで4段階の色に分かれる
//(0~255の256段階なので4段階にするには4で割った64で割れば0~3の4段階になる)
for(var yy:int = 0; yy < 240; yy++){
quants_pixels[yy] = [];
for(var xx:int = 0; xx < 320; xx++){
var red:Number = bd1Red.getPixel(xx,yy) % 0xFFFF00;
var blue:Number = bd1Blue.getPixel(xx,yy) % 0xFFFF00;
var green:Number = bd1Green.getPixel(xx,yy) % 0xFFFF00;
var level:Number = Math.floor((red + blue + green)/3/64) * 64;
quants_pixels[yy][xx] = level; //draw each pixel to the screen
}
}
// 周囲(4ピクセル)の平均値と比較する
//各ピクセルを上下左右の平均と比べて、それより大きいか小さいかで境界線かどうかを判定するようです
for(yy=1; yy<240-1; yy++) {
for(xx=1; xx<320-1; xx++) {
//bd2.setPixel(xx,yy,quants_pixels[yy][xx]);
var around:Number = (quants_pixels[yy - 1][xx] + quants_pixels[yy][xx - 1] + quants_pixels[yy][xx + 1] + quants_pixels[yy + 1][xx]) / 4;
if (around < quants_pixels[yy][xx]) {
bd3.setPixel(xx, yy, 0x000000);
} else {
bd3.setPixel(xx, yy, 0xFFFFFF);
}
}
}
以上のような考え方でやってます。
後半は元のやつとだいたい同じなのでいいと思うけど、前半の処理はかなり効率が悪そう。。
でもとりあえずは境界線を取得できたので、これで雪を降らそうと思ったけど、処理がかなり重いことに気付きました。。
●PixelBenderで
処理を軽くしないといけないんだけどいい方法も思いつかず、検索するのも面倒だなあと思ってたときに「PixelBender使えばいいんじゃない??」って思いついて試しにやってみました。
考え方は一緒で、色を4段階にして、その後に各ピクセルごとに境界線の判定をします。
この2つを1つのPixelBenderの処理でやりたかったけど、PixelBenderも分け分からないので2つに分けました。
でもこれで処理は全然軽くなりましたー。といってもまだ重いけど、ASだけに比べれば全然速い。
考え方は同じでやってるので、ASでやった時とPixelBenderでやった時で特に違いはなく、ただ処理が速くなりました。
これをもっとキレイにして忘年会で使おうと思います。
↓カメラ映像の境界線に雪が積もります。クリックで画面切り替わります。
雪の表現はこちらを参考にしました。
<PixelBender>
kernel FourColor < namespace: ""; vendor: ""; version: 1; description: ""; > { input image4 source; output pixel4 result; void evaluatePixel() { pixel4 color = sampleLinear( source, outCoord() ); float b1 = (color.r + color.g + color.b) / 3.0; float r = 0.0; float g = 0.0; float b2 = floor(b1 * 100.0 / 25.0); float b = b2 * 25.0 / 100.0; result = pixel4(r,g,b,1.0); } }
<PixelBender>
kernel OutLine < namespace: ""; vendor: ""; version: 1; description: ""; > { input image4 source; output pixel4 result; void evaluatePixel() { //ある座標 float2 xy = outCoord(); //その座標の上下左右の座標 float2 xy1 = float2(xy.x, xy.y-1.0); float2 xy2 = float2(xy.x-1.0, xy.y); float2 xy3 = float2(xy.x+1.0, xy.y); float2 xy4 = float2(xy.x, xy.y+1.0); //それぞれの色を取得 pixel4 cl = sampleLinear(source, xy); pixel4 cl1 = sampleLinear(source, xy1); pixel4 cl2 = sampleLinear(source, xy2); pixel4 cl3 = sampleLinear(source, xy3); pixel4 cl4 = sampleLinear(source, xy4); //各色のBlueチャンネルの色を取得(渡される画像は青のチャンネルしかないはず) pixel1 b = cl.b; pixel1 b1 = cl1.b; pixel1 b2 = cl2.b; pixel1 b3 = cl3.b; pixel1 b4 = cl4.b; pixel4 aaa; //上下左右の色の平均値よりも大きいか小さいかで境界線を判定 if ((b1+b2+b3+b4)/4.0 < b) { aaa = pixel4(0.0,0.0,0.0,1.0); } else { aaa = pixel4(1.0,1.0,1.0,1.0); } result = aaa; } }
<ActionScript>
package {
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.display.Shader;
import flash.filters.ShaderFilter;
import flash.filters.BlurFilter;
import flash.geom.ColorTransform;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.media.Camera;
import flash.media.Video;
import flash.display.BlendMode;
import flash.utils.ByteArray;
[SWF(width=640,height=480,backgroundColor=0xFFFFFF,frameRate=120)]
public class Snow extends Sprite {
//青の4段階色に変換するためのPixelBender
[Embed(source = "test09.pbj", mimeType = "application/octet-stream")]
private var shaderData:Class;
//↑の境界線にするためのPixelBender
[Embed(source = "test10.pbj", mimeType = "application/octet-stream")]
private var shaderData2:Class;
private var shader:Shader;
private var shaderFilter:ShaderFilter;
private var shader2:Shader;
private var shaderFilter2:ShaderFilter;
private var particleList:Array = [];
private var my_cam:Camera;
private var video:Video;
private var bd1:BitmapData;
private var bd2:BitmapData;
private var bd3:BitmapData;
private var bdSnow:BitmapData;
private var bdBlack:BitmapData;
private var bdSmall:BitmapData;
private var bmBlack:Bitmap;
private var pt:Point;
private var cl:ColorTransform;
private var mtSmall:Matrix;
private var mtBig:Matrix;
public function Snow() {
addEventListener(Event.ADDED_TO_STAGE, added);
}
private function added(e:Event):void {
removeEventListener(Event.ADDED_TO_STAGE, added);
init();
}
private function init():void {
//PixelBenderのやつをFilterとして使うための処理
shader = new Shader(ByteArray(new shaderData()));
shaderFilter = new ShaderFilter(shader);
shader2 = new Shader(ByteArray(new shaderData2()));
shaderFilter2 = new ShaderFilter(shader2);
pt = new Point();
cl = new ColorTransform();
//カメラ
my_cam = Camera.getCamera();
my_cam.setMode(320, 240, 30, false);
//カメラの映像の表示
//デフォルトで全画面、ステージクリックで裏の処理過程のBitmapDataが見れるようにサイズ変更
video = new Video();
video.attachCamera(my_cam);
video.width = 640;
video.height = 480;
video.x = 0;
video.y = 0;
stage.addEventListener(MouseEvent.CLICK, clickVideo);
function clickVideo(e:MouseEvent):void {
if (video.width > 500) {
video.width = 320;
video.height = 240;
bmBlack.alpha = 0.0;
} else {
video.width = 640;
video.height = 480;
bmBlack.alpha = 0.5;
}
}
//カメラの映像をBitmapData化してBlurを少しかけた状態を出力する用
bd1 = new BitmapData(320, 240, false);
var bm1:Bitmap = new Bitmap(bd1);
bm1.x = 320;
bm1.y = 0;
addChild(bm1);
//PixelBenderを使って青の4段階色に変換した状態を出力する用
bd2 = new BitmapData(320, 240, false);
var bm2:Bitmap = new Bitmap(bd2);
bm2.x = 0;
bm2.y = 240;
addChild(bm2);
//PixelBenderを使って境界線の状態にしたのを出力する用
bd3 = new BitmapData(320, 240, false);
var bm3:Bitmap = new Bitmap(bd3);
bm3.x = 320;
bm3.y = 240;
addChild(bm3);
//カメラの映像は上のレイヤーにしたいのでここでaddChild
addChild(video);
//雪用のBitmapDataを毎回黒で塗りなおすので、それ用
bdBlack = new BitmapData(640, 480, false, 0x000000);
//カメラ映像は少し暗めにすると雪が見やすくなるので↑を使いまわしてアルファかけて配置
bmBlack = new Bitmap(bdBlack);
addChild(bmBlack);
bmBlack.blendMode = BlendMode.MULTIPLY;
bmBlack.alpha = 0.5;
//雪を降らす用のBitmapData
bdSnow = new BitmapData(640, 480, false, 0x000000);
var bmSnow:Bitmap = new Bitmap(bdSnow);
addChild(bmSnow);
//BitmapDataの背景色(黒)を消すためにBlendModeを使う。
//ScreenでもいいかもしれないけどADDのほうが明るくなるので。
bmSnow.blendMode = BlendMode.ADD;
//雪で使うのを作っとく
bdSmall = new BitmapData(320, 240, false, 0x000000);
mtSmall = new Matrix(0.5, 0, 0, 0.5, 0, 0);
mtBig = new Matrix(2, 0, 0, 2, 0, 0);
addEventListener(Event.ENTER_FRAME, loop);
}
private function loop(e:Event):void {
//カメラの映像をdraw
bd1.draw(video);
//境界線が複雑になり過ぎる場合はブラーかける。かけすぎると境界線が取得しにくくなる。
//bd1.applyFilter(bd1, bd1.rect, pt, new BlurFilter(2, 2, 1));
//PixelBenderを使って青の4段階色に変換
bd2.draw(bd1);
bd2.applyFilter(bd2, bd2.rect, pt, shaderFilter);
//PixelBenderを使って境界線に変換
bd3.draw(bd2);
bd3.applyFilter(bd3, bd3.rect, pt, shaderFilter2);
//ブラーをかけて境界線を太くする
//これもPixelBenderでやっちゃうほうが速いかも
bd3.applyFilter(bd3, bd3.rect, pt, new BlurFilter(2, 2, 1));
//雪のオブジェクト(Particle)をaddし、配列particleListに入れてく
addParticle();
addParticle();
addParticle();
addParticle();
addParticle();
//雪の表示用BitmapDataを黒く塗りつぶす
bdSnow.draw(bdBlack);
//particleListに入ってる雪のオブジェクトを1つずつ座標更新、表示していく
for (var i:int = particleList.length - 1; i >= 0; i--) {
//雪オブジェクトを取り出す
var tw:Particle = Particle(particleList[i]);
//境界線のBitmapData(bd3)の中で、雪オブジェクトと同じ座標の色を取得
if (tw.y < 4 || tw.y > 476 || bd3.getPixel(Math.round(tw.x/2), Math.round(tw.y/2)) == 0xFFFFFF) {
//それが白だったら何もない
tw.update(false);
} else {
//白じゃなかったら境界線上
//元は白or黒だけど、ブラーかけたのでグレーの部分も境界線となるので「白以外は境界線」とする
tw.update(true);
}
//雪オブジェクトと同じ座標のピクセルを白で塗りつぶす
bdSnow.setPixel(tw.x, tw.y, 0xFFFFFF);
//雪オブジェクトが画面から消えてたら配列から削除
if (tw.del) {
particleList.splice(i, 1);
}
}
//雪のキラキラをするために1回小さくする
bdSmall.draw(bdSnow, mtSmall);
bdSmall.applyFilter(bdSmall, bdSmall.rect, pt, new BlurFilter(2, 2, 1));
bdSnow.draw(bdSmall, mtBig, cl, BlendMode.SCREEN);
//軽くブラーをかけてみる
bdSnow.applyFilter(bdSnow, bdSnow.rect, pt, new BlurFilter(2, 2, 1));
}
private function addParticle():void {
//雪オブジェクトの追加
var tw:Particle = new Particle();
particleList.push(tw);
tw.x = Math.random() * 640;
tw.y = 1;
}
}
}
import flash.display.Bitmap;
class Particle {
public var x:Number;
public var y:Number;
public var del:Boolean = false;
private var vy:Number;
private var ay:Number;
function Particle() {
vy = 0;
ay = 0.03;
}
public function update(b:Boolean):void {
if (b) {
//境界線上の速度
vy = 0.01;
} else {
//何もないとこの速度
vy += ay;
if (vy > 3) {
vy = 3;
}
}
y += vy;
if (y > 480) {
//画面から消えたらdel=trueにすることで削除対象になる
del = true;
}
}
}
| 固定リンク


コメント