nyanpyou Note

主な目的は調べたり作ったりしたプログラミング備忘録(予定)

Raspberry Pi 4でRaspberry Pi Camera Module v2を動かしたいが難航している話

2020/02/25追記:
進展しました。

nyanpyou.hatenablog.com


Raspberry Pi 4とPythonOpenCVを使って、Raspberry Pi Camera Module v2を動かし、30fpsの動画を1分毎に連番で撮影したいのだが、思った通りに撮影ができずに沼にハマっている。
解像度をコード内で指定して撮影すると、出来上がった動画が早送りになってしまうのだ。具体的には、1分撮影したはずが、デスクトップPCに持っていって動画再生ソフト(VLCメディアプレーヤーなど)で動画を確認すると、15秒とかになっていたりする。

使用コード

#Python3.7.3
#OpenCV3.4.4
import numpy as np
import cv2
import time

windowname = "Camera View"
i = 1

camera_number = input("カメラ番号を入力してください(通常は0)\n")
cap = cv2.VideoCapture(int(camera_number))
if(cap==None):
    print("カメラが見つかりません")

record = False
cap.set(cv2.CAP_PROP_FRAME_WIDTH,640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT,480)
#cap.set(cv2.CAP_PROP_FRAME_WIDTH,1280)
#cap.set(cv2.CAP_PROP_FRAME_HEIGHT,720)
cap.set(cv2.CAP_PROP_FPS, 30)
size = (int(cap.get(3)),
        int(cap.get(4)))
fourcc = cv2.VideoWriter_fourcc("D","I","V","X")
cv2.namedWindow(windowname)
print("sで録画開始、qで録画終了、eでプログラム終了\n")

while(cap.isOpened()):
        # カメラ映像が取得できなくなったら止める
        if(cap==None):
            print("カメラが見つかりません\n")
            break
        ret, frame = cap.read()
        cv2.imshow(windowname, frame)
        
        if(record):
            rec.write(frame)
            keyvalue = cv2.waitKey(1) & 0xFF
            now_time = time.time()
            #録画開始から60秒経過したら動画を閉じて、新たな動画で書き込みを開始する
            if(now_time-start_time>=60):
                rec.release()
                i+=1
                rec = cv2.VideoWriter('record{}.avi'.format(i), fourcc, 30, size)
                start_time = time.time()
        else:
            #録画停止中はカメラ映像を描画
            cv2.imshow(windowname, frame)
            keyvalue = cv2.waitKey(1) & 0xFF 

        if(keyvalue==ord("e")): #ord()で文字列をAsciiコードに変換
            if(record):
               print("録画終了") 
            break
        elif(keyvalue==ord("s") and not record):
            i = 1
            #ビデオファイル書き込みの設定
            rec = cv2.VideoWriter('record{}.avi'.format(i), fourcc, 30, size)
            start_time = time.time()
            print("録画開始")
            record = True
        elif(keyvalue==ord("q") and record):
            rec.release()
            print("録画終了")
            record = False

# Release everything if job is finished
if(record):
    rec.release()
cv2.destroyAllWindows()
print("プログラムを終了します")

解決法の模索

qiita.com まずこちらの記事のテストコードを試させて貰ったが、

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

として実行すると砂嵐が表示されてカメラ映像が映らない。BGR3、MJPG、YUYVだと映る。なんでや…

f:id:nyanpyou106:20200129123842p:plain
原因不明砂嵐
H264コーデックが使えるように2点試したが、どちらも効果はなかった。
1. OpenCVをアンインストールし、

pip install opencv-contrib-python

で入れ直し。contribには商用利用時にライセンスが必要なモジュールも含まれているため、そこで解決しないかを狙った。
2. こちら( python - OpenCV: FFMPEG: tag 0x34363268/'h264' is not supported with codec - Stack Overflow )を参考に、

sudo apt-get install libx264-dev

のインストール。

この時点で、H264コーデックが動かない件は、今の知識で追いかけると無限にハマりそうな雰囲気を感じたため、ここで一旦放置することにした。
知る必要があるのは、どの条件なら望む動画が撮影できるのか

他の人が書いた動画撮影プログラムでどう動作するか(自分が変なことを書いていないか)確認したかったので、試してみた。参考にしたのはこのページ。

www.pyimagesearch.com

「正しいコーデックと拡張子の組み合わせを見つけるのに時間がかかった。わからなかったらMJPGと.aviの組み合わせでまず試しなさい。」と書かれている。Raspberry Pi で動画保存が問題なくできることを確認しているらしい。問題は少しずつ減らすに限るため、以後はひとまずこの組み合わせをデフォルトと考えることにした。掲載されているコードを丸々コピーして試すことにする。このコードは、撮影した映像をRGB成分に分けて4分割して動画にするようになっているので、元動画を保存するように変更したものも作ってみた。

