【IT-DIY】OpenCVを用いて画像から猫を検出するシステムを作ってみる

# はじめに
弊社のスキルアップの活動として、
「OpenCVを用いて機械学習を行い、画像から猫を検出するシステムを自分たちで作る」
というDIY的な事を行ったので、実装方法や結果について共有します。

# OpenCVとはなにか
OpenCVとは、画像処理や画像解析が出来たり、機械学習ができる機能を詰め込んだライブラリです。
pythonで使用されるのが一般的ですが、実はjavaやC等の言語でも使用することができるんです。

今回はOpenCVの中でも「カスケード分類器」と呼ばれる物体検出器に対して機械学習を行い、猫を検出します。

# DIY手順
1. 正解データと不正解データを収集(＆前処理)する
2. "ベクトルファイル"と呼ばれる正解データを集約したファイルを作成する
3. ベクトルファイル(正解データの塊)と不正解データを基に学習モデルであるcascade.xmlを作成する
4. cascade.xmlを使用して、画像から猫を検出する
※cascade.xml＝カスケード分類器

# 1. 正解データと不正解データを収集(＆前処理)する
今回は猫の検出を目的としているため、
:o: 正解データ＝猫
:x: 不正解データ＝猫以外
となります。

正解データはokフォルダに、不正解データはngフォルダに格納していきます。
この段階で正解データの水増しをしておきます。
水増しとは１枚の画像を加工し、複数の画像にすることです。

OpenCVには画像処理にも優れており、次のようなことが出来ます。
・画像の回転・上下反転・左右反転
・グレースケール変換・色チャンネル分解・減色処理
・モザイク処理・マスク処理・2枚の画像を合成　etc..
挙げたのは一部ですが、これらの機能を使用して水増しを行います。

# 2. "ベクトルファイル"と呼ばれる正解データを集約したファイルを作成する
ベクトルファイルを作成するためには、okフォルダに正解画像リストファイルを用意する必要があります。
poslist.txt
```
# ファイル名　対象物の数　x座標(始点)　y座標(始点)　x座標(長さ)　y座標(長さ)
# x座標(始点)　y座標(始点)　は対象物を四角で囲んだ時の左上角の座標
# x座標(長さ)　y座標(長さ)　は始点からのそれぞれの長さ

images_cat000.jpg 1 209 127 141 123
images_cat001.jpg 1 176 128 189 150
images_cat002.jpg 1 131 172 159 101
images_cat003.jpg 1 112 108 284 251
images_cat004.jpg 1 252 88 197 242
```

このようなファイルを用意する必要がありますが、１枚１枚対象物を座標指定するのは大変です。
（というか、人力では不可能だと思います...）

なので、次のような前処理をする必要があります。

- 対象物しか映っていない画像を用意する
- 対象物をトリミングした画像を用意する
- poslist.txtを簡単に生成できるツールを自作する

「対象物しか映っていない画像を用意する」「対象物をトリミングした画像を用意する」の場合は、
画像全体を座標指定すればよいので、☟のように記載します。

```
# 画像サイズが512×512の場合、次のように記載すれば画像全体を指定可能
images_cat000.jpg 1 0 0 512 512
```

ここまでの準備が完了してやっとベクトルファイルを作成できます。
ベクトルファイル作成には次のコマンドを実行します。

```
# info：正解画像リストファイル
# vec：ベクトルファイルの相対パス
# num：学習に使用する画像数＝正解画像リストファイルに記載したファイル数
opencv_createsamples.exe -info ok/poslist.txt -vec vec/ok.vec -num 49
```

# 3. ベクトルファイル(正解データの塊)と不正解データを基に学習モデルであるcascade.xmlを作成する
2.で作成したベクトルファイルと、1.で収集した不正解データを基に
下記のコマンドでcascade.xmlを作成します。
学習画像の枚数にもよりますが、長いと20分以上かかる場合がありました。

```
# data：cascade.xmlの格納パス
# vec：ベクトルファイルパス
# bg：NGファイルパス
# numPos：正解データ枚数
# numNeg：不正解データ枚数
opencv_traincascade.exe -data ./cascade/ -vec ./vec/ok.vec -bg ./ng/nglist.txt -numPos 100 -numNeg 290
```

# 4.cascade.xmlを使用して、画像から猫を検出する
3.で作成したcascade.xmlを使用して、画像から対象物（今回は猫）を赤枠で囲います。
下記のように記載することで、赤枠で囲むことが出来ます。
※コードを切り出しているので、このままでは動きません。

```
# 画像に赤枠を付ける
def mark_image(img):
    # カスケード型識別器（自作した分類器）
    cascade = cv2.CascadeClassifier("cascade.xml")

    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # 顔領域を赤色の矩形で囲む
    for (x, y, w, h) in cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=3, minSize=(30, 30)):
        cv2.rectangle(img, (x, y), (x + w, y+h), (0,0,200), 3)

    # 結果画像を保存
    return img
```

# 結果
画像の水増しや背景除去した画像で学習させた場合には、猫を認識せず、
2.で作成するposlist.txtで、猫の顔を正しく座標指定した場合にも☟の画像の通り、全くダメなわけではありませんでしたが、精度が高いとは到底言えない結果でした。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2950891/3827a011-23d2-7ace-fee6-9d6d51f67bc3.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2950891/f4d1eea2-fd63-51b4-bc5d-ec88348d296a.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2950891/3322d427-ae79-e2fd-7b86-f4caffee27f3.png)

# 改善案
改善点として、学習する画像の枚数が不足していると思いました。
正解データとして100～200枚程度画像を用意し、水増しして学習しましたが高精度のカスケード分類器を作成した方は正解データとして7000枚の画像を用意していたそうです。

必要な正解データの枚数を調べてみると色々な情報がありますが、最低でも1000枚は必要との事でした。
（水増しして1000枚以上にしていましたが、精度は向上せず）

しかし1000枚の異なる猫の画像を用意するのも大変ですが、精度を上げるためには、背景除去を行ったり、顔の座標指定も必要となるので時間と根気が必要だと感じました。。。