6

I'm running this script on Kali linux with intel core i7-4510u:

import cv2
from datetime import datetime
vid_cam = cv2.VideoCapture(0)
vid_cam.set(cv2.CAP_PROP_FPS, 25)
vid_cam.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
vid_cam.set(cv2.CAP_PROP_FRAME_HEIGHT, 360)

lastDate = datetime.now().second
fcount = 0
while(vid_cam.isOpened()):
    if(datetime.now().second>lastDate):
        lastDate = datetime.now().second
        print("Fps: " + str(fcount))
        fcount = 0
    else:
        fcount += 1
    ret, image_frame = vid_cam.read()
    cv2.imshow('frame', image_frame)
    if cv2.waitKey(100) & 0xFF == ord('q'):
        break
vid_cam.release()
cv2.destroyAllWindows()

If I run it, it prints Fps: 4.
If I check Task Manager my cpu is at about 2%.
Where can the problem be?

nathancy
  • 42,661
  • 14
  • 115
  • 137
Filippo
  • 274
  • 1
  • 4
  • 13

2 Answers2

4

Most often, people don't consider the data rate (bandwidth) required to get their data across the wire.

1920 x 1080 pixels at 30 FPS with 24 bits per pixel would require 1.5 Gbit/s. That won't fit through USB 2.

That is why many webcams implement compression. A common compression is MJPEG, which is JPEG as video.

You can tell OpenCV to tell the media API (V4L, dshow, MSMF, AVFoundation...) to request that from the camera. It won't on its own, I have learned.

# cap = cv.VideoCapture(...)
cap.set(cv.CAP_PROP_FOURCC, cv.VideoWriter_fourcc(*"MJPG"))

Less data per frame means the transfer goes faster, allowing more frames per time.

When mixing that with other properties (FRAME_WIDTH etc), order matters. I believe the FOURCC setting needs to come first, but if that doesn't help, try other orders.

When a webcam notices that there isn't enough free data rate on the USB controller, it may decide to reduce frame size or frame rate, or apply stronger compression (worse pictures). When you attach multiple cameras to the same USB hub, this will happen. This negotiation may cause opening to take several seconds, a minute even. If opening the camera is slow, maybe the USB controller already has some bandwidth reservations for other devices.


What does affect frame rate is when your other code in that loop simply takes a lot of time! Can't work around that, except to make that part of the code faster.

What will not generally matter is moving the reading into its own thread. In the OpenCV case (with imshow), a thread makes no sense at all. A thread only makes sense if there's anything else to be done with the time ordinarily spent waiting, besides processing the next frame (e.g. a GUI loop). Communication with consumers needs to be proper. Busy loops aren't the solution.

In the case of actual GUIs (not OpenCV's imshow), the proper thing to do is a thread, but it needs to update the widget as a frame comes in. There must not be any spinning loop that repeatedly reads a variable and assigns a widget's image or anything.

The camera will produce frames at its own pace (no matter what you do), and put them into a queue. Reading a frame costs fairly little (but not nothing).

You must read from the camera, or else the frames queue up. If you read slower than the camera's frame rate, you'll see increasing latency, i.e. movement in front of the camera takes seconds to show up on your screen.

If you try to read quicker, the read() call will just block until a frame is available. Then you'll "waste" time in that call. What would you do with that time? You don't have new data yet, so there's nothing to compute, until that frame arrives. Might as well wait, right?

Christoph Rackwitz
  • 11,317
  • 4
  • 27
  • 36
  • Great answer, really helpful, but I don't get this part: `There must not be any spinning loop that repeatedly reads a variable and assigns a widget's image or anything` What's wrong with a spinning loop in the other thread, the one that doesn't read the capture device, that updates the widget's image with the received frame? – j3141592653589793238 Mar 13 '22 at 17:29
  • 1
    that's in reference to code that *runs in event handlers*. event handlers need to be done with their work quickly because they are called by the event loop. if an event handler takes its sweet time, the event loop won't progress until that is done, meaning the GUI will not respond until the handler is done. – Christoph Rackwitz Mar 13 '22 at 17:32
2

One potential reason could because of I/O latency when reading frames. Since cv2.VideoCapture().read() is a blocking operation, the main program is stalled until the frame is read from the camera device and returned. A method to improve performance would be to spawn another thread to handle grabbing frames in parallel instead of relying on a single thread to grab frames in sequential order. We can improve performance by creating a new thread that only polls for new frames while the main thread handles processing the current frame. Here's a snippet for multithreading frames.

from threading import Thread
import cv2, time

class VideoStreamWidget(object):
    def __init__(self, src=0):
        self.capture = cv2.VideoCapture(src)
        # Start the thread to read frames from the video stream
        self.thread = Thread(target=self.update, args=())
        self.thread.daemon = True
        self.thread.start()

    def update(self):
        # Read the next frame from the stream in a different thread
        while True:
            if self.capture.isOpened():
                (self.status, self.frame) = self.capture.read()
            time.sleep(.01)

    def show_frame(self):
        # Display frames in main program
        cv2.imshow('frame', self.frame)
        key = cv2.waitKey(1)
        if key == ord('q'):
            self.capture.release()
            cv2.destroyAllWindows()
            exit(1)

if __name__ == '__main__':
    video_stream_widget = VideoStreamWidget()
    while True:
        try:
            video_stream_widget.show_frame()
        except AttributeError:
            pass
nathancy
  • 42,661
  • 14
  • 115
  • 137
  • By "processing the current frame" you mean showing the latest frame via `cv2.imshow()`? Is that a CPU-intensive task or why would that require a separate thread? – j3141592653589793238 Mar 13 '22 at 16:49