Python
機械学習
Chainer
TensorFlow

乃木坂46・欅坂46と学ぶ機械学習 その2 画像編

More than 1 year has passed since last update.

どうも懲りずに2回目を投稿させていただきます笑
乃木坂46・欅坂46を題材としたAIを作っていきたいと思います。
前回を読まれていない方は読んでいただけると嬉しいです!
乃木坂46・欅坂46と学ぶ機械学習 その1 導入編
その1で自分のPCにPython,TensorFlow,Chainerを導入したので今回は機械学習を進めるために必要なデータセットを収集していきたいと思います。

5.必要なメンバーの画像を集める

まずはデータを集めないことには始まらないのでデータ(画像)を収集していきたいと思います。
画像クローラーに関してネットの色々な記事を読み漁るとGoogleのAPIは無料利用だと写真に関しては100枚/dayまでしか集められない?そうなので少し枚数に不安が残ります、、、
Bing Search APIなら結構な枚数を集められそうなのでこれで行きます
機械学習に用いるデータセット作成のための画像収集Pythonスクリプト
Bingの画像検索APIを使って画像を大量に収集する
この記事を参考にさせていただきました。
クレカの登録が必須だったのですが無料枠ですので実質タダで画像が集められました。
乃木坂のメンバーは46人、漢字欅は21人ひらがなが12人(ねるの兼任の話は別として)合計すると78人も、、、笑
これでは画像を収集したとしても学習にすごく時間がかかりそうだし結果が見にくくなりそうなのでまずは少しメンバーを減らして完成を目指し、最後に全メンバーの画像を用意して乃木欅けやき全坂メンバー用の判別AIを完成させるという感じで進めていきます。
ということで今回の栄えあるAI選抜()に選ばれたのは「偶然を言い訳にして」のメンバーです笑
4ad663ab0d4691790d339189ea69aada.jpg
まずはこの3人をTensorFlow,Chainerに投げて学習させていきたいと思います。(緑色の服を着たななみん,橋本奈々未さんは卒業してしまったので今回は除外させていただきます)(青色の服=高山一実さん、赤色=白石麻衣さん、黄色=松村沙友理さん)
以下がBingのAPIを使い画像をクローラーするコードです

Img_Crawler.py
import os
import requests
import http.client
import json
import re
import math
import pickle
import urllib
import shutil

def make_dir(path):
    if not os.path.isdir(path):
        os.mkdir(path)

def make_img_path(save_dir_path, i, N, num_imgs_per_transaction):
    #パスと拡張子に分割(例;http://~~/img1.txt=>http://~~/img+.txt)
    file_extenxion = os.path.splitext(url)[-1]
    #lower小文字に変換
    if file_extenxion.lower() in ('.jpg', '.jpeg', '.gif', '.png', '.bmp'):
        full_path = os.path.join(save_dir_path, str(i+N*num_imgs_per_transaction) + file_extenxion.lower())
        return full_path
    else:
        raise ValueError('Not applicable file extension')

def download_image(url, timeout=10):
    response = requests.get(url, allow_redirects=True, timeout=timeout)
    #200は接続成功のHTTPステータスコード
    if response.status_code != 200:
        error = Exception("HTTP status:"+responce.status_code)
        raise error

    content_type = response.headers["content-type"]
    if 'image' not in content_type:
        error = Exception("Content-Type"+content_type)
        raise error
    return response.content

def save_img(filename, image):
    with open(filename, "wb") as fout:
        fout.write(image)

if __name__=="__main__":
    """
    要変更!!!!!!!!!!!!!!
    """

    name_list = ['白石麻衣', '高山一実', '松村沙友理']
    save_dir_path_list = ['shiraishi_mai', 'takayama_kazumi', 'matsumura_sayuri']

    for (name, save_dir_path) in zip(name_list, save_dir_path_list):
        make_dir("Member/" + save_dir_path)

        num_imgs_required = 50
        num_imgs_per_transaction = 50
        offset_count = math.floor(num_imgs_required/num_imgs_per_transaction)

        url_list = []

        headers = {
            'Content-Type': 'multipart/form-date',
            'Ocp-Apim-Subscription-Key': 'My_API_Key',
        }
        for offset in range(offset_count):
            params = urllib.parse.urlencode({
                'q': name,
                'mkt': 'ja-JP',
                'count': num_imgs_per_transaction,
                'offset': offset * num_imgs_per_transaction #検索トップから何件後からの画像をDLするか
            })
            try:
                conn = http.client.HTTPSConnection('api.cognitive.microsoft.com')
                conn.request("POST", "/bing/v5.0/images/search?%s" % params, "{body}", headers)
                response = conn.getresponse()
                data = response.read()

                save_res_path = os.path.join("Member", save_dir_path, 'pickle_files')
                make_dir(save_res_path)
                with open(os.path.join(save_res_path, '{}.pickle'.format(offset)), mode='wb') as f:
                    pickle.dump(data, f)

                conn.close()

            except Exception as err:
                print("[Errno {0}] {1}".format(err.errno, err.strerror))

            else:
                decode_res = data.decode('utf-8')
                data = json.loads(decode_res)

                pattern = r"&r=(http.+)&p="

                for values in data['value']:
                    unquoted_url = urllib.parse.unquote(values['contentUrl'])
                    img_url = re.search(pattern, unquoted_url)
                    if img_url:
                        url_list.append(img_url.group(1))
            for i, url in enumerate(url_list):
                try:
                    img_path = make_img_path("Member/"+save_dir_path, i, offset, num_imgs_per_transaction)
                    image = download_image(url)
                    save_img(img_path, image)
                    print('save image ... {}'.format(url))
                except KeyboardInterrupt:
                    break
                except Exception as err:
                    print("%s" % (err))
        shutil.rmtree(save_res_path)

