Angular×Spring Boot×AWS Rekognitionで笑顔認識アプリ作り

<h1>前回までのあらすじ</h1>
上から順番になっております。
https://qiita.com/NoOne/items/c7b5b2bd84fa9ca317d5

https://qiita.com/NoOne/items/42918d1f4f4e3fe59329

https://qiita.com/NoOne/items/9019baf366aaeb000e68
<hr>

<h1>ソースコード</h1>
https://github.com/yukihiro-maeda0731/AngularDetectFace/tree/master (Angular)
https://github.com/yukihiro-maeda0731/SpringDetectFace/tree/master (Spring Boot)


<h1>Angularで撮影した画像をSpringに送る</h1>
HTMLのvideoで撮影しCanvasに描写した画像データをSpringにHttp通信で送るため.toDataURL()でBase64形式に変換しております。

```app.component.ts
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)
    );
  ;
  }
}
```

<hr>
Http通信を行うためのserviceを作成します。現段階ではローカルのみでの動作となるのでurlはspringのlocalhostです。

```img.service.ts
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'
    })
  }
}
```

<h1>Springで画像情報を受け取り、AWS Rekognitionに提供する</h1>
Angularからのhttpリクエストを受け取るSpringのコントローラーです。先ほどBase64形式になったdataの余分な部分「data:image/jpg;base64」を外し、デコードしデバイスのhomeディレクトリにpng形式で撮影画像を保存します。これで後はAWS Rekognitionにそのディレクトリを教えてあげれば基本的にはAWS docサンプルのAWS SDK固有の処理を流用で問題なく動きます。今回は笑顔ですが、face.smile().value()のsmileのところをBeardに変えるとあごひげ判定などもできたりと他にも色々できます。

```ImgController.java
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;
        }
    }

}
```

<h1>感想など</h1>
ひとまず大まかなコーディングはこれで完了です。
機械学習の知見が全くなくとも顔認識が実装でき、とても感動しました。
画像関連の処理の復習もでき、いい題材を見つけたなとしみじみ思います。
もし読者の方で実装してみて上手くいかない・もしくはこういうのできますか？
などご意見いただければ非常にありがたいです。

<h1>今後の課題</h1>
・WEB上で動くようにする(S3?EC2?Elastic Bean Stalk?)
・デザインをましにする
・携帯アプリちっくにする(PWA)
・テストコード
挙げてみれば課題が山ほどありますね(笑)。

<h1>参考</h1>
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