@wancles3 (Ryo)

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

PythonでUSBカメラ2台を使って録画したい

解決したいこと

PythonとOpenCVとTKinterでUSBカメラ2画面を表示し、ボタンで録画・ストップ(保存)をしたい

発生している問題・エラー

やろうとしていること
1.pythonでUSBカメラ2個を使って、所定の場所に所定のサイズで表示
2.TKinterで録画・ストップボタン(切り替わり)、終了ボタンを作成し他の場所に表示
3.ストップボタンを押したらファイルを保存
4.終了ボタンでカメラ、ボタンが消える

困りごと
1)ボタンでコントロールまでできていないので、qを押したら画面が消えるようにしたが(step1)
  ループの使い方が理解できておらず、置く場所が間違っていると思うので以下の問題が起きている
問題:画面2画面表示し、qを押したらカメラが消え、ボタンが出てくる
→これが解決したらボタンでコントロールに移行したい(step2)

2)コードを短くしたい
・例えばVideoCapture(1)、VideoCapture(2)、frame1、frame2と同じ内容を数字を変えて書いているので一気にできないか

3)USBカメラが接続されていない場合エラーで止まってしまうので、カメラ画面が表示されない状態にしたい。
  カメラが1個しかない場合、1つのみ表示にしたい。つないでない場合は表示されない、もしくはno cameraとか表示したい

以上よろしくお願いします。

該当するソースコード

python3.9、windows10
from datetime import datetime # 時刻関係のライブラリ
import cv2 # OpenCV のインポート
import tkinter as tk
#ポップアップ用メッセージボックスを表示するためのimport
from tkinter import messagebox as mb

#-------------------------------------
#ここからボタンなど作成
# Tkクラス生成
root= tk.Tk()
root.geometry('374x116+623+920') # 画面サイズ('X方向サイズxY方向サイズ+左上の座標)
root.title('Tanishi system ver 1.0') # 画面タイトル

# ボタン1(録画ボタン)作成
#
btn1 = tk.Button(root, text='録画', width=10,height=1,font=("","20","bold"))
# ボタンを左から配列
btn1.pack(side=tk.LEFT,expand=True,anchor=tk.CENTER)

# ボタン2(終了ボタン)作成
#
btn2 = tk.Button(root, text='終了', width=10,height=1,font=("","20","bold"))
# ボタンを左から配列
btn2.pack(side=tk.LEFT,expand=True,anchor=tk.CENTER)


#-------------------------------------ビデオキャプチャ

# VideoCaptureのインスタンスを作成(引数でカメラを選択できる)
cap1 = cv2.VideoCapture(1)
cap2 = cv2.VideoCapture(2)

while True:


    # VideoCaptureから1フレーム読み込む
    ret, frame1 = cap1.read() # 戻り値のframeがimg
    ret, frame2 = cap2.read() # 戻り値のframeがimg

    # 現在時刻の文字列を画像に追加
    date = datetime.now().strftime("%Y/%m/%d %H:%M:%S")
    cv2.putText(frame1, date, (10,30), cv2.FONT_HERSHEY_SCRIPT_SIMPLEX, 1, (0,255,0), 2, cv2.LINE_AA) #画像左上に時間表示
    cv2.putText(frame2, date, (250,450), cv2.FONT_HERSHEY_SCRIPT_SIMPLEX, 1, (0,255,255), 2, cv2.LINE_AA) #画像左上に時間表示

    # 加工した画像を表示
    #画像をアスペクト比を変えずにリサイズする
    def scale_to_width (frame1, width):
      h, w = frame1.shape[:2]
      height = round(h * (width / w))
      dst = cv2.resize(frame1, dsize=(width, height))

      return dst

    dst = scale_to_width (frame1, 440)

    def scale_to_width_2 (frame2, width):
      h, w = frame2.shape[:2]
      height = round(h * (width / w))
      dst2 = cv2.resize(frame2, dsize=(width, height))

      return dst2

    dst2 = scale_to_width_2 (frame2, 440)
    #print(f"{img.shape} -> {dst.shape}")
    #imshow(dst)

    #画像の表示
    cv2.imshow('Camera1', dst)#frame1)#
    cv2.imshow('Camera2', dst2)#frame2)#
    #画像の表示位置設定
    cv2.moveWindow("Camera1", 1005,61) # Window表示位置指定
    cv2.moveWindow("Camera2", 1460,61) # Window表示位置指定

    # キー入力を1ms待って、keyが「q」だったらbreak
    key = cv2.waitKey(1)&0xff
    if key == ord('q'):
        break

