AI動画解析サービスを試してみる 3

今回は[前々回](https://qiita.com/TechSavvy_24/items/62da90a5f126b4775fcc)と[前回](https://qiita.com/TechSavvy_24/items/a2f5c69061ae572a72a7)の記事にある解析結果取得スクリプトで取得した結果を確認して比較してみます。

## オブジェクト解析の違いについて

ここでいうオブジェクト解析とは、動画内に映る物体(=オブジェクト)を検出し、検出した物体のラベル付けと、その物体の位置を示すバウンディングボックス情報を取得できる解析とします。
動画内のオブジェクト解析について、下記のような違いがあります。

* [Amazon Rekognition Video](https://aws.amazon.com/jp/rekognition)  
  * [Detecting labels in a video](https://docs.aws.amazon.com/rekognition/latest/dg/labels-detecting-labels-video.html)  
    Amazon RekognitionではDetecting labelsという名前で、ラベル検出の一貫でオブジェクトを検出します。
    元々の解析する目的がオブジェクトのラベル付けとされているようで、バウンディングボックス情報がない、その場面で認識されたオブジェクト（というよりシーン全体のラベル情報）も取得されます（例:City、Road、Buildingなど）  
    その解析結果の一部にバウンディングボックス情報が付く、ラベル付けされたオブジェクトの解析結果があります。
* [Goolge Video AI (Video Intelligence API)](https://cloud.google.com/video-intelligence?hl=ja)
  * [Track objects](https://cloud.google.com/video-intelligence/docs/object-tracking)  
    Video Intelligence APIではTrack objectsという名前で、オブジェクトのトラッキングを検出しています。  
    こちらの場合は、元々の解析する目的がオブジェクトのトラッキングとなっている為、検出された結果には、オブジェクトのラベルを付けとバウンディングボックス情報が含まれています。

## Aamazon Rekognitionの解析結果

Amazon RekognitionではPythonのSDKを使用した[前々回](https://qiita.com/TechSavvy_24/items/62da90a5f126b4775fcc)の最後のPythonスクリプトで結果を取得すると最初からJSON形式で解析結果を取得できます。

### JSONの構造

基本構造は以下のようになっています。
解析を実行した動画ファイルのメタ情報があり、ラベル検出結果は `Labels` キーの配列として格納されます。

* JobStatus: 解析のステータス、正常に終了している場合は `SUCCEEDED` となります
* VideoMetadata: コーデックや動画の長さ、フレームレート、動画の縦横のサイズなどのメタ情報
* NextToken: 解析結果が1つのファイルに収まらない場合のNextTokenが格納されます
* Labels: 検出したLabelの情報の配列です。詳細は後述します
* LabelModelVersion: モデルのバージョン
* JobId: 実行したジョブのID
* Video: 解析した動画のファイルの情報
  * S3Object: S3のオブジェクト
    * Bucket: バケット名
    * Name: ファイル名
* GetRequestMetadata: 解析結果のソート情報など
* ResponseMetadata: レスポンスのメタ情報

```json:基本構造
{
  "JobStatus": "SUCCEEDED",
  "VideoMetadata": {
    "Codec": "h264",
    "DurationMillis": 60000,
    "Format": "QuickTime / MOV",
    "FrameRate": 25.0,
    "FrameHeight": 1080,
    "FrameWidth": 1920,
    "ColorRange": "LIMITED"
  },
  "NextToken": "<NextToken>",
  "Labels": [
    ... 
  ],
  "LabelModelVersion": "3.0",
  "JobId": "<JobId>",
  "Video": {
    "S3Object": {
      "Bucket": "<BucketName>",
      "Name": "<FileName>"
    }
  },
  "GetRequestMetadata": {
    "SortBy": "TIMESTAMP",
    "AggregateBy": "TIMESTAMPS"
  },
  "ResponseMetadata": {
    "RequestId": "<RequesId>",
    "HTTPStatusCode": 200,
    "HTTPHeaders": {
      "x-amzn-requestid": "<RequesId>",
      "content-type": "application/x-amz-json-1.1",
      "content-length": "202247",
      "date": "Wed, 24 Jul 2024 03:07:02 GMT"
    },
    "RetryAttempts": 0
  }
}

```

Labelsの配列の個々の要素の構造は以下のようになっています。
* Timestamp: オブジェクトを検出した時間(ミリ秒)
* Label: 上記のTimestampで検出したラベルの詳細
  * Name: 検出したラベル名
  * Confidence: 検出結果の信頼度
  * Instances: バウンディングボックスなどの情報が格納される
    * BoundingBox: バウンディングボックス情報  
      * Width: 横幅
      * Height: 縦幅
      * Left: 横軸の開始位置
      * Top: 縦軸の開始位置
* Parents: ラベルの親子関係がある場合、親のラベルを表示します
* Aliases: ラベルの別名
* Categories: ラベルのカテゴリー名

```json:Labels構造
    {
      "Timestamp": 0,
      "Label": {
        "Name": "Alley",
        "Confidence": 53.941802978515625,
        "Instances": [],
        "Parents": [
          {
            "Name": "City"
          },
        ...
        ],
        "Aliases": [
          {
            "Name": "Alleyway"
          }
        ],
        "Categories": [
          {
            "Name": "Buildings and Architecture"
          }
        ]
      }
    },
    ...
    {
      "Timestamp": 1000,
      "Label": {
        "Name": "License Plate",
        "Confidence": 66.8211441040039,
        "Instances": [
          {
            "BoundingBox": {
              "Width": 0.027202924713492393,
              "Height": 0.027604788541793823,
              "Left": 0.5820265412330627,
              "Top": 0.6004706025123596
            },
            "Confidence": 69.17720794677734
          }
        ],
        "Parents": [
          {
            "Name": "Transportation"
          },
          ...
        ],
        "Aliases": [],
        "Categories": [
          {
            "Name": "Vehicles and Automotive"
          }
        ]
      }
    },
    ...
```
### バウンディングボックス情報から動画上の位置へ変換

BoundingBoxの情報から実際の動画上のピクセルに変換するには以下のように計算します。
左上、右上、右下、左下の各点を(x1,y1)、(x2,y2)、(x3,y3)、(x4,y4)とします。

```
x1 = Left
y1 = Top

x2 = Left + Width
y2 = Top

x3 = Left + Width
y3 = Top + Height

y4 = Left
y4 = Top + Height
```

この各点のx1～x4に動画の横幅ピクセル、y1～y4に縦幅ピクセルを掛けると実際の動画上の位置となります。

```
(x1*1920, y1*1080)、(x2*1920,y2*1080)、(x3*1920,y3*1080)、(x4*1920,y4*1080)
```

## Goolge Video AI (Video Intelligence API)の解析結果

Video Intelligence APIではPythonのSDKで結果を取得すると、ProtoBuf(?)で取得されますので[前回](https://qiita.com/TechSavvy_24/items/a2f5c69061ae572a72a7)の最後のPythonスクリプトで
JSON形式に変換して解析結果を保存します。  
Video Intelligence APIの場合はAmazon Rekognitionの様にNextTokenといった機能が無いため、解析結果を1つのJSONファイルとして保存します。よって、動画ファイルの長さによっては数GBの大きなJSONファイルになりますので取り扱いが難しくなる場合がります。

### JSONの構造

* annotation_results: 全ての結果が含まれているキーです
  * input_uri: 解析した動画ファイルのGoogle Cloud Storageに保管したファイルのURI
  * segment: ファイルの時間情報
    * start_time_offset: 開始時間 (秒)
    * end_time_offset: 終了時間 (秒)
  * object_annotations: オブジェクトトラッキングの解析結果
    * entity: 検出したオブジェクト対象
      * entity_id: 検出したオブジェクトの固有ID
      * description: オブジェクト名
      * language_code: 言語コード
    * frames: 検出したオブジェクトの情報の配列です。詳細は後述します。
    * segment: 検出されたオブジェクトのトラッキング中の時間情報
      * start_time_offset: 開始時間 (秒)
      * end_time_offset: 終了時間 (秒)
    * confidence: 検出結果の信頼度
    * version: バージョン情報と思われますが詳細不明

```json:基本構造
{
  "annotation_results": [
    {
      "input_uri": "<InputUri>",
      "segment": {
        "start_time_offset": "0s",
        "end_time_offset": "250.348309s"
      },
      "object_annotations": [
        {
          "entity": {
            "entity_id": "/m/01g317",
            "description": "person",
            "language_code": "en-US"
          },
          "frames": [
          ...
          ],
          "segment": {
            "start_time_offset": "0s",
            "end_time_offset": "67.148720s"
          },
          "confidence": 0.93874115,
          "version": ""
        },
        ...
      ],
      "segment_label_annotations": [],
      "segment_presence_label_annotations": [],
      "shot_label_annotations": [],
      "shot_presence_label_annotations": [],
      "frame_label_annotations": [],
      "face_annotations": [],
      "face_detection_annotations": [],
      "shot_annotations": [],
      "speech_transcriptions": [],
      "text_annotations": [],
      "logo_recognition_annotations": [],
      "person_detection_annotations": []
    }
  ]
}
```

framesの配列の個々の要素の構造は以下のようになっています。
* normalized_bounding_box: バウンディングボックス情報
  * left: 横軸の開始位置
  * top: 縦軸の開始位置
  * right: 横軸の終了位置
  * bottom: 縦軸の終了位置
* time_offset: 検出された時間 (秒)

```json:frames構造
            {
              "normalized_bounding_box": {
                "left": 0.3962229,
                "top": 0.20325422,
                "right": 0.4621494,
                "bottom": 0.5442413
              },
              "time_offset": "0s"
            },
            ...
```

### バウンディングボックス情報から動画上の位置へ変換

BoundingBoxの情報から実際の動画上のピクセルに変換するには以下のように計算します。
左上、右上、右下、左下の各点を(x1,y1)、(x2,y2)、(x3,y3)、(x4,y4)とします。

```
x1 = left
y1 = top

x2 = right
y2 = top

x3 = right
y3 = bottom

x4 = left
y4 = bottom

```

この各点のx1～x4に動画の横幅ピクセル、y1～y4に縦幅ピクセルを掛けると実際の動画上の位置となります。

```
(x1*1920, y1*1080)、(x2*1920,y2*1080)、(x3*1920,y3*1080)、(x4*1920,y4*1080)
```

## まとめ

以上、2つのAI動画解析サービスでオブジェクト(ラベル)検出を使用してみました。
それぞれの特徴があり、現状の汎用AI動画解析サービスがどのようなものか確認いただけたかと思います。

今回は行っていませんがAIの解析結果のバウンディングボックス情報から、OpenCVなどを使用し実際にAIが検出した物体にバウンディングボックスをつけた動画や静止画像作成し、結果を確認したりすることもできますので、ぜひ試してみて下さい。

また、今回はラベル付けを行う物体認識の解析を使用しましたが、その他にも人物をトラッキングするものや、顔検出、テキスト検出など双方のサービスに特徴の異なるAI解析がありますので、それを試してみるのも良いと思います。