小学6年生の長男が夏休みの自由研究にPythonを勉強したいというので付き合ってみました。
どこから手を付けていけばわからないので、いくつか課題を出し、それを実現するコードを一緒に書くということを繰り返しました。
この記事では、「課題10 顔の認識」について扱います。その他の課題については下記の記事をご覧ください。
- 夏休みなので小学生とPythonを勉強してみた - 課題1 FizzBuzz - Qiita
- 夏休みなので小学生とPythonを勉強してみた - 課題2 掛け算九九 - Qiita
- 夏休みなので小学生とPythonを勉強してみた - 課題3 掛け算ゲーム - Qiita
- 夏休みなので小学生とPythonを勉強してみた - 課題4 じゃんけん - Qiita
- 夏休みなので小学生とPythonを勉強してみた - 課題5 サイコロの目の出る確率 - Qiita
- 夏休みなので小学生とPythonを勉強してみた - 課題6 写真の整理 - Qiita
- 夏休みなので小学生とPythonを勉強してみた - 課題7 掛け算ゲームWeb版 - Qiita
- 夏休みなので小学生とPythonを勉強してみた - 課題8 掲示板 - Qiita
- 夏休みなので小学生とPythonを勉強してみた - 課題9 顔の検出 - Qiita
コードはGitHubにて公開しています。
課題10-1 顔画像の分割
指示
顔画像の集合をトレーニング用とテスト用に分割するプログラムを作りましょう。
./srcにある顔画像を、./trainおよび./testにランダムに格納します。
回答例
import glob
import random
import shutil
for image_path in glob.glob('./src/*/*.jpg'):
if random.random() <= 0.8:
shutil.copy(image_path, image_path.replace('./src/', './train/', 1))
else:
shutil.copy(image_path, image_path.replace('./src/', './test/', 1))
課題10-2 顔の認識
指示
顔の認識を行うプログラムを作りましょう。
./trainにある顔画像を学習し、./testにある顔画像を認識させます。./trainおよび./test以下の顔画像が格納されているディレクトリの名前は「ラベル_ラベル名」という形式とします。ラベルは数値です。
ヒント
OpenCVには顔認識のモジュールが提供されています。
今回はLocal Binary Patterns Histogramsというアルゴリズムを使います。トレーニング用顔画像をグレーに変換し、サイズをそろえてラベルと与えて学習させます。テスト用顔画像も同様にグレーに変換し、サイズをそろえて結果を取得します。結果には推測されたラベルと、その確からしさ(0に近いほ良い)が含まれます。
回答例
import glob
import os
import cv2
import numpy
def get_gray_resized_image(image_path):
image = cv2.imread(image_path)
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray_resized_image = cv2.resize(gray_image, (500, 500))
return gray_resized_image
def get_label(path):
return int(path.split(os.sep)[2].split('_')[0])
train_images = []
train_labels = []
for image_path in glob.glob('./train/*/*.jpg'):
train_images.append(get_gray_resized_image(image_path))
train_labels.append(get_label(image_path))
recognizer = cv2.face.LBPHFaceRecognizer_create()
recognizer.train(train_images, numpy.array(train_labels))
for image_path in glob.glob('./test/*/*.jpg'):
image_name = os.path.basename(image_path)
gray_resized_image = get_gray_resized_image(image_path)
predicted_label, confidence = recognizer.predict(gray_resized_image)
image_label = get_label(image_path)
print(f'Image: {image_name}', end='\t')
print(f'Predicted: {predicted_label}', end='\t')
print(f'Confidence: {confidence:3.1f}', end='\t')
print(f'Anser: {image_label}')
実行例
Image: DSC_6322_1023-1776.jpg Predicted: 2 Confidence: 28.3 Anser: 1
Image: DSC_6658_1160-566.jpg Predicted: 2 Confidence: 41.0 Anser: 1
Image: IMG_3036_895-622.jpg Predicted: 1 Confidence: 22.1 Anser: 1
Image: IMG_3663_2257-1166.jpg Predicted: 2 Confidence: 33.7 Anser: 1
Image: IMG_3770_1708-619.jpg Predicted: 2 Confidence: 24.9 Anser: 1
Image: IMG_5088_780-1565.jpg Predicted: 1 Confidence: 36.5 Anser: 1
Image: IMG_3663_1220-1431.jpg Predicted: 2 Confidence: 30.8 Anser: 2
Image: IMG_3671_158-917.jpg Predicted: 1 Confidence: 19.8 Anser: 2
Image: IMG_3772_774-981.jpg Predicted: 1 Confidence: 54.6 Anser: 2
Image: IMG_4337_1521-1070.jpg Predicted: 2 Confidence: 36.5 Anser: 2
Image: IMG_5085_1185-1571.jpg Predicted: 2 Confidence: 36.7 Anser: 2
Image: IMG_6555_1260-2329.jpg Predicted: 2 Confidence: 16.0 Anser: 2
Image: IMG_6707_1411-653.jpg Predicted: 2 Confidence: 24.1 Anser: 2
課題10-3 顔の認識結果をHTML出力
指示
顔の認識を行い、結果をHTMLで出力するプログラムを作りましょう。
ヒント
Flaskで使っていたJinja2は単体でも使えます。
リストの内包表記は辞書にも使えます。
回答例
import glob
import os
import cv2
from jinja2 import Environment, FileSystemLoader, select_autoescape
import numpy
def get_gray_resized_image(image_path):
image = cv2.imread(image_path)
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray_resized_image = cv2.resize(gray_image, (500, 500))
return gray_resized_image
def get_label(path):
return int(path.split(os.sep)[2].split('_')[0])
def get_label_name(path):
return path.split(os.sep)[2].split('_')[1]
label_and_names = {
get_label(path): get_label_name(path) for path in glob.glob('./src/*')
}
train_images = []
train_labels = []
for image_path in glob.glob('./train/*/*.jpg'):
train_images.append(get_gray_resized_image(image_path))
train_labels.append(get_label(image_path))
recognizer = cv2.face.LBPHFaceRecognizer_create()
recognizer.train(train_images, numpy.array(train_labels))
test_results = []
for image_path in glob.glob('./test/*/*.jpg'):
image_name = os.path.basename(image_path)
gray_resized_image = get_gray_resized_image(image_path)
predicted_label, confidence = recognizer.predict(gray_resized_image)
test_results.append(
(image_path,
label_and_names[predicted_label],
confidence,
get_label_name(image_path))
)
env = Environment(
loader=FileSystemLoader('./templates'),
autoescape=select_autoescape()
)
template = env.get_template('02.html')
template.stream(test_results=test_results).dump('output.html')
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Face Recognition</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container">
<h1 class="navbar-brand">Face Recognition</h1>
</div>
</nav>
<div class="container">
<div class="list-group">
{% for image_path, predicted_label, confidence, image_label in test_results %}
<div class="list-group-item">
<div class="d-flex flex-row">
<div class="p-1 bd-highlight">
<img src="{{ image_path }}" width="100px" height="100px">
</div>
<div class="p-1 bd-highlight">
<div class="d-flex flex-column">
{% if predicted_label == image_label %}
<div>Predicted: <span style="color:green">{{ predicted_label }}</span></div>
{% else %}
<div>Predicted: <span style="color:red">{{ predicted_label }}</span></div>
{% endif %}
{% if confidence < 30 %}
<div>Confidence: <span style="color:green">{{ '%3.1f'|format(confidence) }}</span></div>
{% else %}
<div>Confidence: <span style="color:red">{{ '%3.1f'|format(confidence) }}</span></div>
{% endif %}
<div>Answer: {{ image_label }}</div>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
</body>
</html>