icrawlerを使って機械学習用にごちうさの画像を手軽に大量に集める

# 概要

以前、 [Keras(Tensorflow)の学習済みモデルのFine-tuningで少ない画像からごちうさのキャラクターを分類する分類モデルを作成する](https://qiita.com/kazuki_hayakawa/items/c93a21313ccbd235b82b) という記事を書いたのですが、このときの教師データとなるごちうさの画像は手動で集めていました。
好きな作品なので画像収集作業自体はとても捗るのですが、より大量の画像が必要になってくると、さすがに時間がかかりすぎてしまうという欠点がありました。

今回はPythonのライブラリ `icrawler` を用いて効率よくごちうさの画像を集めてみようという記事です。

# 実装

## ディレクトリ構成

画像の保存時には以下の要件を満たすようにします。

- Keras(Tensorflow)での分類を意識して、分類するクラスごとに画像のディレクトリを分けたい
- トレーニング用、バリデーション用で画像ファイルを分割して格納したい
- train, validation というディレクトリ配下に分類クラス名のついたディレクトリを置き、その中に教師データとなる画像を格納する

これを満たすディレクトリ構成は以下のようになります。

```
dataset
├ image_crawler.py (今回実装したクローラー)
├ tmp (画像を一時的に保管する)
├ train
│ └ gochiusa
│   (分類クラス設計はお好みで）
└ validation
　 └ gochiusa
　   (分類クラス設計はお好みで）
```

## クローラーの実装

実装されたコードのクローラーの関数部分は以下になります。

```python 
import os
import shutil
import glob
import random
import numpy as np
from icrawler.builtin import GoogleImageCrawler

def imagecrawl(searchwords, imagenum, title):
    """ 作品の画像を収集して保存する。
    収集した画像の80%をtrainに、20%をvalidationに入れる。

    Parameters
    ----------
    searchwords: 検索クエリ. str型.
    imagenum: 画像の枚数。int型.
    title: 作品タイトル。train/validation 配下のディレクトリを指定。str型.
    """
    # ディレクトリのチェック
    train_dir = './train/' + title + '/'
    dircheck(train_dir)
    valid_dir = './validation/' + title + '/'
    dircheck(valid_dir)

    max_idx = max_file_idx(train_dir, valid_dir)

    # クローラで画像のダウンロード
    crawler = GoogleImageCrawler(storage={"root_dir": "tmp"})
    crawler.crawl(
        keyword=searchwords,
        max_num=imagenum,
        file_idx_offset=max_idx
        )

    # ダウンロードしたファイルをtrain, validationに分割し移動
    image_list = glob.glob('./tmp/*')
    random.shuffle(image_list)
    train_list, valid_list = np.split(
                            np.array(image_list),
                            [int(len(image_list)*0.8)]
                            )
    train_list = list(train_list)
    valid_list = list(valid_list)

    for i in train_list:
        shutil.move(i, train_dir)
    for i in valid_list:
        shutil.move(i, valid_dir)


def dircheck(dirpath):
    """ そのディレクトリがあるかチェックして無ければ作成する """
    if not os.path.isdir(dirpath):
        os.mkdir(dirpath)


def max_file_idx(train_dir, valid_dir):
    """ offset設定のためファイルのインデックスの最大値を求める。 """
    max_idx = 0
    image_files = os.listdir(train_dir)
    image_files.extend(os.listdir(valid_dir))
    for filename in image_files:
        try:
            idx = int(os.path.splitext(filename)[0])
        except ValueError:
            continue
        if idx > max_idx:
            max_idx = idx
    return max_idx
```

## 実際に使ってみた

使うときのコードはこちら。

```python 
def main():
    image_num = 120
    imagecrawl('ご注文はうさぎですか？', imagenum=image_num, title='gochiusa')
    imagecrawl('ご注文はうさぎですか？ 漫画', imagenum=image_num, title='gochiusa')


if __name__ == '__main__':
    main()
```

実行結果はこんな感じです。関数さえ作ってしまえば検索クエリと枚数、ディレクトリ名を書くだけでトレーニングデータとバリデーションデータを8:2で分けて収集してくれるので便利です。

<img width="1137" alt="スクリーンショット 2018-07-27 13.50.09.png" src="https://qiita-image-store.s3.amazonaws.com/0/175708/306f4a7e-a6f2-3fec-3db0-13d62c9fc802.png">

冒頭で紹介した記事ではFine-Tuningという、少ない教師データでかつ高速でそれなりに精度の出るDeep Learning手法を紹介しているのですが、今回のこのクローラーと組み合わせれば更に簡単にDeep Learningができるようになると思います。

ぜひ皆さんも手軽にDeep Learningしてみましょう！
この記事がお役に立てれば幸いです。


# 参考文献

icrawlerの使い方について以下記事を参考にさせていただきました。
[機械学習用の画像を集めるのにicrawlerが便利だった](https://qiita.com/tkt989/items/84c6581dfa1d9a42dc2d)