Flow of Control Python OpenCv : Why cv2.setMouseCallback isn't inside loop? - opencv

I am confused with the flow of control in the following program. The purpose of the code is to draw a rectangle in the live video stream from the webcam.
Working Principal: First click will be initializing coordinates of the starting corner of the rectangle and mark it with a bold circle. Second click will be completing the rectangle.
Now my Question is: Why is cv2.setMouseCallback('Test',draw_rectangle) statement isn't inside the loop?
The code is working perfectly fine but I am unable to understand the flow of control. Please help me out.
import cv2
import os
os.environ["OPENCV_VIDEOIO_PRIORITY_MSMF"] = "0"
#CALLBACK FUNCTION RECTANGLE
def draw_rectangle(event,x,y,flags,param): #Param is the just the additional paramter which u can receive
global pt1, pt2, topLeft_Clicked, botRight_Clicked
if event ==cv2.EVENT_LBUTTONDOWN:
#Reset if rectangle is drawing i.e both var are true
if topLeft_Clicked and botRight_Clicked:
pt1=(0,0)
pt2=(0,0)
topLeft_Clicked=False
botRight_Clicked=False
if topLeft_Clicked == False:
pt1=(x,y)
topLeft_Clicked=True
elif botRight_Clicked == False:
pt2=(x,y)
botRight_Clicked=True
#GLOBAL VARIABLES
pt1=(0,0)
pt2=(0,0)
topLeft_Clicked= False
botRight_Clicked= False
#COnnect to the Callback
cap=cv2.VideoCapture(0)
cv2.namedWindow('Test')
cv2.setMouseCallback('Test',draw_rectangle)
while True:
ret,frame=cap.read()
#Drawing Based on Global Variables
if topLeft_Clicked: # If topleft is true
cv2.circle(frame,center=pt1,radius=5,color=(0,0,255),thickness=-1)
if topLeft_Clicked and botRight_Clicked:
cv2.rectangle(frame,pt1,pt2,(0,0,255),3)
cv2.imshow('Test',frame)
if(cv2.waitKey(1) & 0xFF==ord('q')):
break
cap.release()
cv2.destroyAllWindows()

Callback functions are called on events. Unlike a regular function, you don't need to make a function call every time you want it to run.
The line cv2.setMouseCallback('Test',draw_rectangle) will set the function draw_rectangle as a response to any event received from mouse on the OpenCV window "Test". Once the callback is set, inside your while loop you will catch all your mouse events on your "Test" window.

Callback is the function that is called every time you move the mouse over the display window. It is independent from the flow in main, i.e., it is in a new thread waiting for changes in mouse input.
The reason why you use loop in main is because you want to update the image you display. After calling imshow, you need to call waitKey for impact on rendering.

cv2.setMouseCallback('Test',draw_rectangle) this function is actually an event handler, which is setting the call for draw_rectangle function on every left mouse click. The rest of the code inside the while loop are for all the dynamic operations and at last cv2.imshow is to render that in the image.
One token of gift for better handling of opencv closeWindows, closing only currently used window only:
def showImage(imageName, image):
img = image.copy()
cv2.imshow(imageName, img)
while(1):
pressedKey = cv2.waitKey(0) & 0xFF
if(pressedKey == ord('q')):
cv2.destroyWindow(imageName)
break
else:
cv2.putText(img, "\press q to exit", (10,10), cv2.FONT_HERSHEY_SIMPLEX, 0.45, color=(255,0,0))
cv2.imshow(imageName, img)

Related

Is there a way to store circle coordinates and move them around in python turtle?

