3週間ぐらい前に作った、OpenCVのhaarcascadeで認識した部分に円を描画するツールがけっこう便利なので公開する。
Mac OSXおよびUbuntu用でOpenCV1.0とboostが入ってれば動くはず。

OpenCVをダウンロードして付いてくるサンプルにも似た物が入ってるけど、カメラや動画や画像を入力としてGUIで表示するものだった。

俺は大量の静止画を判定して振り分けたりしたかったので、認識した物体の位置とサイズをスクリプト言語で受け取るために標準出力したり、マーキングした後の画像をファイル名を指定して保存したり、プレビューウィンドウを出すか選べるように改造した。

それと今後のためにboostとOpenCVを同時に使ってみたかったというのもある。
パラメータの解析にはboost::program_optionsを使って、画像まわりは全部OpenCVで書いてある。


■ダウンロード

MacとUbuntu用のMakefileつき。それぞれportとaptでOpenCVとboostをインストールしてあればmakeできる


■実行例
ふつうに顔認識。プレビューウィンドウも出す。結果はresult.jpgに保存。
haartest -p -c ~/haarcascades/haarcascade_frontalface_default.xml -p -i ~/Pictures/faces/shokai-umbrella.jpg -o result.jpg
detect face


sourceforgeのOpenCVのリポジトリの開発版に、目にマッチするhaarcascadeファイルがあるので持ってきて使った。目に似ている部分がたくさんあるので誤認識した。
haartest -p -c ~/haarcascades/haarcascade_mcs_righteye.xml -p -i ~/Pictures/faces/emushi-2face.jpg -o --nogray
emushi-2face.jpg_haarcascade_mcs_mouth



■boost::program_optionsで引数を取る
引数なし、もしくは–helpで実行するとこんなのが出るようにしてある。
cascadeとinputが必要です
options:
-h [ --help ] ヘルプを表示
-c [ --cascade ] arg haarcascade設定ファイル
-i [ --input ] arg 入力画像ファイル名
-o [ --output ] arg 出力ファイル名
-p [ --preview ] プレビュー表示
-f [ --flip ] 左右反転した画像も判定(青でマーク)
--nogray グレースケール、ヒストグラム均一化せずに判定
–input shokai.jpgとか-i shokai.jpgとかで引数を指定。(inputもiも同じとみなすようにヒモ付けできる)
左右反転は、左右非対称なものを認識する時に使える。右手のひら判定で左手も検出したい時とか。


詳しくは下記へ

ちなみにprogram_optionsで取った引数をOpenCVに渡すときは
cvLoadImage( argmap["input"].as<string>().c_str() )
のような感じでC++のstring型からCのchar配列にcastして渡す。


■スクリプト言語から使う
Rubyのワンライナーからディレクトリ内のJPG画像をまとめて顔認識して結果をresultsディレクトリに保存する例
mkdir results
ruby -e 'Dir.glob("*.jpg").each{|f| `haartest -c ~/haarcascades/haarcascade_frontalface_default.xml -i #{f} -o results/#{f}`}'



■コード
haartest.cpp
#include "cv.h"
#include "highgui.h"
#include <boost/program_options.hpp>
#include <iostream>
using namespace boost;
using namespace std;

void detect_draw(IplImage *img, IplImage *dst, CvHaarClassifierCascade *cascade, CvScalar color, bool isFlip);

