0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Lチカで始めるテスト自動化(28)Webカメラの映像にフレームの情報を重畳する

Posted at

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秒録画します。

mov10s.csv
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 手順

  1. OpenCV 4.5.4のソースコードをチェックアウトする3
    1. git clone https://github.com/opencv/opencv.git
    2. git checkout refs/tags/4.5.4
  2. クローンしたフォルダをVisual Studio Codeで開く
  3. CV_CAP_PROP_POS_FRAMESを検索する
  4. (筆者の環境は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に対応するコードから該当部分を引用します。

cap_msmf.cpp
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で安定しています。
frame-interval.png

5. おわりに

Webカメラの映像に付随するフレーム番号を参照することでフレームの取りこぼしが明確にわかるようになりました。また、フレーム間隔も安定した値が得られました。

付録A. camera.pyのソース

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チカで始めるテスト自動化・記事一覧

  1. Lチカで始めるテスト自動化
  2. Lチカで始めるテスト自動化(2)テストスクリプトの保守性向上
  3. Lチカで始めるテスト自動化(3)オシロスコープの組込み
  4. Lチカで始めるテスト自動化(4)テストスクリプトの保守性向上(2)
  5. Lチカで始めるテスト自動化(5)WebカメラおよびOCRの組込み
  6. Lチカで始めるテスト自動化(6)AI(機械学習)を用いたPass/Fail判定
  7. Lチカで始めるテスト自動化(7)タイムスタンプの保存
  8. Lチカで始めるテスト自動化(8)HDMIビデオキャプチャデバイスの組込み
  9. Lチカで始めるテスト自動化(9)6DoFロボットアームの組込み
  10. Lチカで始めるテスト自動化(10)6DoFロボットアームの制御スクリプトの保守性向上
  11. Lチカで始めるテスト自動化(11)ロボットアームのコントローラ製作
  12. Lチカで始めるテスト自動化(12)書籍化の作業メモ
  13. Lチカで始めるテスト自動化(13)外部プログラムの呼出し
  14. Lチカで始めるテスト自動化(14)sleepの時間をランダムに設定する
  15. Lチカで始めるテスト自動化(15)Raspberry Pi Zero WHでテストランナーを動かして秋月のIoT学習HATキットに進捗を表示する
  16. Lチカで始めるテスト自動化(16)秋月のIoT学習HATキットにBME280を接続してテスト実行環境の温度・湿度・気圧を取得する
  17. Lチカで始めるテスト自動化(17)コマンド制御のBLE Keyboard & MouseをM5Stackで製作しiOSアプリをテストスクリプトで操作する
  18. Lチカで始めるテスト自動化(18)秋月のIoT学習HATキットの圧電ブザーでテスト終了時にpass/failに応じてメロディを流す
  19. Lチカで始めるテスト自動化(19)Webカメラの映像を録画しながらテストスクリプトを実行する
  20. Lチカで始めるテスト自動化(20)複数のカメラ映像の同時録画
  21. Lチカで始めるテスト自動化(21)キーボード入力待ちの実装
  22. Lチカで始めるテスト自動化(22)DACをコマンドで制御してLチカする
  23. Lチカで始めるテスト自動化(23)ブレッドボード互換のユニバーサル基板
  24. Lチカで始めるテスト自動化(24)コマンドの実行結果を任意のCSVファイルへ出力する
  25. Lチカで始めるテスト自動化(25)Windowsでもテスト終了時にpass/failに応じてメロディを流す
  26. Lチカで始めるテスト自動化(26)テストランナーのモジュール化
  27. Lチカで始めるテスト自動化(27)矩形の画像を動画のフレームから切り出してファイルに保存する

電子書籍化したものを技術書典ka'sらぼで頒布しています。

  1. Lチカで始めるテスト自動化
  2. Lチカで始めるテスト自動化 -2- リレー駆動回路の設計 (※書き下ろし)
  3. Lチカで始めるテスト自動化 -3- Raspberry Pi Zero WHとIoT学習HATキットで作るテストランナー
  1. Current position of the video file in milliseconds.

  2. 0-based index of the frame to be decoded/captured next.

  3. 最初からタグを指定してクローンしても構いません。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?