I know it's possible to store polygons in a dictionary since they have definitive coordinates, but is there a way to store the coordinates of a circle into a dictionary and move them around? My program consists of detecting whether a mouse click is within a circle and from there, gets the coordinate of that circle moving it to wherever the user desires with another mouse click (the get.poly function, instead of moving the current circle that I already drew, makes another copy). Below is an incomplete snippet of what I want to do:
def buttonclick(x, y): # detects mouseclick
return pen.goto(x, y)
def check(ctr, pt): # check whether the click is within the circle
if (pt[0] - ctr[0])** 2 + (pt[1] - ctr[1])**2 < 5**2:
return True
if check((0,5), mouseclick coordinates): # if true, move circle to next click
# pen = the circle thats detected
# move circle coordinates to next mouseclick
# break
I tried with the code provided by /u/cdlane as follows and here is what I meant by generating a new copy
pen.goto(0,0)
pen.pd()
pen.begin_poly()
pen.circle(radius)
pen.end_poly()
shape.addcomponent(pen.get_poly(), 'red', 'black')
screen.register_shape('1', shape)
pen = Turtle(shape = '1')
pen.pu()
Function does exactly what I need it to do but using an existing circle instead of generating a new copy.
Rather than fix your implementation, I'm going to suggest a different implementation to achieve what you describe.
The circles themselves will be turtles, and clicking on one will "select" it. Clicking anywhere on the screen after that will move it. However, we don't want turtle.onclick() active at the same time as screen.onclick() since they both will get invoked, causing complications. Below is my two turtle example:
from turtle import Screen, Turtle
from functools import partial
selected = None
def on_screen_click(x, y):
global selected
screen.onclick(None)
selected.goto(x, y)
outline, fill = selected.color()
selected.color(fill, outline)
selected = None
for turtle in screen.turtles():
turtle.onclick(partial(on_turtle_click, turtle))
def on_turtle_click(self, x, y):
global selected
for turtle in screen.turtles():
turtle.onclick(None)
selected = self
outline, fill = selected.color()
selected.color(fill, outline)
# (re)enable screen onclick handler after this function, not during
screen.ontimer(partial(screen.onclick, on_screen_click))
screen = Screen()
turtle_1 = Turtle()
turtle_1.shape('circle')
turtle_1.color('red', 'black')
turtle_1.penup()
turtle_2 = turtle_1.clone()
turtle_2.goto(100, 100)
for turtle in screen.turtles():
turtle.onclick(partial(on_turtle_click, turtle))
screen.mainloop()

Clear() clears the entire screen, not only one turtles drawings?

I am trying to make a card program with turtle in python and i ran into a problem. I had multiple turtles and tried to clear only the drawing of one to make the illusion of another card coming into play. When i tried clearing only one of the turtles drawings It cleared the entire screen. I have no idea why it cleared it all. Here is a code i wrote to test it out. (I am on Pythonista)
import turtle
t = turtle.Turtle()
b = turtle.Turtle()
for i in range(4):
t.forward(100)
t.left(90)
for i in range(4):
b.backward(100)
b.right(90)
t.clear()
Your code works fine for me under the standard Python distribution. The usual problem would be calling the screen's clear() method instead of the turtle's clear() method but you appear to have done that correctly:
from turtle import Screen, Turtle
screen = Screen()
a = Turtle()
b = Turtle()
for _ in range(4):
a.forward(100)
a.left(90)
for _ in range(4):
b.backward(100)
b.right(90)
a.clear()
screen.exitonclick()
So if the above also fails, it may be an implementation issue with Pythonista's turtle.py library, which I assume is custom as they probably didn't want to replicate tkinter and Tk.

Python turtle after drawing , click mouse inside drawing, clear screen and redraw

I am working on using python turtle to draw a circle.when i draw the circle, mouse click inside circle erase, click anywhere in the turtle,redraw it and click again inside again erase. So the process like this :
black screen
1.Click mouse
2.Start drawing circle
3.finish
4.click inside circle
5.clear screen
6.click again anywehre in the screen redraw circle
7.click inside circle clear screen
Thanks
https://i.stack.imgur.com/LR8CH.png
import PositionService
import turtle
t=turtle.Turtle()
ts=tur
tle.Screen()
ts.bgpic("shape_window.png")
t.up()
def get_click(x,y):# get click (x,y)
#if counter == 0:
draw_circle(x,y,"green",80)# draw the circle
print("clicking at ({}, {})".format(x,y))
def draw_circle(x,y,color,rad): # draw the circle
t.goto(x,y)
t.down()
t.circle(80)
t.color("green")
t.up()
t.hideturtle()
#t.home()
def main():
#maybe use global _pen
ts.onclick(get_click) # clicker
#set_position( x,y)?
#is_visible(draw_square)
I think were from the same class. I was also struggling until I realized that they supplied a PositionService.py file. You are supposed to use the functions within that file to help.

