Video Intelligence APIで動画の物体検出をする

# やること
動画のどのフレームのどの位置にどんなものが映っているかを特定する。
googleの[Video Intelligence API](https://cloud.google.com/video-intelligence?hl=ja)を使用して動画から物体検出を行う。
本記事で紹介するコードは[公式の入門ガイド](https://cloud.google.com/video-intelligence/docs/how-to?hl=ja)をベースにした実装になっている。

# 準備
## Video Intelligence API
公式ガイドの[APIに対する認証](https://cloud.google.com/video-intelligence/docs/how-to?hl=ja#api-%E3%81%AB%E5%AF%BE%E3%81%99%E3%82%8B%E8%AA%8D%E8%A8%BC)までを完了してサービスアカウントキーファイルを取得しておく。

## google colaboratory
実装と結果の確認はgoogle colaboratoryを使用する。

[こちらの記事](https://qiita.com/sosuke/items/533909d31244f986ad47#%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%82%A2%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88%E3%81%AE%E8%AA%8D%E8%A8%BC%E6%83%85%E5%A0%B1%E3%82%92%E3%82%A2%E3%83%83%E3%83%97%E3%83%AD%E3%83%BC%E3%83%89%E3%81%99%E3%82%8B)にあるようにサービスアカウントキーファイルをcolaboratoryにアップロードしておく。
毎回やるのが面倒な場合はマウントするgoogle driveに入れておく方法もあるがうっかり共有してしまわないように注意が必要である。

## 解析対象の画像
Video Intelligence API では

- GCPのStorageに保存してある動画ファイル
- ローカルに保存してある動画ファイル

を解析することができる。GCPのStorageに保存してある動画ファイルを使用する場合はAPIの料金の他、Storageに使用料金がかかるので、ちょっと試して見る程度であればローカルがおすすめ。

今回は「ローカルに保存してある動画ファイル」を解析する手法で行うため、google driveに解析したい動画を保存しておき、colaboratoryにドライブをマウントしておく。
ドライブのマウントはcolaboratoryの左側のペインから可能。
![スクリーンショット 2020-03-29 11.28.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/220881/91faa29d-0d72-b205-3251-8bd182f255ad.png)

なお、動画を用意する場合はVideo Intelligence APIの[利用料金](https://cloud.google.com/video-intelligence/pricing?hl=ja)について考慮する。

## 【補足】Video Intelligence APIの利用料（2020年3月現在）
Video Intelligence APIは解析する動画の長さ（尺）に応じて従量課金される。
長さは分単位で計算され、1分未満は切り上げになるので以下の3つのパターンでアノテーションを行った場合全て同じ利用料となる。

1. 2分30秒の動画を1つ
1. 1分30秒の動画と30秒の動画を1つずつ
1. 30秒の動画を3つ

各アノテーションの料金は以下の通り

| 機能 | 最初の 1,000 分 | 1,000 分超 |
|:--|:--|:--|
| ラベル検出 | 無料 | $0.10/分 |
| ショット検出 | 無料 | $0.05/分、ラベル検出を利用している場合は無料 |
| 不適切コンテンツ検出 | 無料 | $0.10/分 |
| 音声文字変換 | 無料 | $0.048/分（音声文字変換の課金対象は対応言語の en-US のみ） |
| オブジェクト トラッキング | 無料 | $0.15/分 |
| テキスト検出 | 無料 | $0.15/分 |
| ロゴ検出 | 無料 | $0.15/分 |
| Celebrity recognition | 無料 | $0.10/分 |

# 実装
## videointelligenceを使用する準備
### videointelligenceのクライアントをインストールする
```
!pip install -U google-cloud-videointelligence
```
### videointelligenceのクライアントを作成する
まず、[APIに対する認証](https://cloud.google.com/video-intelligence/docs/how-to?hl=ja#api-%E3%81%AB%E5%AF%BE%E3%81%99%E3%82%8B%E8%AA%8D%E8%A8%BC)で取得したサービスアカウントキーファイルを使用して認証を行う。
`service_account_key_name`はcolaboratoryにアップロードしたサービスアカウントキーファイルのパス。

```python
import json
from google.cloud import videointelligence
from google.oauth2 import service_account

# APIの認証
service_account_key_name = "{YOUR KEY.json}"
info = json.load(open(service_account_key_name))
creds = service_account.Credentials.from_service_account_info(info)

# クライアントを作成
video_client = videointelligence.VideoIntelligenceServiceClient(credentials=creds)
```

## APIを実行する
まず動画をドライブから読み込む。

```python
# 処理対象の動画を指定して読み込む
import io

path = '{YOUR FILE PATH}'
with io.open(path, 'rb') as file:
    input_content = file.read()
```

次にAPIを実行して結果を取得する

```python
features = [videointelligence.enums.Feature.OBJECT_TRACKING]
timeout = 300
operation = video_client.annotate_video(input_content=input_content, features=features, location_id='us-east1')

print('\nProcessing video for object annotations.')
result = operation.result(timeout=timeout)
print('\nFinished processing.\n')
```

## 結果の確認
### 検出されたオブジェクトの一覧を表示する
jupyter notebookはpandasのDataFrameを良い感じに勝手に描画してくれるので、[レスポンス](https://cloud.google.com/video-intelligence/docs/object-tracking?hl=ja#vision-object-tracking-gcs-protocol)から必要な情報だけ抜き取ってDataFrameを生成する。

今回はレスポンスの`object_annotations`から以下のものを取得する。

| カラム名 | 内容 | ソース |
|:--|:--|:--|
| Description | オブジェクトの説明（名前） | entity.description |
| Confidence | 検出の信頼性 | confidence |
| SegmentStartTime | オブジェクトが映っているセグメントの開始時間 | segment.start_time_offset |
| SegmentEndTime | オブジェクトが映っているセグメントの終了時間 | segment.end_time_offset |
| FrameTime| オブジェクトが検出されたフレームが動画の先頭から何秒目にあるか | frames[i].time_offset |
| Box{XXX} | オブジェクトのbounding boxの各辺の座標を100分率にしたもの | frames[i].normalized_bounding_box |

```python
# 検出されたobjectを一覧表示する
import pandas as pd

columns=['Description', 'Confidence', 'SegmentStartTime', 'SegmentEndTime', 'FrameTime', 'BoxLeft', 'BoxTop', 'BoxRight', 'BoxBottom', 'Box', 'Id']
object_annotations = result.annotation_results[0].object_annotations
result_table = []
for object_annotation in object_annotations:
    for frame in object_annotation.frames:
        box = frame.normalized_bounding_box
        result_table.append([
                object_annotation.entity.description,
                object_annotation.confidence,
                object_annotation.segment.start_time_offset.seconds + object_annotation.segment.start_time_offset.nanos / 1e9,
                object_annotation.segment.end_time_offset.seconds + object_annotation.segment.end_time_offset.nanos / 1e9,
                frame.time_offset.seconds + frame.time_offset.nanos / 1e9,
                box.left,
                box.top,
                box.right,
                box.bottom,
                [box.left, box.top, box.right, box.bottom],
                object_annotation.entity.entity_id
        ])
        # 膨大になるので、とりあえず各セグメントの最初のframeだけ
        break

df=pd.DataFrame(result_table, columns=columns)
pd.set_option('display.max_rows', len(result_table))
# Confidenceでソートして表示
df.sort_values('Confidence', ascending=False)
```

実行すると以下のような結果が得られる。
![スクリーンショット 2020-03-29 15.41.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/220881/71522db7-f480-b354-d7c2-5cc9e848913b.png)


### オブジェクトが検出されたフレームを静止画で一覧表示する
まず、上述の`time_offset`の情報をもとに動画からフレームを抜き出す。
動画から静止画の取得は`openCV`を使用する。切り出す静止画をフレームで指定する必要があるため、動画のFPSと`time_offset`（秒）からおおよそのフレーム数を算出する。

```python
import cv2

images = []
cap = cv2.VideoCapture(path)

if cap.isOpened():
    fps = cap.get(cv2.CAP_PROP_FPS)
    for sec in df['FrameTime']:
        # fpsと秒から何フレーム目かを計算する
        cap.set(cv2.CAP_PROP_POS_FRAMES, round(fps * sec))
        ret, frame = cap.read()
        if ret:
            images.append(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
```

次に、`openCV`の`rectangle`で切り出したそれぞれの静止画に映っているオブジェクトに枠を付ける。
`rectangle`は描画したい長方形の左上と右下の頂点を指定する必要があるためこの2点を得る必要がある。
APIから返却される`normalized_bounding_box`の中には4辺の情報（`left`,`top`,`right`,`bottom`）があり、例えば以下の画像で`person`の位置を示すboxの`left`の値は` l（画像の左端からboxの左端までの幅） / width（画像全体の幅）`なので`left`の値から頂点1（`pt1`）のx座標を求める場合は`width * left`と逆算すれば良い。
メソッドを適当に用意しておく。

<img width="631" alt="IMG_3397のコピー.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/220881/cce2b7c4-6b71-5f26-a029-211e2810ff40.jpeg">

```python
# 画像上の座標を求める
def ratio_to_pics(size_pics, ratio):
    return math.ceil(size_pics * ratio)

# box から左上と右下の頂点を得る
def rect_vertex(image, box):
    height, width  = image.shape[:2]
    return[
        (
            ratio_to_pics(width, box[0]), ratio_to_pics(height, box[1])
        ),
        (
            ratio_to_pics(width, box[2]), ratio_to_pics(height, box[3])
        )
    ]
```

上記のメソッドを使用して枠の頂点の位置を計算しながら、実際に枠を画像に書き込んでいく。
```python
boxed_images = []
color = (0, 255, 255)
thickness = 5
for index, row in df.iterrows():
    image = images[index]
    boxed_images.append(cv2.rectangle(image,  *rect_vertex(image, row.Box), color,  thickness = thickness))
```

最後に各画像にDescriptionとConfidenceを添えて表示する。
なお、動画の尺や検出されたオブジェクトの数によってはすべて表示すると時間がかかるためConfidenceにしきい値を設けている。

```python
import math
import matplotlib.pyplot as plt

# confidenceで適当に足切りする
min_confidence = 0.7

# figureの諸々を設定する
col_count = 4
row_count = math.ceil(len(images) / col_count)
fig = plt.figure(figsize = (col_count * 4, row_count * 3), dpi = 100)
num = 0

# 静止画を並べて表示する
for index, row in df.iterrows():
    if row.Confidence < min_confidence:
        continue
    num += 1
    fig.add_subplot(row_count, col_count, num, title = '%s : (%s%s)' % (row.Description, round(row.Confidence * 100, 2), '%'))
    plt.imshow(boxed_images[index], cmap='gray')
    plt.axis('off')
```
実行すると以下のような結果が得られる。
![スクリーンショット 2020-03-29 15.46.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/220881/78be8ef4-a5a0-4597-e0cc-72b06cbba433.png)


# 参考
[Colaboratory 上から Cloud Video Intelligence API を使い、動画を解析した結果を表示する。](https://qiita.com/sosuke/items/533909d31244f986ad47#%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%82%A2%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88%E3%81%AE%E8%AA%8D%E8%A8%BC%E6%83%85%E5%A0%B1%E3%82%92%E3%82%A2%E3%83%83%E3%83%97%E3%83%AD%E3%83%BC%E3%83%89%E3%81%99%E3%82%8B)
[公式の入門ガイド](https://cloud.google.com/video-intelligence/docs/how-to?hl=ja)