int main(int argc, char* argv[]) {
  program_options::options_description opts("options");
  opts.add_options()
    ("help,h", "ヘルプを表示")
    ("cascade,c", program_options::value<string>(), "haarcascade設定ファイル")
    ("input,i", program_options::value<string>(), "入力画像ファイル名")
    ("output,o", program_options::value<string>(), "出力ファイル名")
    ("preview,p", "プレビュー表示")
    ("flip,f","左右反転した画像も判定(青でマーク)")
    ("nogray", "グレースケール、ヒストグラム均一化せずに判定");
  program_options::variables_map argmap;
  program_options::store(parse_command_line(argc, argv, opts), argmap);
  program_options::notify(argmap);
  if (argmap.count("help") || !argmap.count("cascade") || !argmap.count("input")) {
    cerr << "cascadeとinputが必要です" << endl;
    cerr << opts << endl;
    return 1;
  }
  
  CvHaarClassifierCascade *cascade;
  cascade = (CvHaarClassifierCascade*)cvLoad(argmap["cascade"].as<string>().c_str(), 0, 0, 0);
  if(!cascade){
    cerr << "error! Cascade not Found" << endl;
    return -1;
  }
  
  IplImage *image = cvLoadImage(argmap["input"].as<string>().c_str());
  IplImage *image_orig = cvCreateImage(cvSize(image->width, image->height), 8, 3);
  cvCopy(image, image_orig);
  if(!image){
    cerr << "error! Image File not Found" << endl;
    return -11;
  }

  if(!argmap.count("nogray")){
    IplImage* gray = cvCreateImage(cvSize(image->width, image->height), 8, 1);
    cvCvtColor(image, gray, CV_BGR2GRAY);
    cvEqualizeHist(gray, gray);
    image = gray;
  }
  
  if(argmap.count("preview")||argmap.count("output")){
    detect_draw(image, image_orig, cascade, CV_RGB(255, 0, 0), false);
    if(argmap.count("flip")){
      cvFlip(image, image);
      detect_draw(image, image_orig, cascade, CV_RGB(0, 0, 255), true);
    }
  }

  if(argmap.count("output")){
    string out_filename = argmap["output"].as<string>();
    cout << "save! " << out_filename << endl;
    cvSaveImage(out_filename.c_str(), image_orig);
  }
  
  if(argmap.count("preview")){
    char winName[] = "haarcascade test";
    cvNamedWindow(winName, CV_WINDOW_AUTOSIZE);
    cvShowImage(winName, image_orig);
    while (1) {
      if (cvWaitKey(1) == 'q') break;
    }
    cvDestroyWindow(winName);
  }
  
  cvReleaseImage(&image);
  cvReleaseImage(&image_orig);
  return 0;
}

void detect_draw(IplImage *img, IplImage *dst, CvHaarClassifierCascade *cascade, CvScalar color, bool isFlip = false){
  CvMemStorage *storage = 0;
  storage = cvCreateMemStorage(0);
  CvSeq* faces = cvHaarDetectObjects(img, cascade, storage,
                                     1.1, 2, CV_HAAR_DO_CANNY_PRUNING,
                                      cvSize(30, 30));

  for(int i = 0; i < faces->total; i++){
    CvRect *rect = (CvRect*)cvGetSeqElem(faces, i);
    cout << "x:" << rect->x << ", y:" << rect->y
         << ", width:" << rect->width << ", height:" << rect->height << endl;
    CvPoint center;
    center.x = rect->x + rect->width/2.0;
    center.y = rect->y + rect->height/2.0;
    if(isFlip){
      center.x = dst->width - center.x;
      center.y = dst->height - center.y;
    }
    int r = (rect->width + rect->height)/4.0;
    cvCircle(dst, center, r, color, 2, CV_AA, 0);
  }
}


Makefile (Mac用)
# Mac用Makefile
SRC = haartest.cpp
DST = haartest

prefix=/opt/local
INCPATH=$(prefix)/include
LIBPATH=$(prefix)/lib

CV_LIBS= -lcv -lcvaux -lcxcore -lhighgui
BOOST_LIBS= $(LIBPATH)/libboost_program_options-mt.dylib

all:
g++ -O $(SRC) -o $(DST) -I$(INCPATH)/opencv -L. -L$(LIBPATH) $(CV_LIBS) -I$(INCPATH)/boost $(BOOST_LIBS)


Makefile (Linux用)
# Ubuntu用Makefile
SRC = haartest.cpp
DST = haartest

prefix=/usr
INCPATH=$(prefix)/include
LIBPATH=$(prefix)/lib

CV_LIBS= -lcv -lcvaux -lcxcore -lhighgui
BOOST_LIBS= $(LIBPATH)/libboost_program_options-mt.a

all:
g++ -O $(SRC) -o $(DST) -I$(INCPATH)/opencv -L. -L$(LIBPATH) $(CV_LIBS) -I$(INCPATH)/boost $(BOOST_LIBS)