I have a webcam that captures something I want to be able to monitor live. Every 30 seconds an operation needs to be done on the most recent frame of the video. To do this, I've set up a cv2.NamedWindow() in a daemon thread that allows me to see the webcam feed. Then using a scheduler, I can do the operation I need every 30 seconds. The problem is, the camera feed will randomly go black, and save an all black image. Sometimes this happens after 15 minutes of operation, sometimes after 4 hours of operation., so it's very inconsistent and there seems to be no pattern. Here is the code:
import time
import gc
import cv2
import threading
import schedule
import numpy
class operator():
def __init__(self):
print("Start")
def start_cam(self):
is_blurry = True
while is_blurry == True:
print("Start of start_cam")
self.camera = cv2.VideoCapture(0, cv2.CAP_DSHOW) # Make camera object with correct format, size, and autofocus. All of these appear to be necessary to get 4K resolution in OpenCV
self.camera.set(cv2.CAP_PROP_FRAME_WIDTH, 3840) #3840
self.camera.set(cv2.CAP_PROP_FRAME_HEIGHT, 2160) #2160
self.camera.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter.fourcc('m','j','p','g'))
self.camera.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter.fourcc('M','J','P','G'))
self.camera.set(cv2.CAP_PROP_AUTOFOCUS, 1)
self.camera.set(cv2.CAP_PROP_FPS, 60)
i=10
while i>0: # Takes 10 pictures and waits 1 sec in between each. This is done to make sure it has time to autofocus
i=i-1
cv2.waitKey(1000) #1000
ret, self.frame = self.camera.read()
if ret != True:
print("image error") # Check to make sure it actually took a picture and didn't fail
if ret != True and i == 1:
restart_loop = True
print("Image error, restarting loop") #
continue
laplace = cv2.Laplacian(self.frame, cv2.CV_64F).var() # Checks if image is blurry. If it is, it restarts the camera.
print(laplace)
if laplace < 20: # Blurry
print("Blurry image, reinitializing")
self.camera.release()
del(self.camera)
cv2.destroyAllWindows()
gc.collect()
time.sleep(2)
else:
print("image not blurry")
break
def live_loop(self):
loop_bool = True
cv2.namedWindow("Live Feed", cv2.WINDOW_NORMAL) # Creates a normal window so that the 4k image is scaled correctly for the live feed (I have a relatively small monitor)
while loop_bool == True:
ret, self.frame = self.camera.read()
if ret != True:
print("image error")
break
self.frame_rotate = cv2.rotate(self.frame, cv2.ROTATE_180)
cv2.imshow("Live Feed", self.frame_rotate)
k = cv2.waitKey(10)
gc.collect()
def data_operation(self):
print("Start data operation")
imgpath = "path where image is saved"
cv2.imwrite(imgpath, self.frame)
t = time.localtime() # If the image can't be read as a number, saves the image with a timestamp so that it can be examined later
timestamp = time.strftime('%b-%d-%Y_%H;%M', t)
print(timestamp)
if __name__== "__main__":
op = operator()
op.start_cam()
x = threading.Thread(target=op.live_loop, daemon = True)
x.start()
schedule.every(30).seconds.do(op.data_operation)
try:
while True:
schedule.run_pending()
time.sleep(1)
except KeyboardInterrupt:
print("Ended Program")
gc.collect()
This is the smallest amount of code I can reproduce the issue with. There is more going on just this, but this code block has been run and the issue persists. I have found that when I remove the line that saves the image in the data_operation function, which is the cv2.imwrite(imgpath, self.frame), the program seems to work and the camera never goes black and the issue is gone. So I think that is the problem, but I don't know how else to save the frame as an image every 30 seconds while also keeping the live feed up and running. As far as I know, the rest of the code works perfectly except for this, and I do need to be able to save and access the image.
I am not a programmer by trade so I've just been figuring this out as I go. If something is glaringly stupid let me know. If there is a much easier/safer way to display a live webcam feed while also automatically grabbing a picture every 30 seconds and doing stuff with that picture while keeping the live loop going, I would be happy to hear that too.
Any help would be appreciated, and let me know if you have further questions.
EDIT: I have updated the code to simplify and continue shrinking the smallest reproduceable sample. I realized the start_cam function was unnecessary, as all of the cv2 initialization can be done at the start of the live_loop function. The issue persists. I have tried adding a mutex, however that seems to not help (though it's possible I am implementing it wrong). Any info or working examples would be greatly appreciated.
EDIT 2: Another thing to note is that the exact same program running on my laptop (using the built-in webcam rather than the 4k Logitech Brio) doe not seem to have the same issue. Is the frame rate or resolution causing the issue perhaps? Testing is difficult since I need to keep a working program running while working on this one, but I will do more tests when I am able and keep this post updated if anything develops. Still, feel free to toss any ideas you have to me.
Related
I have been experimenting with OpenCV in python and I want to make a game with it in pygame. But it is way too slow, OpenCV is running at 8 fps but I want the pygame game to be running at 60 fps. I don't need the tracking to be fast but I want pygame to be running at a normal frame rate. I have tried to use the built-in sockets in python but it would make both programs run at 8 fps. Then I tried to stop the socket from pausing at waiting for data but changed nothing. Thanks.
pose.py
import cv2
import mediapipe as mp
import time
import numpy as np
cap = cv2.VideoCapture(0)
mpHands = mp.solutions.hands
hands = mpHands.Hands(static_image_mode=False,
max_num_hands=8,
min_detection_confidence=0.5,
min_tracking_confidence=0.5)
mpDraw = mp.solutions.drawing_utils
pTime = 0
cTime = 0
while True:
success, img = cap.read()
imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
results = hands.process(imgRGB)
#print(results.multi_hand_landmarks)
if results.multi_hand_landmarks:
handId = 0
for handLms in results.multi_hand_landmarks:
avX = avY = l = 0
for id, lm in enumerate(handLms.landmark):
#print(id,lm)
h, w, c = img.shape
l += 1
cx, cy = int(lm.x *w), int(lm.y*h)
avX += cx
avY += cy
#if id ==0:
cv2.circle(img, (cx,cy), 3, (255,0,2555), cv2.FILLED)
avX = avX / l
avY = avY / l
mpDraw.draw_landmarks(img, handLms, mpHands.HAND_CONNECTIONS)
handId += 1
cTime = time.time()
fps = 1/(cTime-pTime)
pTime = cTime
cv2.putText(img,str(int(fps)), (10,70), cv2.FONT_HERSHEY_PLAIN, 3, (255,0,255), 3)
#cv2.imshow("Image", img)
cv2.waitKey(1)
poseGame.py
import pygame
pygame.init()
screen = pygame.display.set_mode((960, 540))
clock = pygame.time.Clock()
while True:
screen.fill((0, 0, 0))
# ...
clock.tick(60)
pygame.display.update()
you have three options.
first option is to have the game or the app running as the main python application and the other one running as a child process using the multiprocessing module and use a manager.queue for communication between the two processes, where the game will check if there is something in the queue on each frame (and pop the message if it finds one).
second (harder) option is to use sockets in non-blocking mode using socket.setblocking, so the game will not block until the other app finishes the transmission, but you will need to incrementally update an internal "message buffer" on each frame of the game as the messages will be received on multiple frames, the advantage of this approach is that the game and the commanding application will be two separate applications with no shared code, so the communication doesn't even have to be within python.
third option is to employ threading with blocking sockets where a child thread will be monitoring the received messages from the other application and putting it on a deque or the main thread to read.
one disadvantage of both approaches is that the application has to constantly send messages to the game, as both applications will be running at different rates, therefore the game needs to save the last message and keep using it until it receives the next message.
You need threads.
Do not mess around with sockets for this, it's needlessly complicated. You also don't necessarily need multiprocessing. Threads are just fine. All the heavy lifting (OpenCV, mediapipe, ...) will release the GIL. If you don't know what that means, it's okay. I'm saying that for those that think threading isn't okay to use.
You need a thread for the mediapipe stuff and another thread for reading the camera. And you can do your game logic in the main thread (the one you didn't start yourself).
The thread for the camera reading is required because if you don't read from the camera as quickly as it can produce frames (say you're processing in the same loop), produced frames will queue up in the driver and you'll get latency. Have this thread read from the camera and keep the most recent frame (in a variable), and do nothing else.
Whenever the mediapipe thread is ready for (more) work, it gets the current frame from the camera thread. Do that by simply accessing the variable. It can then work and send its conclusions to the game, either by setting variables or by posting to Queue objects (python comes with a queue module). When reading from a queue that may not contain an object, but you don't wanna block until there is an element added to it, you need to specify a zero timeout in the "get" call and catch the "empty" exception.
Here's a code skeleton. It's simple. I won't show you how to use classes. Global variables are fine for this demo. However, I'll show you how to use a Condition object, to wait for fresh frames and signal a fresh frame. No, this is not about guarding access (although it does that too), it's about making sure you actually have new data to process.
import threading
import numpy as np
import cv2 as cv
# and pygame? and mediapipe?
running = True # global flag. when False, threads end
latest_frame = None
latest_frame_available = threading.Condition()
def camera_worker_function():
global latest_frame
camera = cv.VideoCapture(0)
assert camera.isOpened()
while running:
(rv, frame) = camera.read()
if not rv: break
with latest_frame_available:
latest_frame = frame
latest_frame_available.notify_all()
camera.release()
def mediapipe_worker_function():
# globals for values you need in the game
while running:
with latest_frame_available:
if latest_frame_available.wait(timeout=1.0):
frame = latest_frame
else: # False means timeout
continue # -> recheck `running`
if frame is None: break # sentinel value to shut down
# do whatever you need here, using `frame`
...
camera_thread = threading.Thread(target=camera_worker_function)
camera_thread.start()
mediapipe_thread = threading.Thread(target=mediapipe_worker_function)
mediapipe_thread.start()
... # game logic
# end threads
running = False
with latest_frame_available:
latest_frame = None # sentinel value
latest_frame_available.notify_all()
mediapipe_thread.join()
camera_thread.join()
I didn't test this code. Take it as guidance.
I am trying to create a python program that does automatic pupil detection from a live camera feed. My program has multiple threads to obtain images from my camera, analyze the code and display an edited version of the camera feed.
As I am new to threading, my current code just displays the negative of the camera feed. When run, the program works as expected. However, if I try to run the code a second time after closing the cv2 window the program does not work as intended. My camera turns on (as expected) however, a new cv2 window does not open. I need to re open my ide (spyder) in order to get the program working properly again.
I think this may due to my threads not terminating properly, however, given my lack of experience in the area, I am not certain. If I run
threading.current_thread()
after I close the window I get
<_MainThread(MainThread, started 2468)>
I would appreciate any insight as to where the problem lies.
My Code:
frame_to_detect = None
filtering = True
filter_frame = None
view = True
stopper = None
class Filter(Thread):
#indenting got mess up here
def __init_(self):
Thread.__init__(self)
def run(self):
global frame_to_detect
global filter_frame
while view:
if frame_to_detect is not None:
filter_frame = 255-frame_to_detect
class displayFrame(Thread):
#indenting got messed up here
def __init__(self):
Thread.__init__(self)
def run(self):
global view
while view:
if filter_frame is not None:
cv2.imshow('frame',filter_frame)
if (cv2.waitKey(1) & 0xFF == ord('q')):
view = False
Filter_thread = Filter()
Filter_thread.daemon = True
Filter_thread.start()
display = displayFrame()
display.daemon = True
display.start()
video_capture = cv2.VideoCapture(filepath)
while view:
ret,frame_to_detect = video_capture.read()
filtering = False
video_capture.release()
cv2.destroyAllWindows()
When you close the cv2 window the thread continues to run in the background. The threads for the cv2.imshow will time out eventually. However to speed up things up you can have the threads close with an exception. Such as
thread.raise_exception()
thread.join()
I'm currently writing a Python (2.7) edge detection script with opencv (3.0) which basically works fine so far.
Now I want to switch between my Laptop camera and a second webcam while the program is running.
So i implemented a trackbar as a switch but i have no idea how to get the information that the trackbar has changed.
The normal getTrackbarPos() isn't enough, i need something like:
if TrackbarHasChanged() -> restart program-> cv2.VideoCapture(changed camera) -> while(true) loop
Thanks in advance
You are in luck. Actually that behavior already exists in OpenCV trackbar. If you read the documentation of createTrackbar you will see that for python you have:
cv2.createTrackbar(trackbarName, windowName, value, count, onChange) → None
The onChange argument is:
onChange – Pointer to the function to be called every time the slider changes position. This function should be prototyped as void Foo(int,void*); , where the first parameter is the trackbar position and the second parameter is the user data (see the next parameter). If the callback is the NULL pointer, no callbacks are called, but only value is updated.
Which basically means what you want to do. Instead of checking the pos every loop, if it has a change do the change.
For the restart the program part it is a little bit tricky. As far as I know (I may be wrong) this runs in another thread, and may get some race condition problem....
Here is some small code (which I cannot test fully, since I don't have a webcam) that creates a trackbar, creates the callback function, changes the camera, and avoid thread problems (I think, you may need to actually use Lock when using cameraToUse and cameraChange, to really be thread-safe). Without camera it runs, however it will always print error in connection. With cameras it may actually work :)
I added a lot of comments, but if you don't get a part feel free to ask in a comment
import cv2
import numpy as np
# global variables
amountOfCameras = 3 # how many cameras you want to use
cameraToUse = 0 #initial camera
cameraChange = True #starts true to connect at start up
camera = cv2.VideoCapture() # empty placeholder
# callback function for the tracker, x is the position value
# you may put whatever name in here
def trackerCallback(x):
global cameraToUse
global cameraChange
if cameraToUse != x:
print "I change to this camera", x
cameraToUse = x
cameraChange = True
# function to connect to a camera and replace the videoCapture variable
def connectToCamera():
global cameraChange
global camera
print "Connecting to camera", cameraToUse
camera = cv2.VideoCapture(cameraToUse)
# basic check for connection error
if camera.isOpened():
print "Successfully connected"
else:
print "Error connecting to camera", cameraToUse
cameraChange = False
#initial image with the tracker
img = np.zeros((200,600,3), np.uint8)
cv2.namedWindow('image')
cv2.createTrackbar('Camera','image',0,amountOfCameras-1,trackerCallback)
while(1):
#check if it has to connect to something else
if cameraChange:
connectToCamera()
# if no problems with the current camera, grab a frame
if camera.isOpened():
ret, frame = camera.read()
if ret:
img = frame
# displays the frame, in case of none, displays the previous one
cv2.imshow('image',img)
# if esc button exit
k = cv2.waitKey(1) & 0xFF
if k == 27:
break
cv2.destroyAllWindows()
I am working on a project where we are using the Raspicam attached to a Raspberry Pi to capture (and process) images with python using the PiCamera module.
With our current implementation I am experiencing an unexpected behaviour.
The camera is configured to capture images with 15 frames per second. To simulate processing time the program waits 5 seconds.
Minimal example:
#!/usr/bin/env python
import cv2
from picamera import PiCamera
from picamera.array import PiRGBArray
class RaspiCamera:
def __init__(self, width, height, framerate):
self.camera = PiCamera()
self.camera.resolution = (width, height)
self.camera.framerate = framerate
self.rawCapture = PiRGBArray(self.camera, size=self.camera.resolution)
self.capture_continuous = self.camera.capture_continuous(self.rawCapture, format="bgr", use_video_port=True)
def capture(self):
frame = self.capture_continuous.next()
image = self.rawCapture.array
self.rawCapture.truncate(0)
return image
if __name__ == "__main__":
camera = RaspiCamera(640, 480, 15)
while True:
frame = camera.capture()
cv2.imshow("Image", frame)
if cv2.waitKey(5000) & 0xFF == ord('q'):
break
When capture() is called for the first time, self.capture_continuous.next() returns an up to date image. When calling capture() consecutively, it often happens that self.capture_continuous.next() does not return the latest image but one that is already a few seconds old (verified by pointing the camera at a clock). From time to time, it's even older than 10 seconds. On the other hand, sometimes self.capture_continuous.next() actually returns the latest image.
Since capture_continuous is an object of the type generator, my assumption is that it keeps generating camera images in the background that accumulate in a queue while the program waits and on the next call of self.capture_continuous.next() the next element in the queue is returned.
Anyway, I am only interested in the latest, most up to date image the camera has captured.
Some first attempts to get hold of the latest images failed. I tried to call self.capture_continuous.next() repeatedly in a while loop to get to the latest image.
Since a generator is apparently also an iterator I tried some methods mentioned in this post: Cleanest way to get last item from Python iterator.
Simply using the capture() function of the PiCamera class itself is not an option since it takes approx. 0.3 seconds till the image is captured what is too much for our use case.
Does anyone have a clue what might cause the delay described above and how it could be avoided?
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.
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!
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