顔写真からジャニーズ系かEXILE系かを判定する学習モデルを深層学習を用いて作成してみた(~画像クローリング、顔検出編~)

プログラミング
顔写真からジャニーズ系かEXILE系かを判定する学習モデルを作成してみました。
全3回の記事の予定です。

本記事は、画像のクローリングと画像から顔を切り取る処理まで(記事 1/3)を掲載しています。

はじめに

Udemyの機械学習講座を受講し終わったので、成果物として作ってみました。
アイディアの元になったのは、こちらの記事です。
顔写真から、乃木坂46系かAKB48系か一般人系か判定する学習モデル作ってみた

この記事を見て私が似たようなことで思い浮かんだのが、ジャニーズ系とEXILE系でした。

っというのも、どちらも有名なグループですが、顔も含めて何となく雰囲気違いますよね。
(私もそんなに詳しいわけではありませんが、有名どころのグループの1つくらいは知っているレベルです。)
その微妙な違いも精度良くモデル化出来るのか?と思い、試してみました。

詳細なソースコードはGitHubに上げていますので、よろしければそちらも参照ください。

モチベーション

この判定学習モデルによって何が得られるのか?
 ・ジャニーズやEXILEを知らない人でも、顔写真から分類することが出来るようになる。
 ・ジャニーズやEXILEに詳しい人の判定結果を使うことで、より精度を上げることができるようになる。
その他にもいくつかありますが、私も炎上することが恐いので、これくらいに留めておきます。

全体の処理フロー

全体の処理フローを下図に記します。

本記事では、「画像のクローリング」と「収集画像から顔検出(切り取り)」について説明していきます。

画像のクローリング

 -代表グループの決定
ジャニーズ系かEXILE系かを分類するために、はじめに教師データとなる画像を収集します。

しかし、両方ともいっぱいグループがあるけど、どのグループを教師データとして代表させるか?

ネットの情報などから、今回は人気がありそうな3つのグループを代表とさせていただきました。

ファンの方からすればいろいろと異論等あると思いますが、あくまで画像認識の学習の一環としていることを理解していただけると助かります。

 ・「ジャニーズ系」:嵐、関ジャニ∞、Hey! say! JUMP

 ・「EXILE系」:EXILE、三代目 J SOUL BROTHERS、GENERATIONS

 -Pythonによる画像のクローリング

代表グループが決定したので、画像の収集を行います。

画像のクローリングについては、ネット上で色々情報はあったのですが、

APIによるクローリングは現在色々と厳しくなっているようで悩みました。

結果的に、こちらのサイトのソースコードをそのまま引用させていただきました。
API を叩かずに Google から画像収集をする

大変助かりました。ありがとうございます。

 

ソースコードはこちら。
ターミナルから使う場合は、例えば、”XX.py arashi 100”というように使います。

最後の引数の数字は収集する枚数です。

# -*- coding: utf-8 -*-
import json
import os
import sys
import urllib

from bs4 import BeautifulSoup
import requests

