今回は、Webカメラ(USBカメラ)を使ってProcessing上で動体検知/動体追跡の実験を行ってみます。前回の「
Processing Webカメラ/カラートラッキング」に似たプログラムですが、特定の色を追いかけるのではなく、画面上で動いている物体を検知し、その動きの方向に合わせて物体の座標値を取得します。逆に、動いている物体が画面内に見当たらない場合は、なにも検知しないことになります。
*Windowsの場合、そのままの設定ではこのVideoライブラリを使用することができません。
WinVDIG 1.0.1をインストールする必要があります。
各ピクセルの色の取得:
・カメラ画像における、前回の画面と今回の画面の各ピクセルの色を比較します。
・320×240の画面サイズであれば76800個のピクセルを
for()を使って繰り返しの比較処理をさせることになります。
・各ピクセルの色を抽出するには、
pixels[i]で順番にひとつずつピクセルを取り出します。
・
pixels[i]は、画面内のi番目のピクセルの色の値を返します。
・さらに、その一つのピクセルをRGBの3色に分解し、それぞれの値を取得します。
・3色のそれぞれの値を取得するには、
red()、
green()、
blue()を用います。
・red(pixels[i])と書けば、そのピクセルの赤の値を取得できます(緑、青についても同様に処理)。
色の比較:
前回と今回の画面内のピクセルを比較するためには、一旦前回の全ピクセルの色情報を配列に代入して記憶させておきます。そして、記憶させておいた前回の色情報と今回の色情報を各ピクセルごとに比較します。
・色を比較するには、「前回の赤の値」から「今回の赤の値」を差し引きします(最終的に、絶対値
abs()を使うので逆でも大丈夫です)。
・各色の値は0〜255までの段階があるので、その数値の差となります。緑や青についても同様に値の差を求めておきます。
・各色において、ある一定以上の差があるときに、画面内に「動作」があったと見なします。
・多少細かなノイズなどが含まれるので、差についてはある程度の許容値を設けておきます。例えば、±20以内の差であればノイズと見なし「動作なし」と判断し、それ以上の差があるときにだけ「動作あり」と見なすことにします。
平均値で座標を求める:
上記の方法で、設定した許容値を超えるピクセルがあったときに、そのピクセルの画面内でのXY座標値を調べておきます。今回の方法では、許容値を超えるピクセル(変化があったピクセル)のXY座標と個数から平均値を求め、その値をXとYの座標値として利用することにします。
例えば、X座標値100に10個、101に12個、102に8個あるときは、(100*10+101*12+102*8)/(10+12+8)=100.9333となり、この値を平均値としてX座標値にします。
(緑の部分が変化のあったピクセル、赤い正方形の位置がそれらの平均座標値、左上に許容値表示)
「変化があったピクセルを緑で表示し座標値を求めるプログラム」:
以下のプログラムでは、見やすくするために、変化があったピクセルを緑color(0,255,0)で塗りつぶすことにします。そして、それらのピクセルの平均座標値を求めて、赤い正方形を動かすことにします。
光や明るさの状況に合わせて許容値を調整できるプログラムにしておきます。
左右の矢印キーで色の許容値(変数:tolerance)を調節できるようにします(「←」:-1、「→」:+1)。
「c」キーを押せば、カメラセッティング画面に切り替わります(手動露出や手動コントラストなどに切り替えた方が認識しやすくなります)。
[プログラムを表示]
import processing.video.*;
Capture video;
PFont font;
int w=320;
int h=240;
//前回画面ピクセル色を保存するための配列を用意
color[] exColor=new color[w*h];
//許容値の変数:50に設定しておく
int tolerance=50;
int sumX,sumY;//平均値を求めるための合計座標値の変数
int pixelNum;//変化のあったピクセルを数えるための変数
float x,y;//座標値の変数
float filterX,filterY;//フィルタをかけた座標値の変数
boolean movement=false;//動体の有無のフラグ
void setup(){
size(w, h);
video = new Capture(this, w, h);
font=createFont("Monaco",10);
textFont(font);
rectMode(CENTER);
noStroke();
}
void draw() {
if(video.available()){
background(0);
video.read();
set(0,0,video);//カメラ映像表示
loadPixels();//画面内ピクセルをロードしておく
movement=false;//動体のフラグをfalseに戻しておく
for(int i=0;i<w*h;i++){
//前回と今回の画面のピクセルの各色の差を求める
float difRed=abs(red(exColor[i])-red(video.pixels[i]));
float difGreen=abs(green(exColor[i])-green(video.pixels[i]));
float difBlue=abs(blue(exColor[i])-blue(video.pixels[i]));
//色の差が許容値以上の場合(動体がある場合)
if(difRed>tolerance && difGreen>tolerance && difBlue>tolerance){
movement=true;//動体有りのフラグをtrueにしておく
pixels[i]=color(0,255,0);//そのピクセルを緑にする
sumX+=i%w;//平均値を求めるためにX座標値を加算する
sumY+=i/w;//平均値を求めるためにY座標値を加算する
pixelNum++;//変化のあったピクセル数を数える
}
//次回ループのために今回の画面を前回の画面として保存しておく
exColor[i]=video.pixels[i];
}
//動体があった場合(画面内に変化があった場合)
if(movement==true){
updatePixels();//画面内ピクセルをアップデート
x=sumX/pixelNum;//X座標平均値を求める
y=sumY/pixelNum;//Y座標平均値を求める
//各変数を初期化しておく
sumX=0;
sumY=0;
pixelNum=0;
}
}
filterX+=(x-filterX)*0.3;//X座標にフィルタをかける
filterY+=(y-filterY)*0.3;//Y座標にフィルタをかける
fill(255,0,0);
rect(filterX,filterY,20,20);//正方形描画
text(tolerance,10,10);//許容値を表示
}
void keyPressed(){
if(key=='c'){
video.settings();//カメラセッティング
}
if(key==CODED){
if(keyCode==LEFT){
tolerance-=1;//許容値を-1する
}
if(keyCode==RIGHT){
tolerance+=1;//許容値を+1する
}
}
}
「Pongをプレイ」
次に、応用として「Pong」のパドルをモーショントラッキングで動かすサンプルをつくってみます。
動作によって変化があったピクセルの位置が画面内の左側あるいは右側を判別し、左右のパドルを個別に動かせるようにします。画面の端から50ピクセル幅のエリアで動作検知します(画面中央付近では反応しません)。
(モーショントラッキングで「Pong」をプレイする)
プレイしやすいように、カメラ映像は左右反転(鏡像)しています。
左右矢印キーで許容値を調整します(画面には許容値は表示されません)。
画面上部に点数を表示。
「c」キーでカメラセッティング。
「スペース」キーで点数をリセット。
[プログラムを表示]
import processing.video.*;
Capture video;
PFont font;
int w=320;
int h=240;
//前回画面ピクセル色を保存するための配列を用意
color[] exColor=new color[w*h];
int tolerance=20;
float y;//右パドルのY座標
int sumY;//平均値を求めるための合計座標値の変数
int pixelNum;//変化のあったピクセル数を数えるための変数
float filterY;//フィルタをかけたY座標変数
boolean movement=false;//動体検知フラグ
float y2;//左パドルのY座標
int sumY2;//平均値を求めるための合計座標値の変数
int pixelNum2;//変化のあったピクセル数を数えるための変数
float filterY2;//フィルタをかけたY座標変数
boolean movement2=false;//動体検知フラグ
int ballX=-200,ballY=height/3;//ボール座標
int dirX=1,dirY=1;//ボールの向きの変数
int speedX=2,speedY=2;//ボール移動量変数
int pt=0,pt2=0;//点数の変数
void setup(){
size(w, h);
video = new Capture(this, w, h);
font=createFont("Monaco",10);
textFont(font);
rectMode(CENTER);
noStroke();
}
void draw() {
if(video.available()){
background(0);
video.read();
scale(-1,1);//画面を鏡像(左右反転)
image(video,-w,0);//鏡像のため映像のX座標を-wにして表示
scale(-1,1);//鏡像を戻しておく
movement=false;//動体検知のフラグをfalseにしておく(初期化)
movement2=false;
for(int i=0;i<w*h;i++){
//前回と今回の画面のピクセルの各色の差を求める
float difRed=abs(red(exColor[i])-red(video.pixels[i]));
float difGreen=abs(green(exColor[i])-green(video.pixels[i]));
float difBlue=abs(blue(exColor[i])-blue(video.pixels[i]));
//色の差が許容値以上の場合(動体がある場合)
if(difRed>tolerance && difGreen>tolerance && difBlue>tolerance){
if(i%w<50){//X座標が50以下の場合(画面右側の場合)
movement=true;//動態検知フラグをtrueにしておく
sumY+=i/w;//平均値を求めるために座標値を加算しておく
pixelNum++;//ピクセル数を数えておく
}
if(i%w>width-50){//X座標が270以上の場合(画面左側の場合)
movement2=true;
sumY2+=i/w;
pixelNum2++;
}
}
//次回ループのために今回の画面を前回の画面として保存しておく
exColor[i]=video.pixels[i];
}
if(movement==true){//動体検知した場合(画面右)
y=sumY/pixelNum;//平均Y座標を求める
//0に戻しておく(初期化)
sumY=0;
pixelNum=0;
}
if(movement2==true){//動体検知した場合(画面左)
y2=sumY2/pixelNum2;
sumY2=0;
pixelNum2=0;
}
}
//フィルタをかけたパドルのY座標値
filterY+=(y-filterY)*0.4;
filterY2+=(y2-filterY2)*0.4;
//ボール上下跳ね返りの処理
if(ballY<5){
ballY=5;
dirY=1;
}
if(ballY>height-5){
ballY=height-5;
dirY=-1;
}
//ボールがパドルに当たって跳ね返る処理
if(ballX>25 && ballX<30 && ballY>filterY2-28 && ballY<filterY2+28 && dirX==-1){
ballX=30;
dirX=1;
}
if(ballX>width-30 && ballX<width-25 && ballY>filterY-28 && ballY<filterY+28 && dirX==1){
ballX=width-30;
dirX=-1;
}
//ボールが画面端から出てしまったときの処理(得点処理)
if(ballX<0 && dirX==-1){
ballX=width+200;//ボールを画面反対側へ移動
pt+=1;//左プレイヤー点数加算
}
if(ballX>width && dirX==1){
ballX=-200;//ボールを画面反対側へ移動
pt2+=1;//右プレイヤー点数加算
}
//ボールの移動量
ballX+=speedX*dirX;
ballY+=speedY*dirY;
fill(255);//塗り色(白)
rect(width-20,filterY,10,50);//右パドル表示
rect(20,filterY2,10,50);//左パドル表示
rect(ballX,ballY,10,10);//ボール表示
text(pt,60,20);//左プレイヤー点数表示
text(pt2,width-65,20);//右プレイヤー点数表示
}
void keyPressed(){
if(key=='c'){//cキーでカメラセッティング
video.settings();
}
if(key==' '){//スペースキーで点数をリセット
pt=0;
pt2=0;
}
if(key==CODED){
if(keyCode==LEFT){
tolerance-=1;
if(tolerance<0){
tolerance=0;
}
}
if(keyCode==RIGHT){
tolerance+=1;
}
}
}
関連:
Webカメラ:
「
Processing Video (Webカメラ)」--Webカメラの使い方/映像にフィルタをかけて表示。
「
Arduino+Processing マトリクスLED+Webカメラ」--Webカメラ映像をマトリクスLEDに映す。
「
Processing Webカメラを光センサとして使う」--点光源で画面内に線を描く。
「
Processing Webカメラ/定点記録画像」--Webカメラ映像を0.5秒おきに画像保存(JPEG)する。
「
Processing Webカメラ/カラートラッキング」--Webカメラを使い、色を手がかりに物体を追いかける。
0 件のコメント:
コメントを投稿