35

How do you capture video from two or more cameras at once (or nearly) with OpenCV, using the Python API?

I have three webcams, all capable of video streaming, located at /dev/video0, /dev/video1, and /dev/video2.

Using the tutorial as an example, capturing images from a single camera is simply:

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

And this works fine.

However, if I try to initialize a second camera, attempting to read() from it returns 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?!

Just to ensure I wasn't accidentally giving OpenCV a bad camera index, I tested each camera index individually and they all work by themselves. e.g.

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?!

What am I doing wrong?

Edit: My hardware is a Macbook Pro running Ubuntu. Researching the issue specifically on Macbooks, I've found others that have run into this problem too, both on OSX and with different types of cameras. If I access the iSight, both calls in my code fail.

Lightsout
  • 3,454
  • 2
  • 36
  • 65
Cerin
  • 60,957
  • 96
  • 316
  • 522
  • If you change the order of the videoCapture statements does the error occur on the other channel? – kpie Apr 16 '15 at 02:47
  • This seems to work fine for me on windows. – derricw Apr 16 '15 at 03:48
  • 4
    Are the cameras both integrated or USB? It's possible that they can't operate simultaneously if [they're on the same serial bus](http://answers.opencv.org/question/19556/videocapture-open-wont-open-second-camera/) because a serial bus is, well, serial. – Alex W Apr 16 '15 at 03:51
  • can you try to .grab for both cameras first and .retrieve later instead of .read? .read is combination of .grab and .retrieve... .grab will capture the images and .retrieve will decode them. should give better results (= images better time-aligned) if it works... – Micka Apr 16 '15 at 06:33
  • @kpie, Yes, like I said, I tested them both. – Cerin Apr 16 '15 at 12:47
  • @AlexW, The "serial" in USB refers to how the data is transmitted. It doesn't mean only a single device can be used on the entire bus. Google it. Lots of people have shown examples of using identical USB cameras on the same bus. – Cerin Apr 16 '15 at 12:49
  • 2
    @Cerin, it's a matter of saturation. usb2 can barely handle the throughput of a single webcam, if you want to run more cams, you need more (independant) usb controllers. ordinary pc's usually got one in the front , and another in the back – berak Apr 16 '15 at 13:01
  • @berak, That's my suspicion too. But still, I don't understand the hard constraint. I don't need a realtime stream at 60fps. I'd settle for two very slow streams. It shouldn't fail to load a second camera. It should just slow down both streams. Unfortunately, my Macbook only seems to have one controller. – Cerin Apr 16 '15 at 13:04
  • try to reduce the framesize. – berak Apr 16 '15 at 13:09
  • @Micka, Yes, unfortunately, the call to grab() on the second camera also fails if the first has not been released. – Cerin Apr 16 '15 at 13:10
  • can you try to `loop` the grab until it succeeds and measure the time? – Micka Apr 16 '15 at 13:16
  • @berak, That's why I'm not 100% sure it's a bandwidth or framesize issue. Even if I set OpenCV to capture at the smallest size, 160x120, it can still only use one camera at a time. USB2.0 should be fast enough to handle that. – Cerin Apr 16 '15 at 13:23
  • 2
    @berak, I spoke too soon. It turns out `read()` does work with multiple cameras when I set it to 160x120. I guess it is entirely a bandwidth issue. – Cerin Apr 16 '15 at 13:41

10 Answers10

16

Using OPENCV and two standard USB cameras, I was able to do this using multithreading. Essentially, define one function which opens an opencv window and VideoCapture element. Then, create two threads with the camera ID and window name as inputs.

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()

Great resource for learning how to thread in python: https://www.tutorialspoint.com/python/python_multithreading.htm

  • This does not work for me, video window pops up and greys out. – amitnair92 Jun 12 '18 at 14:20
  • Does this only come up when you attempt to multi-thread? Perhaps the camera address (usb port address) that you're requesting is wrong, some computers have different usb mapping addresses. [Sorry for late reply] – TheoreticallyNick Sep 30 '18 at 19:22
  • camera numbering starts from 0 – oferb Jun 13 '19 at 06:15
  • this works only on cameras connected directly to USB port on a computer - if you are connecting more than one camera to a USB hub it will show one camera. reduce image size like [here](https://techoverflow.net/2018/12/18/how-to-set-cv2-videocapture-image-size/) – oferb Jun 13 '19 at 10:24
  • I think this code is a great solution. but there should be a time difference between two cameras, but it seems another problem...anyway Thank you! – Cho Mar 04 '21 at 01:42
  • I don't see `run` function get executed in your snippet. When we start the thread, do they automatically executed all sub function in Class? – Cloud Cho Apr 08 '21 at 19:21
14

Yes you're definitely limited by the USB bandwidth. Attempting to read from both devices at full-rez you probably got error:

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

And then when you reduce the res to 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?!

now it seems to work! I bet you have both cams connected on the same USB card. You can run lsusb command to make sure, and it should indicate something like:

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

(Note both cameras on same bus.) If possible, you can add another USB card to your machine to gain more bandwidth. I've done this before in order to run multiple cams at full resolution on a single machine. Albeit that was a tower workstation with available motherboard slots, and unfortunately you may not have that option on a MacBook laptop.

Velimir Mlaker
  • 10,664
  • 4
  • 46
  • 58
  • Oddly, it doesn't always work at 160x120 either. I bought 6 identical cheap webcams, and with some camera combinations, the reduced resolution works fine, and with other camera combinations, they still fail. – Cerin Apr 17 '15 at 19:27
  • It may depend on what the native resolutions your webcam supports. Try running ``v4l2-ctl --all`` or ``v4l-info`` or ``v4l-conf`` or others to find what minimal res your cams support, then work your way up to failure. – Velimir Mlaker Apr 17 '15 at 20:13
  • **This was totally on point with what was happening to me.** I had 2 USB cameras connected to an external powered hub, and was getting errors when trying to pull frames out of both cameras at the same time. When I plugged one camera at the powered USB hub and another camera directly at my laptop USB port, then streaming both cameras at the same time worked flawlessly with no errors this time. – Raphael Setin Feb 10 '22 at 17:39
7

Adding a little to what @TheoreticallyNick posted earlier:

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())

This will open up a new thread for each webcam you have. In my case, I wanted to open up three different feeds. Tested on Python 3.6. Let me know if you have any issues, also thanks to TheoreticallyNick for the readable/functioning code!

Jacob W. Dallas
  • 383
  • 3
  • 12
5

I have use "imutils" and read webcam show on the image.

import imutils

capture vedio frames

#--- 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)

i create function read_frame send parameter about Image.fromarray and display

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

and final function show video on the "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)

example. 4-webcam

Youtube: Youtube

busybear
  • 10,194
  • 1
  • 25
  • 42
5

A bit late, but you can use my VideGear library's CamGear API that inheritably provide multi-threading and you can write same code in way fewer lines. Also on plus side, all camera streams will be exactly synchronized.

Here's the example code for two Camera streams:

# 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()

More usage examples can be found here

abhiTronix
  • 1,248
  • 13
  • 17
1

This has been a pain for me for a long time, so I made a library on top of OpenCV to handle multiple cameras and viewports. I ran into a bunch of problems like videos not compressing by default, or windows only displaying in the main thread. I'm able to display two 720p webcams in real time on Windows so far.

Try:

pip install CVPubSubs

Then, in 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()

It's relatively new though, so tell me about any bugs or unoptimized code.

SimLeek
  • 132
  • 7
  • shows only 1 camera on win10. cameras connected to a usb hub – oferb Jun 13 '19 at 08:21
  • I've been updating this library, so that code may not work right anymore. The new code should be `display(0,1)`. I'll have to double check this. – SimLeek Jul 09 '19 at 19:30
0

try to use this code... it worked as expected... this is for two cams,if you want more cams, just create the "VideoCapture()" objects...for example 3rd cam will have : cv2.VideoCapture(3) and corresponding code in the while loop

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()

ALL THE BEST !

0
frame0 = cv2.VideoCapture(1)
frame1 = cv2.VideoCapture(2)

must be:

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

So it runs

Zoe
  • 27,060
  • 21
  • 118
  • 148
0

An option to get around the USB bandwidth limitation is to release the first camera before starting to use the second one, as in

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

Releasing a camera and opening a new one takes 0.5-1 secs for me, whether this is an acceptable time lag will depend on your use case.

Aside from that and reducing the output resolution of your camera (if the camera allows it...), the only option seems to be to add a PCI USB board for each camera (only really possible on a desktop computer).

Multithreading will not get you around the bandwidth limitation.

Bruno Degomme
  • 883
  • 10
  • 11
0

This solution might not work for the OP, but this is my experience might help others.

Today I had the same issue on Windows PC. I have 3 webcams from different brands and models. None of the above solutions worked. But I've found the issue is that all webcams were connected to the USB2 ports. I connected one of them to a USB3 port and everything worked correctly. At the same time, only one webcam works on USB2 ports. (No difference between front and rear ports in my case) but all webcams work fine if I connect to the rear USB3 ports.

Ahmad Behzadi
  • 1,006
  • 15
  • 30