[Amazon Rekognition] Custom Labelsの検出結果を効率的に評価する

2020.03.24

1 はじめに

CX事業本部の平内(SIN)です。

Amazon Rekognition の Custom Labelsでは、独自のオブジェクトの検出が可能ですが、作成したモデルが、要件を満たしているかどうかを確認する作業があると思います。

この作業は、概ね以下のとおりとなりますが、Confidenceを調整したり、検出結果のサイズを検討したりする場合、3及び4の作業は、やや面倒になってしまいます。

  1. 評価画像の撮影
  2. モデルによるオブジェクト検出
  3. 評価結果の表示
  4. 評価結果の評価

今回は、この3,4の作業を効率化するための仕組みを作成してみました。

最初に、利用している様子です。Webカメラで色々撮影しながら、評価したいタイミングでボタンを押すと、その時点の画像をモデルで評価して可視化します。 また、Confidence及び、オブジェクトのサイズは、コントロールで変更が可能であり、色々を軽易に試すことができます。

なお、このアプリを利用して、先日、下記の記事を投稿させて頂きました。
[Amazon Rekognition] 部分画像の利用と、サイズのフィルタでCustom Labelsの検出精度が上げられることを確認してみました

2 detectCustomLabels

Custom Labesでのオブジェクト検出は、AWS SDK for JavaScript から Rekognition() - detectCustomLabels()を使用しています。


参考:Class: AWS.Rekognition

detectCustomLabels()では、評価対象の画像をS3のバケット若しくは、バイナリデータで指定できますが、ここでは、ブラウザからバイナリで直接送っています。

Rekognitionを扱っているコードは、下記のとおりとなっています。

class Rekognition {

    constructor(model){
        this.model = model
        AWS.config.region = 'us-east-1';
        AWS.config.credentials = new AWS.CognitoIdentityCredentials({
            IdentityPoolId: 'us-east-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx',
        });
        this.rekognition = new AWS.Rekognition();
        console.log(this.rekognition)
    }

    async detectCustomLabels(buffer){
        return new Promise((resolve, reject) => {
            var params = {
                Image: {
                    Bytes: buffer
                },
                ProjectVersionArn: this.model,
                MinConfidence: 0
            };
            this.rekognition.detectCustomLabels(params, (err, data)=> {
                if (err){
                    reject(err)
                }
                resolve(data);
            });
        })
    }
}

3 OpenCV.js

Webカメラの画像を処理するために、今回は、OpenCVを使用しました。ここで、OpenCVは必須ではありませんが、今後の拡張を見据えて使用しています。

ブラウザ(JavaScript)から、OpenCVを利用するためには、OpenCV.jsが利用可能です。
https://docs.opencv.org/3.4/d5/d10/tutorial_js_root.html

下記のとおり、読み込むことで、利用が可能になります。

<script async src="https://docs.opencv.org/3.4.1/opencv.js" onload="main()"></script>

OpenCV.jsによる動画の取得と表示は、概ね以下の通りです。

<canvas id="output"></canvas>

<script>
    // 任意のVideoデバイスを指定
    const deviceIndex = 0;
    const devices = await navigator.mediaDevices.enumerateDevices();
    const videoDevices= devices.filter(
        device => device.kind === 'videoinput'
    );
    // メディア(Video)の利用
    navigator.mediaDevices.getUserMedia({
        video:  {
            deviceId: videoDevices[deviceIndex].deviceId, 
            width: width, 
            height: height 
        },
        audio: false
    })
    .then(function(s) {
        video.srcObject = s;
        video.play();
    })
    .catch(function(err) {
        console.log("An error occured! " + err);
    });

    video.setAttribute("width", width);
    video.setAttribute("height", height);

    const vc = new cv.VideoCapture(video);
    const src = new cv.Mat(height, width, cv.CV_8UC4);
    
    while(true){
        vc.read(src);
        cv.imshow(name, src);
        await sleep(100);
    }
</script>

4 バイナリデータへの変換

OpenCVでは、画像をMatオブジェクトで管理していますが、Custom Labelsに送るために、これをバイナリデータに変換する必要があります。

バイナリデータは、作業用のコントロール(非表示)に描画してから、canvas.toDataURL()で取得しています。

// OpenCVの画像データからバイト配列を取得する
function getBytes(src){
      var canvas = document.getElementById("tmpCanvas");
      cv.imshow("tmpCanvas", src);
      var type = 'image/jpeg';
      var dataurl = canvas.toDataURL(type);
      var bin = atob(dataurl.split(',')[1]);
      var buffer = new Uint8Array(bin.length);
      for (var i = 0; i < bin.length; i++) {
        buffer[i] = bin.charCodeAt(i);
      }
      return  buffer;
}

5 検出結果の描画

検出結果を描画しているコードです。 rangeタイプのコントロールで変更された値を元に、OpenCVで描画しています。

// 検出結果の描画
function dispDetection() {

  let response = $("#response");

  // 動画再生中
  if(isRecoding){
    response.html('');

    return;
  }

  // テキスト表示
  let text = '';
  if(detectionResult && detectionResult.CustomLabels){
    detectionResult.CustomLabels.forEach( label => {
      if(label.Confidence > confidence){
        text += `<br>Name:${label.Name} Confidence:${label.Confidence}`;
      }
    })
  }
  response.html(text);

  // 静止画用の画像を複製する
  if(disp != null){
    delete disp;
  }
  disp = src.clone();

  const color = new cv.Scalar(255, 255, 0);
  if(detectionResult && detectionResult.CustomLabels){
    detectionResult.CustomLabels.forEach( label => {
        const name = label.Name;
        const c = label.Confidence;
        const box = label.Geometry.BoundingBox
        const l = box.Left * width;
        const t = box.Top * height;
        const w = box.Width * width;
        const h = box.Height * height;

        if(c > confidence){

          if(box.Height > min_h){
            p1 = new cv.Point(x = l, y = t);
            p2 = new cv.Point(x = l + w, y = t + h);
            cv.rectangle(disp, p1, p2, color, width/200);
            
            // p1 = new cv.Point(x = l, y = t-18)
            // p2 = new cv.Point(x = l + 150, y = t);

            p1 = new cv.Point(x = l+3, y = t-5);
            
            const fontSize = height/200;
            const fontWidth = height/200;
            cv.putText(disp, name, p1, cv.FONT_HERSHEY_PLAIN, fontSize, color, fontWidth, cv.LINE_AA)
          }
        }
    })
  }
  show("output", disp, magnification)
}

6 最後に

今回は、Amazon Rekognition Custom Labelsのモデルを評価する仕組みを作成してみました。

オブジェクト検出で精度を上げるためには、対象となる画像や、その利用方法を一定の型にはめることが有効だと思います。

モデルの評価と同時に、利用方法自体の評価も行う、このような仕組みは、有効ではないかと妄想しています。

全部のコードは、下記に置きました。
https://github.com/furuya02/evaluation_of_custom_labels