画像を認識して切り取る_カスケード分類器作成

こんにちは。

今回はOpenCVを利用してチルノちゃんを切り出す作業をご紹介しようと思います。

# 動機
チルノが可愛すぎて・・・もう・・・
ﾄﾞｩﾌﾌ

# 注意事項
プログラミング初心者でバカです。
何かおかしいところ等あれば逐次教えていただければこれ幸い。
また、今回Windows10を利用しています。

### お品書き

- opencv_createsamplesを利用してOpenCVたんが認識できるバイナリファイルを作成
- opencv_traincascadeを利用してカスケード分類器を生成
- OpenCVでチルノ認識

# 実作業
それではいってみよう。
基本的には上述のお品書きメインで説明していきます。

## 1．チルノ画像（ポジティブ画像）の収集&不正解画像（ネガティブ画像）の収集
まずは画像収集なんだけど、これはざっくりで。細かいとこ割愛。
私はGoogleで画像検索して、拡張機能の「Page Expand」利用してダウンロードした。
大体1時間くらいだったと思う。
前はAzureのサービスを利用したクローラ利用していたんですが、そんなに関連画像がないし、制限あるしで手動のほうが楽だった。
まぁこれはスキル不足ということなんすけどね。
ポジティブ（以降pos）ネガティブ（以降neg）共に1000枚くらいあればbetterだと思います。
知らんけど。

## 2．チルノだけを切り出す
さて、ダウンロードしてきたチルノ画像を認識するために、
まずはサンプルとして手動でチルノを切り出さなければならない。
これはどうしても手動、もしくは半自動になると思います。
[Image Clipper](https://www.pro-s.co.jp/engineerblog/opencv/post_6397.html)を使える人は使うのが便利なのではないかな。
俺はもう英語カスカスなのでとりあえずWinShotで切り抜いた。
労力は別にそんな変わらん。（と思いたい）

ついでに、後々めんどくさくなったのでこのタイミングで画像のサイズを調整したほうが良いです。
私は結局すべての画像サイズを統一しました。
一応参考までに。

```python:resize.py
# coding: utf-8

from PIL import Image
import os

def resizeimage(outdir):
    for dirpath, dirname, filenames in os.walk(outdir):
        for filename in filenames:
            filefullpath = os.path.join(dirpath, filename)
            image = Image.open(filefullpath)
            if image is not None:
                #200x200にリサイズ
                resizeimg = image.resize((200, 200))
                #リサイズした画像の保存先
                resizeimg.save("C:\\TensorFlow\\cascade\\pos\\" + filename)
# 元画像のディレクトリを渡してあげる
resizeimage("C:\\TensorFlow\\cascade\\pos")
```
　
Windowsで利用する際は￥マークを2つつけることに注意です。
ここまで行うと、以下のような大きさのファイルができます。

![WS000001.JPG](https://qiita-image-store.s3.amazonaws.com/0/146681/7f9d87b3-9373-cae5-1624-f99f0aed0e6f.jpeg)


## 3．opencv_createsamplesを利用してバイナリファイル作成
これがきつかった。1日くらいはこれにとられた。
そもそもマニュアルがわけわからん。英語だし。

### 必要ファイルの配置
まず、OpenCVを[ダウンロード](https://github.com/opencv/opencv/releases)します。
私は3系じゃできなかったから2.4系にしました。
ダウンロードしてきたファイルを、[こちら](https://www.pro-s.co.jp/engineerblog/opencv/post_6202.html)のサイト様を参考にさせていただき、
DLLファイル等を作業ディレクトリに配置していきます。
これはエラーが出るかもしれませんが、
不足しているDLLファイルも併せて配置することで何とかなりました。

### 正解画像のリストファイル作成
今回はopencv_createsamplesに読み込ませる画像が100枚
1枚だけで分類器作るのであればリストファイルはいらなんですが、
精度がうんちなのでリスト作成して大量の画像を読み込ませます。
記述は以下の通り。

```text:poslist.txt
C:\TensorFlow\cascade\pos\WS000000.JPG 1 0 0 200 200
C:\TensorFlow\cascade\pos\WS000001.JPG 1 0 0 200 200
C:\TensorFlow\cascade\pos\WS000002.JPG 1 0 0 200 200
```
※可愛い
![aaa.png](https://qiita-image-store.s3.amazonaws.com/0/146681/3b9c086a-277b-30d9-6ce1-95359f49a512.png)

　
これは、[画像ファイル] [対象物体数] [X座標] [Y座標] [幅] [高さ]
の順番で記載しています。スペース区切りです。

これほんと意味わからなかった。
指定したxとyから幅高さ指定して対象の位置を指定しているのだと思う。(知ってる人いたら教えて。)
画像のサイズを統一しておかないと、ここでめんどくさいことになります。
なのでresize.pyで画像の大きさを固定しておきました。
こうすればすべて同じパラメータでおｋ。
xとyは0で大丈夫だった。多分開始位置の指定だから切り取ってれば問題ないはずです。

因みに、dirコマンド利用すればフルパスは一瞬で取得できる。
`dir [パス] /s /b > list.txt`
数字は秀丸とかで縦列指定して丸ごとコピペしました。
スクリプト組むより全然早い。

### opencv_createsamplesの実行
以下をcmdで実行
`.\opencv_createsamples.exe -info poslist.txt -vec .\vec\Cirno.vec -num 115 -h 48 -w 48 -show`
[サンプル]
`.\opencv_createsamples.exe -info ポジティブリスト -vec ベクトルファイルの出力先 -num pos画像データ数 -h 出力高さ -w 出力幅 -show`

引数なしで実行するとhelpが出ます。

```
Usage: opencv_createsamples.exe
  [-info <collection_file_name>]（作成したリストファイルを指定）
  [-img <image_file_name>]（1つのサンプルファイルだけの場合は-infoではなく-imgを指定）
  [-vec <vec_file_name>](作成されるvectorファイル)
  [-bg <background_file_name>](背景に利用する画像)
  [-num <number_of_samples = 1000>](pos画像の枚数)
  [-bgcolor <background_color = 0>](背景の色)
  [-inv] [-randinv] [-bgthresh <background_color_threshold = 80>]
  [-maxidev <max_intensity_deviation = 40>]
  [-maxxangle <max_x_rotation_angle = 1.100000>]（x軸にどの程度曲げるか）
  [-maxyangle <max_y_rotation_angle = 1.100000>]（y軸にどの程度曲げるか）
  [-maxzangle <max_z_rotation_angle = 0.500000>]（z軸にどの程度曲げるか）
  [-show [<scale = 4.000000>]](画像を1枚ずつ確認する)
  [-w <sample_width = 24>](作成する画像の幅)
  [-h <sample_height = 24>](作成する画像の高さ)
  [-pngoutput]
```
-wと-hは気を付けてください。
大きすぎるとtrainingする際にoom-errorになってしまう事があります。
ここはエラーがクソほど出る。
マニュアルもわかりづらい。
stackoverflowに同じようにエラー出まくってる人がうじゃうじゃいるので
そちらを見たほうが吉です。

まぁトライ&エラーしかないっす。
もしくはコメント欄に記載してくれれば私が出会ったエラーは教えられるかも。。。

今回はとりあえずこんなものとさせてもらいます。
次回があればまた・・・