Edited at

OpenCVのカメラ読み込みを高速化する


この記事について

Raspberry PiにUSBカメラを接続してOpenCVで読み込むと、速度(FPS)が非常に遅いことがあります。また、PiCameraを使っても、解像度が高いと速度が出ないことがあります。

これを高速化します。対策は、単に圧縮フォーマットを指定するだけです。


  • 速度が必要な場合は、非圧縮フォーマットじゃなくて、H264フォーマットなどを指定しましょう


    • cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('H', '2', '6', '4'));



  • H264非サポートなWebカメラの場合には、MJPGが使える可能性があります。だけど、MJPGだとCPUパワーを結構使います


環境


  • Raspberry Pi 3 B+


    • 2019-07-10-raspbian-buster

    • Python3 + OpenCV 3.2.0 (今はaptでインストール可能になってた sudo apt install python3-opencv )

    • Pi Camera v2.1



  • USBカメラ


    • Logicool C270m



 * Jetson Nanoでもほぼ同じ結果でした。


測定方法

カメラはカバーせずに、明るいシーンを撮影(←重要)。

これをやらないと、シャッタースピードの影響を受けるためか、キャプチャ時間(cap.read() )が遅くなることがあります。


フォーマット指定と速度


フォーマットの指定方法

VideoCapture を開いた後に、CAP_PROP_FOURCC プロパティにfourcc形式でフォーマットを指定できます。

Pythonだと以下のようになります。C++でも同様です。

cap = cv2.VideoCapture(0)

cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('H', '2', '6', '4'));


処理時間の比較

各カメラで、フォーマットと処理時間(cap.read() )の関係を比較しました。


PiCamera V2.1 (1920x1080)

フォーマット
BGR3
MJPG
H264

Capture時間 [msec]
249
72
34

CPU使用率 [%]
5
27
15

PiCamera V2.1の場合、デフォルトはBGR3フォーマットになります。1920x1080で読み込もうとするとめちゃくちゃ遅くなります。

MJPGだと少し速くなり、H264にすると、ほぼリアルタイムな30fpsが出ます。

CPU使用率はFPSが異なるので、あまり意味はない数字です。


PiCamera V2.1 (1280x720)

フォーマット
BGR3
MJPG
H264

Capture時間 [msec]
33
33
33

CPU使用率 [%]
13
27
8

1280x720だとどのフォーマットでもほぼリアルタイムな30fpsが出ます。

1920x1080に比べて速くなったのは、おそらくイメージセンサの動作モードが違うためです (https://picamera.readthedocs.io/en/release-1.12/fov.html )

CPU使用率を見ると、H264が最も少ないという面白い結果になりました。おそらくエンコード処理はハードウェアで行い、データ転送量もBGRに比べて少ないためだと思われます。


USB Camera (1280x720)

フォーマット
YUYV
MJPG
H264

Capture時間 [msec]
135
33
-

CPU使用率 [%]
10
23
-

今回使ったLogicool C270mだと、デフォルトフォーマットはMJPGでした。なので、意識しないと遅いことには気づかないのですが、CPU使用率が高いです。あえてYUYVフォーマットを使うと当然遅くなります。H264は非サポートでした。

確か、上位機種のC920rはデフォルトがYUYVだったので、デフォルトのままだと遅いので注意が必要です。

C270mでも、他のアプリやコマンドからフォーマットを変えたら、その設定は残るので、コード内でフォーマット指定した方が無難です。

(実は本記事は、これに気付かずに遅いな~とハマったのが発端です。。。)

ちなみに、対応フォーマットはv4l2-ctl -d /dev/video1 --list-formats-ext で確認可能です。


テストコード


# -*- coding: utf-8 -*-
import sys
import time
import cv2

def decode_fourcc(v):
# https://amdkkj.blogspot.com/2017/06/opencv-python-for-windows-playing-videos_17.html
v = int(v)
return "".join([chr((v >> 8 * i) & 0xFF) for i in range(4)])

cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1920)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 1080)
# cap = cv2.VideoCapture(1)
# cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
# cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)

# cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('B', 'G', 'R', '3'));
# cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('M', 'J', 'P', 'G'));
cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('H', '2', '6', '4'));
# cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('Y', 'U', 'Y', 'V'));
print(decode_fourcc(cap.get(cv2.CAP_PROP_FOURCC)))

if cap.isOpened() == False:
print("cannot open")
sys.exit(1)
_, img = cap.read() # dummy

cnt = 0
time_start = time.time()

try:
while True:
cnt += 1
print(cnt)
_, img = cap.read()
# cv2.imshow('image', img)
# key = cv2.waitKey(1)
# if key == 27: # ESC
# break
except KeyboardInterrupt:
print("Exit loop by ctrl-c")

cap.release()
cv2.destroyAllWindows()
time_end = time.time()
print ("Capture time: {0}".format((time_end - time_start) * 1000 / cnt) + "[msec]")


[おまけ]シャッタースピードとキャプチャ処理時間

おそらくOpenCVの仕様として、露光時間(≒ 1/シャッタースピード)だけcap.read() で待たされます。

例えば、上記コードで、

_, img = cap.read()

のかわりに

_, img = cap.read()

time.sleep(0.01)

として、cap.read() の処理時間(キャプチャ処理時間)だけを測定したら20msecになりました。

さらに、time.sleep(0.1) として待ち時間をさらに増やしたら、キャプチャ処理時間は10~20msecにバラついたので、cap.read() を呼んだタイミングの露光が完了するのを待っているのだと思います。

待たされるのが嫌だという場合には、キャプチャ処理は別スレッドでやった方がよさそうですね。

↓の記事のような感じ。

https://qiita.com/PINTO/items/dd6ba67643bdd3a0e595


念のため追記

圧縮フォーマットを利用したら速度は向上しますが、当然画質は下がります。