目的
Pythonとkerasと事前に生成した顔分類モデルを利用して、画像ファイルの人物が本田翼か佐倉綾音かを分類できましたので、Djangoフレームワークを利用してWebアプリを作ってみました。
これまでに生成した顔分類モデルと、OpenCVのカスケードファイルを利用しています。
参考
【顔分類その1】PythonとGoogleカスタム検索APIを利用した画像ダウンロード
【顔分類その2】PythonとOpenCVを利用した画像ファイルからの顔の切り出し
【顔分類その3】PythonとOpenCVを利用した画像ファイルの水増し
【顔分類その4】Pythonとkerasを利用した顔分類モデルの生成
【顔分類その5】Pythonとkerasを利用して本田翼か佐倉綾音かを分類
【顔分類その6】Djangoとkerasを利用した本田翼か佐倉綾音かを分類するWebアプリ
環境
- Windows 10 x64 1809
- Python 3.6.5 x64
- Power Shell 6 x64
- Visual Studio Code x64
- Git for Windows x64
環境構築
-
Windows 10 x64 に Python 3.6.x x64 をインストールします。
-
環境変数の PATH に、インストールしたPythonのフォルダとPython\Scriptsフォルダを設定しておきます。
-
下記手順で Python の仮想環境を構築し、有効化してpipをアップデートします。
> python -m venv venv
> .\venv\Scripts\activate.ps1
(venv)> python -m pip install --upgrade pip
- pip でいろいろインストールします。
(venv)> pip install pylint
(venv)> pip install Django~=2.0.10
(venv)> pip install opencv-python
(venv)> pip install Pillow
(venv)> pip install tensorflow
(venv)> pip install keras
後ほど作成するプロジェクトのルートに、下記のような requirements.txt を作成しています。
Django~=2.0.10
pylint
opencv-python
Pillow
tensorflow
keras
プログラム作成
- Django のプロジェクトを作成します。
(venv)> django-admin.exe startproject mysite .
プロジェクト名は mysite としました。
基本的な手順はDjango Girls のチュートリアルを参考にしています。
- mysite/settings.py を修正します。
TIME_ZONE = 'Asia/Tokyo' # 修正
LANGUAGE_CODE = 'ja' # 修正
# 追加
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
- 念のためマイグレートしておきます。
(venv)> python manage.py migrate
- ここまでの手順でWebサーバーを起動し、ブラウザから参照できることを確認します。
(venv)> python manage.py runserver
Ctrl+C でWebサーバーを停止します。
- Django のアプリケーションを作成します。
(venv)> python manage.py startapp pred
アプリケーション名は pred としました。
- mysite/settings.py を修正します。
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'pred', # 追加
]
- プロジェクトのルートにモデルファイルとカスケードファイルを配置します。
img_pred_web
├─mysite
│ ├─settings.py
│ ├─urls.py
│ └─wsgi.py
├─pred
│ ├─migrations
│ ├─admin.py
│ ├─apps.py
│ ├─models.py
│ ├─tests.py
│ ├─urls.py
│ └─views.py
├─venv
├─db.sqlite3
├─haarcascade_frontalface_default.xml ← 配置
├─manage.py
├─model.h5 ← 配置
└─requirements.txt
- mysite/settings.py を修正します。
# 追記
CASCADE_FILE_PATH = os.path.join(BASE_DIR, 'haarcascade_frontalface_default.xml')
MODEL_FILE_PATH = os.path.join(BASE_DIR, 'model.h5')
- mysite/urls.py を修正します。
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('pred.urls')),
]
pred アプリケーションの urls を設定しています。
- pred/urls.py を修正します。
from django.urls import path
from .views import PredView
urlpatterns = [
path('', PredView.as_view(), name='index'),
]
後ほど作成するビューへのURLパターンを設定しています。
- pred/forms.py を作成します。
from django import forms
class ImageForm(forms.Form):
image = forms.ImageField(label="判定する画像を選択してください",
error_messages={'missing' : '画像ファイルが選択されていません。',
'invalid' : '分類する画像ファイルを選択してください。',
'invalid_image' : '画像ファイルではないようです。'})
画像ファイルをアップロードするためのフォームを作成しています。
- pred/views.py を修正します。
from django.shortcuts import render
from django.views.generic import TemplateView
from .forms import ImageForm
from .main import detect
class PredView(TemplateView):
# コンストラクタ
def __init__(self):
self.params = {'result_list': [],
'result_name': "",
'result_img': "",
'form': ImageForm()}
# GETリクエスト(index.htmlを初期表示)
def get(self, req):
return render(req, 'pred/index.html', self.params)
# POSTリクエスト(index.htmlに結果を表示)
def post(self, req):
# POSTされたフォームデータを取得
form = ImageForm(req.POST, req.FILES)
# フォームデータのエラーチェック
if not form.is_valid():
raise ValueError('invalid form')
# フォームデータから画像ファイルを取得
image = form.cleaned_data['image']
# 画像ファイルを指定して顔分類
result = detect(image)
# 顔分類の結果を格納
self.params['result_list'], self.params['result_name'], self.params['result_img'] = result
# ページの描画指示
return render(req, 'pred/index.html', self.params)
後ほど作成する main.py を利用して顔分類を行います。
- pred/main.py を作成します。
import base64
import io
import cv2
import keras
import numpy as np
from PIL import Image
from keras.backend import tensorflow_backend as backend
from django.conf import settings
def detect(upload_image):
result_name = upload_image.name
result_list = []
result_img = ''
# 設定からカスケードファイルのパスを取得
cascade_file_path = settings.CASCADE_FILE_PATH
# 設定からモデルファイルのパスを取得
model_file_path = settings.MODEL_FILE_PATH
# kerasでモデルを読み込む
model = keras.models.load_model(model_file_path)
# アップロードされた画像ファイルをメモリ上でOpenCVのimageに格納
image = np.asarray(Image.open(upload_image))
# 画像をOpenCVのBGRからRGB変換
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# 画像をRGBからグレースケール変換
image_gs = cv2.cvtColor(image_rgb, cv2.COLOR_RGB2GRAY)
# カスケードファイルの読み込み
cascade = cv2.CascadeClassifier(cascade_file_path)
# OpenCVを利用して顔認識
face_list = cascade.detectMultiScale(image_gs, scaleFactor=1.1,
minNeighbors=5, minSize=(64, 64))
# 顔が1つ以上検出できた場合
if len(face_list) > 0:
count = 1
for (xpos, ypos, width, height) in face_list:
# 認識した顔の切り抜き
face_image = image_rgb[ypos:ypos+height, xpos:xpos+width]
# 切り抜いた顔が小さすぎたらスキップ
if face_image.shape[0] < 64 or face_image.shape[1] < 64:
continue
# 認識した顔のサイズ縮小
face_image = cv2.resize(face_image, (64, 64))
# 認識した顔のまわりを赤枠で囲む
cv2.rectangle(image_rgb, (xpos, ypos),
(xpos+width, ypos+height), (0, 0, 255), thickness=2)
# 認識した顔を1枚の画像を含む配列に変換
face_image = np.expand_dims(face_image, axis=0)
# 認識した顔から名前を特定
name, result = detect_who(model, face_image)
# 認識した顔に名前を描画
cv2.putText(image_rgb, f"{count}. {name}", (xpos, ypos+height+20),
cv2.FONT_HERSHEY_DUPLEX, 1, (0, 0, 255), 2)
# 結果をリストに格納
result_list.append(result)
count = count + 1
# 画像をPNGに変換
is_success, img_buffer = cv2.imencode(".png", image_rgb)
if is_success:
# 画像をインメモリのバイナリストリームに流し込む
io_buffer = io.BytesIO(img_buffer)
# インメモリのバイナリストリームからBASE64エンコードに変換
result_img = base64.b64encode(io_buffer.getvalue()).decode().replace("'", "")
# tensorflowのバックエンドセッションをクリア
backend.clear_session()
# 結果を返却
return (result_list, result_name, result_img)
def detect_who(model, face_image):
# 予測
predicted = model.predict(face_image)
# 結果
name = ""
result = f"本田 翼 の可能性:{predicted[0][0]*100:.3f}% / 佐倉 綾音 の可能性:{predicted[0][1]*100:.3f}%"
name_number_label = np.argmax(predicted)
if name_number_label == 0:
name = "Honda Tsubasa"
elif name_number_label == 1:
name = "Sakura Ayane"
return (name, result)
画像をBASE64エンコードする処理は https://qiita.com/miler0528/items/59caf9bd1ed4212edd3a を参考にさせて頂きました。
- static/css/style.css を作成します。
スタイルシートの設定は割愛します。
- templete/pred/base.html を作成します。
{% load static %}
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>kerasで深層学習</title>
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
<link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body>
<div class="container">
{% block content %}
{% endblock %}
</div>
</body>
</html>
- templete/pred/index.html を作成します。
{% extends 'pred/base.html' %}
{% block content %}
<div style="padding:1em;">
<div class="page-header">
<h1>kerasで深層学習 <small>本田翼か佐倉綾音のどちらかに分類してみる</small></h1>
</div>
<form method="POST" action="{% url 'index' %}" enctype="multipart/form-data" class="form-horizontal">
{% csrf_token %}
<div class='form-group'>
<div class='col-sm-4'>{{form.as_p}}</div>
<div class='col-sm-8'><button type="submit" class="btn btn-primary">分類する</button></div>
</div>
</form>
<p class="message">判定画像</p>
<ul>
{% if result_name != "" %}
<li>{{ result_name }}</li>
{% else %}
<li>画像なし</li>
{% endif %}
</ul>
<p class="message">判定結果</p>
<ol>
{% if result_list %}
{% for result in result_list %}
<li>{{ result }}</li>
{% endfor %}
{% else %}
<li>結果なし</li>
{% endif %}
</ol>
<div class="image">
{% if result_img != "" %}
<img src="data:image/png;base64,{{result_img}}"/>
{% endif %}
</div>
</div>
{% endblock %}
- ここまででこんな感じになっているはずです。
img_pred_web
├─mysite (プロジェクト)
│ ├─__init__.py
│ ├─settings.py ← 修正
│ ├─urls.py ← 修正
│ └─wsgi.py
├─pred (アプリケーション)
│ ├─migrations
│ ├─static
│ │ └─css
│ │ └─style.css ← 作成
│ ├─templates
│ │ └─pred
│ │ ├─base.html ← 作成
│ │ └─index.html ← 作成
│ ├─__init__.py
│ ├─admin.py
│ ├─apps.py
│ ├─forms.py ← 作成
│ ├─main.py ← 作成
│ ├─models.py
│ ├─tests.py
│ ├─urls.py ← 修正
│ └─views.py ← 修正
├─venv
├─db.sqlite3
├─haarcascade_frontalface_default.xml ← 配置
├─manage.py
├─model.h5 ← 配置
└─requirements.txt ← 作成
プログラム実行
- Webサーバーを起動し、ブラウザから参照できることを確認します。
(venv)> python manage.py runserver
-
ブラウザを起動し、http://127.0.0.1:8000/ にアクセスします。
-
画像ファイルを選択して分類してみます。
最後に
深層学習で画像の分類を行いたいという勢いから、いろいろな情報を参考にやってみました。
keras(tensorflow)を利用したWebアプリ開発のお作法が正しいのか不安なので、時間があるときに見直してみます。
顔分類はいったん区切りとして、まったく別のことをやってみようかと妄想中です。