2012年10月16日火曜日

Raspberry Pi + OpenCV で顔検出してみたけど・・・

 ミサイルランチャーの内蔵カメラは玉砕中ですが、とりあえず、動作確認できているELECOMのUVCカメラで顔検出を試してみました。OpenCV.jpなどにあったサンプルを元にカメラ取得画像に対して顔検出するようにしました。
 最近は、まずPCのVMWare Workstation上にインストールしたDebian上で一度プログラムを作成・動作させてから、Raspberry Piへソースを持って行ってコンパイルして動作確認、という開発スタイルになっています。やはり、Raspberry Pi上でのコンパイルは時間がかかるので回数を減らしたいというのがあります。ただ、VMのDebian上では快適あるいは正常に動くのにRaspberry Piに持って行くとパフォーマンスやドライバ(アーキテクチャ)の違いでダメというのは往々にしてあります。
 今回の顔検出もVMのDebian上では快適に動作しましたが、Raspberyy Pi 上だと、動くは動くんですが、画像フレームの更新が3秒に1回くらいで、(処理されて)画面に表示されるまで10秒くらいの遅延があります。


 これだと、ミサイルランチャーにカメラを載せて(本当は内蔵カメラを使いたい)、カメラで顔検出することでミサイルランチャーをフェイストラッキングさせて、ミサイル発射(って、おいおい・・・)させる目論見は難しそうです。相手が静止していればいいですが、普通に動いていたら、数秒前のあさっての方向にミサイルが飛んでいくことになります。となると、Raspberry Piは諦めて、リモートブレイン的なPCと無線でやり取りする方法に切り替えるか・・・。あるいは小型のノートPCで試してみるか。(っていうか、Raspberry Piでミサイルランチャーを遊ぶのが目的だったので、ちょっと脱線だけど。)
 とりあえず、顔検出のソース。実行フォルダにOpenCVに含まれている正面顔検出用のテンプレート haarcascade_frontalface_default.xml を置いておく必要があります。

// OpenCV カメラ画像顔検出のテスト。
// gcc -Wall -o "%e" "%f" -g -L/usr/local/lib 
//     -lopencv_core -lopencv_imgproc -lopencv_highgui 
//     -lopencv_ml -lopencv_video -lopencv_features2d 
//     -lopencv_calib3d -lopencv_objdetect -lopencv_contrib 
//     -lopencv_legacy -lopencv_flann -lusb
// 実行フォルダに haarcascade_frontalface_default.xml を置く。
#include <stdio.h>
#include <ctype.h>
#include <time.h>
#include <sys/time.h>
#include <unistd.h>

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

int main(int argc, char **argv)
{
 CvCapture *capture = 0;
 IplImage *frame = 0, *src_gray = 0;
 int max_detect, min_detect;
 int c ,i;
 
 double width = 320, height = 240;
 //double width = 640, height = 480;
 
 // 正面顔検出のための検出器を読み込む。
 CvHaarClassifierCascade *cvHCC =
  (CvHaarClassifierCascade*)cvLoad("haarcascade_frontalface_default.xml", 0, 0, 0);
 // 検出用のメモリストレージの用意。
 CvMemStorage *cvMStr = cvCreateMemStorage(0);
 // 検出情報格納用シーケンスの用意。
 CvSeq *detectFace;
 // 検出最小及び最大サイズ設定。
 max_detect = (int)height;
 min_detect = (int)(height / 5);
 
 
 // カメラに対するキャプチャ構造体を作成。
 if (argc == 1 || (argc == 2 && strlen(argv[1]) == 1 && isdigit(argv[1][0])))
  capture = cvCreateCameraCapture(argc == 2 ? argv[1][0] - '0' : 0);
 
 // キャプチャサイズの設定。
 cvSetCaptureProperty(capture, CV_CAP_PROP_FRAME_WIDTH, width);
 cvSetCaptureProperty(capture, CV_CAP_PROP_FRAME_HEIGHT, height);
 
 // ウィンドウ作成。
 cvNamedWindow("face_detect", CV_WINDOW_AUTOSIZE);
 
 while(1)
 {
  // 画像キャプチャ。
  frame = cvQueryFrame(capture);
  
  // グレイスケールイメージに変換。
  src_gray = cvCreateImage(cvGetSize(frame), IPL_DEPTH_8U, 1);
  
  // 顔検出処理。
  detectFace = cvHaarDetectObjects(frame, // 対象イメージ。 
      cvHCC,  // Haar分類器カスケード。
      cvMStr, // メモリストレージ。
      1.1,  // スケーリングファクタ。
      3,   // 近接矩形グループ化。
      CV_HAAR_DO_CANNY_PRUNING,   // 処理モード。
      cvSize(min_detect, min_detect),  // 最小窓サイズ。
      cvSize(max_detect, max_detect)); // 最大窓サイズ。  
  // 検出結果を矩形&クロス表示。
  for (i = 0; i < detectFace->total; i++)
  {
   CvRect *faceRect = (CvRect*)cvGetSeqElem(detectFace, i);
   cvRectangle(frame,
      cvPoint(faceRect->x, faceRect->y),
      cvPoint(faceRect->x + faceRect->width, faceRect->y + faceRect->height),
      CV_RGB(255, 0, 0),
      3,
      CV_AA,
      0);
   cvLine(frame,
    cvPoint(faceRect->x + (int)(faceRect->width / 2) -10,
     faceRect->y + (int)(faceRect->height / 2)),
    cvPoint(faceRect->x + (int)(faceRect->width /2) + 10,
     faceRect->y + (int)(faceRect->height /2)),
     CV_RGB(255, 0, 0),
     3,
     CV_AA,
     0);
   cvLine(frame,
    cvPoint(faceRect->x + (int)(faceRect->width / 2) ,
     faceRect->y + (int)(faceRect->height / 2) - 10),
    cvPoint(faceRect->x + (int)(faceRect->width /2),
     faceRect->y + (int)(faceRect->height /2) + 10),
     CV_RGB(255, 0, 0),
     3,
     CV_AA,
     0);
  }
  
  cvShowImage("face_detect", frame);
  
  c = cvWaitKey(10);
  if(c != -1) break;
  //sleep(2);
 }
 
 // 後片付け。
 cvReleaseMemStorage(&cvMStr);
 cvReleaseHaarClassifierCascade(&cvHCC);
 cvReleaseCapture(&capture);
 cvDestroyWindow("face_detect");
 
 return 0;
}