前回までのあらすじ
上から順番になっております。
https://qiita.com/NoOne/items/c7b5b2bd84fa9ca317d5
https://qiita.com/NoOne/items/9019baf366aaeb000e68
ソースコード
https://github.com/yukihiro-maeda0731/AngularDetectFace/tree/master (Angular)
https://github.com/yukihiro-maeda0731/SpringDetectFace/tree/master (Spring Boot)
Angularで撮影した画像をSpringに送る
HTMLのvideoで撮影しCanvasに描写した画像データをSpringにHttp通信で送るため.toDataURL()でBase64形式に変換しております。
import { Component } from '@angular/core';
import { ImgService } from './img.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
constructor(private service: ImgService) { }
private video: any;
// Springから返ってくる笑顔の判定メッセージを格納
result: String = "";
//撮影フォーム設定
ngOnInit(): void {
this.video = document.querySelector('video')!;
const options = {
video: true
}
navigator.mediaDevices.getUserMedia(options)
.then(stream => {
this.video.srcObject = stream;
})
.catch((error) =>{
console.log(error);
})
}
//撮影ボタン押下時の処理
captureImg(){
let canvas = document.getElementById('canvas') as HTMLCanvasElement;
const context = canvas.getContext('2d');
context?.drawImage(this.video, 0, 0, canvas.width, canvas.height);
let base64CapturedImg : ConstrainDOMString = canvas.toDataURL("image/png");
this.service.transferImg(base64CapturedImg).subscribe(
data => this.result = data,
error => console.log(error)
);
;
}
}
Http通信を行うためのserviceを作成します。現段階ではローカルのみでの動作となるのでurlはspringのlocalhostです。
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class ImgService {
constructor(private http: HttpClient) { }
private destinationUrl = 'http://localhost:8080';
transferImg(base64CapturedImg: String): Observable<any> {
return this.http.post(this.destinationUrl, base64CapturedImg,{
headers: {
"Content-Type": "text/plain; charset=UTF-8",
//CORS対策
"Access-Control-Allow-Headers": "*",
"Access-Control-Allow-Origin": "*"
},
//jsonではなく文字列で結果が欲しいので追記(なくても落ちはしないが余分なエラーメッセージ出る)
'responseType': 'text'
})
}
}
Springで画像情報を受け取り、AWS Rekognitionに提供する
Angularからのhttpリクエストを受け取るSpringのコントローラーです。先ほどBase64形式になったdataの余分な部分「data:image/jpg;base64」を外し、デコードしデバイスのhomeディレクトリにpng形式で撮影画像を保存します。これで後はAWS Rekognitionにそのディレクトリを教えてあげれば基本的にはAWS docサンプルのAWS SDK固有の処理を流用で問題なく動きます。今回は笑顔ですが、face.smile().value()のsmileのところをBeardに変えるとあごひげ判定などもできたりと他にも色々できます。
package suita.tarumi.SpringDetectFace.Controller;
import org.springframework.web.bind.annotation.*;
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.rekognition.RekognitionClient;
import software.amazon.awssdk.services.rekognition.model.*;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.List;
@CrossOrigin(origins = "http://localhost:4200")
@RestController
public class ImgController {
@RequestMapping(value = "/", method = RequestMethod.POST)
public String transferImg(@RequestBody String base64CapturedImg) throws IOException {
//Base64のデコードをする前にdataから「data:image/jpg;base64」を削除する必要があるためデータと分ける
String[] base64CapturedImgForDecode = base64CapturedImg.split(",");
//デコード・base64CapturedImgForDecode[1]はデータを指す、[0]は「data:image/jpg;base64」
byte[] decodedCapturedImg = Base64.getDecoder().decode(base64CapturedImgForDecode[1].getBytes(StandardCharsets.UTF_8));
//撮影画像の置き場をOSに依存しないように作成
System.out.println("User homeは" + System.getProperty("user.home")+File.separator);
Path destinationFile = Paths.get(System.getProperty("user.home")+File.separator, "detectedFace.png");
//撮影画像をpngファイルとして作成
Files.write(destinationFile, decodedCapturedImg);
//ここからAWS Rekognition
String sourceImage = destinationFile.toString();
Region region = Region.AP_NORTHEAST_1;
RekognitionClient rekClient = RekognitionClient.builder()
.region(region)
.build();
String result = detectFaceImage(rekClient, sourceImage );
rekClient.close();
return result;
}
public static String detectFaceImage(RekognitionClient rekClient,String sourceImage ) {
String result = "";
try {
InputStream sourceStream = new FileInputStream(new File(sourceImage));
SdkBytes sourceBytes = SdkBytes.fromInputStream(sourceStream);
Image souImage = Image.builder()
.bytes(sourceBytes)
.build();
DetectFacesRequest facesRequest = DetectFacesRequest.builder()
.attributes(Attribute.ALL)
.image(souImage)
.build();
DetectFacesResponse facesResponse = rekClient.detectFaces(facesRequest);
List<FaceDetail> faceDetails = facesResponse.faceDetails();
for (FaceDetail face : faceDetails) {
System.out.println("笑顔判定 : "+ face.smile().value().toString());
if(face.smile().value().toString() == "true"){
result = "いってらっしゃい!";
} else {
result = "笑顔でもう一度!";
}
}
} catch (RekognitionException | FileNotFoundException e) {
System.out.println(e.getMessage());
System.exit(1);
} finally {
return result;
}
}
}
感想など
ひとまず大まかなコーディングはこれで完了です。
機械学習の知見が全くなくとも顔認識が実装でき、とても感動しました。
画像関連の処理の復習もでき、いい題材を見つけたなとしみじみ思います。
もし読者の方で実装してみて上手くいかない・もしくはこういうのできますか?
などご意見いただければ非常にありがたいです。
今後の課題
・WEB上で動くようにする(S3?EC2?Elastic Bean Stalk?)
・デザインをましにする
・携帯アプリちっくにする(PWA)
・テストコード
挙げてみれば課題が山ほどありますね(笑)。
参考
AWS SDK For Java2.0 Rekognitionのサンプルコード
https://github.com/awsdocs/aws-doc-sdk-examples/tree/master/javav2/example_code/rekognition/src/main/java/com/example/rekognition
Rekognitionのモデル群
https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/rekognition/model/package-summary.html