1.はじめに
Webカメラの映像にdatetime.datetime.now()で取得した時刻情報を重畳すると時刻の間隔にムラがあることがLチカで始めるテスト自動化(27)矩形の画像を動画のフレームから切り出してファイルに保存するで判明しました。
そこでWebカメラの映像に埋め込まれているタイムスタンプ情報を利用することを考えます。筆者が使用しているOpenCV Ver.4.5.4のVideoCapturePropertiesによるとCAP_PROP_POS_MSEC1を取得すれば良さそうです。併せてフレームの取りこぼしが発生しているかを確認できるようCAP_PROP_POS_FRAMES2も取得します。
2.プログラム
Lチカで始めるテスト自動化(20)複数のカメラ映像の同時録画 5.camera.pyに以下の改修を行います。
2.1 text_over_img
タイムスタンプ情報を引数でもらうようにします。併せて文字の大きさや重畳する座標を調整します。
def text_over_img(self, img, timestamp):
#timestamp = (datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S.%f")[:-3])
videotxt = self.get_video_txt()
cv2.rectangle(img, (5,5), (self.w-5,45), (255,255,255), thickness=-1)
cv2.putText(img, timestamp, ( 10,35), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0,0,0), 1, cv2.LINE_AA)
cv2.putText(img, videotxt, (950,35), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0,0,0), 1, cv2.LINE_AA)
2.2 capture_cam
datetime.datetime.now()を取得しtext_over_img()へ渡すようにします。
def capture_cam(self, filename):
if self.cam == False:
print("CAM Not Ready.")
return False
ret, img = self.cam.read()
if ret == True:
timestamp = (datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S.%f")[:-3])
self.text_over_img(img, timestamp)
cv2.imwrite(filename, img)
return True
else:
return False
2.3 video_record
datetime.datetime.now()、CAP_PROP_POS_FRAMES、CAP_PROP_POS_MSECを取得しtext_over_img()へ渡すようにします。
def video_record(self, filename):
if self.cam == False:
print("CAM Not Ready.")
return False
fourcc = cv2.VideoWriter_fourcc('m','p','4','v')
out = cv2.VideoWriter(filename, fourcc, self.f, (self.w, self.h))
msg = "CAM " + str(self.camera_id) + " : q to quit"
while True:
ret, img = self.cam.read()
pos_frames = str(self.cam.get(cv2.CAP_PROP_POS_FRAMES))
pos_msec = str(self.cam.get(cv2.CAP_PROP_POS_MSEC))
timestamp = (datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S.%f")[:-3]) + "/" + pos_frames + "/" + pos_msec
print(timestamp)
if ret == True:
self.text_over_img(img, timestamp)
out.write(img)
cv2.imshow(msg, img)
if (cv2.waitKey(1) == ord('q')) or (self.get_video_rec() == False):
print("### rec stop ###")
break
2.4 実行例
Webカメラの画像をmov10s.mp4というファイル名で10秒録画します。
open_cam,1,1280,720
rec_start,1,mov10s.mp4
sleep,10
rec_stop,1
python testrunner.py runscript mov10s.csv mov10s_res.csv
print(timestamp)の出力の一部を以下に示します。
##### 2023/03/26 22:43:42 4 #####
4:sleep,10,
2023/03/26 22:43:44.693/21689529.0/722984293.1444
2023/03/26 22:43:44.807/21689533.0/722984426.4777
2023/03/26 22:43:44.881/21689534.0/722984459.8111
2023/03/26 22:43:44.919/21689535.0/722984493.1444
2023/03/26 22:43:44.982/21689537.0/722984559.8111
2023/03/26 22:43:45.043/21689539.0/722984626.4777
3. OpenCVのソースを見てみる
フレーム番号が飛んでいるのはともかく小数点一桁まで表示されているのは違和感がありますね。Webカメラが1枚の画像をインターレース方式で2回に分けて伝送しているとも思えないし、もしそうだとしても0ばかりというのもおかしなところです。そこでOpenCVのソースを見てみます。
3.1 手順
- OpenCV 4.5.4のソースコードをチェックアウトする3
- git clone https://github.com/opencv/opencv.git
- git checkout refs/tags/4.5.4
- クローンしたフォルダをVisual Studio Codeで開く
- CV_CAP_PROP_POS_FRAMESを検索する
- (筆者の環境はmsmfを使用しているので)modules\videoio\src\cap_msmf.cppの当該コードを見る
3.2 cap_msmf.cppの当該コード
cap_msmf.cpp内でCV_CAP_PROP_POS_FRAMESが登場するのは以下の関数です。
- double CvCapture_MSMF::getProperty( int property_id )
- bool CvCapture_MSMF::setProperty( int property_id, double value )
getに対応するコードから該当部分を引用します。
double CvCapture_MSMF::getProperty( int property_id ) const
{
long cVal = 0;
if (isOpen)
switch (property_id)
{
(略)
case CV_CAP_PROP_POS_FRAMES:
return floor(((double)sampleTime / 1e7)* captureFormat.getFramerate() + 0.5);
case CV_CAP_PROP_POS_MSEC:
return (double)sampleTime / 1e4;
(略)
default:
break;
}
return -1;
}
floor()関数で小数点以下を切り捨てしたdouble型の整数が返ってくることが分かりました。double型といっても整数なのでf文字列で小数点以下を割愛します。
#pos_frames = str(self.cam.get(cv2.CAP_PROP_POS_FRAMES))
pos_frames = str(f'{self.cam.get(cv2.CAP_PROP_POS_FRAMES):.0f}')
フレーム番号がスッキリしました。
##### 2023/04/02 16:02:36 4 #####
4:sleep,10,
2023/04/02 16:02:38.188/1884971/62832380.2888
2023/04/02 16:02:38.347/1884976/62832546.9555
2023/04/02 16:02:38.399/1884977/62832580.2888
2023/04/02 16:02:38.436/1884979/62832646.9555
2023/04/02 16:02:38.484/1884980/62832680.2888
2023/04/02 16:02:38.540/1884981/62832713.6221
2023/04/02 16:02:38.571/1884982/62832746.9555
2023/04/02 16:02:38.596/1884983/62832780.2888
2023/04/02 16:02:38.620/1884984/62832813.6221
実使用上はprint(timestamp)をコメントアウトします。
4. タイムスタンプ情報を分析する
録画開始直後を除き、フレーム番号が1ずつ増えていてフレームの取りこぼしが発生していないことが分かります。
フレーム間隔もnow()関数で取得している方は波打っていますがCV_CAP_PROP_POS_MSECで取得している方は録画開始直後を除き33msで安定しています。
5. おわりに
Webカメラの映像に付随するフレーム番号を参照することでフレームの取りこぼしが明確にわかるようになりました。また、フレーム間隔も安定した値が得られました。
付録A. camera.pyのソース
# -*- coding: utf-8 -*-
# camera.py
# python 3.x
#
# This software includes the work that is distributed
# in the Apache License 2.0
# ka's@pbjpkas 2021, 2023
#
import cv2
import datetime
class Camera:
def __init__(self, camera_id):
self.camera_id = camera_id
self.w = 640
self.h = 480
self.f = 30.0
self.video_txt = "null" # Text over Video Frame
self.video_rec = False # Video Recording Thread flag
self.thread = False
self.cam = False # cv2.VideoCapture() handler
def set_video_txt(self, txt):
self.video_txt = str(txt)
def get_video_txt(self):
return self.video_txt
def set_video_rec(self, flag):
if flag:
self.video_rec = True
else:
self.video_rec = False
def get_video_rec(self):
return self.video_rec
def open_cam(self, width, height):
if self.cam == False:
self.cam = cv2.VideoCapture(self.camera_id)
self.cam.set(cv2.CAP_PROP_FRAME_WIDTH, width)
self.cam.set(cv2.CAP_PROP_FRAME_HEIGHT, height)
self.w = int(self.cam.get(cv2.CAP_PROP_FRAME_WIDTH))
self.h = int(self.cam.get(cv2.CAP_PROP_FRAME_HEIGHT))
self.f = self.cam.get(cv2.CAP_PROP_FPS)
print(str(self.w) + "x" + str(self.h) + "," + str(self.f) + "fps")
else:
print("CAM " + str(self.camera_id) + " Already Opend.")
def close_cam(self):
if self.cam != False:
self.cam.release()
self.cam = False
print("CAM " + str(self.camera_id) + " Released.")
else:
print("CAM " + str(self.camera_id) + " Not Opend.")
def text_over_img(self, img, timestamp):
#timestamp = (datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S.%f")[:-3])
videotxt = self.get_video_txt()
cv2.rectangle(img, (5,5), (self.w-5,45), (255,255,255), thickness=-1)
cv2.putText(img, timestamp, ( 10,35), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0,0,0), 1, cv2.LINE_AA)
cv2.putText(img, videotxt, (950,35), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0,0,0), 1, cv2.LINE_AA)
def capture_cam(self, filename):
if self.cam == False:
print("CAM Not Ready.")
return False
ret, img = self.cam.read()
if ret == True:
timestamp = (datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S.%f")[:-3])
self.text_over_img(img, timestamp)
cv2.imwrite(filename, img)
return True
else:
return False
def video_record(self, filename):
if self.cam == False:
print("CAM Not Ready.")
return False
fourcc = cv2.VideoWriter_fourcc('m','p','4','v')
out = cv2.VideoWriter(filename, fourcc, self.f, (self.w, self.h))
msg = "CAM " + str(self.camera_id) + " : q to quit"
while True:
ret, img = self.cam.read()
#pos_frames = str(self.cam.get(cv2.CAP_PROP_POS_FRAMES))
pos_frames = str(f'{self.cam.get(cv2.CAP_PROP_POS_FRAMES):.0f}')
pos_msec = str(self.cam.get(cv2.CAP_PROP_POS_MSEC))
timestamp = (datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S.%f")[:-3]) + "/" + pos_frames + "/" + pos_msec
#print(timestamp)
if ret == True:
self.text_over_img(img, timestamp)
out.write(img)
cv2.imshow(msg, img)
if (cv2.waitKey(1) == ord('q')) or (self.get_video_rec() == False):
print("### rec stop ###")
break
out.release()
return True
付録B. Lチカで始めるテスト自動化・記事一覧
- Lチカで始めるテスト自動化
- Lチカで始めるテスト自動化(2)テストスクリプトの保守性向上
- Lチカで始めるテスト自動化(3)オシロスコープの組込み
- Lチカで始めるテスト自動化(4)テストスクリプトの保守性向上(2)
- Lチカで始めるテスト自動化(5)WebカメラおよびOCRの組込み
- Lチカで始めるテスト自動化(6)AI(機械学習)を用いたPass/Fail判定
- Lチカで始めるテスト自動化(7)タイムスタンプの保存
- Lチカで始めるテスト自動化(8)HDMIビデオキャプチャデバイスの組込み
- Lチカで始めるテスト自動化(9)6DoFロボットアームの組込み
- Lチカで始めるテスト自動化(10)6DoFロボットアームの制御スクリプトの保守性向上
- Lチカで始めるテスト自動化(11)ロボットアームのコントローラ製作
- Lチカで始めるテスト自動化(12)書籍化の作業メモ
- Lチカで始めるテスト自動化(13)外部プログラムの呼出し
- Lチカで始めるテスト自動化(14)sleepの時間をランダムに設定する
- Lチカで始めるテスト自動化(15)Raspberry Pi Zero WHでテストランナーを動かして秋月のIoT学習HATキットに進捗を表示する
- Lチカで始めるテスト自動化(16)秋月のIoT学習HATキットにBME280を接続してテスト実行環境の温度・湿度・気圧を取得する
- Lチカで始めるテスト自動化(17)コマンド制御のBLE Keyboard & MouseをM5Stackで製作しiOSアプリをテストスクリプトで操作する
- Lチカで始めるテスト自動化(18)秋月のIoT学習HATキットの圧電ブザーでテスト終了時にpass/failに応じてメロディを流す
- Lチカで始めるテスト自動化(19)Webカメラの映像を録画しながらテストスクリプトを実行する
- Lチカで始めるテスト自動化(20)複数のカメラ映像の同時録画
- Lチカで始めるテスト自動化(21)キーボード入力待ちの実装
- Lチカで始めるテスト自動化(22)DACをコマンドで制御してLチカする
- Lチカで始めるテスト自動化(23)ブレッドボード互換のユニバーサル基板
- Lチカで始めるテスト自動化(24)コマンドの実行結果を任意のCSVファイルへ出力する
- Lチカで始めるテスト自動化(25)Windowsでもテスト終了時にpass/failに応じてメロディを流す
- Lチカで始めるテスト自動化(26)テストランナーのモジュール化
- Lチカで始めるテスト自動化(27)矩形の画像を動画のフレームから切り出してファイルに保存する
電子書籍化したものを技術書典のka'sらぼで頒布しています。
- Lチカで始めるテスト自動化
- Lチカで始めるテスト自動化 -2- リレー駆動回路の設計 (※書き下ろし)
- Lチカで始めるテスト自動化 -3- Raspberry Pi Zero WHとIoT学習HATキットで作るテストランナー