アンジェの画像を集めて顔検出する。


Vtuberも、にじさんじも知らないという人はココにたどり着かないと思いますが、
「さんばか」を識別できるような機械学習をしてみたく、まずはアンジェの画像ダウンロード、顔検出してみました。いろいろ参考サイトはあるんですが、以下のサイトをベースにしています。

https://qiita.com/Tatsuro64/items/95b0ce48b6bb155bfe29

プログラム：pytyon3.7
環境：ubuntu18.04
使用ライブラリ：BeautifulSoup(スクレイピング)、opencv(顔検出)、urllib(画像ダウンロード)

コードのメイン処理は以下。

```python
if __name__ == '__main__':

    downloads = ImageDownloader('アンジェ・カトリーナ').go()
    for i, d in enumerate(downloads):
        FaceDetector(d).cutout_faces('image/faces/faces_{}.jpg'.format(i + 1))
```

ダウンローダでDLして、そのリストを顔検出する、これだけです。

ダウンローダは以下。

```python
class ImageDownloader(object):
    def __init__(self, keyword):
        session = requests.session()
        session.headers.update(
            {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:10.0) \
             Gecko/20100101 Firefox/10.0'})
        params = urllib.parse.urlencode(
            {'q': keyword, 'tbm': 'isch', 'ijn': '1'})
        query = "https://www.google.co.jp/search" + '?' + params
        self._bs = BeautifulSoup(session.get(query).text, 'html.parser')

    def go(self):
        downloads = []
        for img in self._bs.find_all('img'):
            try:
                url = img['data-iurl']
                downloads.append('image/image_{}.jpg'.format(len(downloads) + 1))
                urllib.request.urlretrieve(url, downloads[-1])
            except KeyError:
                print('failed to get url : {}'.format(img))
        return downloads

```
■コンストラクタ
　Googleさんにクエリーを発行して(キーワード：アンジェ)、検索結果ページ（今回は１ページ目のみ）をスクレイピングライブラリ(BeautifulSoup)にかけています。

■goメソッド
　imgタグを全検索し、その属性['data-iurl']にリンクが格納されてるので、urllibモジュールでダウンロードします。中には['data-iurl']属性を持たない、おそらく検索画像以外のタグが見つかるので、それはKeyError例外を捕らえてスルーしてます。

顔切り出し器は以下。

```python
class FaceDetector(object):

    # 学習済モデル
    FACE_CASCADE = '/home/websoler/anaconda3/lib/python3.7/site-packages/cv2/data/lbpcascade_animeface.xml'

    def __init__(self, fname):
        self._img = cv2.imread(fname)

    def cutout_faces(self, fname):
        gray = cv2.cvtColor(self._img, cv2.COLOR_BGR2GRAY)
        classifier = cv2.CascadeClassifier(FaceDetector.FACE_CASCADE)
        faces = classifier.detectMultiScale(gray, scaleFactor=1.2, minSize=(30, 30))
        if len(faces):
            for (x, y, w, h) in faces:
                region = self._img[y:y + h, x:x + w]
                region_resized = cv2.resize(region, (128, 128))
                cv2.imwrite(fname, region_resized)
                break  #TODO とりあえず最初の一件だけとする。
```
■コンストラクタ
　渡されたファイルをopencvに読み込ませてます。

■cut_facesメソッド
　顔検出したら、それを切り取り、128 x 128にリサイズしてファイル保存してます。

学習モデルはOpenCVが提供している標準モデルでなく、アニメ顔に特化したlbpcascade_animeface.xmlを使ってます。ローカルに置くとうまく動かなかったので、pythonライブラリパスに直接置いてます。うちの環境だといつのまにかanaconda入ってたのでその下に。

アニメモデルは以下からDLしました。
https://github.com/nagadomi/lbpcascade_animeface

minSizeを指定したのは、小さい領域ほど誤検出率が増加しそうだったので。
なんかアンジェの袖と指を「顔」とか言ってくるので。

次回は機械学習、TensorFlowにかけるために、TFRecords形式を用意してみようと思います。

コード全体は以下。

```python

from bs4 import BeautifulSoup
import cv2
import os
import requests
import shutil
import urllib


# 環境整備
shutil.rmtree('image')
os.mkdir('image')
os.mkdir('image/faces')


class ImageDownloader(object):
    def __init__(self, keyword):
        session = requests.session()
        session.headers.update(
            {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:10.0) \
             Gecko/20100101 Firefox/10.0'})
        params = urllib.parse.urlencode(
            {'q': keyword, 'tbm': 'isch', 'ijn': '1'})
        query = "https://www.google.co.jp/search" + '?' + params
        self._bs = BeautifulSoup(session.get(query).text, 'html.parser')

    def go(self):
        downloads = []
        for img in self._bs.find_all('img'):
            try:
                url = img['data-iurl']
                downloads.append('image/image_{}.jpg'.format(len(downloads) + 1))
                urllib.request.urlretrieve(url, downloads[-1])
            except KeyError:
                print('failed to get url : {}'.format(img))
        return downloads


class FaceDetector(object):

    # 学習済モデル
    FACE_CASCADE = '/home/websoler/anaconda3/lib/python3.7/site-packages/cv2/data/lbpcascade_animeface.xml'

    def __init__(self, fname):
        self._img = cv2.imread(fname)

    def cutout_faces(self, fname):
        gray = cv2.cvtColor(self._img, cv2.COLOR_BGR2GRAY)
        classifier = cv2.CascadeClassifier(FaceDetector.FACE_CASCADE)
        faces = classifier.detectMultiScale(gray, scaleFactor=1.2, minSize=(30, 30))
        if len(faces):
            for (x, y, w, h) in faces:
                region = self._img[y:y + h, x:x + w]
                region_resized = cv2.resize(region, (128, 128))
                cv2.imwrite(fname, region_resized)
                break  #TODO とりあえず最初の一件だけとする。

if __name__ == '__main__':

    downloads = ImageDownloader('アンジェ・カトリーナ').go()
    for i, d in enumerate(downloads):
        FaceDetector(d).cutout_faces('image/faces/faces_{}.jpg'.format(i + 1))
```