一次从 OpenCV 中的两个摄像头捕获视频

Posted

技术标签:

【中文标题】一次从 OpenCV 中的两个摄像头捕获视频【英文标题】:Capturing video from two cameras in OpenCV at once 【发布时间】:2015-06-22 06:00:43 【问题描述】:

如何使用 Python API 使用 OpenCV 同时(或几乎)从两个或多个摄像头捕获视频?

我有三个网络摄像头,都可以进行视频流传输,位于 /dev/video0、/dev/video1 和 /dev/video2。

以tutorial 为例,从单个摄像头捕获图像很简单:

import cv2
cap0 = cv2.VideoCapture(0)
ret0, frame0 = cap0.read()
cv2.imshow('frame', frame0)
cv2.waitKey()

这很好用。

但是,如果我尝试初始化第二个摄像头,尝试从中 read() 返回 None:

import cv2
cap0 = cv2.VideoCapture(0)
cap1 = cv2.VideoCapture(1)
ret0, frame0 = cap0.read()
assert ret0 # succeeds
ret1, frame1 = cap1.read()
assert ret1 # fails?!

为了确保我不会不小心给 OpenCV 一个错误的相机索引,我单独测试了每个相机索引,它们都可以自己工作。例如

import cv2
#cap0 = cv2.VideoCapture(0)
cap1 = cv2.VideoCapture(1)
#ret0, frame0 = cap0.read()
#assert ret0
ret1, frame1 = cap1.read()
assert ret1 # now it works?!

我做错了什么?

编辑:我的硬件是运行 Ubuntu 的 Macbook Pro。专门研究 Macbook 上的问题,我发现其他人也遇到了这个问题,无论是在 OSX 上还是在不同类型的相机上。如果我访问 iSight,我的代码中的两个调用都会失败。

【问题讨论】:

如果您更改 videoCapture 语句的顺序,是否会在其他通道上发生错误? 这对我来说似乎在 Windows 上运行良好。 相机是集成的还是USB的?如果they're on the same serial bus,它们可能无法同时运行,因为串行总线是串行的。 @Cerin,这是一个饱和问题。 usb2 几乎无法处理单个网络摄像头的吞吐量,如果要运行更多摄像头,则需要更多(独立)USB 控制器。普通电脑一般是一个在前面,一个在后面 @berak,我说得太早了。事实证明,当我将read() 设置为 160x120 时,它确实适用于多台摄像机。我想这完全是带宽问题。 【参考方案1】:

有点晚了,但您可以使用我的 VideGear 库的 CamGear API,它可继承地提供多线程,并且您可以用更少的行数编写相同的代码。此外,所有相机流都将完全同步。

这是两个相机流的示例代码:

# import required libraries
from vidgear.gears import VideoGear
import cv2
import time

# define and start the stream on first source ( For e.g #0 index device)
stream1 = VideoGear(source=0, logging=True).start() 

# define and start the stream on second source ( For e.g #1 index device)
stream2 = VideoGear(source=1, logging=True).start() 

# infinite loop
while True:
    
    frameA = stream1.read()
    # read frames from stream1

    frameB = stream2.read()
    # read frames from stream2

    # check if any of two frame is None
    if frameA is None or frameB is None:
        #if True break the infinite loop
        break
    
    # do something with both frameA and frameB here
    cv2.imshow("Output Frame1", frameA)
    cv2.imshow("Output Frame2", frameB)
    # Show output window of stream1 and stream 2 seperately

    key = cv2.waitKey(1) & 0xFF
    # check for 'q' key-press
    if key == ord("q"):
        #if 'q' key-pressed break out
        break

    if key == ord("w"):
        #if 'w' key-pressed save both frameA and frameB at same time
        cv2.imwrite("Image-1.jpg", frameA)
        cv2.imwrite("Image-2.jpg", frameB)
        #break   #uncomment this line to break out after taking images

cv2.destroyAllWindows()
# close output window

# safely close both video streams
stream1.stop()
stream2.stop()

更多使用示例可以查看here

Vidgear 文档: https://abhitronix.github.io/vidgear CamGear 文档: https://abhitronix.github.io/vidgear/v0.2.1-stable/gears/camgear/overview/

【讨论】:

【参考方案2】:

绕过 USB 带宽限制的一种方法是在开始使用第二个摄像头之前释放第一个摄像头,如下所示

import cv2
cap0 = cv2.VideoCapture(0)
ret0, frame0 = cap0.read()
assert ret0 # succeeds
cap0.release()

cap1 = cv2.VideoCapture(1)
ret1, frame1 = cap1.read()
assert ret1 # succeeds as well

释放相机并打开新相机对我来说需要 0.5-1 秒,这是否是可接受的时间延迟取决于您的使用案例。

除此之外并降低相机的输出分辨率(如果相机允许...),唯一的选择似乎是为每个相机添加一个 PCI USB 板(只有在台式计算机上才真正可行)。

多线程不会让您绕过带宽限制。

