2016年02月07日(Sun)

OpenCV3とパイカメラのメモ

電子オタクに走っているこの頃。

Raspberry PiというLinuxが走るマイコンボードは、安く入手できることもあってDIY電子工作の世界でブレークしています。
このブログの読者には多分あまり関係ない記事ですが、現時点で最新のOpenCV3を取り扱う日本語のガイドが少なく、PiカメラでOpenCV3のライブラリを利用するまでの記事が見あたらないので足跡を残しておこう。

Raspberry Piは名刺サイズのマイコン基板で、フリーの画像処理ライブラリであるOpenCVと組み合わせれば、手軽にコンピュータビジョンを試すことが出来る。
今回は、OpenCVの最新バージョン3で、Piカメラを使ったプログラムを作る手順までを記事にします。
以下、ダラダラと長文になります。

準備編
0. OSは Raspbian Wheezy 2015-05-05です。  SDカードは16GB以上の物が必要です。
1. Raspberry PiにOpenCV3.0.0をインストールする。
2. Pi camera(Raspberry Pi オフィシャルカメラ)を利用するためのライブラリとサンプルのインストール。

Piカメラ応用編
OpenCV3のサンプルのC++ソースに手を加えて、Piカメラを使用する手順まで。

準備編1. OpenCV3.0.0のインストール

これは、他のブログ等で既出の通り
ソースからビルドしてインストールするためのツールを導入
$ sudo apt-get -y install autoconf libtool automake git

必要なツールとライブラリ(OpenCV 3.0をコンパイルするために必要な物)
$ sudo apt-get -y install build-essential cmake cmake-qt-gui pkg-config libpng12-0 libpng12-dev libpng++-dev libpng3 libpnglite-dev zlib1g-dbg zlib1g zlib1g-dev pngtools libtiff4-dev libtiff4 libtiffxx0c2 libtiff-tools

$ sudo apt-get -y install libjpeg8 libjpeg8-dev libjpeg8-dbg libjpeg-progs ffmpeg libavcodec-dev libavcodec53 libavformat53 libavformat-dev libgstreamer0.10-0-dbg libgstreamer0.10-0 libgstreamer0.10-dev libxine1-ffmpeg libxine-dev libxine1-bin libunicap2 libunicap2-dev libdc1394-22-dev libdc1394-22 libdc1394-utils swig libv4l-0 libv4l-dev python-numpy libpython2.6 python-dev python2.6-dev libgtk2.0-dev pkg-config

$ sudo apt-get -y install libswscale-dev

sudo apt-get update (インストール済みパッケージの更新)
sudo apt-get upgrade

OpenCVの入手とビルド
$ wget http://sourceforge.net/projects/opencvlibrary/files/opencv-unix/3.0.0/opencv-3.0.0.zip
  または
$ wget https://github.com/Itseez/opencv/archive/3.0.0.zip -O opencv-3.0.0.zip

$ unzip opencv-3.0.0.zip
$ cd opencv-3.0.0
$ 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 ..
 (結果表示  Build files have been written to: /home/pi/opencv-3.0.0/build)
$ make -j 4(-jは4コアのPi2だけが可、1時間ほどかかる)
$ sudo make install
$ sudo ldconfig


準備編2. Piカメラのライブラリをインストールし、サンプルプログラム RaspiCamTest をコンパイル、実行する

sudo apt-get install git(既にインストールされていると思われるが念のため)

$ mkdir ~/git
$ cd  ~/git
$ mkdir raspberrypi
$ cd raspberrypi
$ git clone https://github.com/raspberrypi/userland.git
$ cd userland
$ ./buildme

$ cd  ~/git
$ git clone https://github.com/robidouille/robidouille.git
$ cd robidouille/raspicam_cv

opencvのインストール先の違いにより、Makefileを書き換えます
変更前: CFLAGS_OPENCV = -I/usr/include/opencv
変更後: CFLAGS_OPENCV = -I/usr/local/include/opencv

OpenCV2ではこれで良かったが、OpenCV3.xではもう一つ、OpenCV1.xの古いライブラリに対応しないので、レガシーライブラリの参照を削除する
変更前: LDFLAGS2_OPENCV = -lopencv_highgui -lopencv_core -lopencv_legacy  ....
変更後: LDFLAGS2_OPENCV = -lopencv_highgui -lopencv_core  ....