# キャプチャをリリースして、ウィンドウをすべて閉じる
cap1.release()
cap2.release()
cv2.destroyAllWindows()


root.mainloop()
1 likes

1Answer

こんな感じでどうでしょう?

from datetime import datetime # 時刻関係のライブラリ
import cv2 # OpenCV のインポート
import tkinter as tk
#ポップアップ用メッセージボックスを表示するためのimport
from tkinter import messagebox as mb

#-------------------------------------
#ここからボタンなど作成
# Tkクラス生成
root= tk.Tk()
root.geometry('374x116+623+920') # 画面サイズ('X方向サイズxY方向サイズ+左上の座標)
root.title('Tanishi system ver 1.0') # 画面タイトル

# ボタン1(録画ボタン)作成
#
btn1 = tk.Button(root, text='録画', width=10,height=1,font=("","20","bold"))
# ボタンを左から配列
btn1.pack(side=tk.LEFT,expand=True,anchor=tk.CENTER)

# ボタン2(終了ボタン)作成
#
btn2 = tk.Button(root, text='終了', width=10,height=1,font=("","20","bold"))
# ボタンを左から配列
btn2.pack(side=tk.LEFT,expand=True,anchor=tk.CENTER)


#-------------------------------------ビデオキャプチャ

import multiprocessing as mp
from queue import Empty


# キャプチャ処理は子プロセスで行うため関数化しておく
def capture(capnum, q: mp.Queue):

    # 加工した画像を表示
    #画像をアスペクト比を変えずにリサイズする
    def scale_to_width (frame, width):
      h, w = frame.shape[:2]
      height = round(h * (width / w))
      dst = cv2.resize(frame, dsize=(width, height))

      return dst

    # VideoCaptureのインスタンスを作成(引数でカメラを選択できる)
    cap = cv2.VideoCapture(capnum)

    while True:
        # カメラが無いならループを抜ける
        if not cap.isOpened():
            break

        # VideoCaptureから1フレーム読み込む
        ret, frame = cap.read() # 戻り値のframeがimg
        if frame is None:
            continue

        # 現在時刻の文字列を画像に追加
        date = datetime.now().strftime("%Y/%m/%d %H:%M:%S")
        cv2.putText(frame, date, (10,30),
                    cv2.FONT_HERSHEY_SCRIPT_SIMPLEX,
                    1, (0,255,0), 2, cv2.LINE_AA) #画像左上に時間表示

        dst = scale_to_width (frame, 440)

        # キャプチャした画像を送る
        q.put(dst)

    cap.release()


# キャプチャ操作用クラス
class CaptureController:

    def __init__(self, capnum, posx, posy):
        # カメラの番号
        self.capnum = capnum
        # キャプチャ画像のウィンドウの名前
        self.name = "Camera" + str(capnum)
        # キャプチャ画像のウィンドウの位置
        self.posx = posx
        self.posy = posy
        # キャプチャした画像を受け取るためのキュー
        self.q = mp.Queue()
        # 画像をキャプチャするための子プロセス
        self.p = mp.Process(target=capture, args=(capnum, self.q))

    def start(self):
        # キャプチャ開始
        self.p.start()

    def update(self):
        # キャプチャした画像の表示を更新
        try:
            # キャプチャした画像を受け取る
            dst = self.q.get_nowait()  # 受け取る画像がなければ raise Empty する
            # 画像の表示
            cv2.imshow(self.name, dst)
            # 画像の表示位置設定
            cv2.moveWindow(self.name, self.posx, self.posy)  # Window表示位置指定
        except Empty:
            pass

    def cancel(self):
        # 子プロセスを終了
        self.p.terminate()


