opencv库分别封装了拍照和录像函数,调用threading库多线程运行实现一边拍照一边录像时报错

一:问题描述:使用opencv库分别封装了拍照和录像函数,然后想实现一边拍照一边录像的效果,调用threading库来多线程运行时报错cv2.imwrite()为空

二:代码如下

import cv2
import threading
import time
import json
from datetime import datetime


with open("calibration.json", "r", encoding="utf-8") as load_f:
    load = json.load(load_f)


def picture_shoot(image_name, image_path=r"E:\report") -> None:
    '''
    调用摄像头拍照并保存图片到本地
    :param image_name: 图片名
    :param image_path: 图片保存路径
    :return: None
    '''
    cap = cv2.VideoCapture(0)
    while (cap.isOpened()):
        ret, frame = cap.read()
        # cv2.imshow("Capture_Paizhao", frame) # 显示相机窗口
        # k = cv2.waitKey(1) & 0xFF #通过键盘获取值并赋值给k
        # if k == ord('s'):  # 按下s键,进入下面的保存图片操作
        # cv2.imwrite(image_path + '\\' + image_name + ".jpg", frame)
        cv2.imwrite(image_path + '\\' + image_name, frame)
        # print(cap.get(3))
        # print(cap.get(4))
        print("保存" + image_name + "成功!")
        # elif k == ord('q'):  # 按下q键,程序退出
        break
    cap.release()
    cv2.destroyAllWindows()


def video_record(video_path) -> None:
    '''
    调用摄像头录制视频并保存到本地
    :param video_path: 视频保存路径
    :return: None
    '''
    FPS = 24.0
    # 必须指定CAP_DSHOW(Direct Show)参数初始化摄像头,否则无法使用更高分辨率
    cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
    # 视频写入的图像尺寸与画布尺寸不对应会导致视频无法播放,需要实时获取
    WIDTH = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    HEIGHT = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    # 设置摄像头设备分辨率
    cap.set(cv2.CAP_PROP_FRAME_WIDTH, WIDTH)
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, HEIGHT)
    # 设置摄像头设备帧率,如不指定,默认600
    cap.set(cv2.CAP_PROP_FPS, 24)
    # 建议使用XVID编码,图像质量和文件大小比较都兼顾的方案
    fourcc = cv2.VideoWriter_fourcc(*'XVID')
    out = cv2.VideoWriter(video_path, fourcc, FPS, (WIDTH, HEIGHT))
    start_time = datetime.now()
    while (cap.isOpened()):
        ret, frame = cap.read()
        if ret:
            out.write(frame)
            # 显示预览窗口
            # cv2.imshow('Preview_Window', frame) # 显示相机窗口
            # 录制5秒后停止
            if (datetime.now() - start_time).seconds == load['record_duration']:
                cap.release()
                print('视频录制成功!')
                break
    out.release()
    cv2.destroyAllWindows()


if __name__ == '__main__':
    path = r'E:\report' + '\\' + 'video.avi'
    thread = threading.Thread(target=video_record, args=(path,))
    thread.start()

    picture_shoot(image_name='1.png', image_path=r'E:/report/AI2_reverse/standard_camera')

三:运行结果

D:\Python3.8.6\python.exe D:/PythonWorkSpace/Auto_test/Camera_Invoke.py
[ WARN:0] global D:\a\opencv-python\opencv-python\opencv\modules\videoio\src\cap_msmf.cpp (376) `anonymous-namespace'::SourceReaderCB::OnReadSample videoio(MSMF): OnReadSample() is called with error status: -1072875772
[ WARN:0] global D:\a\opencv-python\opencv-python\opencv\modules\videoio\src\cap_msmf.cpp (388) `anonymous-namespace'::SourceReaderCB::OnReadSample videoio(MSMF): async ReadSample() call is failed with error status: -1072875772
[ WARN:1] global D:\a\opencv-python\opencv-python\opencv\modules\videoio\src\cap_msmf.cpp (1022) CvCapture_MSMF::grabFrame videoio(MSMF): can't grab frame. Error: -1072875772
Traceback (most recent call last):
  File "D:/PythonWorkSpace/Auto_test/Camera_Invoke.py", line 77, in <module>
    picture_shoot(image_name='1.png', image_path=r'E:/report/AI2_reverse/standard_camera')
  File "D:/PythonWorkSpace/Auto_test/Camera_Invoke.py", line 26, in picture_shoot
    cv2.imwrite(image_path + '\\' + image_name, frame)
