haarcascadeのテスト用コマンドラインツール

boost::program_optionsとOpenCVでhaarcascadeファイル、入力画像、出力画像、プレビューありなし等を選んで画像1枚から顔などの位置を認識するコマンドラインツール作った。サーバーに置いてスクリプト言語から呼び出せばいいのではないでしょうか。

リポジトリと、Mac用のバイナリはここ。

boostとSTLOpenCVしか使ってないのでMakefile直せばどの環境でもコンパイルできると思う(あとで試す)

ついでにマスク画像を選べるようにしようかと思ったけどOpenCVは透過画像上手くいかないんだった。合成はImageMagick(RMagick)とかを使って自前でやればいいや


こんなふうに実行すると

./haartest -p -c /opt/local/share/opencv/haarcascades/haarcascade_frontalface_default.xml -i ~/Pictures/faces/masatooo-berger.jpg -o result.jpg

複数箇所認識したらそれぞれ位置と範囲が出力される。

x:91, y:169, width:39, height:39
x:93, y:183, width:41, height:41
x:105, y:84, width:85, height:85

この例ではhaarcascadeに顔正面にマッチするものを使ったけど、別のを指定すれば別の部位にマッチする。-oでファイル名を指定して結果をファイルに保存できるし、-pスイッチでOpenCV上でプレビューが出せる。このへんの引数の解析はboost::program_optionsが便利だった。
detect face
detect face
detect face
detect face
detect face
detect face


haartest.cpp

#include "cv.h"
#include "highgui.h"
#include <boost/program_options.hpp>
#include <iostream>
using namespace boost;
using namespace std;

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", "プレビュー表示");
  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());
  if(!image){
    cerr << "error! Image File not Found" << endl;
    return -11;
  }
  
  CvMemStorage *storage = 0;
  storage = cvCreateMemStorage(0);
  CvSeq* faces = cvHaarDetectObjects(image, cascade, storage,
                                     1.1, 2, CV_HAAR_DO_CANNY_PRUNING,
                                      cvSize(30, 30));

  bool isOutput = (argmap.count("preview")||argmap.count("output"));
  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;
    if(isOutput){
      CvPoint center;
      center.x = rect->x + rect->width/2.0;
      center.y = rect->y + rect->height/2.0;
      int r = (rect->width + rect->height)/4.0;
      cvCircle(image, center, r, CV_RGB(255, 0, 0), 2, CV_AA, 0);
    }
  }

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


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)


たとえばRubyで、ディレクトリ内の画像全ての顔をマークするならこう使える

ruby -e 'Dir.glob("*.jpg").each{|f| `~/src/cpp/opencv/study/haar/haartest -c /opt/local/share/opencv/haarcascades/haarcascade_frontalface_default.xml -i #{f} -o #{f}_detected.jpg`}'

上に貼った写真はこれで一発で作成した。