今回は, 画像認識の基本を説明する.

3. 18 顔を検出する[1][2][3]
OpenCVには, Haar-Like特徴やLBP(Local Binary Pattern)特徴を用いて, オブジェクト(顔, 目等)検出をするための仕組みが準備されている.
各特徴で, いくつかのオブジェクトを学習した学習結果ファイルが以下のディレクトリにあるので, 検出するオブジェクトの学習ファイルをres/rawの下にコピーする.
OpenCV-2.X.X-android\OpenCV-2.X.X\share\OpenCV\{haarcascades, lbpcascades}
 (1) Haar-Like特徴
   - haarcascade_eye.xml
   - haarcascade_eye_tree_eyeglasses.xml
   - haarcascade_frontalface_alt.xml
   - haarcascade_frontalface_alt2.xml
   - haarcascade_frontalface_alt_tree.xml
   - haarcascade_frontalface_default.xml
   - haarcascade_fullbody.xml
   - haarcascade_lefteye_2splits.xml
   - haarcascade_lowerbody.xml
   - haarcascade_mcs_eyepair_big.xml
   - haarcascade_mcs_eyepair_small.xml
   - haarcascade_mcs_leftear.xml
   - haarcascade_mcs_lefteye.xml
   - haarcascade_mcs_mouth.xml
   - haarcascade_mcs_nose.xml
   - haarcascade_mcs_rightear.xml
   - haarcascade_mcs_righteye.xml
   - haarcascade_mcs_upperbody.xml
   - haarcascade_profileface.xml
   - haarcascade_righteye_2splits.xml
   - haarcascade_upperbody.xml
 (2) LBP特徴
   - lbpcascade_frontalface.xml

[手順]
 (1) 画像データを読み込む.
 (2) グレースケール画像に変換し, 画像データを縮小する.
 (3) 顔の分類器を読み込む.
 (4) 顔を探索する.

[コード]
package com.moonlight_aska.android.opencv.image18;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedList;
import java.util.List;

import org.opencv.android.Utils;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
import org.opencv.core.Size;

import org.opencv.highgui.Highgui;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.CascadeClassifier;

import android.os.Bundle;
import android.widget.ImageView;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;

public class Image18Activity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        // cv::Mat img = cv::imread("../../iamge/lenna.png", 1);
        Mat srcMat = Highgui.imread("/sdcard/OpenCV/sample/lena.jpg", 1);
        if (!srcMat.empty()) {
            double scale = 4.0;
            // cv::Mat gray, smallImg(cv::saturate_cast<int>(img.rows/scale),
            //        cv::saturate_cast<int>(img.cols/scale), CV_8UC1);

            Mat grayMat = new Mat();
            Size smallSize = new Size(srcMat.rows()/scale, srcMat.cols()/scale);
            Mat smallMat = new Mat(smallSize, CvType.CV_8UC1);

            // グレースケール画像に変換
            // cv::cvtColor(img, gray, CV_RGB2GRAY);

            Imgproc.cvtColor(srcMat, grayMat, Imgproc.COLOR_RGB2GRAY);
            // 処理時間短縮のために画像を縮小
            // cv::resize(gray, smallImg, smallImg.size(), 0, 0, cv::INTER_LENEAR;
            // cv::equalizeHist(smallImg, smallImg);

            Imgproc.resize(grayMat, smallMat, smallMat.size(), 0, 0, Imgproc.INTER_LINEAR);
            Imgproc.equalizeHist(smallMat, smallMat);

           
            try {
                // 分類器の読み込み
                // std::string cascadeName = "./haarcascade_frontalface_alt.xml"; // Haar-like

                InputStream inStream = getResources().openRawResource(R.raw.haarcascade_frontalface_alt);
                File cascadeDir = this.getDir("cascade", Context.MODE_PRIVATE);
                File cascadeFile = new File(cascadeDir, "haarcascade_frontalface_alt.xml");
       
                // // std::string cascadeName = "./lbpcascade_frontablface.xml"; // LBP
                // InputStream inStream = getResources().openRawResource(R.raw.lbpcascade_frontalface);
                // File cascadeDir = this.getDir("cascade", Context.MODE_PRIVATE);
                // File cascadeFile = new File(cascadeDir, "lbpcascade_frontalface.xml");
                FileOutputStream outStream;
                outStream = new FileOutputStream(cascadeFile);
                byte[] buf = new byte[2048];
                int rdBytes;
                while ((rdBytes = inStream.read(buf)) != -1) {
                    outStream.write(buf, 0, rdBytes);
                }
                outStream.close();
                inStream.close();
                // cv::CascadeClassifier cascade;
                // if(!cascade.load(cascadeName))
                //     return -1;

                CascadeClassifier cascade = new CascadeClassifier(cascadeFile.getAbsolutePath());
                if (cascade.empty()) {
                    cascade = null;
                    return;
                }
                else {
                    cascadeDir.delete();
                    cascadeFile.delete();  
                }
                // std::vector<cv::Rect> faces;
                List<Rect> faces = new LinkedList<Rect>();
                /// マルチスケール(顔)探索
                // 画像, 出力矩形, 縮小スケール, 最低矩形数, (フラグ), 最小矩形
                // cascade.detectMultiScale(smallImg, faces,
                //                          1.1, 2
                //                          CV_HAAR_SCALE_IMAGE
                //                          ,
                //                          cv::Size(30,30);

                cascade.detectMultiScale(smallMat, faces,
                        1.1, 2, 2 // TODO: objdetect.CV_HAAR_SCALE_IMAGE
                        , new Size(30, 30));

                // 結果の描画
                // std::vector<cv::Rect>::const_iterator r = faces.begin();
                // for(; r != faces.end(); ++r ) {
                //  cv::Point center;
                //  int radius;
                //  center.x = cv::saturate_cast<int>((r->x + r->width*0.5)*scale);
                //   center.y = cv::saturate_cast<int>((r->y + r->height*0.5)*scale);
                //  radius = cv::saturate_cast<int>((r->width + r->height)*0.25*scale);
                //  cv::sircle(img, center, radious, cv::Scalar(80,80,255), 3, 8, 0);
                // }
                for(Rect r : faces) {
                    Point center = new Point();
                    int radius;
                    center.x = (int)((r.x + r.width*0.5)*scale);
                    center.y = (int)((r.y + r.height*0.5)*scale);
                    radius = (int)((r.width + r.height)*0.25*scale);
                    Core.circle(srcMat, center, radius, new Scalar(80,80,255), 3, 8, 0);
                }
                Bitmap faceImg = convMatToBitmap(srcMat);
                ImageView faceView = (ImageView)findViewById(R.id.face_view);
                faceView.setImageBitmap(faceImg);
            } catch (FileNotFoundException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
   
    // MatからBitmapに変換
    Bitmap convMatToBitmap(Mat src) {
        Mat dst = new Mat();
        // BGR→RGBAに変換
        Imgproc.cvtColor(src, dst, Imgproc.COLOR_BGR2RGBA, 4);
        Bitmap img = Bitmap.createBitmap(src.width(), src.height(), Bitmap.Config.ARGB_8888);
        // MatからBitmapに変換
        Utils.matToBitmap(dst, img);
        return img;
    }
}

[入力画像]
image02-1

[実行結果]
Image18

今回のポイントは, 顔を認識するための学習結果ファイル(xxxx.xml)をリソースから一旦ファイルに書き出し, 読み込んでいる点である.

----
参照URL:
 [1] OpenCV 2 プログラミングブック OpenCV2.2/2.3対応
 [2] OpenCV逆引きリファレンス―OpenCV-CookBook
 [3] OpenCVで学ぶ画像認識