【讨论】:

【参考方案3】:

我使用了“imutils”并阅读了图像上的网络摄像头节目。

import imutils

捕捉视频帧

#--- WebCam1
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH,300)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT,300)
#--- WebCam2
cap1 = cv2.VideoCapture(1)
cap1.set(cv2.CAP_PROP_FRAME_WIDTH,300)
cap1.set(cv2.CAP_PROP_FRAME_HEIGHT,300)
#--- WebCam3
cap2 = cv2.VideoCapture(2)
cap2.set(cv2.CAP_PROP_FRAME_WIDTH,300)
cap2.set(cv2.CAP_PROP_FRAME_HEIGHT,300)
#--- WebCame4
cap3 = cv2.VideoCapture(3)
cap3.set(cv2.CAP_PROP_FRAME_WIDTH,300)
cap3.set(cv2.CAP_PROP_FRAME_HEIGHT,300)

我创建函数read_frame发送关于Image.fromarray的参数并显示

def read_frame():
    webCameShow(cap.read(),display1)
    webCameShow(cap1.read(),display2)
    webCameShow(cap2.read(),display6)
    webCameShow(cap3.read(),display7)   
    window.after(10, read_frame)

最后一个函数在“imageFrame”上显示视频

def webCameShow(N,Display): 
    _, frameXX = N
    cv2imageXX = cv2.cvtColor(frameXX, cv2.COLOR_BGR2RGBA)
    imgXX = Image.fromarray(cv2imageXX)
    #imgtkXX = ImageTk.PhotoImage(image=imgXX)
    Display.imgtk = imgtkXX 
    Display.configure(image=imgtkXX)

例子。 4-webcam

优酷: Youtube

【讨论】:

【参考方案4】:

在@TheoreticallyNick 之前发布的内容中添加一点内容:

import cv2
import threading

class camThread(threading.Thread):
    def __init__(self, previewName, camID):
        threading.Thread.__init__(self)
        self.previewName = previewName
        self.camID = camID
    def run(self):
        print("Starting " + self.previewName)
        camPreview(self.previewName, self.camID)

def camPreview(previewName, camID):
    cv2.namedWindow(previewName)
    cam = cv2.VideoCapture(camID)
    if cam.isOpened():
        rval, frame = cam.read()
    else:
        rval = False

    while rval:
        cv2.imshow(previewName, frame)
        rval, frame = cam.read()
        key = cv2.waitKey(20)
        if key == 27:  # exit on ESC
            break
    cv2.destroyWindow(previewName)

# Create threads as follows
thread1 = camThread("Camera 1", 0)
thread2 = camThread("Camera 2", 1)
thread3 = camThread("Camera 3", 2)

thread1.start()
thread2.start()
thread3.start()
print()
print("Active threads", threading.activeCount())

这将为您拥有的每个网络摄像头打开一个新线程。就我而言,我想打开三个不同的提要。在 Python 3.6 上测试。如果您有任何问题,请告诉我,同时感谢 TheoreticallyNick 的可读/功能代码!

【讨论】:

【参考方案5】:
frame0 = cv2.VideoCapture(1)
frame1 = cv2.VideoCapture(2)

必须是:

frame0 = cv2.VideoCapture(0)  # index 0
frame1 = cv2.VideoCapture(1)  # index 1

所以它运行

【讨论】:

【参考方案6】:

尝试使用此代码... 它按预期工作...... 这是两个摄像头,如果你想要更多的摄像头,只需创建“VideoCapture()”对象......例如第三个摄像头将有:cv2.VideoCapture(3)和while循环中的相应代码

import cv2

frame0 = cv2.VideoCapture(1)
frame1 = cv2.VideoCapture(2)
while 1:

   ret0, img0 = frame0.read()
   ret1, img00 = frame1.read()
   img1 = cv2.resize(img0,(360,240))
   img2 = cv2.resize(img00,(360,240))
   if (frame0):
       cv2.imshow('img1',img1)
   if (frame1):
       cv2.imshow('img2',img2)

   k = cv2.waitKey(30) & 0xff
   if k == 27:
      break

frame0.release()
frame1.release()
cv2.destroyAllWindows()

一切顺利!

【讨论】:

【参考方案7】:

这对我来说一直很痛苦,所以我在 OpenCV 之上制作了一个库来处理多个相机和视口。我遇到了一堆问题,比如视频默认不压缩,或者窗口只显示在主线程中。到目前为止,我能够在 Windows 上实时显示两个 720p 网络摄像头。

试试:

pip install CVPubSubs

然后,在python中:

import cvpubsubs.webcam_pub as w
from cvpubsubs.window_sub import SubscriberWindows

t1 = w.VideoHandlerThread(0)
t2 = w.VideoHandlerThread(1)

t1.start()
t2.start()

SubscriberWindows(window_names=['cammy', 'cammy2'],
              video_sources=[0,1]
              ).loop()

t1.join()
t1.join()

