OpenCV for Unityを使って顔検出をしてみた話

# はじめに
この記事は[AizuAdventCalendar 2019](https://adventar.org/calendars/3938)の23日目の記事になります。
# なにをしたのか
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/366802/abb31c61-a3cf-41ec-6df4-31cb596d980a.png)
こんなかんじで顔を検出したらその部分を赤色の矩形で囲むといったものを作りました。
#なにをここで話すのか
ここでは中身の解説などはあまりせず（というのも中身はサンプルと同じようなものなため）初めて顔検出を使ったものを作りたい方に向けた顔検出プロジェクトの作り方の流れなどを説明していきたいと思います。
実際の制作はOpenCV for Unityを導入したプロジェクトでAssets/OpenCVForUnity/Examples/MainModule/objdetect/FaceDetectionExample以下に置かれているFaceDetectionWebCamTextureExampleを見てください。
一応どういったようなコードを書いたのは
[Github](https://github.com/Namerou7716/FaceDetectionCode.git)
に置いてありますのでPC上でのみ試したい方は参考にしてみてください。
また私が今回作るにあたって参考にさせていただいたQiitaの記事を載せておきます。
[詳しいことが書いてある記事](https://qiita.com/utibenkei/items/fb93623a7ccaabb342f2)
#顔検出の流れ
まず画像から顔を検出する前に画像に少しばかりの変更を加えなくてはいけません。簡単に書くと
1. Texture2D形式からMat形式への変更
2. 検出に不必要な色のデータをなくすためにグレースケールへの変更
3. 検出に適した画像にするためのヒストグラムの平坦化
4. 解析して顔を検出したのちにTexture2D形式への変換
これをするのにOpenCV for Unityは簡単にできるAPIを各種用意しています。以下にそれを書いておきます。
Texture2DからMat形式への変換

```
//これをサンプルだとWebCamTextureToMatHelper.csのGetMat()で処理しているので実装の際に書くことはない。
Utils.webCamTextureToMat(webcamTex,rgbmat,colors);
 ```
グレースケールへの変更

```
Imgproc.cvtColor(rgbMat,grayMat,Imgproc.COLOR_RGBA2GRAY);
```
ヒストグラムの平坦化

```
Imgproc.equalizeHist(grayMat,grayMat);
```
Texture2D形式への変換

```
Utils.fastMatToTexture2D(rgbaMat,texture2D);
```

では次に肝心の顔の検出の部分ですがこれはあらかじめ学習によって作られた検出器(cascade)といったものを利用してやっていきます。
まず検出器をアセットから読み込んでおきます。

```
cascade.load(Utils.getFilePath("lbpcascade_frontalface.xml"));
```
あとは変換されたMat形式の画像に対して検出器を当てていき顔の検出を行います。

```
cascade.detectMultiScale(grayMat, faces, 1.1, 2, 2, new Size(grayMat.cols() * 0.2, grayMat.rows() * 0.2), new Size());
```
検出部分もこれだけです。あとは顔が検出された際にかこむ赤い矩形をつけるために

```
 OpenCVForUnity.CoreModule.Rect[] rects = faces.toArray();
            for (int i = 0; i < rects.Length; i++)
            {
                Imgproc.rectangle(rgbaMat, new Point(rects[i].x, rects[i].y), new Point(rects[i].width, rects[i].y + rects[i].height), new Scalar(255, 0, 0, 255), 2);

            }
```
という処理を書いています。
特別APIを使って何かをしているのはこれだけで後はカメラへのアクセスやカメラの画像を表示する際のいろいろな設定を書いているだけです。簡単ですね。あと少しスクリプトのなかで困りそうな部分は
[メモツイート](https://twitter.com/sketch_namerou/status/1201360686237904896?s=20)
ここにCvTypeについてとcameraの設定をorthographicにする理由をメモしています。
#蛇足
 以上でOpenCVForUnityを使用した画像検出の大体の流れは解説しましたがここで少し私がそもそも検出器ってどうやって作ってるのかについて調べたメモを供養しておきます。気になる方はどうぞ
## 物体検出
[参考スライド](https://www.slideshare.net/takmin/20141008-40019861)   
大量の学習画像から特徴を抽出し学習する。そのあと認識させたい画像から特徴を抽出し学習データと照らし合わせる。   
学習画像ー＞特徴量抽出:Haar-Like   
特徴量抽出->学習:Ada Boost
### AdaBoost
弱い識別器を並べて強い識別器を作成する機械学習アルゴリズム   
[アルゴリズム](https://ja.wikipedia.org/wiki/AdaBoost)   
例）●△□->🚙   
- 顔の学習の流れ   
1. 学習用の画像（顔を含む正解と含まない不正解）を用意する
2.  各々の画像ごとの重みを一様にする
3. 以下を繰り返す   
重みの合計が一になるように正規化   
各々の識別器に画像の判定を行わせて学習画像毎の重みをもとにエラー率を算出する  
エラー率が最も小さい識別器を選択する   
その識別器が間違った画像の重みを重くしていく。
4. 選択した識別器の重み付き和を最終的な強識別器にする。


### Haar-Like特徴
[詳細和訳](http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_objdetect/py_face_detection/py_face_detection.html#face-detection)
1. Edge
2. Line
3. Center-surround

の三つ。特徴量とは白領域の画素数-黒領域の画素数。   
#### 高速化
HaarLike特徴をAdaBoostアルゴリズムで使用することで高速化を望めるが，それ以上に高速化をするためにいくつかのグループに特徴を分けて１グループごとに画像を解析する。これをすることで1グループ目に人であることが検出されなかった場合そこの領域で検出を行う必要がなくなり必要最低限の領域の解析が可能となる。(Cascade-Classifer)
##### コラム
convolution->積和演算，二つの関数を重ね合わせた時の領域の和を足していく積分
カーネル->上の積の係数の3*3マトリックス
#  終わりに
今回私も初めてこの技術を触っての記事だったので拙い点が多々あるかもしれませんが参考になれば幸いです。
[サンプルのコード](https://github.com/EnoxSoftware/OpenCVForUnity/blob/master/Assets/OpenCVForUnity/Examples/MainModules/objdetect/FaceDetectionExample/FaceDetectionWebCamTextureExample.cs)です，私のコードもここから不必要なものを除いただけですがわからなかったらこちらも参考にしてみてください。