There is a third way to do this without using global variables or encompassing it in a class; move to the callback via arguments everything you need. We can pass a dictionary with the key "grayFrame" and that has as value the array (frame). Being a mutable dictionary, we can modify it from both functions (in Python the arguments are passed through assignment). You can pass the arguments you want using the dictionary or another mutable object such as a list, DataClass, etc. In C ++ we could use a struct, for example.
In this case setMouseCallback
puts at our disposal the argument param
( userdata
in C ++) that exists for this precisely:
import numpy as np
import cv2
# Función main
def main():
cap = cv2.VideoCapture(1)
params = {"grayFrame": None}
cv2.namedWindow('ventana')
cv2.setMouseCallback('ventana', buscar_circulos, param=params)
while True:
ret, frame = cap.read()
grayFrame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
grayFrame = cv2.medianBlur(grayFrame, 5)
params["grayFrame"] = grayFrame
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cv2.imshow('ventana', params["grayFrame"])
cap.release()
cv2.destroyAllWindows()
def buscar_circulos(event, x, y, flags, param):
if event == cv2.EVENT_LBUTTONDOWN:
grayFrame = param["grayFrame"]
circles = cv2.HoughCircles(grayFrame, cv2.HOUGH_GRADIENT, 1, 20,
param1=50, param2=30, minRadius=0, maxRadius=0
)
if circles is not None:
circles = np.round(circles[0, :]).astype(int)
for x, y, r in circles:
cv2.circle(grayframe, (x, y), r, (255, 0, 0), 1)
print(x, y)
if __name__ == '__main__':
main()
With grayFrame = param["grayFrame"]
we make the variable grayFrame
point to the reference of the object pointed to by at that time the key "grayFrame"
. This apart from allowing to write less code avoids the search in the dictionary every time we need the value, however, if we are going to assign a new object it must be done by param["grayFrame"] = obj
, if it is assigned to grayFrame
only this variable is modified local, not param["grayFrame"]
and therefore does not affect main()
. Always remember that the Python variables are only reference identifiers to an object in memory.
I do not know if your code will work as I think you expect, the detection will only take place in one frame (when you click) and only the circles in that frame will be drawn for a moment. If you want to activate or deactivate the detection by clicking on the left you can use a flag and perform the detection on the mainloop:
import numpy as np
import cv2
def buscar_circulos(event, x, y, flags, param):
if event == cv2.EVENT_LBUTTONDOWN:
param["buscar"] = not(param["buscar"])
def main():
cap = cv2.VideoCapture(0) # Cambiar dispositivo si procede
params = {"buscar": False}
cv2.namedWindow('ventana')
cv2.setMouseCallback('ventana', buscar_circulos, param=params)
while True:
ret, frame = cap.read()
if params["buscar"]:
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray,(9, 9), 2, 2);
circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, 75,
param1=100, param2=50,
minRadius=0, maxRadius=0
)
if circles is not None:
circles = np.round(circles[0, :]).astype("int")
for (x, y, r) in circles:
cv2.circle(frame, (x, y), r, (0, 255, 0), 4)
cv2.rectangle(frame, (x - 5, y - 5), (x + 5, y + 5), (0, 128, 255), -1)
cv2.imshow('ventana', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
if __name__ == "__main__":
main()
Clicking on the window activates the detection, when clicking again it is deactivated and so on. The previous filter and the HoughCircles
parameters must be fine-tuned for each particular case.