OpenCVで遊んだ

同期とOpenCVやらARToolkitやらの環境を構築して遊んでみた.以下はWebカメラから動画を取り込み,リアルタイムに顔検出するコード.

参考:カメラ利用の基本形 - OpenCV@Chihara-Lab.
opencv.jp - OpenCV-1.0:CV 物体検出(Object Detection)リファレンス マニュアル -

#include <iostream>
#include <opencv/cv.h>
#include <opencv/highgui.h>

int main(int argc, char *argv[]){
	int key = 0;

	CvCapture* capture = NULL;
	IplImage* captureImage;
	IplImage* outputImage;

	if(NULL==(capture = cvCaptureFromCAM(-1)))
	{
		std::cout << "カメラが見つかりません" << std::endl;
		return -1;
	}

	char* captureWindow = "Capture";
	char* outputWindow  = "Output";
	cvNamedWindow(captureWindow,  CV_WINDOW_AUTOSIZE); 
	cvNamedWindow(outputWindow,  CV_WINDOW_AUTOSIZE);

	// 正面顔検出器の読み込み
	CvHaarClassifierCascade* cvHCC = 
		(CvHaarClassifierCascade*)cvLoad("haarcascade_frontalface_default.xml");

	// 検出に必要なメモリストレージを用意する
	CvMemStorage* cvMStr = cvCreateMemStorage(0);

	// 検出情報を受け取るためのシーケンスを用意する
	CvSeq* face;

	while(1){
		captureImage = cvQueryFrame(capture);
		outputImage = cvCloneImage(captureImage);

		// 画像中から検出対象の情報を取得する
		face = cvHaarDetectObjects
			(outputImage, cvHCC, cvMStr, 1.2, 2, CV_HAAR_DO_CANNY_PRUNING, cvSize(50.0, 50.0));
		
		for (int i = 0; i < face->total; i++) {
			//検出情報から顔の位置情報を取得
			CvRect* faceRect = (CvRect*)cvGetSeqElem(face, 0);

			// 取得した顔の位置情報に基づき、矩形描画を行う
			cvRectangle(outputImage,
				cvPoint(faceRect->x, faceRect->y),
				cvPoint(faceRect->x + faceRect->width, faceRect->y + faceRect->height),
				CV_RGB(255, 0 ,0),
				3, CV_AA);
		}

		cvShowImage(captureWindow, captureImage);
		cvShowImage(outputWindow, outputImage);
		
		cvReleaseImage(&outputImage);
		
		key = cvWaitKey(1);
		if (key == 0x1b) break;
	}

	// 用意したメモリストレージを解放
	cvReleaseMemStorage(&cvMStr);

	// カスケード識別器の解放
	cvReleaseHaarClassifierCascade(&cvHCC);

	// キャプチャの解放
	cvReleaseCapture(&capture);

	// ウィンドウの破棄
	cvDestroyWindow(captureWindow);
	cvDestroyWindow(outputWindow);

	return 0;
}

第一感はお手軽!そしてメモリリークこえー!みたいな感じ.これは慣れだろうなあ.コードは殆どサンプルのまんま.一点だけ注意が必要なのは,cvHaarDetectObjectsの引数.静止画を弄るときと同様に引数の一部を省略し

// 画像中から検出対象の情報を取得する
face = cvHaarDetectObjects(outputImage, cvHCC, cvMStr);

と書くと,割と使い物にならないくらい遅くなる.第4〜第6引数を適切に与えることで,枝刈りが効果的に働く(詳細はリファレンス参照).調子に乗って,顔を宮崎あおいに置き換えるバージョンも作ってみた.矩形描画を画像のリサイズと合成に書き換えればOK.ちゃんと動画で実用(??)レベルの速度が出る.

参考:OpenCVで顔認識→笑い男アイコン貼り付け - ぬいぐるみライフ(仮)

		for (int i = 0; i < face->total; i++) {
			//検出情報から顔の位置情報を取得
			CvRect* faceRect = (CvRect*)cvGetSeqElem(face, 0);
			IplImage* aoi_resized = cvCreateImage(cvSize(faceRect->width, faceRect->height), outputImage->depth, outputImage->nChannels)

			cvResize(aoi, aoi_resized, CV_INTER_CUBIC);
			
			int x = faceRect->x, y = faceRect->y;
			const int i_max = ((x + aoi_resized->width ) > outputImage->width ) ? outputImage->width  - x : aoi_resized->width;
			const int j_max = ((y + aoi_resized->height) > outputImage->height) ? outputImage->height - y : aoi_resized->height;

			for (int j = 0; j < j_max; ++j){
			  for (int i = 0; i < i_max; ++i){
				int r = aoi_resized->imageData[aoi_resized->widthStep * j + i * 3];
				int g = aoi_resized->imageData[aoi_resized->widthStep * j + i * 3 + 1];
				int b = aoi_resized->imageData[aoi_resized->widthStep * j + i * 3 + 2];

				if (r || g || b) {
				  outputImage->imageData[outputImage->widthStep * (y+j) + (x+i) * 3] = r;
				  outputImage->imageData[outputImage->widthStep * (y+j) + (x+i) * 3 + 1] = g;
				  outputImage->imageData[outputImage->widthStep * (y+j) + (x+i) * 3 + 2] = b;
				}
			  }
			}
			cvReleaseImage(&aoi_resized);
		}