Amazon RekognitionをLaravelから使う
案件でWebカメラから取得した画像を元に表情を分析してどうのこうのする必要があり調べてみたのでメモ。
Amazon Rekognition
を選定した理由は料金がGCPのサービスに比べて安かったから…!
Nuxt.js
から直接APIに対してPOSTすることもできるんだけど、CORSを回避する手段も用意しておきたいと思ってLaravel
も利用している
環境
- MacOS: 10.15.6
- node.js: v14.12.0
- yarn: v1.22.5
- Nuxt.js: v.2.14.6
- Laravel: 5.3.8
手順
1-1. IAMでユーザを作る
1-2. AWS SDKを利用して実装
まずはSDKをインストール
$ composer require aws/aws-sdk-php
1-3. 表情分析用のコントローラを作成する
$ php artisan make:controller FaceController
Amazon Rekognition
自体はAws\Rekognition\RekognitionClient
を通して行う
<?php namespace App\Http\Controllers; use Aws\Rekognition\RekognitionClient; use Illuminate\Http\Request; class FaceController extends Controller { /* フロントからのPOSTデータを受け取る */ public function post(Request $request) { $photo = $request->photo; $detectedData = $this->detectFaces($photo); $ret = [ 'detectedData' => $detectedData, ]; return $ret; } /* Amazon Rekognitionで表情分析を行う */ private static function detectFaces($file) { $options = [ 'region' => 'ap-northeast-1', 'version' => 'latest', 'credentials' => [ 'key' => 'XXXX', // ここを編集してね 'secret' => 'XXXX' // ここを編集してね ] ]; $result = null; try { $client = new RekognitionClient($options); // 顔検出 $result = $client->detectFaces([ 'Image' => [ 'Bytes' => file_get_contents($file), ], // 'ALL': 全ての結果が返ってくる // 'DEFAULT': 一部結果(顔の角度, 明るさetc)のみ返る 'Attributes' => ['ALL'] ]); } catch (\Exception $e) { echo $e->getMessage() . PHP_EOL; } return isset($result['FaceDetails']) ? $result['FaceDetails'] : null; } }
api.php
にルーティングを追加する
... Route::group(['prefix' => 'face'], function ($route) { $route->post('/post', 'FaceController@post'); });
1-4. フロント部分を作成する
フロントは先日ブログに書いたNuxt.jsでWebカメラで映像を撮影 & 画像の取得をしてみる - @Satoh_D no blogのコードに以下を追記する
<template> <section class="section"> <div class="columns"> <div class="column"><video id="localvideo" ref="localVideo" autoplay :srcObject.prop="localstream" width="640" height="350" ></video></div> </div> <div class="columns"> <div class="column"> <canvas id="localPic" ref="localPicCanvas" width="640" height="350" ></canvas> </div> </div> <div class="buttons"> <b-button @click="getUserMedia">映像を取得する</b-button> <b-button @click="getUserPic">写真を撮る</b-button> <b-button @click="getEmotion">表情判定</b-button> </div> </section> </template> export default { data() { return { localstream: undefined, canvas: undefined, video: undefined, // movie -> videoに変更 photo: undefined, // 追加 res: undefined, // 追加 streamConst: { video: { width: 640, height: 350, }, }, } }, mounted() { this.canvas = this.$refs.localPicCanvas this.video = this.$refs.localVideo }, computed: {}, methods: { getLocalMediaStream(mediaStream: any) { this.localstream = mediaStream }, handleLocalMediaStreamError(error: string) { console.error(`navigator.getUserMedia error: ${error}`) }, getUserMedia() { navigator.mediaDevices .getUserMedia(this.streamConst) .then(this.getLocalMediaStream) .catch(this.handleLocalMediaStreamError) }, getUserPic() { const ctx = this.canvas.getContext('2d') ctx.drawImage(this.video, 0, 0, 640, 350) this.photo = this.canvas.toDataURL() }, getEmotion() { // base64 -> blobに変換 const dataurl = this.photo const bin = atob(dataurl.split(',')[1]) const buffer = new Uint8Array(bin.length) for (let i = 0; i < bin.length; i++) { buffer[i] = bin.charCodeAt(i) } const blob = new Blob([buffer.buffer], { type: 'image/png' }) const formdata = new FormData() formdata.append('photo', blob, 'image.png') formdata.append('hogehoge', 'fugafuga') this.res = this.$axios .$post('/face/post', formdata, { headers: { 'content-type': 'multipart/form-data' }, }) .then((res) => { console.log('resnpose data', res) }) .catch((error) => { console.log('response error', error) }) }, }, }
必要なものはgetEmotion()
の部分
canvas
要素に書き出した画像を画像ファイルとして実体化し、axios
でLaravel側にPOSTする
canvas
の画像をblob
に変換する方法としてHTMLCanvasElement.toBlob()
という関数が用意されているようだが、現時点でSafariがサポートされていないのでこのような実装になっている
const dataurl = this.photo const bin = atob(dataurl.split(',')[1]) const buffer = new Uint8Array(bin.length) for (let i = 0; i < bin.length; i++) { buffer[i] = bin.charCodeAt(i) } const blob = new Blob([buffer.buffer], { type: 'image/png' })
POST部分で気をつけることは、画像などファイルを含む場合はheaders: { ‘content-type’: ‘multipart/form-data’ }
でheaderをつけてあげる
今は戻り値をconsole.log()
で出力するだけ(必要に応じて処理を追加する)