[番外編]機械学習プログラミング 日向坂46で顔認識してみた。@Python (1)

(番外編)日向坂46で顔認識

 最近流行のAI(機械学習)でプログラムを書いてみました。
 といいますか、仕事で顔認識処理を開発することになったので、事前学習ということで休暇中に作成してみたものです。

 尚、この一連の記事ではOracleは全く登場しません。

 いろいろ調べてみたところ、下記の記事が人気のようでしたので、これらの記事を参考に日向坂46メンバーの顔認識処理を作成してみることにしました。


TensorFlowによるももクロメンバー顔認識(前編・中編・後編)

 前編:https://qiita.com/kenmaz/items/4b60ea00b159b3e00100
 中編:https://qiita.com/kenmaz/items/ef0a1308582fe0fc5ea1
 後編:https://qiita.com/kenmaz/items/8a9c382af3f8397710e9

機械学習で乃木坂46を顏分類してみた

機械学習で乃木坂46メンバーの誰に似ているかを判定する簡易Webアプリケーションを作った一連の流れ

https://qiita.com/yu8muraka3/items/81360c48a9c067c2ccdb


何故「日向坂46で顔認識」なのか

前述のサイトを眺めながら、「ももクロ」、「乃木坂46」ときたので、私は当初「欅坂46」で作成しようと思っていました。
そんな折、ふと訪れたココイチ日向坂46のキャンペーンをやっておりました(尚、このキャンペーンは2019年12月26日で終了しております)
私は特にアイドル好きでもない普通のオッサンなので、よくありがちな「誰が誰だかさっぱりわからない・・・」という状況に陥りました。
ここまでであればさしたる問題ではなかったのですが、事態はより深刻でした。。。

・・・全員同じ顔に見えてしまう。

いくらオッサンとはいえ、これではヤバい!と思い、日向坂46をAIで顔画像認識してみることにしました。

顔画像認識処理の流れ

画像の収集からモデルの作成、認識用の画面作成まで以下の流れで書いてみたいと思います。

  1. 画像ダウンロード処理
  2. 顔検知抽出処理
  3. データ増幅処理
  4. 学習・モデル生成処理
  5. 顔認識処理+画面

機械学習を行うにあたっては、まず大量の学習データが必要になります。
今回でしたら日向坂46メンバーの顔画像が必要になります。
Pythonを使用し、インターネット上から画像を収集するところから始まります。

収集した画像は、そのままでは学習データとしては使用できません。
今回は顔認識を行うのが目的ですが、収集した画像は必ずしも顔画像とは限りません。
そこで、2.の処理で収集した画像ファイルから顔検知を行い、顔だけを抽出した画像を作成します。

機械学習は学習データが多いに越したことはありません。そこで学習データを擬似的に増やします。これが3.の記事になります。
ここまで、前半はほぼスクレイピングと画像の加工です。
ある意味、AIにおけるデータ準備の重要性を表しているとも言えるかもしれません。

終盤の「4. 学習・モデル生成処理」まで来ると、ようやくAIらしくなってきます。
ここでようやく収集・加工した画像を学習させます。
そして最後の「5.顔認識処理」にたどりつきます。

前提条件

Pythonのバージョンは3.6.9で動作確認を行っています。
OSはUbuntu18.04。
Pythonのモジュールのバージョンは以下のとおりです。
特に、kerasとnumpyはバージョンの依存性があるようで、numpy1.16.3以降では正常に動作しませんでした。
beautifulsoup4 (4.8.2)
html5lib (1.0.1)
Keras (2.3.1)
Keras-Applications (1.0.8)
Keras-Preprocessing (1.1.0)
lxml (4.4.2)
matplotlib (3.1.2)
numpy (1.16.2)
opencv-python (4.1.2.30)
pandas (0.25.3)
requests (2.18.4)
scipy (1.4.1)
tensorboard (1.14.0)
tensorflow (1.14.0)
tensorflow-estimator (1.14.0)
tensorflow-gpu (1.14.0)
urllib3 (1.22)

GPUはなくても大丈夫なはずです。

それでは早速、画像収集のためのスクレイピング処理を記述してみましょう。

1.画像ダウンロード処理

まずモデルを作成するための画像データの収集です。
こちらは、下記の記事を参考にしました。

<参考サイト> PythonでGoogleの画像を大量スクレープする
https://qiita.com/Jixjia/items/881c03c50c6f07b0b6ab

Googleの画像検索結果から画像のURLを取得し、オリジナルサイトの画像を直接取得します。
Googleの画像取得API(「Google Custom Search API」)を使用する必要はありませんし、検索回数の制限もありません。
1回の検索結果で出力されたURL情報を取得しているだけなので、Googleへの負荷もそれほど大きくはないはず(とはいえ、限度があると思いますので、良識の範囲内で使用しましょう)