虽然它相对较新,所以请告诉我任何错误或未优化的代码。

【讨论】:

win10 上只显示 1 个摄像头。连接到 USB 集线器的相机 我一直在更新这个库,所以代码可能不再正常工作了。新代码应为display(0,1)。我必须仔细检查一下。【参考方案8】:

使用 OPENCV 和两个标准 USB 摄像头,我能够使用多线程来做到这一点。本质上,定义一个打开 opencv 窗口和 VideoCapture 元素的函数。然后,使用相机 ID 和窗口名称作为输入创建两个线程。

import cv2
import threading

class camThread(threading.Thread):
    def __init__(self, previewName, camID):
        threading.Thread.__init__(self)
        self.previewName = previewName
        self.camID = camID
    def run(self):
        print "Starting " + self.previewName
        camPreview(self.previewName, self.camID)

def camPreview(previewName, camID):
    cv2.namedWindow(previewName)
    cam = cv2.VideoCapture(camID)
    if cam.isOpened():  # try to get the first frame
        rval, frame = cam.read()
    else:
        rval = False

    while rval:
        cv2.imshow(previewName, frame)
        rval, frame = cam.read()
        key = cv2.waitKey(20)
        if key == 27:  # exit on ESC
            break
    cv2.destroyWindow(previewName)

# Create two threads as follows
thread1 = camThread("Camera 1", 1)
thread2 = camThread("Camera 2", 2)
thread1.start()
thread2.start()

学习如何在 python 中线程的好资源:https://www.tutorialspoint.com/python/python_multithreading.htm

【讨论】:

这对我不起作用,视频窗口弹出并变​​灰。 只有当您尝试多线程时才会出现这种情况吗?可能你申请的摄像头地址(usb端口地址)不对,有些电脑的usb映射地址不同。 [抱歉回复晚了] 相机编号从0开始 这仅适用于直接连接到计算机 USB 端口的相机 - 如果您将多个相机连接到 USB 集线器,它将显示一个相机。减小图像大小,例如 here 我认为这段代码是一个很好的解决方案。但是两个摄像头之间应该有时间差,不过好像又是个问题……反正谢谢!【参考方案9】:

是的,您肯定受到 USB 带宽的限制。尝试以 full-rez 从两个设备读取您可能会遇到错误:

libv4l2: error turning on stream: No space left on device
VIDIOC_STREAMON: No space left on device
Traceback (most recent call last):
  File "p.py", line 7, in <module>
    assert ret1 # fails?!
AssertionError

然后当你将分辨率降低到 160x120 时:

import cv2
cap0 = cv2.VideoCapture(0)
cap0.set(3,160)
cap0.set(4,120)
cap1 = cv2.VideoCapture(1)
cap1.set(3,160)
cap1.set(4,120)
ret0, frame0 = cap0.read()
assert ret0 # succeeds
ret1, frame1 = cap1.read()
assert ret1 # fails?!

现在它似乎工作了!我敢打赌,你的两个摄像头都连接在同一张 USB 卡上。您可以运行lsusb 命令来确定,它应该显示如下内容:

Bus 001 Device 006: ID 046d:081b Logitech, Inc. Webcam C310
Bus 001 Device 004: ID 0409:005a NEC Corp. HighSpeed Hub
Bus 001 Device 007: ID 046d:0990 Logitech, Inc. QuickCam Pro 9000
Bus 001 Device 005: ID 0409:005a NEC Corp. HighSpeed Hub
Bus 001 Device 003: ID 0409:005a NEC Corp. HighSpeed Hub
Bus 001 Device 002: ID 1058:0401 Western Digital Technologies, Inc. 
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 003 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub

(注意两个摄像头在同一总线上。)如果可能,您可以在您的机器上添加另一个 USB 卡以获得更多带宽。我以前这样做是为了在一台机器上以全分辨率运行多个摄像头。尽管那是一个带有可用主板插槽的塔式工作站,但不幸的是,您在 MacBook 笔记本电脑上可能没有该选项。

【讨论】:

奇怪的是,它也不总是在 160x120 下工作。我买了 6 个相同的廉价网络摄像头,使用一些摄像头组合,降低的分辨率效果很好,而使用其他摄像头组合,它们仍然失败。 这可能取决于您的网络摄像头支持的原生分辨率。尝试运行v4l2-ctl --allv4l-infov4l-conf 或其他方法,以找到您的摄像头支持的最小分辨率,然后按自己的方式工作直至失败。

以上是关于一次从 OpenCV 中的两个摄像头捕获视频的主要内容,如果未能解决你的问题,请参考以下文章

[OpenCV-Python] OpenCV 中的 Gui 视频

通过单端口多头(立体声)usb 摄像头使用 Opencv 捕获视频,提供单输出

Python OpenCV 用摄像头捕获视频

无法在 OpenCV 2.4.7 中从摄像头捕获视频

为啥用opencv捕获摄像头视频流时,突然显示框内就变灰了呢

OpenCV 视频捕获 API