画像をアップロードするとそれが何であるかを判別するWebアプリを作ります。
作成
1. 仮想環境やライブラリインストール諸々
pip と venv を使います。同等の他の方法でも大丈夫です。
pythonは3系です
python -m venv .
source bin/activate
pip install keras tensorflow numpy pillow bottle
pip freeze > requirements.txt
2. 画像判別クラス作成
使用するモデルを差し替えやすいように2ファイルにしていますが、まとめてもいいと思います。
keras_app.py
from keras.applications.vgg16 import VGG16 as KerasApp, preprocess_input, decode_predictions
target_size=(224, 224)
discriminator.py
from keras_app import KerasApp, preprocess_input, decode_predictions, target_size
import keras.preprocessing.image as PilImage
import numpy
class Discriminator:
def __init__(self):
self.model = KerasApp(weights='imagenet')
def predict(self, file):
image = PilImage.load_img(file, target_size=target_size)
x = PilImage.img_to_array(image)
x = numpy.expand_dims(x, axis=0)
x = preprocess_input(x)
preds = self.model.predict(x)
results = decode_predictions(preds, top=3)[0]
return list(map(lambda result:{'name': result[1], 'ratio': numpy.float(result[2]) }, results))
3. サーバ処理作成
bottleという軽量フレームワークを使います。
画像をアップロードするためのページ(index.html)の表示と画像を受け取って判別結果を返すWebAPIを作ります。
from bottle import route, run, static_file, request, response
from os import getenv, path
import json
from discriminator import Discriminator
def relative_path(target_path):
return path.normpath(path.join(path.dirname(__file__), target_path))
@route('/')
def index():
response.content_type = 'text/html; charset=utf-8'
return static_file('index.html', root = relative_path('./'))
@route('/api/upload', method='POST')
def upload():
upload = request.files.get('upload')
result = discriminator.predict(upload.file)
return json.dumps(result)
discriminator = Discriminator()
run(host='0.0.0.0', port=getenv('PORT', 8080), debug=True)
4. クライアント処理
カメラボタンをクリックorタップすると保存済みの画像や、カメラを起動して撮った写真をアップロードし、判別結果を表示する処理です。
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>画像判別機</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" integrity="sha256-eZrrJcwDc/3uDhsdt61sL2oOBY362qM3lon1gyExkL0=" crossorigin="anonymous" />
<style type="text/css">
.wrapper {
padding: 60px 40px;
}
.upload-wrapper {
position: relative;
width: 300px;
height: 300px;
border: 3px solid black;
text-align: center;
vertical-align: middle;
display:table-cell;
}
#preview {
width: 300px;
height: 300px;
position: absolute;
top: 0px;
left: 0px;
}
#result {
white-space: pre-wrap;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/native-promise-only/0.8.1/npo.js" integrity="sha256-o/UXdF4sFrbgV5UCIWF5ca7VMLDdplhzA4knJ4nFsc0=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/2.0.4/fetch.min.js" integrity="sha256-eOUokb/RjDw7kS+vDwbatNrLN8BIvvEhlLM5yogcDIo=" crossorigin="anonymous"></script>
<script>
(function () {
function uploadFile (file) {
var formData = new FormData();
formData.append('upload', file)
return fetch('/api/upload', {
method: 'POST',
body: formData
}).then(function (response) {
return response.json();
});
}
function showResult (text) {
var result = document.getElementById('result');
result.innerHTML = text;
}
document.addEventListener('DOMContentLoaded', function () {
showResult('画像を選択して下さい');
var upload = document.getElementById('upload');
var preview = document.getElementById('preview');
upload.addEventListener('change', function (e) {
preview.removeAttribute('src');
var file = e.target.files[0];
if (!file) {
showResult('画像を選択して下さい');
return;
}
showResult('判別中...');
var uploadFileReader = new FileReader();
uploadFileReader.readAsDataURL(file);
uploadFileReader.onload = function () {
preview.setAttribute('src', uploadFileReader.result);
};
uploadFile(file).then(function (results) {
showResult(results.map(function (result) {
return result.name + ': ' + (Math.round(result.ratio * 10000) / 100) + '%';
}).join('\n'));
}).catch(function (e) {
showResult('判別に失敗しました。');
console.error(e);
});
});
});
})();
</script>
</head>
<body>
<div class="wrapper">
<div class="upload-wrapper">
<label for="upload">
<i id="icon-camera" class="fa fa-camera fa-5x" aria-hidden="true"></i>
<img id="preview" />
</label>
<input id="upload" type="file" name="upload" accept="image/*" style="display:none;" />
</div>
<div id="result">画像が選択されていません</div>
</div>
</body>
</html>
実行
起動
python server.py
初回はモデルのダウンロードが走るので起動が遅いです。
http://localhost:8080 をWebブラウザで開くと表示されます。
同一LAN内の別PCやスマートフォン・タブレットで表示したい場合は http://(起動マシンのIP):8080
で開けます。
使い方
カメラアイコン部分をタップするとファイルダイアログが表示されます。画像を選択するとサーバへアップロードされ判別結果が返ってきます。