# メインモジュールの安全なインポートより
# https://docs.python.org/ja/3/library/multiprocessing.html#the-spawn-and-forkserver-start-methods
if __name__ == '__main__':
    mp.freeze_support()

    # キャプチャ操作インスタンス作成
    caps = (
        CaptureController(capnum=0, posx=1005, posy=61),
        CaptureController(capnum=1, posx=1460, posy=61),
    )

    # キャプチャ開始
    [c.start() for c in caps]

    while True:
        # カメラ映像更新
        [c.update() for c in caps]

        # キー入力を1ms待って、keyが「q」だったらbreak
        key = cv2.waitKey(1) & 0xff
        if key == ord('q'):
            break

        # root.mainloop()の代わり
        root.update_idletasks()
        root.update()

    # キャプチャキャンセル
    [c.cancel() for c in caps]

    # キャプチャをリリースして、ウィンドウをすべて閉じる
    cv2.destroyAllWindows()

0Like

Comments

  1. @wancles3

    Questioner

    こんにちは。わかりやすくコメントまでつけていただきありがとうございます。
    今実行してみたのですが、最初にボタンがでるものの、下記エラーでカメラ画面が出てきません。
    どうしたらよろしいでしょうか?

    _tkinter.TclError: can't invoke "update" command: application has been destroyed
    だそうです
  2. こんにちは。
    私の方でも動かないことを確認しました。


    # VideoCaptureのインスタンスを作成(引数でカメラを選択できる)
    cap = cv2.VideoCapture(capnum)

    の部分を

    # VideoCaptureのインスタンスを作成(引数でカメラを選択できる)
    cap = cv2.VideoCapture(capnum, cv2.CAP_DSHOW)

    としてみてください。

    どうもMSMF (microsoft media foundation) APIにエラーが有るのではないかという、情報を見つけましたので参考としてリンクをはります。
    https://stackoverflow.com/questions/52503187/getting-error-videoiomsmf-async-readsample-call-is-failed-with-error-statu
  3. @wancles3

    Questioner

    こんにちは。
    やはり結果は同じでした。
    念のためPC再起動もしたのですが、変わりません。
    どうしましょう・・・
  4. こんにちは
    CAP_DSHOW オプションの他に CAP_MSMF や CAP_V4L2 オプションがあります。
    他のオプションではどうでしょうか?
  5. @wancles3

    Questioner

    すべて試しましたが、カメラの画像は出てきませんでした
  6. 以下のcapnum=でキャプチャするカメラの番号を設定しています。
    カメラ設定は問題ないでしょうか?

    # キャプチャ操作インスタンス作成
    caps = (
    CaptureController(capnum=0, posx=1005, posy=61),
    CaptureController(capnum=1, posx=1460, posy=61),
    )


    また、前回実行時カメラの開放に失敗していたり、別のソフトでカメラが使われている場合、capture関数のwhlieループをcap.isOpenedのチェックで抜けていると思います。
    以下コードを書き換えて、エラーメッセージが表示されるのであれば、念の為カメラのUSBを抜き差ししてみてください。

    # カメラが無いならループを抜ける
    if not cap.isOpened():
    print("error: camera {} open".format(capnum))
    break


    他には、VideoCaptureの設定がCAP_MSMFでエラーが起きている場合、エラーメッセージが出力されます。手がかりになるかもしれません。

    # VideoCaptureのインスタンスを作成(引数でカメラを選択できる)
    cap = cv2.VideoCapture(capnum, cv2.CAP_MSMF)

Your answer might help someone💌