これは、OpenCV Advent Calendar 2015 19日目の記事です。関連記事は目次にまとめられています。
一部修正しました。12/20 00:50
#はじめに
多くの人がOpenCVでためしてみる機能は、もちろんCascade ClassifierのdetectMultiScaleを使用した顔検出ですね。
この関数のチュートリアルは、ここです。
Cascade Classifierクラスの説明はここで見ることができます。detectMultiScaleの説明はここです。
detectMultiScaleはこのように使用します、以下の例は最小限の使い方です。(細かいところは気にしないでください)
cv::CascadeClassifier face_cascade;
std::vector<Rect> faces;
cv::Mat image = cv::imread("lena.jpg");
face_cascade.load( "haarcascade_frontalface_alt2.xml")
face_cascade.detectMultiScale( image, faces);
これだけでOKです。検出結果の領域データは、facesに格納されています。
##サンプルの補足
本来なら、imageをグレースケールに変換し、equalizeHistを行うべきでしょうが省略しています
また、equalizeHistを行い内のであれば、グレースケールへの変換も省略できます。
detectMultiScaleはカラー画像にも対応しており、内部でchannelを調べ、カラーの場合必要は、変換して処理します。
このコードを実行すると、1つの候補が見つかります。lenaさんに描画するとこうなります。
左上の座標は、219,203でサイズは 174x174です。
#前置きが長くなりましたがここからが本題です。
本日のとりあげる知られざる機能はsetImageです。説明はここです。
この関数は、detectMultiScaleの内部で使用されており、通常使う機会はないと思います。
ただ一部の人には、使えると検出結果のさらなる絞込みができるので、うれしいことがあるかと思いますが、その話は別の機会に
setImageは、runAtと対で使用されるものです。runAtの説明はここです
setImageを簡単に説明すると、設定されたimageに対し、FeatureEvaluatorで指定した特徴量を作成しています。
runAtは、設定されたFeatureEvaluatorで、指定した場所を左上とし、辞書サイズの大きさの部分領域の画像の分類を行います。
runAtを実行した結果
対象の領域がpositiveの場合は戻り値が1となります。negativeの場合は、0 および マイナスになります。戻り値の解釈は、使用する特徴量により異なります。また、wは判定に使用した評価値であり、この解釈も特徴量により異なります。
不思議なことに
setImageとrunAtは対で使用するはずですが、なぜかsetImageはpublicで、runAtは protectedの属性が指定されており、CascadeClassifierからは、runAtを呼び出すことはできません
そこで、CascadeClassifierクラスをpublic 継承した CMyCascadeクラスを作成し、自作のdetect関数内でこの2つにアクセスすることになります。
#pragma once
#include <opencv2/opencv.hpp>
class CMyCascade : public cv::CascadeClassifier
{
cv::CascadeClassifier cascade;
public:
CMyCascade();
~CMyCascade();
void detect(cv::Mat);
};
void CMyCascade::detect(cv::Mat image)
{
load("haarcascade_frontalface_alt2.xml");
cv::Mat gray;
cv::cvtColor(image, gray, CV_BGR2GRAY);
cv::Ptr<cv::FeatureEvaluator> evaluator = this->featureEvaluator;
setImage(evaluator, gray);
double w;
for (int j = 0; j < image.size().height - 20; j++){
for (int i = 0; i < image.size().width - 20; i++) {
cv::Point p(i, j);
int r = this->runAt(evaluator, p, w);
if (r > 0) {
std::cout << i << "," << j << " " << w << std::endl;
}
}
}
}
CMyCascadeを呼び出すメイン関数
#include "MyCascade.h"
cv::Mat image = cv::imread("lena.jpg");
cv::Mat small;
cv::resize(image, small, cv::Size(59,59));
CMyCascade cascade2;
cascade2.detect(small);
今回のサンプルで入力されるimageは,lenaの画像そのものではなく、512x512の大きさの元画像を59x59に縮尺した画像を使用しています。このサイズは、検出された174x174のサイズから辞書サイズ20x20の比率に合わせて縮小しています。
サンプルを実行すると、5点の座標がpositive(顔領域)として判定されます。
detectMultiScale内では、複数の処理が行われております。物体検出の基本は、setImageとrunAtで実現されていますが、
ここに至る前処理と、後処理の説明はそれなりの量になりますので省略しています。
そのため上のサンプルだけでは、まったく理解できないかと思います。
#詳しく知りたい場合は
cascadedetectのソースを読めば全体の流れがわかるかと思います。ソースは
opencv2411\sources\modules\objdetect\src\cascadedetect.cpp です
余談ですが
高速化のために、並列処理が行われており、13日に取り上げられた parallel_for_が使用されています.
(並列化の処理内でpositive発見ごとに、mutexのlockを行っているのはどうかと思いますが)
今回の話は、
OpenCV 2.4.11
OS Windows 7 Home 64bit
Visual Studio 2013 update 5
で動作確認したものです。
#補足
**18日の記事でICFが取り上げられています。
3.1のDraftが公開されています。そこで
the results are available as a part of OpenCV 3.1 のリストに
Improved ICF detector, waldboost implementation - opencv_contrib/xobjdetect
が含まれており、プロジェクトの場所が移動しただけだと思います。
(すいません 裏はまだ取っていませんので本当になくなったかも知れません)