👶

Webカメラで感情を取得するAPIを作ってみた

2021/12/28に公開

モブプロで作成したよ

作ってくものぉ

FastAPIでFaceAPIを叩いて、感情値を返すというもの
今回作ったものは泣き顔を検出すると音楽が流れ出し、赤ちゃんを自動であやすというコンセプトのもと作られた
ngrokなどを使い、いろんな人に使ってみて欲しかったので、OpenCVなどは使わず、web上で使えるようにした

ディレクトリ構成は下記の通り

project/
  ├ face.py
  ├ main.py
  ├ .gitignore
  ├ .env
  │
  ├ static/
  │  └ img/ ここに一度画像を保存する
  │  └ sound/
  │     └ music.mp3
  │
  └ templates/
     └ index.html

書いていくぅ

AzureでFaceAPIのリソースを作成しており、キーとエンドポイントを取得している前提で書いていく

まずFaceAPIを叩くためのコードをface.pyに書いていく
感情のjsonさえ取ってくれればいいので、下記のように記述

import cognitive_face as CF
import os
from dotenv import load_dotenv #環境変数を使用

load_dotenv('.env') 

KEY = os.environ.get("KEY")
BASE_URL = os.environ.get("BASE_URL")

CF.Key.set(KEY)
CF.BaseUrl.set(BASE_URL)

def face_api(image_url):
    result = CF.face.detect(image_url, attributes="emotion") # 感情を取得
    return result

次にAPIをFastAPIで作成していく
なんかめっちゃimportしてるけどこんなもん?教えて偉い人

画像を保存しないとデータをFaceAPIに投げることができなかったので、保存することに
ただ上書きをするので、常に1ファイルが保存されるようにする(これだといろんな人が同時に触ってるとおかしなことになるのは言わずもがなだけど未実装)

一定以上の悲しい感情を取得するとJSON形式で返すように作成

from fastapi import FastAPI, Request # FastAPIの仕様でrequestがないと、テンプレートが表示されない
from fastapi.templating import Jinja2Templates # テンプレート(html)使用時に利用
from fastapi.staticfiles import StaticFiles # 静的ファイルを自動的に提供
from pydantic import BaseModel # 型情報を提供してくれる
import base64
from face import face_api
from io import BytesIO # メモリ上でバイナリデータを扱うための機能
from PIL import Image # 画像の取り込み、保存、表示処理

app = FastAPI()
templates = Jinja2Templates(directory = 'templates')
app.mount("/static", StaticFiles(directory="static"), name="static")

@app.get('/')
def root(request: Request):
  return templates.TemplateResponse('index.html',dict(request = request))

class ImageData(BaseModel):
  data: str

@app.post('/')
def root_post(image_data: ImageData):

  s = image_data.data
  target = 'data:image/jpeg;base64,' # いらない文字列
  idx = s.find(target) # いらない文字列をもとデータから探す
  r = s[idx+len(target):] # いらない文字列以降を抽出

  img_raw = base64.b64decode(r) # デコード
  img = Image.open(BytesIO(img_raw)) # 画像の読み込み

  img.save("static/img/image.jpeg") # 常に上書き保存
  image_url = 'static/img/image.jpeg'

  result = face_api(str(image_url)) # FaceAPIを叩く

  emotion = (result[0]['faceAttributes']['emotion']['sadness']) # JSONから特定の数値を抽出

  if emotion >= 0.7 :
    return {"result":"泣いてるよ!"} # ココ苦戦ポイント
  else:
    return {"result":"泣いてないよ!"}

jsは切り出しておくべき派だけど、ちょっと時間がなかったので、とりあえずindex.html

無料枠を超えない程度(5秒に一回)に顔認証を実行するように設定

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <title>Document</title>
  </head>

  <body>
    <div>
      <h1>WEBカメラの映像を表示</h1>
      <div>
        <video id="video" width="320" height="240"></video>
        <canvas id="canvas" width="320" height="240" style="opacity: 0;"></canvas>
      </div>
    </div>
    <script>
      const video = document.getElementById("video");
      navigator.mediaDevices // webカメラを起動
        .getUserMedia({
          video: true,
          audio: false,
        })
        .then((stream) => {
          video.srcObject = stream;
          video.play();
        })
        .catch((e) => {
          console.log(e);
        });

      const makePicture = () => { // webカメラの情報から画像を作成
        const ctx = canvas.getContext("2d");
        ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
        const dataURL = canvas.toDataURL("image/jpeg");
        return postImg(dataURL);
      };
      setInterval(makePicture, 5000);

      const music = new Audio('static/sound/music.mp3');

      async function postImg(dataURL) { // 画像情報を自作APIに送信、帰ってきた値によっては音楽が流れるように設定
        const HEADER = { headers: { "Content-Type": "application/json" } }; // データのやり取りはJSONを設定、リクエストもレスポンスもJSON形式にする必要がある
        const params = {
          data: dataURL,
        };
        const response = await axios.post("/", params, HEADER); // ココ苦戦ポイント2

        if (response.data.result == "泣いてるよ!"){
          music.play();
        } else {
          music.pause();
        }
      };
    </script>
  </body>
</html>

以上

  • jsからFastAPIにうまく画像データを送信する方法
  • FastAPIからjsに値を渡す方法

上記二点で苦戦した
データはJSON形式でやりとりするってことを忘れない(おれおれ実装になっちゃう)

GitHubで編集を提案

Discussion