機械学習で女性のタイプ判定処理を行う
こんにちは、江口です 。
前回に引き続き、pythonネタです。
皆さんは、同僚との会話で「この女優、どう?」と聞かれて困ったことはありませんか?
私はあります。そして、良いとも悪いとも思えず、なんと言いますか、興味が湧かない時が一番返答に困ります。
今回はズバリ「機械学習で好みの女性なのかどうか仕分ける!」です。
ネタではありますが、真剣です。これによりデータに基づいた返答ができるようになります。
▼概要
1. 自分好みの女優の画像とそうでない画像を収集する。
2. 収集した画像から顔だけを抽出し、100×100にリサイズして保存する。
3. SVM分類器にかけて、機械学習を行い、学習結果をpickleに漬け込む
4. 女優画像が自分の好みかどうか判定する
こんな流れになってます。
詳しい流れ、実装は後述するソース解説で紹介します。
▼使用技術とその用途
・python 3.6
・scikit-learn -> 機械学習ライブラリ
・GoogleCustomSearch API -> 画像取得に使用
・BeautifulSoup -> スクレイピングに使用
・SVM分類器 -> 実際に機械学習するアルゴリズム
・OpenCV -> 画像から顔・輪郭抽出するのに使用
・joblib->pickleファイル漬け込み
▼1. 自分好みの女優の画像とそうでない画像の収集
収集はsave_beautiful.pyで行います。
# -*- coding: utf-8 -*-
import requests
import re
import os
from PIL import Image as resizer
API_URL = "https://www.googleapis.com/customsearch/v1?key=[APIKEY] &cx=[ENGINE] &q=%s &searchType=image &start=%s"
def getImageFromCustomAPI(start, end, word, kind):
for i in range(start, end, 10):
print(API_URL % (word, str(i)))
response = requests.get(API_URL % (word + ",正面", str(i)))
for j in range(len(response.json()["items"])):
json = response.json()["items"][j]
res = requests.get(json["link"], stream=True)
if res.status_code == 200:
with open("images/target_%s_%s.png" % (kind, (str(i) + "_" + str(j))), 'wb') as file:
for chunk in res.iter_content(chunk_size=1024):
file.write(chunk)
def resizeImages(dirname, regular):
"""
images下の画像を2次元の特徴ベクトルに変換する為に100x100リサイズを行う
"""
for image in os.listdir(dirname):
if re.match(regular, image):
print(image)
img = resizer.open("images/" + image, 'r')
img = img.resize((100, 100))
img.save("images/" + image, 'png', quality=100, optimize=True)
print("resized")
def main():
# 「人名,正面」にすると、より多くの正面画像を拾える
words = ['北川景子', '吉高由里子', '新垣結衣', '榮倉奈々', '安室奈美恵', '長澤まさみ', '西内まりや', '麻生久美子', '倉科カナ', '井上真央', '石原さとみ', 'ガッキー', '堀北真希']
# words = ['フィーフィー', '安藤なつ', '澤穂希', '白鳥久美子', '光浦靖子', 'ブルゾンちえみ', 'おかずクラブオカリナ']
for word in words:
getImageFromCustomAPI(1, 92, word, "YES-" + word + "_")
resizeImages("images", "target_")
if __name__ == '__main__':
main()
解説・・・
画像の収集にはGoogleCustomSearchを利用しています。
qパラメータに自動で「,正面」という文字列を組み立て、searchインデックス(startパラメータ)に1~91までを10区切りで指定しています。
つまり、
https://www.googleapis.com/customsearch/v1?key=[APIKEY] &cx=[ENGINE] &q=[新垣結衣] &searchType=image &start=1
https://www.googleapis.com/customsearch/v1?key=[APIKEY] &cx=[ENGINE] &q=[新垣結衣] &searchType=image &start=11
https://www.googleapis.com/customsearch/v1?key=[APIKEY] &cx=[ENGINE] &q=[新垣結衣] &searchType=image &start=21
https://www.googleapis.com/customsearch/v1?key=[APIKEY] &cx=[ENGINE] &q=[新垣結衣] &searchType=image &start=31
https://www.googleapis.com/customsearch/v1?key=[APIKEY] &cx=[ENGINE] &q=[新垣結衣] &searchType=image &start=91
このような具合で順繰りにリクエストを発行していきます。
一回にリクエストで10枚、一人の女優につき1~91でつまり100枚近くの画像を収集しています。
今回は無料枠で利用しているため、以下のような制約があります。
・100req/day(17:00にリセットされる)
・一回に取得できるデータ量は10count
取得した画像は残さずimages/下に
target_YES-[女優名]__81_1.png
の形で保存しています。YESがタイプで、NOがタイプでないことを表すようにしています。
また、なるべく正面を向いている画像を取得しようと試みています。
最後に100×100にリサイズして改めて保存しています。
こんな感じで、私のタイプの女優画像が蓄積されていきます!
▼2.収集した画像から顔だけを抽出して保存
好みの女優の系統を判定したいので、背景や服のような副次的情報は不要ですし、むしろ判断を難しくさせますので、純粋に顔だけを抽出しにいきます!
・pickup-faces.py
# -*- coding: utf-8 -*-
import os
import cv2
def main():
"""
images下の美人画像一覧を読み出し、faces下に顔画像検出した結果を保存する
"""
for image_path, _, files in os.walk('images'):
if len(_):
continue
face_path = image_path.replace('images', 'faces')
if not os.path.exists(face_path): os.makedirs(face_path)
for filename in files:
if not filename.startswith('.'):
save_faces(image_path, face_path, filename)
def save_faces(image_path, face_path, filename):
"""
真正面顔判定用のOpenCVファイルを使って、顔画像を切り出す
"""
print(image_path, face_path, filename)
# カスケード分類器を読み込む(正面顔の検出分類器)
cascade = cv2.CascadeClassifier('haarcascades/haarcascade_frontalface_alt2.xml')
image = cv2.imread('{}/{}'.format(image_path, filename))
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
faces = cascade.detectMultiScale(gray_image)
# Extract when just one face is detected
if (len(faces) == 1):
(x, y, w, h) = faces[0]
image = image[y:y+h, x:x+w]
image = cv2.resize(image, (100, 100))
cv2.imwrite('{}/{}'.format(face_path, filename), image)
else:
cv2.imwrite('{}/{}'.format(face_path + "/misses", filename), image)
print("skipped.")
if __name__ == '__main__':
main()
解説・・・
顔・輪郭の画像抽出にはOpenCVを利用しています。
imagesフォルダにあるYES or NO画像を全てOpenCVの顔抽出分類器にかけて、うまいこと輪郭画像が抽出できたら、faces下に顔画像を保存しています。
参考になるように、失敗した場合は、faces/misses下に保存しています。
missesを見ると大体が横顔画像だったり、複数人写っていたり、遠すぎたり、影りが多かったりとする画像が殆どでした。
faces下の画像はこんな感じです。
▼3. SVM分類器にかけて、機械学習を行い、学習結果をpickleに漬け込む
save_vector3.py
# -*- coding: utf-8 -*-
"""
Face画像特徴ベクトルに変換し、保存する
"""
from PIL import Image
import os
import re
import numpy as np
from sklearn.externals import joblib
from sklearn import svm
def convertImageVector3(img):
"""
3次元(100x100x3(RGB))から1次元に変換する
"""
s = img.shape[0] * img.shape[1] * img.shape[2]
img_vector3 = img.reshape(1, s)
return img_vector3[0]
def getDatas():
"""
3次元ベクトル学習画像データと正解ラベルを対にして、pickleファイルにして保存する
"""
files = ["faces/" + f for f in os.listdir("faces") if re.match("target_", f)]
labels = []
datas = []
for image in files:
as_arrayed_img = np.asarray(Image.open(image))
# 3次元かどうか
if (len(as_arrayed_img.shape) == 3):
# RGBが3がどうか
if (as_arrayed_img.shape[2] == 3):
datas.append(convertImageVector3(np.asarray(Image.open(image))))
# 「YES」と「NO」を抽出
labels.append(image.split("faces/target_")[1].split("-")[0].replace("reversed_", ""))
else:
print("skip not rgb3")
print("converted.")
return (datas,