$ mkdir objs
$ make

エラー無く、サンプルコードRaspiCamTest がコンパイルされるので、
Xウィンドー上のターミナルから RaspiCamTest を実行すると、Piカメラの画面(テキスト表示付き)が表示される
(Piカメラのライブラリと環境構築終了)


応用編:OpenCV3のサンプルのC++ソースに手を加えて、Piカメラを使用する手順まで。
OpenCV3.0.0のサンプルコードのコンパイル

例えばsampleA.cpp のコンパイルはこのようにすればよい(pkg-configを使ってヘッダとライブラリの参照先を渡している)
g++ sampleA.cpp -o sampleA `pkg-config --libs --cflags opencv`

注意)OpenCV2.x用のサンプルコードをMakefileを使ってコンパイルする場合、Makefile中のOpenCV1.x対応ライブラリを使用しないよう配慮しないと参照エラーが出て失敗する
(opencv_legacy moduleがOpenCV 3.0からdeprecateになっており、エラーになる)

Pi camera(Piカメラ)のC++用ライブラリraspicam_cvを利用するソースの場合は、準備としてヘッダをコピーしておき
最初は、サンプルのディレクトリ ~/opencv-3.0.0/samples/cpp/ に移動してとりあえず1個試してみる。
$ cd ~/opencv-3.0.0/samples/cpp

一つのサンプルソースをコンパイルしてみる
$ g++ facedetect.cpp -o facedetect `pkg-config --libs --cflags opencv`

実行してみよう
$ ./facedetect ../data/lena.jpg

デフォルトの定義ファイルを参照しているので、ディレクトリを移動してコンパイルした場合は、--cascadeオプションを使って顔認識の定義xmlファイルの場所を指定する必要がある(コマンド実行時に表示されるし、ソースを見ても分かる)

lenaの写真の顔部分に円が描かれれば成功!
20160206.jpg

次に、このfacedetect.cppをもとに、Piカメラからの画像を顔認識させてみる。(いよいよコンピュータビジョンの入り口!)

$ cd ~/opencv-3.0.0/samples/cpp
Piカメラのライブラリ利用準備
$ cp ~/git/robidouille/raspicam_cv/RaspiCamCV.h ./
$ cp ~/git/robidouille/raspicam_cv/libraspicamcv.a ./

元ソースをコピーし、
$ cp facedetect.cpp facedetect_raspicam.cpp
次のように facedetect_raspicam.cppを修正します(// ★ の部分)

#include "opencv2/objdetect.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/videoio.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/core/utility.hpp"

#include "opencv2/videoio/videoio_c.h"
#include "opencv2/highgui/highgui_c.h"

#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;    // → 1280
    config->height=480;    // → 720
    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( "../data/lena.jpg", 1 );
        if(image.empty()) cout << "Couldn't read ../data/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 = cv::cvarrToMat(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

修正部分終わり


次のようにコンパイルします
(”-L .”より後ろがraspicam_cvライブラリを使用するための指定で、最後の-pthreadはOpsnCV3から必要なので要注意)
g++  facedetect_raspicam.cpp -o facedetect_raspicam `pkg-config --cflags opencv` `pkg-config --libs opencv` -L . libraspicamcv.a -L ~/git/raspberrypi/userland/build/lib -lmmal_core -lmmal -lmmal_util -lvcos -lbcm_host -pthread

実行してみます
$ ./facedetect_raspicam

Piカメラの画像をリアルタイムで顔認識し、顔の中心に円が描かれます。
環境構築で不足したり、別バージョンのインストールなどが邪魔をしなければうまくいくはず。
このやりかたで、OpenCV3とPiカメラにトライしてみてください。
うまくいかなかった方、本ブログではまともにアドバイスできませんので、フォーラム等で質問してみましょう。
posted by エイ at 02:02 | Comment(0) | TrackBack(0) | エレクトロニクス
この記事へのコメント
コメントを書く
お名前: [必須入力]

メールアドレス(管理者のみ閲覧可能):

ホームページアドレス:

コメント: [必須入力]

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
この記事へのトラックバックURL
http://blog.sakura.ne.jp/tb/173522739

この記事へのトラックバック