OpenCVのフレームを指定したreadの高速化

#1.はじめに
OpenCVでmp4を使う際にsetでフレーム位置を指定しreadするのが遅い現象がありました。
動画を一定間隔(0.5秒ごと)で取得する処理に時間がかかり過ぎていたため、その対策内容を記載します。
(他により良い手段があればコメント欄で教えて頂けると嬉しいです。）

#2.読者対象
・動画データを1フレームより大きい間隔で利用する方。(1フレームごとであればreadのみが適切)

#3.結論
不要なフレームはgrabで飛ばし、必要なフレームをreadで取得します。

36%の時間短縮が可能でした。


| 条件| 処理時間(秒) |
|:-----------------|:------------------|
| set + read|10.131|
| grab + read|6.491 |

#4.検証
検証対象：
　[Luis Fonsi - Despacito ft. Daddy Yankee](https://www.youtube.com/watch?v=kJQP7kiw5Fk)
　720p 30fps
　動画時間　4分41秒

OpenCV::VideoCaptureを使い、1秒ごとにフレームを取得する場合に
1. set + read
2. grab + read

の2パターンの処理時間を計測。

検証環境
Windows10 64bit
Python 3.6.9
OpenCV 4.1.1
numpy 1.16.5


---
**1. set + read**
結果　10.131 seconds

```ruby:set+read
import cv2


movie_path = "./movie/Luis Fonsi - Despacito ft. Daddy Yankee.mp4"
video = cv2.VideoCapture(movie_path)

# フレーム数を取得
frame_count = int(video.get(7))

# フレームレート(1フレームの時間単位はミリ秒)の取得
frame_rate = int(video.get(5))

# n秒ごと
n = 1

# 取得フレーム総数
frame_all = int((frame_count / frame_rate) / n)

for i in range(frame_all):  # 取得フレーム総数分回す
    frame_set = frame_rate * n * i
    if frame_set > frame_count:
        break

    video.set(1, frame_set)
    ret, work_frame = video.read()
    if ret is False:
        break

    # ここにフレームへの処理が入る

video.release()
```

```ruby:set+read結果
         569 function calls in 10.131 seconds

   Ordered by: internal time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
      281    8.791    0.031    8.791    0.031 {method 'set' of 'cv2.VideoCapture' objects}
      281    1.248    0.004    1.248    0.004 {method 'read' of 'cv2.VideoCapture' objects}
        1    0.087    0.087   10.131   10.131 time_check.py:4(test)
        1    0.006    0.006    0.006    0.006 {method 'release' of 'cv2.VideoCapture' objects}
        1    0.000    0.000   10.131   10.131 <string>:1(<module>)
        1    0.000    0.000   10.131   10.131 {built-in method builtins.exec}
        2    0.000    0.000    0.000    0.000 {method 'get' of 'cv2.VideoCapture' objects}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
```

---

**2. grab + read**

結果　6.491 seconds

```ruby:grab+read
import cv2


movie_path = "./movie/Luis Fonsi - Despacito ft. Daddy Yankee.mp4"
video = cv2.VideoCapture(movie_path)

# フレーム数を取得
frame_count = int(video.get(7))

# フレームレート(1フレームの時間単位はミリ秒)の取得
frame_rate = int(video.get(5))

# n秒ごと
n = 1

# read間隔の設定
read_interval = int((frame_rate * n) - 1)

for i in range(frame_count):  # フレーム数分回す
    ret = video.grab()
    if ret is False:
        break

    if i % read_interval is 0:
        ret, work_frame = video.read()
        if ret is False:
            break

        # ここにフレームへの処理が入る

video.release()
```

```ruby:grab+read結果
         8437 function calls in 6.491 seconds

   Ordered by: internal time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     8149    5.154    0.001    5.154    0.001 {method 'grab' of 'cv2.VideoCapture' objects}
      281    1.232    0.004    1.232    0.004 {method 'read' of 'cv2.VideoCapture' objects}
        1    0.098    0.098    6.490    6.490 time_check_set.py:4(test)
        1    0.006    0.006    0.006    0.006 {method 'release' of 'cv2.VideoCapture' objects}
        1    0.000    0.000    6.490    6.490 <string>:1(<module>)
        1    0.000    0.000    6.491    6.491 {built-in method builtins.exec}
        2    0.000    0.000    0.000    0.000 {method 'get' of 'cv2.VideoCapture' objects}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
```


#5.おわりに

最後まで見てくださりありがとうございました。
今回の処理は約4分の動画に対し3秒程度の短縮というものであまり大きいとは感じませんが、

・長い動画を解析
・レンタルサーバーなどスペックが限られる場面

ではより効果を発揮できると思われます。