OpenCV Python Trackbar Callback - python

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

Related

Can't save a picture from a Basler acA5472-17um Camera using cam.StartGrabbing()

Im having troubles with saving Images from a Basler camera. I wanted to write a code so that I can press the key "s" and save a picture. It seems like the code gets hung up on cam.RetrieveResult(2000). Any Idea how I can fix this problem ?
#Code inspiration from: https://github.com/basler/pypylon/blob/master/samples/save_image.py
from pypylon import pylon
import platform
import cv2
count = 0
#path_root = 'C:\\Users'
img = pylon.PylonImage()
tlf = pylon.TlFactory.GetInstance()
cam = pylon.InstantCamera(tlf.CreateFirstDevice())
cam.Open()
cam.StartGrabbing()
while True:
with cam.RetrieveResult(2000) as result:
# Calling AttachGrabResultBuffer creates another reference to the
# grab result buffer. This prevents the buffer's reuse for grabbing.
img.AttachGrabResultBuffer(result)
key = cv2.waitKey() & 0xFF
if platform.system() == 'Windows' and key == ord('s'): # press s to safe:
filename = "saved_pypylon_img_%d.png" % count
img.Save(pylon.ImageFileFormat_Png, filename)
print('png image saved')
count += 1
if key == ord('q'):
break
# In order to make it possible to reuse the grab result for grabbing
# again, we have to release the image (effectively emptying the
# image object).
img.Release()
cam.StopGrabbing()
cam.Close()
Your code looks OK and works for the camera on my desk. This might be some misconfiguration. For instance, if your camera's Trigger Mode is On then you'll always expire not providing pulses to cam's inputs.
go to pylon Viewer and reset camera to default settings (User Set Control > User Set Selector: Default User Set > User Set Load: Execute)
put camera in Continuous Shot to make sure no errors are popping up in Messages pane and camera runs smoothly
close camera & close pylon Viewer & try your sample again

Python cv2 VideoCapture() in daemon thread turns black randomly

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.

python opencv and tkinter capture webcam problem

