最近、画像認識でアレコレ作るのが楽しくなってきて、今回は指定された色を追跡して、カメラ上での座標を取得するプログラムを作成してみました。完全にopenFrameworks WikiのColorTrackingのページの写経ですが、コメントをつけています。
目次
colorImg
)を取得するofxCvContourFinder
を利用して、追跡したい色の重心(欲しかった座標)を取得する#ifndef _TEST_APP
#define _TEST_APP
#include "ofMain.h"
#include "ofxVectorMath.h"
#include "ofxOpenCv.h"
//色の基本的な情報を持ったクラスを作ります。
class Color {
public:
float hue, sat, bri;
ofxVec2f pos;
};
class testApp : public ofBaseApp{
public:
//この辺はいつも通り
void setup();
void update();
void draw();
void keyPressed (int key);
void keyReleased(int key);
void mouseMoved(int x, int y );
void mouseDragged(int x, int y, int button);
void mousePressed(int x, int y, int button);
void mouseReleased(int x, int y, int button);
void windowResized(int w, int h);
//カメラの映像を取得するためのオブジェクト
ofVideoGrabber vidGrabber;
//カメラの幅と高さ
int camWidth;
int camHeight;
//もともとの映像情報
ofxCvColorImage colorImg;
//HSV系に変換した映像情報
ofxCvColorImage colorImgHSV;
//HSV系の色相、彩度、明度のマップ
ofxCvGrayscaleImage hueImg;
ofxCvGrayscaleImage satImg;
ofxCvGrayscaleImage briImg;
//色を追跡して輪郭を出すための映像情報
ofxCvGrayscaleImage reds;
//追跡する色です。
Color one;
//もとの映像情報のピクセルの彩度と明度が
//指定した色に近ければ255を代入、遠ければ0を代入
unsigned char * colorTrackedPixelsRed;
//二値画像
ofTexture trackedTextureRed;
//輪郭を判別してくれるメチャクチャ便利なやつです。
ofxCvContourFinder finderRed;
};
#endif
#include "testApp.h"
//--------------------------------------------------------------
void testApp::setup(){
//最初は色が原点にあることにします
one.pos = ofxVec2f(0,0);
//カメラの大きさを指定
camWidth = 320;
camHeight = 240;
//それぞれの映像情報の大きさを指定してあげます。
colorImg.allocate(camWidth, camHeight);
colorImgHSV.allocate(camWidth, camHeight);
hueImg.allocate(camWidth, camHeight);
satImg.allocate(camWidth, camHeight);
briImg.allocate(camWidth, camHeight);
reds.allocate(camWidth, camHeight);
//二値画像を作るための配列の大きさを指定
colorTrackedPixelsRed =new unsigned char [camWidth*camHeight];
//二値画像の大きさ
trackedTextureRed.allocate(camWidth, camHeight, GL_LUMINANCE);
//Grabberの「何か」と大きさ設定
//setVerboseってなんだろ? Verbose:冗長な、言葉数の多い
vidGrabber.setVerbose(true);
vidGrabber.initGrabber(camWidth, camHeight);
}
//--------------------------------------------------------------
void testApp::update(){
//映像を取得!
vidGrabber.grabFrame();
//colorImgの中身をピクセルごとに指定
colorImg.setFromPixels(vidGrabber.getPixels(), camWidth, camHeight);
//HSV系に変換
colorImgHSV = colorImg;
colorImgHSV.convertRgbToHsv();
//色相、彩度、明度にマッピング
colorImgHSV.convertToGrayscalePlanarImages(hueImg, satImg, briImg);
//ここが何やってんのか分からん。
hueImg.flagImageChanged();
satImg.flagImageChanged();
briImg.flagImageChanged();
//ピクセルの配列をそれぞれに作成
unsigned char * huePixels = hueImg.getPixels();
unsigned char * satPixels = satImg.getPixels();
unsigned char * briPixels = briImg.getPixels();
//ピクセルの数
int nPixels = camWidth*camHeight;
//ピクセルの色が指定した色と色相と彩度が近ければ、
//colorTrackedPixelsRedに255を、遠ければ0を代入。
for (int i=0; i < nPixels; i++) {
if ( (huePixels[i] >=one.hue-12 && huePixels[i] <= one.hue + 12) &&
(satPixels[i] >=one.sat-24 && satPixels[i] <=one.sat+200)){
colorTrackedPixelsRed[i] = 255;
}else {
colorTrackedPixelsRed[i]=0;
}
}
//colorTrackedPixelsRedをもとにredsを作成
//redsは輪郭線を求めるためだけにあるのかな?
reds.setFromPixels(colorTrackedPixelsRed, camWidth, camHeight);
//輪郭線を見つける
finderRed.findContours(reds, 10, nPixels/3, 1, false, true);
//colorTrackedPixelsRedをもとにtrackedTextureRedを作成
//これが二値画像になってるっぽい
trackedTextureRed.loadData(colorTrackedPixelsRed,
camWidth, camHeight, GL_LUMINANCE);
//追跡する色の位置を中心にあわせる
if (finderRed.blobs.size() > 0) {
one.pos = ofxVec2f(finderRed.blobs[0].centroid.x,
finderRed.blobs[0].centroid.y);
}
}
//--------------------------------------------------------------
void testApp::draw(){
//背景色を指定
ofBackground(100, 100, 100);
ofSetColor(0xffffff);
//元映像を表示
vidGrabber.draw(0, 0);
//HSV系に変換したものを表示
colorImgHSV.draw(340, 0);
//二値画像を表示
trackedTextureRed.draw(20, 300);
ofDrawBitmapString("red", 20, 280);
//元映像に輪郭線を表示
finderRed.draw();
//二値画像の方に輪郭線表示
glPushMatrix();
glTranslatef(20, 300, 0);
finderRed.draw();
glPopMatrix();
//追跡する色の位置を表示
if (finderRed.blobs.size() > 0) {
char tempStr1[255];
sprintf(tempStr1, "x:%fny:%f",
finderRed.blobs[0].centroid.x,
finderRed.blobs[0].centroid.y);
ofDrawBitmapString(tempStr1, 20, 250);
}
}
//--------------------------------------------------------------
void testApp::mousePressed(int x, int y, int button){
unsigned char * huePixels = hueImg.getPixels();
unsigned char * satPixels = satImg.getPixels();
unsigned char * briPixels = briImg.getPixels();
//クリックした場所の色を追跡する色に設定。
x=MIN(x,hueImg.width-1);
y=MIN(y,hueImg.height-1);
if (button==0) {
one.hue = huePixels[x+(y*hueImg.width)];
one.sat = satPixels[x+(y*satImg.width)];
one.bri = briPixels[x+(y*briImg.width)];
}
}
testApp::update()
の最後にあるone.pos
に色を追跡した結果の座標が入っています。
色ごとにシンセサイザーの音、ドラムパターン、フィルターという役割を持たせて、カメラ上で各色がどこにあるのかを解析し、それに応じて音が出力されます。
すこしややこしいですが、重要なのはtestApp::update()
だけです。大まかな流れを理解した上で、落ち着いて読めばさほど難しいものではないので、是非挑戦してみてください!