みなさん、こんにちは。今回は自分の動きに合わせて体動くようにprocessingでプログラムを作成していきます。この記事は前回の続きです。前回の記事を読んでいない方はまずはそちらをご覧頂くとどのようにして作成しているか分かると思います。

記事主の顔出しをするのは抵抗があったのて、今回はグレイシアのぬいぐるみを用意して、動作確認用として使ってみました。実際に使った機材は下記の通りです。


ELECOM WEBカメラ
UCAM-C310FBBK HD 720p 30FPS
今回の記事は画像処理のクラスタリングがメインのため、内容が難しくなると思われます。よって、順を追って説明していきます。
最初にやることはwebカメラから画像を表示させることです。これができなければ、画像処理以前の問題なのでささっと解説します。昔作ったプログラムを移植して作ったため、プログラムの書き方が独特なのでご了承くださいm(_ _)m
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
import processing.video.*; Capture cap; PImage img1; void setup() { size(320, 240); frameRate(100); String[] cameras = Capture.list(); for (i = 0; i < cameras.length; i++) { println(cameras[i]); } ///////////////////カメラ関係 cap = new Capture(this, 320,240); cap.start(); loadPixels(); } void draw() { if(cap.available() == true)////読み込み成功 { cap.read(); //カメラからの画像を読み込む img1 = (PImage)cap; } if(img1 != null) { image(img1,0,0); } } |
このプログラムを動作させるには、スケッチのライブラリーインポートから video ライブラリーをインポートさせる必要があります。まずはwebカメラから読み取った画像を表示させましょう。なお、カメラのサイズは320×240にしています。サイズが多くなればなるほどwebカメラの画質は良くなりますが、その分処理が重くなります。今回は特定の色の重心座標を取得するだけなので、320×240のサイズで十分です。
次はwebカメラから取得した各ピクセルの色を取得し、特定の色かどうか判別します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
int i,j; float a,b,c,d,e; int R, G, B; //赤、緑、青それぞれの値 float area[]; //それぞれのクラスタ番号の面積 float area_x[]; //それぞれのクラスタ番号のxの合計 float area_y[]; //それぞれのクラスタ番号のyの合計 int map[][]; //クラスタリング[x][y] float minimum; float dis[]; //前回の重心からの距離 int gg; //今回の重心の位置(番号) int I; //何個あるか float dis_min; //最小距離 int Line; //行数、クラスタリングの番号 float m; //重心の個数 float ce_x, ce_y; //重心の座標 void setup() { area = new float[2000]; area_x = new float[2000]; area_y = new float[2000]; dis = new float[2000]; map = new int[320][240]; ce_x = 160; ce_y = 120; } PImage Clustering(PImage img) { //初期化 Line = 1; a = 0; for( i = 0; i < 2000; i++) { area[i] = 0; area_x[i] = 0; area_y[i] = 0; dis[i] = 1000; } for( i = 0; i < img.height; i++) { for( j = 0; j < img.width; j++) { //ビデオのピクセルを取り出す pixelColor = img.pixels[i*img.width + j]; //赤、緑、青をそれぞれ抽出する(ビットシフト) R = (pixelColor >> 16) & 0xff; G = (pixelColor >> 8 ) & 0xff; B = pixelColor & 0xff; if(0.298912*R + 0.586611*G + 0.114478*B < 50) { img.pixels[i*img.width + j] = color(0,0,255); map[j][i] = Line; if (a == 0) Line++; a = 1; } else { //ウィンドウにピクセルを当てはめる img.pixels[i*img.width + j] = color(R,G,B); map[j][i] = 0; a = 0; } } } ……続く…… |
最初に前回の値が配列に残っている可能性があるので、初期化作業を行います。その後、各ピクセルのRBG値を取得します。今回は髪の毛の色を取得したいので、黒色を抽出します。RBGからHSV系に変換し、条件を満たす色の場合はそのピクセルの場所に値を振り分けます。条件を満たさない場合は0を振り分けていきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
///////////////////////右上から左下へ for(i = 1; i <= img.height-2; i++) { for(j = 1; j < img.width-2; j++) { a=map[j][i];//中心のpixel b=map[j][i-1];//上のpixel c=map[j-1][i];//左のpixel d=map[j][i+1];//下のpixel e=map[j+1][i];//右のpixel if (a != 0 && b != 0 && c != 0 && d != 0 && e != 0) { minimum = a; if (minimum >= b) minimum = b; if (minimum >= c) minimum = c; if (minimum >= d) minimum = d; if (minimum >= e) minimum = e; map[j][i] = (int)minimum; map[j][i-1] = (int)minimum; map[j-1][i] = (int)minimum; map[j][i+1] = (int)minimum; map[j+1][i] = (int)minimum; } } } //////////////右下から左上へ for(i = img.height-2; i >= 1; i--) { for(j = img.width-2; j >= 1; j--) { a=map[j][i];//中心のpixel b=map[j][i-1];//上のpixel c=map[j-1][i];//左のpixel d=map[j][i+1];//下のpixel e=map[j+1][i];//右のpixel if (a != 0 && b != 0 && c != 0 && d != 0 && e != 0) { minimum = a; if (minimum >= b) minimum = b; if (minimum >= c) minimum = c; if (minimum >= d) minimum = d; if (minimum >= e) minimum = e; map[j][i] = (int)minimum; map[j][i-1] = (int)minimum; map[j-1][i] = (int)minimum; map[j][i+1] = (int)minimum; map[j+1][i] = (int)minimum; } } } ……続く…… |
次は色ごとに分ける作業です。mapという配列に数字を振り分けていきました。隣り合った数字を確認し最も最小の数字に振り分けていきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
///////////////////各物体の面積を求める for( i = 0;i < img.height; i++) { for( j = 0; j < img.width; j++) { if(map[j][i] >= 2000) { //エラー回避 } else if(map[j][i] > 0) { area[map[j][i]]++; //面積をインクリメントする area_x[map[j][i]] += j; //x座標の合計を求める area_y[map[j][i]] += i; //y座標の合計を求める } } } //////////////////////////////////////////////////////////////////////////////////// //重心計算 //////////////////////////////////////////////////////////////////////////////////// /////////////////面積が大きい順に並び替える for(i = 0; i < 2000; i++) { for( j = 1+i; j < 2000; j++) { if(area[i] < area[j]) { a = area[i]; c = area_x[i]; d = area_y[i]; area[i] = area[j]; area_x[i] = area_x[j]; area_y[i] = area_y[j]; area[j] = a; area_x[j] = c; area_y[j] = d; } } } /////////////////それぞれの重心の座標を求める for( i = 0; i < 2000; i++ ) { if(area[i] <= 50) { I = i; break; } area_x[i] /= area[i]; area_y[i] /= area[i]; } ……続く…… |
次に各物体の面積を求めます。この処理に来た段階では、各物体ごとで数字が振り分けられている状態になっています。物体の面積を求めるために各数字ごとの個数をカウントします。加えて、物体の重心座標を計算するため、xy座標の和も並列して計算します。
その後、各物体の重心座標を求めます。今回は面積が50pixel以上の物体のみに絞って計算します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
///////////////重心の距離を求める a = int(ce_x); b = int(ce_y); for( i = 0; i <= I; i++) { c = area_x[i]; d = area_y[i]; dis[i] = int(dist(a,b,c,d)); } ///////////////マウス操作によって割り込みをする if(mousePressed == true & (mouseButton == LEFT)) { for(i = 0; i <= I; i++) { a=mouseX; b=mouseY; c = area_x[i]; d = area_y[i]; dis[i]=int( dist(a,b,c,d) ); } } ///////////////距離が短い方が重心 dis_min = dis[0]; gg = 0; for(i = 1; i < I; i++) { if(dis_min > dis[i]) { dis_min = dis[i]; //最小 gg = i; //何番目か } } /////////////重心が決定 ce_x = area_x[gg]; ce_y = area_y[gg]; m = area[gg]; return(img); } |
最後に重心座標の決定を行います。前回取得した重心座標と現在取得した各物体の重心座標との距離を測ります。最も近い重心座標を現在追っている物体であると判定し、重心座標を取得します。万が一、重心が違う物体へ移動してしまった場合、マウスクリックで好きな物体へ重心座標を選択できるように、割り込みプログラムを入れて完成です。
最後に今回作成したプログラムを前回作成したプログラムに組み込み、重心のx座標の位置からパチリスが左右に頭が傾くように設定すれば完了です。私が学生の頃、この画像処理を理解するのに1か月かかりました。がっつりプログラムの話でしたが、ここを突破できれば、自作でVチューバ―を簡易的に作成できると思います。webカメラの代金2000円ほどでできたので安価で作りたい方はぜひ参考にしてみてください。
コメント