#Python3.7.3
#OpenCV3.4.4
# import the necessary packages
from __future__ import print_function
from imutils.video import VideoStream
import numpy as np
import argparse
import imutils
import time
import cv2

# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-o", "--output", required=True,
    help="path to output video file")
ap.add_argument("-p", "--picamera", type=int, default=-1,
    help="whether or not the Raspberry Pi camera should be used")
ap.add_argument("-f", "--fps", type=int, default=20,
    help="FPS of output video")
ap.add_argument("-c", "--codec", type=str, default="MJPG",
    help="codec of output video")
args = vars(ap.parse_args())

# initialize the video stream and allow the camera
# sensor to warmup
print("[INFO] warming up camera...")
vs = VideoStream(usePiCamera=args["picamera"] > 0).start()
time.sleep(2.0)

# initialize the FourCC, video writer, dimensions of the frame, and
# zeros array
fourcc = cv2.VideoWriter_fourcc(*args["codec"])
writer = None
(h, w) = (None, None)

# loop over frames from the video stream
while True:
    # grab the frame from the video stream and resize it to have a
    # maximum width of 300 pixels
    frame = vs.read()
    frame = imutils.resize(frame, width=300)

    # check if the writer is None
    if writer is None:
        # store the image dimensions, initialize the video writer,
        # and construct the zeros array
        (h, w) = frame.shape[:2]
        writer = cv2.VideoWriter(args["output"], fourcc, args["fps"],
            (w, h), True)

    # write the output frame to file
    writer.write(frame)
    # show the frames
    cv2.imshow("Frame", frame)
    key = cv2.waitKey(1) & 0xFF

    # if the `q` key was pressed, break from the loop
    if key == ord("q"):
            break

# do a bit of cleanup
print("[INFO] cleaning up...")
cv2.destroyAllWindows()
vs.stop()
writer.release()

状況整理

ここで一度状況を整理するため、各場合について動画を撮影し比較を行う。機械式のストップウォッチを30秒間撮影し、出来上がった動画のコマ数を調べて、撮影時のカメラのfpsを計算する。コマ数の集計は、こちらの動画を静止画に分解するプログラムを用い、ストップウォッチのスタート時と30秒経過時の静止画を目視で決定して行う。撮影以外の作業はデスクトップPCで行う。以後、自作コードは「自作」、pyimageserchの記事のコードは「四分割」、pyimageserchのコードを変更して作ったコードを「改変」と呼ぶ。また、自作コードは「30fps」を、四分割と改変コードは「20fps」を指定していることに注意する。

  1. 自作・DIVX・avi・解像度640×480指定の場合
  2. 自作・DIVX・avi・解像度1280×720指定の場合
  3. 自作・MJPG・avi・解像度640×480指定の場合
  4. 自作・MJPG・avi・解像度1280×720指定の場合
  5. 四分割・MJPG・avi・元コードそのまま(width=300)の場合
  6. 改変・MJPG・avi・width=300の場合
  7. 改変・MJPG・avi・width=640の場合
  8. 改変・MJPG・avi・width=1280の場合

これらの場合について撮影を行った。

結果

  1. 自作・DIVX・avi・解像度640×480指定の場合
    30秒:36~933フレーム→30fps
  2. 自作・DIVX・avi・解像度1280×720指定の場合
    30秒:13~416フレーム→13fps
  3. 自作・MJPG・avi・解像度640×480指定の場合
    30秒:17~724フレーム→24fps
  4. 自作・MJPG・avi・解像度1280×720指定の場合
    30秒:10~273フレーム→9fps
  5. 四分割・MJPG・avi・元コードそのまま(width=300)の場合
    30秒:17~696フレーム→23fps
  6. 改変・MJPG・avi・width=300の場合
    30秒:57~1963フレーム→64fps
  7. 改変・MJPG・avi・width=640の場合
    30秒:26~746フレーム→24fps
  8. 改変・MJPG・avi・width=1280の場合
    30秒:7~223フレーム→7fps

自作コードでコーデックDIVX、保存形式aviを選択し、640×480の解像度を指定した時のみ、カメラが指定したfpsで動作することが分かった。
四分割と改変width=640の場合もおおよそ指定した通りのfpsだが、少し大きい値になった。動画再生ソフトで動画を再生してみるとやはり少し早送りに見える。

考察

結局どうやったら640×480以上の解像度で30fpsの動画が撮影できるのかは不明のまま。仕様上は1920×1080までは30fpsで撮影が可能らしいが…

Buy a Camera Module V2 – Raspberry Pi

考えられる原因で一番ありそうなのは、Raspberry Piが処理しきれなくなっている事だろうか。動画のリアルタイム表示をやめれば、負荷が軽減されてより大きな解像度でも30fpsで撮影可能かもしれない。
しかし、この問題に割ける時間がこれ以上取れなくなったため、ここで一端打ち止めとする。


解決の糸口を探して参考にさせて頂いたページ

madeinpc.blog.fc2.com

qiita.com

note.nkmk.me