cv2.error: OpenCV(4.5.4) D:\a\opencv-python\opencv-python\opencv\modules\imgcodecs\src\loadsave.cpp:799: error: (-215:Assertion failed) !_img.empty() in function 'cv::imwrite'

视频录制成功!
[ WARN:1] global D:\a\opencv-python\opencv-python\opencv\modules\videoio\src\cap_msmf.cpp (438) `anonymous-namespace'::SourceReaderCB::~SourceReaderCB terminating async callback

Process finished with exit code 1

四:尝试过的方法
1、观察发现两个函数都实例化了cap对象,并发运行时可能会产生冲突,把其中一个函数的cap改成了cap_2,运行后仍然报同样的错误
2、两个函数都是通过cv2下的方法来实现拍照或者录像,但是cv2是三方库的内置函数,改不了名字

五:我想要达到的效果
能够一边录制视频一边拍照,并且可以控制视频什么时候结束

1、因为两个函数要用到想用的对象,所以尝试封装类,把共用的属性作为类属性

import cv2
import threading
import json
from datetime import datetime

with open("calibration.json", "r", encoding="utf-8") as load_f:
    load = json.load(load_f)


class Camera_invoke(object):
    cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
    ret, frame = cap.read()

    def picture_shoot(self, image_name, image_path=r"E:\report") -> None:
        '''
        调用摄像头拍照并保存图片到本地
        :param image_name: 图片名
        :param image_path: 图片保存路径
        :return: None
        '''
        self.image_name = image_name
        self.image_path = image_path
        cv2.imwrite(self.image_path + '\\' + self.image_name, self.frame)
        print("保存" + self.image_name + "成功!")

    def video_record(self, video_path) -> None:
        '''
        调用摄像头录制视频并保存到本地
        :param video_path: 视频保存路径
        :return: None
        '''
        self.video_path = video_path
        FPS = 24.0
        # 视频写入的图像尺寸与画布尺寸不对应会导致视频无法播放,需要实时获取
        WIDTH = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
        HEIGHT = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
        # 设置摄像头设备分辨率
        self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, WIDTH)
        self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, HEIGHT)
        # 设置摄像头设备帧率,如不指定,默认600
        self.cap.set(cv2.CAP_PROP_FPS, 24)
        # 建议使用XVID编码,图像质量和文件大小比较都兼顾的方案
        fourcc = cv2.VideoWriter_fourcc(*'XVID')
        out = cv2.VideoWriter(self.video_path, fourcc, FPS, (WIDTH, HEIGHT))
        start_time = datetime.now()
        while (self.cap.isOpened()):
            ret, frame = self.cap.read()
            if ret:
                #加时间戳
                font = cv2.FONT_HERSHEY_SIMPLEX
                datet = str(datetime.now())
                frame = cv2.putText(frame, datet, (10, 50), font, 1,
                                    (0, 255, 255), 2, cv2.LINE_AA)
                out.write(frame)
                # 录制5秒后停止
                if (datetime.now() - start_time).seconds == load['record_duration']:
                    self.cap.release()
                    print('视频录制成功!')
                    break
        out.release()
        cv2.destroyAllWindows()


if __name__ == '__main__':
    path = r'E:\report' + '\\' + 'video16.avi'
    camera = Camera_invoke()
    thread = threading.Thread(target=camera.video_record, args=(path,))
    thread.start()

    camera.picture_shoot(image_name='16.png', image_path=r'E:/report/AI2_reverse/standard_camera')


封装成类后,能够同时录像和拍照,解决问题

  1. 你这样做是肯定不行的!
  2. 方法很简单,将图像数据保存到内存中,然后通过另外一个线程/进程去专门保存内存中的图像。

1、原因分析:
因为两个函数都实例化了cap对象,产生了冲突导致报错,但是两个函数都需要用到这个对象

2、解决方法:
于是我把cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)这步放到函数外面作为全局变量,两个函数里实例化cap对象步骤注释掉后解决报错问题。

3、产生新问题:
但是产生了新的问题,图片拍出来是全黑的,视频录制是成功的。