ダウンロードのみのプログラムを独立させているのは、ここでダウンロードした画像を目視で確認する必要があるからです。
Google検索で上位になった画像が、必ずしも当人の画像とは限りません。また、画像の形式やサイズが、このあとの処理で使用するには不適切かもしれませんし、別サイトの同一画像が複数枚検出されているかもしれません。
そういった機械学習に不都合な画像は、手動で除外する必要があります。

スクレイピング直後の画像の状態です。様々な状態の画像があるので、手動でのピックアップが必要です。
#========================================
# 日向坂46メンバーの顔認識
# 1.画像ダウンロード処理
#========================================
import os
import sys
import bs4
import urllib
import argparse
import json
import html5lib
 
# 引数解析
def arg_parse():
    parser = argparse.ArgumentParser(description='Options for scraping Google images')
    parser.add_argument('-s', '--search', default='日向坂46', type=str, help='search term')
    parser.add_argument('-n', '--num_images', default=10, type=int, help='num of images to scrape')
    parser.add_argument('-o', '--directory', default='<DEFAULT_SAVE_DIRECTORY>', type=str, help='output directory')
    return parser.parse_args()
 
# 画像のスクレイピング
# 引数1. imageinfo: 画像情報(URL, Type)
# 引数2. max_images: 取得画像数
# 引数3. save_directory: 保存先ディレクトリ名
def scraping(imageinfo, max_images, save_directory):
    for idx, (img, Type) in enumerate(imageinfo[0:max_images]):
        try:
            # 画像タイプが設定されていなければjpgとして扱う
            Type = Type if len(Type) > 0 else 'jpg'
            print("Downloading image {} ({}), type is {}".format(idx, img, Type))
            # 画像のURLに直接アクセスしてダウンロード
            raw_img = urllib.request.urlopen(img).read()
            # ファイルに保存
            f = open(os.path.join(save_directory , "img_"+str(idx)+"."+Type), 'wb')
            f.write(raw_img)
            f.close()
        except Exception as e:
            print ("could not load : "+img)
            print (e)
 
# 画像情報(オリジナルURLと画像タイプ)を取得
# 引数1. soup: 画像情報検索結果bs4オブジェクト
def file_url(soup):
    # 配列初期化
    imageinfo=[]
    for imgelem in soup.find_all("div",{"class":"rg_meta"}):
        # ou: オリジナル画像のURL
        # ity: 画像タイプ(jpg, png等)
        link , Type =json.loads(imgelem.text)["ou"]  ,json.loads(imgelem.text)["ity"]
        if ('facebook.com' not in link) and ('jpg' in Type or 'png' in Type):
            imageinfo.append((link,Type))
    return imageinfo
 
# 画像検索結果URLのスクレイピング
# 引数1. query: 検索文字列
def get_soup(query):
    url="https://www.google.co.jp/search?q="+urllib.parse.quote(query)+"&source=lnms&tbm=isch"
    header={'User-Agent':"Mozilla/5.0 (Windows NT 8.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.5357.134 Safari/597.45"}
    return bs4.BeautifulSoup(urllib.request.urlopen(urllib.request.Request(url,headers=header)),'html5lib')
 
def main():
    args = arg_parse()
 
    # 複数のキーワードを"+"で繋げる
    query = '+'.join(args.search.split())
    max_images = args.num_images
 
    # 保存先ディレクトリの下に、検索キーワードを+で連結してフォルダを作成する
    save_directory = args.directory + '/' + query
    if not os.path.exists(save_directory):
        os.makedirs(save_directory) 
 
    # スクレイピングで画像情報を取得
    soup = get_soup(query)
 
    # 画像のオリジナルURLと画像タイプを取得
    scraping(file_url(soup), max_images, save_directory)
  
if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        pass
    sys.exit()
 

起動時の引数として以下の3つを指定できるようにしています。
-s: Google画像検索に渡す検索キーワード、複数の場合は""で括る
-n: ダウンロードする画像の数。常識の範囲内で。初期値は10にしています。
-o: 画像の保存先。カレントディレクトリ以下のサブディレクトリ名を指定します。

コマンドの発行例は以下の通りです。
python3 facerecg1_imagedl.py -s “日向坂46 小坂菜緒" -n 50 -o images

プログラム的に特筆すべきポイントは、、、あまりありません。
一つ挙げると、検索文字列のエンコードには注意です。今回は恐らく日本語を使用すると思いますので。URL全体ではなく検索文字列部分のみをエンコードするのがポイントです。
下記のサイトを参考に解決しました。

URLの日本語エンコーディング(パーセントエンコーディングについて)
https://note.nkmk.me/python-urllib-parse-quote-unquote/

次回は、顔部分抽出処理~データ増幅処理を行います。

 

2020-02-06

Posted by tfurukaw