Julian Jocque
Julian Jocque

Reputation: 964

OpenCV VideoCapture only updates after 5 read()s

I have a very strange error that has been plaguing my research for a few years now. I'm using OpenCV2 with Python to read image data from a webcam. However, the image is lagged by 5 frames. In other words, each call to read() is 5 frame behind real-time.

One bandage fix I have been using is to grab() 4 frames and then read the 5th every time I need an updated image, but that absolutely murders my performance.

Here's the code that I am using to display the images from the webcam

    frame = self.getGoodFrame()

    if self.DEBUG:
        window = cv2.namedWindow("Angles")
    imgHSV = cv2.cvtColor(frame, cv2.cv.CV_BGR2HSV)

    ... Reseach specific code I shouldn't be giving out here ...
    ... It finds the center of a few bright colors in an image

    if self.DEBUG:
        debugImage = numpy.zeros((self.CAMERA_HEIGHT, self.CAMERA_WIDTH), numpy.uint8) #blank image

       ... Then we draw some stuff to the image to be displayed ...

        cv2.imshow("Angles", debugImage)
        cv2.waitKey(1)
        raw_input()

and getGoodFrame()

def getGoodFrame(self):
    MIN_READS_FOR_GOOD_FRAME = 4

    for i in xrange(MIN_READS_FOR_GOOD_FRAME):
        successful_read = self.capture.grab() 

    successful_read, frame = self.capture.read()

    if not successful_read:
        print "Unable to read from webcam, exiting."

    return frame

You'll notice that I have a raw_input() call. That makes it so I can see how many reads need to happen by pressing enter a few times in console. This shows that there is exactly 5 frames of lag.

I don't think it's a hardware issue, I've had this happen with multiple webcams and multiple USB cords. I haven't tried reproducing the error on another machine though.

Upvotes: 0

Views: 4805

Answers (2)

Default buffer is set to 4.0 instead of 1.0.

To Fix it:

cap.set(38,1)

reference: https://docs.opencv.org/4.x/d4/d15/group__videoio__flags__base.html#ga41c5cfa7859ae542b71b1d33bbd4d2b4

you can see on picture parameter nr 38.

Upvotes: 2

Julian Jocque
Julian Jocque

Reputation: 964

So the problem was something to do with the way that my hardware was buffering the frames. I never quite got to the bottom of it but the solution I found was very simple paralellization. I modified my code to open up a thread and constantly update a variable holding the current frame. Then, when I need the current frame I just ask for whatever that variable is at the moment.

Note that this is still 5 read() calls behind because of the ever-present buffering, however because I am doing read() calls continuously and it's all on its own thread, it is very up to date. This is because I can call many read() calls per second. The image I get is not noticeably behind real-time at all.

The Python class I made to do this is below. It is not very nice code but it works. To do this properly, I would (and will) add graceful ways to exit the infinite loop. It will work for me though and it has improved the speed of my image detection code by well over 100 fold, which is extremely awesome and exciting for me :)

class webcamImageGetter:

    def __init__(self):
        self.currentFrame = None
        self.CAMERA_WIDTH = #webcam width
        self.CAMERA_HEIGHT = #webcam height
        self.CAMERA_NUM = 0

        self.capture = cv2.VideoCapture(0) #Put in correct capture number here
        #OpenCV by default gets a half resolution image so we manually set the correct resolution
        self.capture.set(cv2.cv.CV_CAP_PROP_FRAME_WIDTH,self.CAMERA_WIDTH)
        self.capture.set(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT,self.CAMERA_HEIGHT)

    #Starts updating the images in a thread
    def start(self):
        Thread(target=self.updateFrame, args=()).start()

    #Continually updates the frame
    def updateFrame(self):
        while(True):
            ret, self.currentFrame = self.capture.read()

            while (self.currentFrame == None): #Continually grab frames until we get a good one
                ret, frame = self.capture.read()

    def getFrame(self):
        return self.currentFrame

To use this, you initialize it then called start on the instance. This will make it so when you later called getFrame(), it will have the most up to date frame from the webcam. Woohoo!

Upvotes: 1

Related Questions