hello everyone i have a code for read video from webcam and show it into the tkinter window with timer threading.
when user click show button, app make a thread and run it every x second to show frame by frame.
here's the problem : every several frame app shows first frame that captured from video source.
that's weird i know but i cant find out why!!!
here's my code:
import tkinter as tk
from tkinter import ttk
from tkinter import *
import threading
import cv2
import PIL.Image, PIL.ImageTk
from PIL import Image
from PIL import ImageTk
import time
class App(threading.Thread):
def __init__(self, root, window_title, video_source=0):
self.root = root
self.root.title(window_title)
self.video_source = video_source
self.show_switch=False
self.showButton = Button(self.root, text="PlayStream",command=self.showStram,width=15, padx="2", pady="3",compound=LEFT)
self.showButton.pack()
# Create a canvas that can fit the above video source size
self.canvas = tk.Canvas(root, width = 530, height = 397, cursor="crosshair")
self.canvas.pack()
self.root.mainloop()
def updateShow(self):
# Get a frame from the video source
cap=cv2.VideoCapture(0)
while True:
if(cap.isOpened()):
#read the frame from cap
ret, frame = cap.read()
if ret:
#show frame in main window
self.photo = PIL.ImageTk.PhotoImage(image = PIL.Image.fromarray(frame))
self.canvas.create_image(0, 0, image = self.photo, anchor = tk.NW)
else:
break
raise ValueError("Unable to open video source", video_source)
if self.show_switch==False:
cap.release()
return False
time.sleep(0.0416666666666667)
#release the cap
cap.release()
def showStram(self):
if self.show_switch:
self.showButton["text"]="StartStream"
# self.showButton.configure(image=self.playIcon)
self.show_switch=False
else:
self.showButton["text"]="StopStream"
self.show_switch=True
# self.showButton.configure(image=self.stopIcon)
self.showTimer=threading.Thread(target=self.updateShow,args=())
#self.showTimer.daemon=True
self.showTimer.start()
App(tk.Tk(), "Main")
This is a complicated question, because your example code is extensive and combines multiple things that can go wrong. I cannot test your code in its current form.
First off, you are accessing your Tk instance from a thread that is not the MainThread. This can cause all sorts of issues. There are also bugs present in the implementation of supposed thread-safety in Tkinter, and the solution has not yet been merged. Checkout mtTkinter if you really need to use multiple threads with Tkinter, and even then, it is better not to, especially not if you are building a new application and have the option to use Queues or some other system instead.
Second, you create a (subclass) instance of threading.Thread, but you never call threading.Thread.__init__ in App.__init__ (which is an absolute requirement if you want to use it as a Thread!). Then you create a new Thread in def showStream(self), while you actually already had a thread. Now, this does not break your code, but you do not need to subclass threading.Thread if you do not intend to use your class as a Thread. As you create a Thread in your class, there is no need to make a Thread out of your class.
Then, moving on through your code, you do start the Thread, so updateShow gets run. However, in your while loop, there is a problem:
while True:
if (cap.isOpened()):
ret, frame = cap.read()
if ret:
...
else:
break
# Error will never be raised because of break
# break exits the loop immediately, so the next line is never evaluated
raise ValueError()
cap.release()
# No notification of the loop having ended
There may be two things going wrong here. The first is that maybe your loop just ends because of the break in the else-clause. Break immediately exits the loop code. Anything following it will never be executed. If it does exit the loop because it failed to get the next frame, you cannot know for sure as you do not check whether the thread is still alive (threading.Thread.is_alive) or have any print statements to indicate that the loop has ended.
Secondly, your program might actually be hard-crashing on you accessing Tkinter from a second thread. Doing this causes undefined behaviour, including weird errors and Python-interpreter lockups because the Tk interpreter and Python interpreter are fighting for flow control in a deadlock (simply put).
Last but not least, there is a problem with the way you create your images:
self.canvas.create_image(0, 0, image = self.photo, anchor = tk.NW)
In this line, you create a new image on the Canvas. However, if an image already exists on the Canvas in the location where you are creating a new image, it will appear under the image already shown. If you do not delete your old image (which is in any case advisable to prevent a huge memory leak), it will remain visible on the Canvas.
def __init__(...):
...
self.im = None
def updateShow(self):
...
while True:
if (cap.isOpened()):
...
if ret:
if self.im is not None:
self.canvas.delete(self.im)
...
self.im: str = self.canvas.create_image(0, 0, image=self.photo, anchor=tk.NW)
Summarizing: With your current posted code, there are multiple things that can be going wrong. Without additional information, it is impossible to know. However, if you fix accessing Tkinter from a different Thread, adjust your while loop to not break but raise the error, you adjust your Canvas image creation code and your video source actually does work properly, it should work out.

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.
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

How to capture a frame from a camera stream exactly when a key is hit using opencv in python?

I am trying to capture image from a webcam when a key is hit. Following code is successful
import cv
cv.NamedWindow("w1")
camera = cv.CaptureFromCAM(-1)
while True:
key = cv.WaitKey(0);
if key == 'q':
break;
image = cv.QueryFrame(camera)
cv.ShowImage("w1", image)
cv.DestroyWindow("w1")
It works fine for the first keypress. For the next keydown it shows a frame very close to the first one even if you moved. After several key presses it changes to the actual image. What i can infer is that, there is some kind of buffer where frames are stored
I am wondering if someone could please help me in getting the precise frame when a key is being hit.
I am using opencv with interface to python. The operating system is ubuntu 11.04. The calls for capture frame are sent to v4l library. I have an integrated webcam with my dell laptop.
I am wondering if someone can help me with this issue.
Thanks a lot
I suggest you try it in a slightly different manner:
import cv
cv.NamedWindow("w1")
camera = cv.CaptureFromCAM(-1)
while True:
image = cv.QueryFrame(camera)
key = cv.WaitKey(33)
if key == 'q':
break
elif key != -1:
cv.ShowImage("w1", image)
cv.DestroyWindow("w1")
Notice the change in the call to cv.WaitKey(): instead of blocking on it, just wait for a reasonable time. Then check if a key was actually pressed (key != -1).
I should note that your code worked on my Mac OS X 10.6 with OpenCV 2.3.

Categories