colour detection bot

I want to create a script to automatically click on a moving target on a game.
To do this I want to check colours on the screen to determine where my target is and then click him, repeating this if he moves.
I'm not skilled at programming and there might be an easier solution to what I have proposed below:
1/Split the screen into equal tiles - size of tile should represent the in game object.
2/Loop through each pixel of each tile and create a histogram of the pixel colours.
3/If the most common recorded colour matches what we need, we MIGHT have the correct tile. Save the coords and click the object to complete task
4/Every 0.5 seconds check colour to determine if the object has moved, if it hasnt, keep clicking, if it has repeat steps 1, 2 and 3.
The step I am unsure of how to do technically is step 1. What data structure would I need for a tile? Would a 2D array suffice? Store the value of each colour in this array and then determine if it is the object. Also in pseudo how would I split the screen up into tiles to be searched? The tile issue is my main problem.
EDIT for rayryeng 2:
I will be using Python for this task. This is not my game, I just want to create a macro to automatically perform a task for me in the game. I have no code yet, I am looking more for the ideas behind making this work than actual code.
3rd edit and final code:
#!/usr/bin/python
import win32gui, win32api
#function to take in coords and return colour
def colour_return(x,y):
colours = win32gui.GetPixel(win32gui.GetDC(win32gui.GetActiveWindow()), x,y)
return colours
def click(x,y):
win32api.SetCursorPos((x,y))
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN,x,y,0,0)
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP,x,y,0,0)
#variable declaration
x = 1
y = 1
pixel_value = []
colour_found = 0
while x < 1600:
pixel_value = colour_return(x,y)
if pixel_value == 1844766:
click(x,y)
x=x+1
#print x
print y
if x == 1600:
y=y+1
x=1
#print tile
pixel_value = 0
This is the final code that I have produced. It works but it is incredibly slow. It takes 30 seconds seconds to search all 1600 pixels of y=1. I guess it is my method that is not working. Instead of using histograms and tiles I am now just searching for a colour and clicking the coordinates when it matches. What is the fastest method to use when searching an entire screen for a certain colour? I've seen colour detection bots that manage to keep up every second with a moving character.

XNA 4.0: How to remove text after drawn to screen?

Good day everyone.
I'm trying to draw the text "Pass Complete!" to screen with this code:
spriteBatch.DrawString(font, "PASS COMPLETE!", new Vector2(30, 130), Color.White);
Which does fire off the proper IF statement. However, how do I go about then removing that text from the screen? I'm really not sure at all where to go on from here and my instructor wants me to google the answer or find it in textbook. I have been all over my XNA textbook and I have found no outlet to removing that text.
Thanks for any and all help.
Update:
protected override void Draw(GameTime gameTime)
I have the IF statement included in here. Basically it checks collision with p_Receiver and if it the bool checks out, it draws the DrawString. Should I maybe be looking at this from a different angle?
Final:
I went ahead with the following as the answer and it's working better then before. :)
if (PassInfo == 3) {
(timer code)
(IF timer not "used up" then run the draw)
Working good for now.
I appreciate it.
I'm doing this by function that add text with some parameters into generic list. and then i update and draw items from that list. in pseudo code:
function addText(text,position,duration)
texts.add(new t(text,position,duration))
end function
function updateText()
for each t as text in texts.findall(where t.active)
t.duration -= 1
if t.duration < 0 then t.active = false
next
end function
function drawText()
for each t as text in texts.findall(where t.active)
//draw it
next
end function
so by this you can add unlimited number of texts on different position and duration on screen.
A lot of games redraw the entire window / screen each time through the draw cycle so there's a distinct chance that the solution to removing it is simply to stop drawing it.
i.e. have your if condition not draw the text when it is no longer required.
If, on the other hand, you've some more complex drawing logic that only draws portions of the window / screen that need updating then you'll need to include logic to redraw that part of the screen that contained the text once it is no longer needed.

Resources