{bytes-like object is required, not 'str'}
とエラーを何度も吐かれてしまいました、理由は画像を保存する時に渡すURLがバイナリー型でないといけない?ということで結構悩まされてしまいました><笑
実際にこれを用いて画像を集めると以下のようになります。スクリーンショット 2017-09-17 3.00.45.png
いまのところのファイル構成はこのような感じです

├── Img_Crawler.py
├── Member
    └── shiraishi_mai
        ├── 0.jpg
        ├── 1.jpg
        ・
        ・
    └── takayama_kazumi
    ・
・

7.集まった画像から顔のみを切り取る

上のコードで画像は収集できたので次はTensorFlow,Chainerに学習させるために顔のみを切り出していきます。
ここで自力でカットなどをすると日が暮れてしまうのPython+OpenCVを用いて画像から自動で顔を切り出していきます。
ディープラーニングでザッカーバーグの顔を識別するAIを作る①(学習データ準備編)
この記事を参考にさせていただきました、ありがとうございます。

face_recognize.py
import os
import cv2
import numpy as np


cascade_path = "/usr/local/Cellar/opencv/3.3.0_3/share/OpenCV/haarcascades/haarcascade_frontalface_default.xml"
faceCascade = cv2.CascadeClassifier(cascade_path)

mem_list = ['shiraishi_mai', 'takayama_kazumi', 'matsumura_sayuri']
for n in mem_list:
    member = os.path.join("Member", n)
    img_files = os.listdir(member)
    save_path = os.path.join(member, "cutted")
    os.mkdir(save_path)
    face_recognize_num = 0
    for f in img_files:
        root, ext = os.path.splitext(f)
        if ext == '.jpg' or ext == '.jpeg':
            path = os.path.join(member, f)
            img = cv2.imread(path, cv2.IMREAD_COLOR)
            gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
            face = faceCascade.detectMultiScale(gray, 1.11, 3)
            if len(face) > 0:
                for (x,y,w,h) in face:
                    dst = img[y:y+h, x:x+w]
                    resize_img = cv2.resize(dst, (64,64))
                    save_res_path = os.path.join(save_path) + '/' + str(face_recognize_num) + '.jpg'
                    cv2.imwrite(save_res_path, resize_img)
                    print(save_res_path)
                    face_recognize_num += 1

流れとしては「ディレクトリの下のリストを取ってきてその中の要素1つずつについて拡張子がjpgなどなら切り出しを行いcuttedの中に入れていく」という感じになってます。
この時点で学習させやすいように顔の画像サイズを64*64にリサイズしてあります。

スクリーンショット 2017-09-18 17.43.48.png
作業後はこのような感じに切り出しが行われますが、誤認識されたものが混入してしまっているので手作業で除去していきます。(結構疲れるのでMTG中などの暇な時にポチポチしてましたw、正直ここが一番疲れますが乃木坂のライブ映像を見ながら乗り越えましょう笑)

├── Img_Crawler.py
├── face_recognize.py
├── Member
    └── shiraishi_mai
        ├── 0.jpg
        ├── 1.jpg
        ・
        ・
        ├── cutted
            ├── 0.jpg
            ├── 1.jpg
             ・
                    ・
    └── takayama_kazumi
    ・
・

このような感じのファイル構成になっています。

8.画像の水増しを行う(重要)

機械学習などで画像を扱う際は画像を拡大縮小反転コントラスト調整などを行うことでモデルの汎化性能が向上すると一般的に言われています。そのためこの章で画像の水増しについてを書きたいと思ったのですが一度画像の水増しなしに学習を行った後に水増しをしたものと比較を行いたいと思ったので今回は割愛させていただきます。(また後で書き加えたいと思います)

最後に

今回も最後に乃木坂欅坂について語っていきたいと思いますw
今回はライブについて語っていきます。
自分は「何度目の青空か」新規でして今まで参戦したライブは乃木坂はバスラ3・4・5th,全ツ神宮その他,アンダラAiiA・武道館2015・2016・東京体育館,etcで欅はバスラ以外の関東は全て参戦しています。(欅のバスラはマネパを所持していないため抽選外しましたw)
この記事を読んでいただけた方は乃木坂・欅坂・アイドルのライブに行かれたことはありますか?アイドルのライブは敷居が高いと思われがちですが行ってみるとかなり楽しいです!!
ネットで検索をかけると雰囲気がわかると思いますのでコーディングのお供にぜひ!!個人的にライブでのオススメ曲は「ロマンスのスタート・ハウス・ダンケシェーン・転がった鐘をならせ」などでしょうか(どれも定番のアンコール曲ばっかになってしまいました笑)
今までに行ったライブで1位2位を争うのはアンダラ武道館2015とバスラ5thのダブアンのガルルでしょうか?アンダラ武道館はアンダーの目標であった武道館でしかもらりんの卒業発表に元アンダーメンバのサプライズ登場といろいろ胸が熱くなりました、5thバスラのダブアンではななみんの卒業を跳ね飛ばしてくれるほど元気なガールズルール乃木坂のどのライブよりも一番アツいと保証できるくらいのガルルだったのではないでしょうか笑、正直ガルルであそこまで揃ったまいやんコールはバスラ5thの時だと思います。
東京ドームの1次応募は外れてしまいました><。2次応募で当たることを祈っています。。。。
正直最近の乃木坂はもうチケットを入手するのが難しいのが現状ですが希望を持って待ちたいと思います。
ここが一番長くなってしまいそうなので今回はここまでにしておきます笑、次回からはTensorFlow,Chainerを使って実装をしていきます。次回は選抜発表についても少し書きたいなと思っています笑
乃木坂欅坂のことでもプログラムのことでもどっちでもいいのでコメント待ってます笑