前回の記事(【Raspberry Pi 2】 RasPiカメラモジュールをOpenCVで使用する)にて、RaspberryPiカメラモジュールをOpenCVから使用するテストを行いました。
今回は定番の顔認識を行ってみましたので、手順などを掲載します。
OpenCV2.4.11のインストール
今回はOpenCVのバージョンは2015年2月26日にリリースされたばかりの2.4.11を使用しました。
2.4.11のインストール手順は前回と同じでOKでした。
インストール手順は下記になります。詳しくは前回の記事を参照ください。
# wget http://sourceforge.net/projects/opencvlibrary/files/opencv-unix/2.4.11/opencv-2.4.11.zip # unzip opencv-2.4.11.zip # cd opencv-2.4.11 # mkdir build # cd build # cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local -D BUILD_NEW_PYTHON_SUPPORT=ON -D BUILD_EXAMPLES=ON .. # make -j 4 # make install # ldconfig
なお、前回の記事で、1時間でコンパイルが終わったと書きましたが、今回は2時間かかりました。もしかすると、前回の1時間は見誤ったかもしれません。
USBカメラでサンプルの顔認識を動かしてみる
まずはUSBカメラで顔認識をしてみます。OpenCVに付属している顔認識のサンプルは以下になります。
opencv-2.4.11/samples/c/facedetect.cpp
まずは試しにそのままビルドしてみます。(※下記例では顔認識のサンプル以外もビルドされます)
# cd ~/opencv-2.4.11/samples/c # chmod +x build_all.sh # ./build_all.sh
JPG画像で顔認識するには以下のように実行します。
# ./facedetect --scale=1.5 lena.jpg
USBカメラの映像で顔認識するには以下のように実行します。
# ./facedetect --scale=4
なお、scaleのパラメータは1以上を指定すると画像を縮小して処理を行うため、処理が速くなります。
あまり大きくしすぎると認識しなくなるため、画像の中の顔のサイズと相談(※)ですが、1.5から4ぐらいが良いようです。
※顔のサイズが大きい場合はscaleを大きくできます。
RasPiカメラモジュールで顔認識
さて、いよいよRaspberryPiカメラモジュールで顔認識してみます。
なお、raspicam_cvライブラリが前回の手順でインストールされているものとします。
顔認識のサンプルソースをコピーします。
# mkdir ~/facedetect # cp ~/opencv-2.4.11/samples/c/facedetect.cpp ~/facedetect/facedetect_raspicam.cpp
raspicam_cvライブラリをコピーします。
# cp ~/git/robidouille/raspicam_cv/RaspiCamCV.h ~/facedetect/ # cp ~/git/robidouille/raspicam_cv/libraspicamcv.a ~/facedetect/
顔認識のサンプルソースを以下のように変更します。★の個所が変更箇所です。
facedetect_raspicam.cpp
#include "opencv2/objdetect/objdetect.hpp" #include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include <cctype> #include <iostream> #include <iterator> #include <stdio.h> // -------------------------------------------------------------- // ★raspicam対応 // -------------------------------------------------------------- #include "RaspiCamCV.h" using namespace std; using namespace cv; static void help() { cout << "\nThis program demonstrates the cascade recognizer. Now you can use Haar or LBP features.\n" "This classifier can recognize many kinds of rigid objects, once the appropriate classifier is trained.\n" "It's most known use is for faces.\n" "Usage:\n" "./facedetect [--cascade=<cascade_path> this is the primary trained classifier such as frontal face]\n" " [--nested-cascade[=nested_cascade_path this an optional secondary classifier such as eyes]]\n" " [--scale=<image scale greater or equal to 1, try 1.3 for example>]\n" " [--try-flip]\n" " [filename|camera_index]\n\n" "see facedetect.cmd for one call:\n" "./facedetect --cascade=\"../../data/haarcascades/haarcascade_frontalface_alt.xml\" --nested-cascade=\"../../data/haarcascades/haarcascade_eye.xml\" --scale=1.3\n\n" "During execution:\n\tHit any key to quit.\n" "\tUsing OpenCV version " << CV_VERSION << "\n" << endl; } void detectAndDraw( Mat& img, CascadeClassifier& cascade, CascadeClassifier& nestedCascade, double scale, bool tryflip ); string cascadeName = "../../data/haarcascades/haarcascade_frontalface_alt.xml"; string nestedCascadeName = "../../data/haarcascades/haarcascade_eye_tree_eyeglasses.xml"; int main( int argc, const char** argv ) { // -------------------------------------------------------------- // ★raspicam対応 // -------------------------------------------------------------- //CvCapture* capture = 0; RaspiCamCvCapture* capture = 0; RASPIVID_CONFIG * config = (RASPIVID_CONFIG*)malloc(sizeof(RASPIVID_CONFIG)); config->width=640; config->height=480; config->bitrate=0; // zero: leave as default config->framerate=0; config->monochrome=0; // -------------------------------------------------------------- Mat frame, frameCopy, image; const string scaleOpt = "--scale="; size_t scaleOptLen = scaleOpt.length(); const string cascadeOpt = "--cascade="; size_t cascadeOptLen = cascadeOpt.length(); const string nestedCascadeOpt = "--nested-cascade"; size_t nestedCascadeOptLen = nestedCascadeOpt.length(); const string tryFlipOpt = "--try-flip"; size_t tryFlipOptLen = tryFlipOpt.length(); string inputName; bool tryflip = false; // -------------------------------------------------------------- // ★FPS対応 // -------------------------------------------------------------- #define FPS_SAMPLING_SIZE 50 double fps_sampling[FPS_SAMPLING_SIZE]; int i; for (i=0; i<FPS_SAMPLING_SIZE; i++) { fps_sampling[i] = 0; } double freq = cvGetTickFrequency(); int fps_idx = 0; int64 t_pre = cvGetTickCount(); int64 pre_ping_tick = t_pre; // ------------------------------------------- help(); CascadeClassifier cascade, nestedCascade; double scale = 1; for( int i = 1; i < argc; i++ ) { cout << "Processing " << i << " " << argv[i] << endl; if( cascadeOpt.compare( 0, cascadeOptLen, argv[i], cascadeOptLen ) == 0 ) { cascadeName.assign( argv[i] + cascadeOptLen ); cout << " from which we have cascadeName= " << cascadeName << endl; } else if( nestedCascadeOpt.compare( 0, nestedCascadeOptLen, argv[i], nestedCascadeOptLen ) == 0 ) { if( argv[i][nestedCascadeOpt.length()] == '=' ) nestedCascadeName.assign( argv[i] + nestedCascadeOpt.length() + 1 ); if( !nestedCascade.load( nestedCascadeName ) ) cerr << "WARNING: Could not load classifier cascade for nested objects" << endl; } else if( scaleOpt.compare( 0, scaleOptLen, argv[i], scaleOptLen ) == 0 ) { if( !sscanf( argv[i] + scaleOpt.length(), "%lf", &scale ) || scale < 1 ) scale = 1; cout << " from which we read scale = " << scale << endl; } else if( tryFlipOpt.compare( 0, tryFlipOptLen, argv[i], tryFlipOptLen ) == 0 ) { tryflip = true; cout << " will try to flip image horizontally to detect assymetric objects\n"; } else if( argv[i][0] == '-' ) { cerr << "WARNING: Unknown option %s" << argv[i] << endl; } else inputName.assign( argv[i] ); } if( !cascade.load( cascadeName ) ) { cerr << "ERROR: Could not load classifier cascade" << endl; help(); return -1; } if( inputName.empty() || (isdigit(inputName.c_str()[0]) && inputName.c_str()[1] == '\0') ) { // -------------------------------------------------------------- // ★raspicam対応 // -------------------------------------------------------------- //capture = cvCaptureFromCAM( inputName.empty() ? 0 : inputName.c_str()[0] - '0' ); capture = (RaspiCamCvCapture *) raspiCamCvCreateCameraCapture2(0, config); // -------------------------------------------------------------- int c = inputName.empty() ? 0 : inputName.c_str()[0] - '0' ; if(!capture) cout << "Capture from CAM " << c << " didn't work" << endl; } else if( inputName.size() ) { image = imread( inputName, 1 ); if( image.empty() ) { // -------------------------------------------------------------- // ★raspicam対応 // -------------------------------------------------------------- //capture = cvCaptureFromAVI( inputName.c_str() ); if(!capture) cout << "Capture from AVI didn't work" << endl; } } else { image = imread( "lena.jpg", 1 ); if(image.empty()) cout << "Couldn't read lena.jpg" << endl; } cvNamedWindow( "result", 1 ); if( capture ) { cout << "In capture ..." << endl; for(;;) { // ------------------------------------------- // ★raspicam対応 // ------------------------------------------- //IplImage* iplImg = cvQueryFrame( capture ); IplImage* iplImg = raspiCamCvQueryFrame( capture ); // ------------------------------------------- // ★FPS対応 // ------------------------------------------- int64 t = cvGetTickCount(); fps_sampling[fps_idx] = (t - t_pre) / freq; t_pre = t; fps_idx++; if (fps_idx>=FPS_SAMPLING_SIZE) { fps_idx = 0; } double total = 0; for (i=0; i<FPS_SAMPLING_SIZE; i++) { total += fps_sampling[i]; } double fps = 0; if (total != 0) { fps = FPS_SAMPLING_SIZE * 1000 * 1000/ total; } printf("FPS: %.0f\n", fps); // ------------------------------------------- frame = iplImg; if( frame.empty() ) break; if( iplImg->origin == IPL_ORIGIN_TL ) frame.copyTo( frameCopy ); else flip( frame, frameCopy, 0 ); detectAndDraw( frameCopy, cascade, nestedCascade, scale, tryflip ); if( waitKey( 10 ) >= 0 ) goto _cleanup_; } waitKey(0); _cleanup_: // ------------------------------------------- // ★raspicam対応 // ------------------------------------------- //cvReleaseCapture( &capture ); raspiCamCvReleaseCapture(&capture); } else { cout << "In image read" << endl; if( !image.empty() ) { detectAndDraw( image, cascade, nestedCascade, scale, tryflip ); waitKey(0); } else if( !inputName.empty() ) { /* assume it is a text file containing the list of the image filenames to be processed - one per line */ FILE* f = fopen( inputName.c_str(), "rt" ); if( f ) { char buf[1000+1]; while( fgets( buf, 1000, f ) ) { int len = (int)strlen(buf), c; while( len > 0 && isspace(buf[len-1]) ) len--; buf[len] = '\0'; cout << "file " << buf << endl; image = imread( buf, 1 ); if( !image.empty() ) { detectAndDraw( image, cascade, nestedCascade, scale, tryflip ); c = waitKey(0); if( c == 27 || c == 'q' || c == 'Q' ) break; } else { cerr << "Aw snap, couldn't read image " << buf << endl; } } fclose(f); } } } cvDestroyWindow("result"); return 0; } void detectAndDraw( Mat& img, CascadeClassifier& cascade, CascadeClassifier& nestedCascade, double scale, bool tryflip ) { int i = 0; double t = 0; vector<Rect> faces, faces2; const static Scalar colors[] = { CV_RGB(0,0,255), CV_RGB(0,128,255), CV_RGB(0,255,255), CV_RGB(0,255,0), CV_RGB(255,128,0), CV_RGB(255,255,0), CV_RGB(255,0,0), CV_RGB(255,0,255)} ; Mat gray, smallImg( cvRound (img.rows/scale), cvRound(img.cols/scale), CV_8UC1 ); cvtColor( img, gray, CV_BGR2GRAY ); resize( gray, smallImg, smallImg.size(), 0, 0, INTER_LINEAR ); equalizeHist( smallImg, smallImg ); t = (double)cvGetTickCount(); cascade.detectMultiScale( smallImg, faces, 1.1, 2, 0 //|CV_HAAR_FIND_BIGGEST_OBJECT //|CV_HAAR_DO_ROUGH_SEARCH |CV_HAAR_SCALE_IMAGE , Size(30, 30) ); if( tryflip ) { flip(smallImg, smallImg, 1); cascade.detectMultiScale( smallImg, faces2, 1.1, 2, 0 //|CV_HAAR_FIND_BIGGEST_OBJECT //|CV_HAAR_DO_ROUGH_SEARCH |CV_HAAR_SCALE_IMAGE , Size(30, 30) ); for( vector<Rect>::const_iterator r = faces2.begin(); r != faces2.end(); r++ ) { faces.push_back(Rect(smallImg.cols - r->x - r->width, r->y, r->width, r->height)); } } t = (double)cvGetTickCount() - t; printf( "detection time = %g ms\n", t/((double)cvGetTickFrequency()*1000.) ); for( vector<Rect>::const_iterator r = faces.begin(); r != faces.end(); r++, i++ ) { Mat smallImgROI; vector<Rect> nestedObjects; Point center; Scalar color = colors[i%8]; int radius; double aspect_ratio = (double)r->width/r->height; if( 0.75 < aspect_ratio && aspect_ratio < 1.3 ) { center.x = cvRound((r->x + r->width*0.5)*scale); center.y = cvRound((r->y + r->height*0.5)*scale); radius = cvRound((r->width + r->height)*0.25*scale); circle( img, center, radius, color, 3, 8, 0 ); } else rectangle( img, cvPoint(cvRound(r->x*scale), cvRound(r->y*scale)), cvPoint(cvRound((r->x + r->width-1)*scale), cvRound((r->y + r->height-1)*scale)), color, 3, 8, 0); if( nestedCascade.empty() ) continue; smallImgROI = smallImg(*r); nestedCascade.detectMultiScale( smallImgROI, nestedObjects, 1.1, 2, 0 //|CV_HAAR_FIND_BIGGEST_OBJECT //|CV_HAAR_DO_ROUGH_SEARCH //|CV_HAAR_DO_CANNY_PRUNING |CV_HAAR_SCALE_IMAGE , Size(30, 30) ); for( vector<Rect>::const_iterator nr = nestedObjects.begin(); nr != nestedObjects.end(); nr++ ) { center.x = cvRound((r->x + nr->x + nr->width*0.5)*scale); center.y = cvRound((r->y + nr->y + nr->height*0.5)*scale); radius = cvRound((nr->width + nr->height)*0.25*scale); circle( img, center, radius, color, 3, 8, 0 ); } } cv::imshow( "result", img ); }
以下のようにコンパイルします。”-L .”より後ろがraspicam_cvライブラリを使用するための指定となります。
# g++ `pkg-config --cflags opencv` -o facedetect_raspicam facedetect_raspicam.cpp `pkg-config --libs opencv` -L . libraspicamcv.a -L ~/git/raspberrypi/userland/build/lib -lmmal_core -lmmal -lmmal_util -lvcos -lbcm_host
以下のように実行します。
# ./facedetect_raspicam --scale=4
モニタに映ったクエリちゃんを認識させてみました。クエリちゃんのバルーン集めのタイトル画面です。
※画像が汚いのはVNC経由のためです。色合いがおかしいのはRasPiカメラの性質と思います。
なお、上記クエリちゃんの顔認識では以下のようにcascade(検出器)にhaarcascade_frontalface_default.xml を指定しました。クエリちゃんはデフォルトのhaarcascade_frontalface_alt.xmlよりこちらの方が認識されやすかったので。
# ./facedetect_raspicam --cascade="~/opencv-2.4.11/data/haarcascades/haarcascade_frontalface_default.xml" --scale=4
(時間があれば)Raspberry Pi 2の4コアを活かすべく顔認識を別スレッドにするようにしてみたいです。
初めまして、ラズベリパイを勉強中のものです。
サンプルプログラム拝見致し、同じように実行しましたがエラーが発生します。 ★FPS対応からエラーが発生しますが、プログラムスキールがなくてどのように直せば良いか分かりません。ご指導よろしくお願いします。
kskさん
エラーメッセージを送信いただければ何かアドバイスができるかもしれません。
すみません、返信が遅くなりました。コンパイルする際に下記のようなエラーが発生します。
ご指導よろしくお願いします。
facedetect_raspicam.cpp: In function ‘int main(int, const char**)’:
facedetect_raspicam.cpp:78:34: error: expected ‘;’ before ‘)’ token
facedetect_raspicam.cpp:79:17: error: ‘fps_idx’ was not declared in this scope
facedetect_raspicam.cpp:82:23: error: ‘iorigin’ was not declared in this scope
facedetect_raspicam.cpp:82:48: error: expected ‘;’ before ‘)’ token
facedetect_raspicam.cpp:84:13: error: ‘else’ without a previous ‘if’
facedetect_raspicam.cpp:87:39: error: ‘cascade’ was not declared in this scope
facedetect_raspicam.cpp:87:48: error: ‘nestedCascade’ was not declared in this scope
facedetect_raspicam.cpp:87:63: error: ‘scale’ was not declared in this scope
facedetect_raspicam.cpp:90:22: error: label ‘_cleanup_’ used but not defined
facedetect_raspicam.cpp: At global scope:
facedetect_raspicam.cpp:93:16: error: expected constructor, destructor, or type conversion before ‘(’ token
facedetect_raspicam.cpp:95:10: error: found ‘:’ in nested-name-specifier, expected ‘::’
facedetect_raspicam.cpp:95:1: error: ‘_cleanup_’ does not name a type
facedetect_raspicam.cpp:102:5: error: expected declaration before ‘}’ token
kskさん
貼りつけていたソースですが、ブラウザによっては表示が崩れてしまう場合があったようです。ソースを表示するためのツールを導入してみましたので、再度試してみてください。
ありがとうございます。
再度試した結果、問題無くpiカメラで顔認識出来ました。
今後も「PONTAの工房 ~プログラミング・電子工作~」を拝見しながらラズベリパイの勉強に力をつけたいと思います。どうぞよろしくお願いします。
はじめまして。
RasPiで画像処理を行いたく、管理人様の手順を参考にさせていただいております。
初心者ながらraspicam_cvを導入してOpenCVでカメラモジュール操作を行うことは出来たのですが、
当記事のカメラモジュールで顔認証を行うプログラムを実行しようとすると、エラーがでてしまいます。
ターミナルで
./facedetect_raspicam –scale=4
を打ち込むと
Processing 1 –scale=4
from which we read scale = 4
ERROR: Could not load classifier cascade
と出てきます。
ビルド自体は出来ているみたいなのですが、実行がどうもうまくいきません。
原因がお分かりでしたらご指導お願いします。
KTさん
「ERROR: Could not load classifier cascade」とのことですので、cascadeファイルが読み込めないのだと思います。本文中の「facedetect_raspicam.cpp」のソースだと40行目、41行目で相対パスでファイルを指定していますので、ファイルを参照できるディレクトリに移動してから実行する必要があります。または、–cascadeオプションおよび–nested-cascadeオプションで明示的に指定してあげればよいと思います。
回答ありがとうございます。
ご指摘の通り、cascadeファイルが読み込めていませんでした。
facedetect.cppがあった場所に移動させて実行してみると正常に動作しました。
また当記事の「facedetect_raspicam.cpp」ソースの40,41行目のパスを変えたところ、別のフォルダでも動作を確認することができました。
管理人様、丁寧に答えていただいて本当にありがとうございました。
今後も当ブログの更新をがんばってください。楽しみにしております。
はじめまして 上記のプログラムをコンパイルして実効したら以下のような文が出ます。
This program demonstrates the cascade recognizer. Now you can use Haar or LBP features.
This classifier can recognize many kinds of rigid objects, once the appropriate classifier is trained.
It’s most known use is for faces.
Usage:
./facedetect [–cascade= this is the primary trained classifier such as frontal face]
[–nested-cascade[=nested_cascade_path this an optional secondary classifier such as eyes]]
[–scale=]
[–try-flip]
[filename|camera_index]
see facedetect.cmd for one call:
./facedetect –cascade=”../../data/haarcascades/haarcascade_frontalface_alt.xml” –nested-cascade=”../../data/haarcascades/haarcascade_eye.xml” –scale=1.3
During execution:
Hit any key to quit.
Using OpenCV version 2.4.10
ERROR: Could not load classifier cascade
This program demonstrates the cascade recognizer. Now you can use Haar or LBP features.
This classifier can recognize many kinds of rigid objects, once the appropriate classifier is trained.
It’s most known use is for faces.
Usage:
./facedetect [–cascade= this is the primary trained classifier such as frontal face]
[–nested-cascade[=nested_cascade_path this an optional secondary classifier such as eyes]]
[–scale=]
[–try-flip]
[filename|camera_index]
see facedetect.cmd for one call:
./facedetect –cascade=”../../data/haarcascades/haarcascade_frontalface_alt.xml” –nested-cascade=”../../data/haarcascades/haarcascade_eye.xml” –scale=1.3
During execution:
Hit any key to quit.
Using OpenCV version 2.4.10
opencv2.4.10を使っていることが原因なのでしょうか?
ERROR: Could not load classifier cascade
とのエラーが出ているようですので、
cascade=”../../data/haarcascades/haarcascade_frontalface_alt.xml” –nested-cascade=”../../data/haarcascades/haarcascade_eye.xml”
で指定しているパスが正しいか確認してみてください。
カレントパスから
ls ../../data/haarcascades/haarcascade_frontalface_alt.xml
などで参照できるでしょうか?
絶対パスにしてみても良いかもしれません。
ご指摘の通りパスの部分を直したら実行できました。
ありがとうございました
うまくいってよかったです