はじめに
現在、方言を話すおしゃべり猫型ロボット「ミーア」を開発中。
ミーアの表情、特に目の動き、を検証するために、今までは、3Dプリンターで印刷した筐体に、目に相当するLCDディスプレイを当てはめて、実際にマイコン起動して目の表情を動かしてみて、その場で確認するという方法をとっていた。
ただ、それだと、検証に時間がかかるのと、協力していただけるようになったデザイナーへの共有が大変なので、2Dでwebサイト上でリモートでも検証できるようにしたいと思う。
Flaskでローカル環境でアプリケーション作成
下記のようにpythonでコード作成
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>猫の顔の表情アップロード</title>
<!-- Bootstrap CSSの追加 -->
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">
<style>
#cat-face-container {
position: relative;
display: inline-block;
}
#cat-face {
width: 450px; /* 猫の画像の幅を設定 */
display: block; /* 画像をブロック要素として配置 */
}
/* Bootstrap風のデザインのための追加スタイル */
form {
margin-top: 15px;
}
</style>
</head>
<body>
<div id="cat-face-container" class="container">
<!-- 省略 -->
{% if cat_image_data %}
<!-- Base64エンコードされた画像データを表示 -->
<img src="data:image/png;base64,{{ cat_image_data }}">
{% else %}
<img id="cat-face" src="{{ url_for('static', filename='mia_face.png') }}" alt="猫の顔">
{% endif %}
<form action="/" method="post" enctype="multipart/form-data" class="form-inline">
<div class="form-group mb-2">
<input type="file" name="eye_image" accept="image/png, image/jpeg, image/gif" class="form-control-file">
</div>
<button type="submit" class="btn btn-primary mb-2">アップロード</button>
</form>
<!-- 初期化ボタンをBootstrap風に装飾 -->
<form action="/reset" method="get" class="form-inline">
<button type="submit" class="btn btn-secondary mb-2">初期化</button>
</form>
</div>
</body>
</html>
Python
from flask import Flask, request, render_template
from PIL import Image, ImageSequence
import io
import base64
# appという名前でFlaskのインスタンスを作成
app = Flask(__name__)
cat_face_image_path = 'static/mia_face.png'
# どこのアドレスで実行するか指定
# 今回は http://127.0.0.1:5000/ にアクセスされたらindex()を実行
@app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'POST':
if 'eye_image' not in request.files:
return 'ファイルがありません', 400
file = request.files['eye_image']
uploaded_image = Image.open(file.stream)
# GIF画像かどうかをチェック
if uploaded_image.format == 'GIF':
frames = [] # 新しいGIFのフレームを保存するリスト
for frame in ImageSequence.Iterator(uploaded_image):
frame = frame.convert('RGBA')
frame = frame.resize((128, 128)) # サイズ調整
cat_face = Image.open(cat_face_image_path)
cat_face = cat_face.resize((450, 450))
cat_face.paste(frame, (85, 165), frame) # 左目の位置
cat_face.paste(frame, (240, 165), frame) # 右目の位置
# 各フレームをリストに追加
frame_byte_arr = io.BytesIO()
cat_face.save(frame_byte_arr, format='PNG')
frame_byte_arr.seek(0)
frames.append(Image.open(frame_byte_arr))
# 新しいGIFを生成
img_byte_arr = io.BytesIO()
frames[0].save(img_byte_arr, format='GIF', save_all=True, append_images=frames[1:], loop=0)
else:
uploaded_image = uploaded_image.convert('RGBA')
uploaded_image = uploaded_image.resize((128, 128))
cat_face = Image.open(cat_face_image_path)
cat_face = cat_face.resize((450, 450))
cat_face.paste(uploaded_image, (85, 165), uploaded_image) # 左目の位置
cat_face.paste(uploaded_image, (240, 165), uploaded_image) # 右目の位置
img_byte_arr = io.BytesIO()
cat_face.save(img_byte_arr, format='PNG')
encoded_img_data = base64.b64encode(img_byte_arr.getvalue()).decode('utf-8')
return render_template('index.html', cat_image_data=encoded_img_data)
return render_template('index.html')
@app.route('/reset')
def reset():
return render_template('index.html')
if __name__ == '__main__':
# 作成したappを起動
# ここでflaskの起動が始まる
app.run(debug=True)
python3 app.pyでアプリケーションを実行すると、下記のような結果になり、http://127.0.0.1:5000 にアクセスすると、routeに定義したindexファイルが開く。Flaskのデフォルトポートは5000。
Zsh
~/dev/automate/mia-eye-test python3 app.py
* Serving Flask app 'app'
* Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5000
Press CTRL+C to quit
* Restarting with stat
* Debugger is active!
* Debugger PIN: 339-865-232
コード解説
画像データをHTML内で直接表示する部分
img_byte_arr
という名前のio.BytesIO
オブジェクトを作成。画像データをバイナリ形式で一時的に保持するためのものcat_face
という、猫の筐体の画像(目がくり抜かれた部分)をロードし、その画像上にアップロードされた目の画像を合成する(Pillowライブラリの.paste()
メソッドを使用)。cat_face
に合成された画像が含まれた後、cat_face
をio.BytesIO
オブジェクトに保存。これにより、処理された画像がメモリ内でバイナリデータとして利用可能になる。base64.b64encode
関数を使用して、バイナリデータをBase64エンコード。このエンコードされたデータはテキスト文字列として取得できる。- エンコードされた画像データをUTF-8エンコードして文字列に変換し、HTMLテンプレートの
cat_image_data
として渡す。この変数を使用して、フロントエンド側で画像を表示できる。
画像の合成はバイナリ形式のままPillowライブラリを用いる
画像の読み込み、変換、合成、保存などの操作は、バイナリ形式のままPythonのPillowライブラリを用いて行うことができる。
画像の埋め込みはBase64エンコードを用いる
ただし、画像データはバイナリ形式のままではHTML内に直接埋め込むことはできない。HTMLではテキストデータのみを直接埋め込むことができる。そのため、画像データをHTML内に表示するためには、画像データをテキストデータに変換する必要があり、そのためにBase64エンコードを使う。Base64エンコードは、バイナリ形式の画像データをテキストデータに変換する。
完成!
これで、無事ローカル環境では、目の画像をアップロードすると、猫の正面の画像の目の部分に入るようになった。GIFにも対応
ただ、このままだとローカル環境で、私しか検証できないので、FlaskアプリケーションをDocker化して、Cloud Runにデプロイを次に進める。続きはこちら。
コメント