【Visual Studio】OpenCVでHaar-like特徴分類器を用いたオブジェクト検出

#はじめに
######※これは、VC++ Visual Studioを利用する初心者向けの学習用記事です。
今回はVisual Studioを利用し、画像処理ライブラリOpenCVを用いて、Haar-like特徴分類器を用いたオブジェクト検出を説明する。
Haar-like特徴分類器を用いた認識は、オブジェクト検出ともいわれますね。
Haar-like特徴分類器を変更することによって、検出したい顔やオブジェクトを検出することができます。
OpenCV 4.0.0を利用しています。

#OpenCVでのHaar-like特徴分類器の関数
OpenCVでHaar-like特徴分類器を使用する方法は、以下の変数型と２つの関数を利用する。

```cpp:
cv::CascadeClassifier::CascadeClassifier(const String &filename)
```
１つ目の関数は、Haar-like特徴分類器を宣言するための変数型と関数である。

```cpp:
void cv::CascadeClassifier::detectMultiScale(
	InputArray image,
	std::vector<Rect> &objects,
	double  scaleFactor = 1.1,
	int 	minNeighbors = 3,
	int 	flags = 0,
	Size 	minSize = Size(),
	Size 	maxSize = Size() 
)	
```
２つ目の関数は、Haar-like特徴分類器を利用して、オブジェクト検出する関数である。

```cpp:Haar-like特徴分類器の宣言と分類器の使用方法
cv::CascadeClassifier cascadeclassifier("cascade_filename.xml");
//cascadeclassifier：変数宣言です
//"cascade_filename.xml"：String型：カスケードファイル[.xml]を読み込むファイル名をしています。

cv::CascadeClassifier cascadeclassifier;		//先に宣言する場合
cascadeclassifier.load("cascade_filename.xml");	//カスケードファイル[.xml]を読み込むファイル名をしています。

cascadeclassifier.detectMultiScale(src_gray, objects, 1.3, 3, CASCADE_SCALE_IMAGE, Size(50, 50), Size(150, 150));
//cascadeclassifier：オブジェクトの識別変数：宣言時にカスケードファイル[.xml]と呼ばれる識別ファイルを読み込みます。
//src_gray：Mat型：入力画像　Haar-like特徴分類器では、１チャンネルのグレースケール画像のみ入力可能です。(CV_8U, CV_8UC1)
//objects：std::vector<Rect>：検出されたオブジェクトの位置と範囲が格納されます。
//1.3：double型：検出するサイズを拡大縮小するときの拡大率。
//3：int型：オブジェクトを検出した際に、領域の重なりがこの数値以上であれば検出とする。
//CASCADE_SCALE_IMAGE：int型(定義値使用)：現在は利用されてないそうです。定値と思ってください。
//Size(50, 50)：Size型：最小の検出サイズです。
//Size(150, 150)：Size型：最大の検出サイズで。
```

detectMultiScaleは、入力画像と出力先のstd::vector<Rect>は必要ですが、それ以降の値は初期値が設定されているのでなしでも大丈夫です。ただし、検出されない場合は設定を変更する必要があります。

```cpp:detectMultiScaleについて
cascadeclassifier.detectMultiScale(src_gray, objects); //これでも動く
```

##カスケードファイル[.xml]について
OpenCVでは、学習されたカスケードファイル[.xml]が用意されているので、それを利用することで検出が簡単に可能です。
カスケードファイルは、OpneCVのインストールディレクトリから「build」－「etc」－「haarcascades」に保存されています。
カスケードファイルの絶対パスを利用するか、画像を読み込む場所と一緒に場所にコピーして使用してください。
以下にカスケードファイル名とその検出内容の抜粋を示します。

|カスケードファイル名          |検出内容|
|:-----------------------------------------|:---------------------------|
|haarcascade_frontalface_alt2.xml          |顔（正面）                   |
|haarcascade_fullbody.xml                  |全身                         |


##Haar-like特徴分類器を利用したオブジェクト検出のテストプログラム