class Google:
    def __init__(self):
        self.GOOGLE_SEARCH_URL = 'https://www.google.co.jp/search'
        self.session = requests.session()
        self.session.headers.update(
            {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20100101 Firefox/10.0'})

    def search(self, keyword, maximum):
        print('begin searching', keyword)
        query = self.query_gen(keyword)
        return self.image_search(query, maximum)

    def query_gen(self, keyword):
        # search query generator
        page = 0
        while True:
            params = urllib.parse.urlencode({
                'q': keyword,
                'tbm': 'isch',
                'ijn': str(page)})

            yield self.GOOGLE_SEARCH_URL + '?' + params
            page += 1

    def image_search(self, query_gen, maximum):
        # search image
        result = []
        total = 0
        while True:
            # search
            html = self.session.get(next(query_gen)).text
            soup = BeautifulSoup(html, 'lxml')
            elements = soup.select('.rg_meta.notranslate')
            jsons = [json.loads(e.get_text()) for e in elements]
            imageURLs =  for js in jsons]

            # add search result
            if not len(imageURLs):
                print('-> no more images')
                break
            elif len(imageURLs) > maximum - total:
                result += imageURLs[:maximum - total]
                break
            else:
                result += imageURLs
                total += len(imageURLs)

        print('-> found', str(len(result)), 'images')
        return result


def main():
    google = Google()
    if len(sys.argv) != 3:
        print('invalid argment')
        print('> ./image_collector_cui.py [target name] [download number]')
        sys.exit()
    else:
        # save location
        name = sys.argv[1]
        data_dir = 'temp_image_data/'
        os.makedirs(data_dir, exist_ok=True)
        os.makedirs('temp_image_data/' + name, exist_ok=True)

        # search image
        result = google.search(
            name, maximum=int(sys.argv[2]))

        # download
        download_error = []
        for i in range(len(result)):
            print('-> downloading image', str(i + 1).zfill(4))
            try:
                urllib.request.urlretrieve(
                    result[i], data_dir + name + '/' + str(i + 1).zfill(4) + '.jpg')
            except:
                print('--> could not download image', str(i + 1).zfill(4))
                download_error.append(i + 1)
                continue

        print('complete download')
        print('├─ download', len(result)-len(download_error), 'images')
        print('└─ could not download', len(
            download_error), 'images', download_error)


if __name__ == '__main__':
    main()

 

 -収集画像のごみデータ削除

収集した画像を見てもらえるとわかりますが、関係ない画像も含まれています。

イラスト画像やロゴしか写っていなかったりと。

これらを手動で消していきます。

ここが一番めんどくさて地味な作業。。

ちなみに私はジャニーズの画像からこの作業をしているところを、奥さんに見られてしまい、「頭おかしくなった?」と言われてしまいました。

皆さんもお気を付けください(汗)

収集画像から顔検出(切り取り)

関係ない画像のスクリーニングが終わったところで、顔検出をやってみたいと思います。

さすがにこれだけのデータからPhotoshopとかを使って一枚一枚顔の切り出しはやりたくなかったので、みんながお世話になってるOpenCVを使うことにしました。

Haar-like特徴分類器を使って検出し、さらに画像のリサイズ「50 x 50 pix」を行ってから、顔部分をすべて画像データとして保存しました。
ソースコードはこちら。
# -*- coding: utf-8 -*-

import cv2, os, sys

def detect_face(img_path, cascade_path, dir, file, image_size):

    #ファイル読み込み
    image = cv2.imread(img_path)

    re_file_name = dir + "_" + file
    print("Detect face: ", re_file_name)

    #グレースケール変換
    if len(image.shape) == 3:
        height, width, channels = image.shape[:3]
    else:
        height, width = image.shape[:2]
        channels = 1

    if (channels == 3):
        image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

    #カスケード分類器の特徴量を取得する
    cascade = cv2.CascadeClassifier(cascade_path)

    facerect = cascade.detectMultiScale(image_gray, scaleFactor=1.1, minNeighbors=1, minSize=(1, 1))
    #facerect = cascade.detectMultiScale(image_gray)

    if len(facerect) > 0:
        for rect in facerect:
            dst = image[rect[1]:rect[1]+rect[3],rect[0]:rect[0]+rect[2]]
            dst = cv2.resize(dst, dsize=(image_size, image_size))
            cv2.imwrite("./face_all/" + re_file_name, dst)

def main():
    image_size = 50

    #HAAR分類器の顔検出用の特徴量
    cascade_path = "C:/Anaconda/envs/tf140/Lib/site-packages/cv2/data/haarcascade_frontalface_alt.xml"

    # オリジナル画像のディレクトリ
    org_img_path = "./temp_image_data/"
    dirs = []
    for i in os.listdir(org_img_path):
        if os.path.isdir(org_img_path + i):
            dirs.append(i)
    if len(dirs) == 0:
        print("not exist original image direcotry")
        sys.exit()

    # フォルダごとのループ処理
    for dir in dirs:
        each_dir_path = org_img_path + dir + "/"
        # 画像ファイルごとのループ処理
        for file in os.listdir(each_dir_path):
            each_img_path = each_dir_path + file
            detect_face(each_img_path, cascade_path, dir, file, image_size)

    print("Finish detect face")

if __name__ == "__main__":
    main()

 

そして、ここでもまたごみデータが発生してしまいます。

顔以外の部分を誤検出した画像を一枚一枚手動で削除していきます。

ここがめんどくさて地味な作業。。

私の場合ですと、最終的に残った顔画像は、

「ジャニーズ系」:163枚

「EXILE系」:173枚

となりました。
いよいよ次からはこれらを教師データとして深層学習を用いてモデルを作っていきます。

本記事はここまで。

参考

スポンサーリンク



コメント

タイトルとURLをコピーしました