```cpp:haarlike_Test1.cpp
#include <opencv2/opencv.hpp>

#ifdef _DEBUG
#pragma comment(lib, "opencv_world400d.lib")
#else
#pragma comment(lib, "opencv_world400.lib")
#endif

using namespace std;
using namespace cv;

int main()
{
    Mat src = imread("src.png");                                        //識別したい画像を読み込む
    Mat src_gray;                                                       //グレースケール画像変換用変数
    vector<Rect> objects;                                               //検出結果出力用

    cvtColor(src, src_gray, COLOR_BGR2GRAY);                            //読み込んだ画像をグレースケール画像にする

    cv::CascadeClassifier cascadeclassifier("haarcascade_frontalface_alt2.xml");    //カスケードファイル[.xml]を読み込み識別器を宣言する
    //cv::CascadeClassifier cascadeclassifier("C:\opencv4.0.0\opencv\build\etc\haarcascades\haarcascade_frontalface_alt2.xml");
    //例：カスケードファイル[.xml]を絶対パスで読み込み識別器を宣言する

    cascadeclassifier.detectMultiScale(src_gray, objects, 1.1, 2, CASCADE_SCALE_IMAGE, Size(70, 70), Size(150, 150));    //検出する

    vector<Rect>::iterator itr;                                         //vector内のデータを検索するためのイテレータ
    for (itr = objects.begin(); itr != objects.end(); itr++) {
        rectangle(src, Point(itr->x, itr->y), Point(itr->x + itr->width, itr->y + itr->height), Scalar(0, 0, 255), 3, LINE_AA);
        //元画像の検出した顔の領域を赤色の矩形で囲む

        cout << "検出： 座標：" << Point(itr->x, itr->y) << " サイズ：" << Size(itr->width, itr->height) << endl;
    }

    /*
    //イテレータの使い方がわからない人はこちら
    for (int i = 0; i < objects.size(); i++) {
        rectangle(src, Point(objects[i].x, objects[i].y), Point(objects[i].x + objects[i].width, objects[i].y + objects[i].height), Scalar(0, 0, 255), 3, LINE_AA);
        //元画像の検出した顔の領域を赤色の矩形で囲む

        cout << "検出： 座標：" << Point(objects[i].x, objects[i].y) << " サイズ：" << Size(objects[i].width, objects[i].height) << endl;
    }
    */

    imshow("src", src);                                                 //元画像表示
    waitKey();

    return 0;
}
```
![dst.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/660146/fe7e98c0-e3e0-52de-451c-c25c37e86ca1.png)

```bash:出力結果
検出： 座標：[53, 33] サイズ：[95 x 95]
検出： 座標：[243, 440] サイズ：[103 x 103]
検出： 座標：[52, 233] サイズ：[119 x 119]
検出： 座標：[56, 624] サイズ：[126 x 126]
```
サンプルなので、精度は悪いです。
人の顔写真とかで行うと精度が良いと思います。

#Haar-like特徴分類器の読み込み確認関数
Haar-like特徴分類器を宣言し、カスケードファイル[.xml]を読み込み後、正しく読み込みができているか確認を行う関数がある。

```cpp:Haar-like特徴分類器の読み込み確認
bool cv::CascadeClassifier::empty();
```
empty()は、返戻値がbool型であり、Trueであれば読み込みができてない状態。Falseであれば読み込みができていると確認が可能である。
カスケードファイルが正しく読み込まれずに検出処理を行うとAssertion failedの原因となるため、必ず確認を行ったほうが無難である。

##Haar-like特徴分類器の読み込み確認のプログラム

```cpp:haarlike_Test2.cpp
#include <opencv2/opencv.hpp>

#ifdef _DEBUG
#pragma comment(lib, "opencv_world400d.lib")
#else
#pragma comment(lib, "opencv_world400.lib")
#endif

using namespace std;
using namespace cv;

int main()
{
    cv::CascadeClassifier cascadeclassifier("haarcascade_frontalface_alt2.xml");    //カスケードファイル[.xml]を読み込み識別器を宣言する

    if (cascadeclassifier.empty()) {                                                //カスケードファイル読み込みの確認
        cout << "Cascade file Not Loaded!" << endl;                                 //読み込みできてない場合はエラー
        return -1;                                                                  //プログラム終了
    }

    return 0;
}
```


#注意点
カスケードファイルを正しく読み込まないと検出時にエラーが起きますので注意してください。

#さいごに
今回はVisual Studioを利用し、画像処理ライブラリOpenCVを用いてHaar-like特徴分類器を用いたオブジェクト検出を行った。
今回の利用した関数の引数がいきなり多くなったので、混乱する人も多いと思います。
また、閾値がサイズや拡大縮小のパラメータ、長方形の近傍数など複数の閾値も設定しなければならず、
うまく検出できないことが多いと思います。

余談：
自作のカスケードファイルの作成方法も記事にまとめておいたほうがいいですよね。ほしい人いるのか？
深層学習が主になってきた今、Haar-like特徴分類器の精度を保つのは難しいですけど。。