Related
I'm doing a fun exercise where I draw a spline and control it using four handle points. I want to be able to drag any of the four points individually, and this works fine if I hardcode a specific state for each instance of the handle object, but I'd like to make the drag event part of the point class so I can spawn as many handles as I'd like. But now I'm at a bit of a loss because while I can get the most recently defined object to show the correct behavior, the cv2 mouse callback seems to only honor one object at a time.
import cv2
import numpy as np
# Lerp for handles
def lerp(start, end, dt):
lerp_list = []
t = 1
while t >= 0:
x = start[0] + (end[0] - start[0]) * t
y = start[1] + (end[1] - start[1]) * t
lerp_list.append([x, y])
t = round(t-dt, 2)
return np.int32(lerp_list)
# Lerp for spline
def lerp_spline(l1, l2):
lerp_list = []
for pt in range(len(l1)):
t = pt/(len(l1)-1)
x = l1[pt][0] + (l2[pt][0] - l1[pt][0]) * t
y = l1[pt][1] + (l2[pt][1] - l1[pt][1]) * t
lerp_list.append([x, y])
return np.int32(lerp_list)
class Handle:
def __init__(self, x, y):
self.dragging = False
self.x = x + origin[0]
self.y = y + origin[1]
self.pt = [self.x, self.y]
self.radius = 5
def on_mouse_event(self, event, x, y, flags, param):
if event == cv2.EVENT_LBUTTONDOWN:
if (x - self.x)**2 + (y - self.y)**2 < self.radius**2:
self.dragging = True
print("click")
elif event == cv2.EVENT_MOUSEMOVE:
if self.dragging:
self.x = x
self.y = y
self.pt = [self.x, self.y]
print("dragging")
elif event == cv2.EVENT_LBUTTONUP:
self.dragging = False
print("release")
# Canvas Params
height = 512
width = 512
canvas = np.zeros((height, width, 3), dtype=np.uint8)
origin = [int(width/2), int(height/2)]
# Initial Points for Handles
radius = 5
p0 = Handle(0, -150)
p1 = Handle(-224, 200)
p2 = Handle(192, 0)
p3 = Handle(232, 192)
# Mouse events
cv2.namedWindow('Spline')
cv2.setMouseCallback('Spline', p0.on_mouse_event, param=p0)
cv2.setMouseCallback('Spline', p1.on_mouse_event, param=p1)
cv2.setMouseCallback('Spline', p2.on_mouse_event, param=p2)
cv2.setMouseCallback('Spline', p3.on_mouse_event, param=p3)
while True:
# Wipe canvas each frame
canvas = np.zeros((height, width, 3), dtype=np.uint8)
# Spline Functions
t = .05
lp0 = lerp(p0.pt, p1.pt, t)
lp1 = lerp(p3.pt, p2.pt, t)
spline = lerp_spline(lp0, lp1)
# Draw Spline
canvas = cv2.polylines(canvas, [spline], False, (255, 255, 0), 1, lineType=cv2.LINE_AA)
# Draw Handles
canvas = cv2.circle(canvas, lp0[0], 2, (255, 0, 0), -1, lineType=cv2.LINE_AA)
canvas = cv2.circle(canvas, lp1[0], 2, (255, 255, 0), -1, lineType=cv2.LINE_AA)
canvas = cv2.circle(canvas, lp0[-1], 2, (0, 255, 255), -1, lineType=cv2.LINE_AA)
canvas = cv2.circle(canvas, lp1[-1], 2, (0, 0, 255), -1, lineType=cv2.LINE_AA)
canvas = cv2.line(canvas, p0.pt, p1.pt, (255, 255, 255), 1, lineType=cv2.LINE_AA)
canvas = cv2.line(canvas, p2.pt, p3.pt, (255, 255, 255), 1, lineType=cv2.LINE_AA)
# Show Canvas
cv2.imshow('Spline', canvas)
if cv2.waitKey(16) == ord('q'):
break
cv2.destroyAllWindows()
Apologies for the large amount of code. It is fully operational. The part I'm concerned with is from the Handle class down.
Currently, only the most recently referenced Handle object, p3, shows correct functionality. In order to simplify the example I've repeated the mouse callback four times, once for each Handle object. Not an elegant solution but hopefully illustrates the issue.
Intention and result:
I was hoping that the Handle class might behave something like a CSS class, where the mouse event is automatically assigned to all instances of the class, and each instance performs individually. This of course was wishful thinking. What ended up happening was that only the final mouse callback seems to count. So when the script is checking if the mouse is close to self.x or self.y, it's only checking against the most recently defined coordinates.
Thanks for the help!
I figured out a solution. By calling on_mouse_event() as a recursive function, it worked.
def on_mouse_event(event, x, y, flags, param):
for handle in handles:
handle.on_mouse_event(event, x, y, flags, param)
Then later called this function in a single mouse callback:
cv2.setMouseCallback('Spline', on_mouse_event)
Hopefully this obscure little problem will help somebody out someday :)
I am developing my computer vision skills So I made a script to detect things inside the screen
We have this script I've put the disclosure image
The problem is that when you zoom in and out, it is not detected
import keyboard
import mss
import cv2
import numpy
from time import time, sleep
import pyautogui
pyautogui.PAUSE = 0
sct = mss.mss()
dimensions_left = {'left': 0,'top': 0,'width': 2560,'height': 1440}
print("Press 's' to start playing.")
print("Once started press 'p' to quit.")
keyboard.wait('s')
detect = cv2.imread('scend.png', cv2.COLOR_RGB2BGR)
w = detect.shape[1]
h = detect.shape[0]
ww , hh = int(w/2),int(h/2)
fps_time = time()
while True:
im = numpy.array(sct.grab(dimensions_left))
im = numpy.flip(im[9:, 9:, :1], 2) # 1
im = cv2.resize(im, (1000, 500))
im = cv2.cvtColor(im, cv2.COLOR_RGB2BGR)
result = cv2.matchTemplate(im, detect, cv2.TM_CCOEFF_NORMED)
threshold = .4
yloc, xloc = numpy.where(result >= threshold)
rectangles = []
for (x, y) in zip(xloc, yloc):
rectangles.append([int(x), int(y), int(w), int(h)])
rectangles.append([int(x), int(y), int(w), int(h)])
rectangles, weights = cv2.groupRectangles(rectangles, 1, 0.2)
for (x, y, w, h) in rectangles:
cv2.rectangle(im, (x, y), (x + w, y + h), (0, 255, 255), 2)
cv2.rectangle(im, (x + ww, y + hh), (x + w - ww, y + h - hh), (0, 0, 255), 5)
# pyautogui.moveTo(x, y, 0)
cv2.imshow('Screen Shot', im)
cv2.waitKey(1)
if keyboard.is_pressed('p'):
break
print('FPS: {}'.format(1 / (time() - fps_time)))
fps_time = time()
I am currently trying to implement a function that will allow me to use the cursor to "grab" a video and move it around. I want to be able to only move the video when the cursor is held down and moved. I have defined the mouse events, captured the coordinates of my cursor but I am not sure how to write the function to crop the video.
x1, x2, y1, y2 = 0, 0, 0, 0
mouse_down = False
def mouse_event_callback(event, x, y, flags, param):
global x1, x2, y1, y2, mouse_down
if event == cv2.EVENT_LBUTTONDOWN:
x1, y1 = x, y
mouse_down = True
elif event == cv2.EVENT_MOUSEMOVE:
if mouse_down:
x2, y2 = x, y
mouse_grab_video(x1, x2, y1, y2)
elif event == cv2.EVENT_LBUTTONUP:
mouse_down = False
def mouse_grab_video(x1, x2, y1, y2):
...
Any help would be much appreciated!
You can crop the image directly based on the coordinate you get from your mouse event.
#!/usr/bin/env python3
import argparse
import cv2
import numpy as np
from PIL import Image
drawing = False # true if mouse is pressed
ix,iy = -1,-1
refPt = []
img = ""
clone = ""
ROIRegion = []
# mouse callback function
def draw_rectangle(event,x,y,flags,param):
global ix,iy,drawing,img,clone,refPt, ROIRegion
if event == cv2.EVENT_LBUTTONDOWN:
drawing = True
ix,iy = x,y
refPt = [(x, y)]
ROIRegion.append(refPt)
#clone = img.copy()
elif event == cv2.EVENT_MOUSEMOVE:
if drawing == True:
img = clone.copy()
cv2.rectangle(img,(ix,iy),(x,y),(0,255,0),3)
a=x
b=y
if a != x | b != y:
cv2.rectangle(img,(ix,iy),(x,y),(0,0,0),-1)
elif event == cv2.EVENT_LBUTTONUP:
drawing = False
refPt.append((x,y))
img = clone.copy()
cv2.rectangle(img, (ix,iy),(x,y), (0, 255, 0), 2)
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required=True, help="Path to the image")
args = vars(ap.parse_args())
img = cv2.imread(args["image"])
img = np.array(img)
clone = img.copy()
cv2.namedWindow('image')
cv2.setMouseCallback('image',draw_rectangle)
while(1):
cv2.imshow('image',img)
k = cv2.waitKey(1) & 0xFF
if k == ord("r"):
del ROIRegion[-1]
del refPt[-1]
img = clone.copy()
elif k == 27:
break
#Crop image to ROI
for region in range(len(ROIRegion)):
cv2.rectangle(img, ROIRegion[region][0],ROIRegion[region][1], (0, 255, 0), 2)
roi = clone[ROIRegion[region][0][1]:ROIRegion[region][1][1], ROIRegion[region][0][0]:ROIRegion[region][1][0]]
For zoom you may use EVENT_MOUSEWHEEL to increment scale and resize the image based on the scale. update imshow for latest image you scale. you can refer to this link
I'm writing a Programm to crop inmages and my problem is I want that rectangle to have a specific aspect ratio (90:90) and is it possible to make the rectangle instead of the mouse, change its size with mousewheel and confirm with mouse click?
import cv2
import numpy as np
cropping = False
x_start, y_start, x_end, y_end = 0, 0, 0, 0
image = cv2.imread('example.jpg')
oriImage = image.copy()
def mouse_crop(event, x, y, flags, param):
# grab references to the global variables
global x_start, y_start, x_end, y_end, cropping
# if the left mouse button was DOWN, start RECORDING
# (x, y) coordinates and indicate that cropping is being
if event == cv2.EVENT_LBUTTONDOWN:
x_start, y_start, x_end, y_end = x, y, x, y
cropping = True
# Mouse is Moving
elif event == cv2.EVENT_MOUSEMOVE:
if cropping == True:
x_end, y_end = x, y
# if the left mouse button was released
elif event == cv2.EVENT_LBUTTONUP:
# record the ending (x, y) coordinates
x_end, y_end = x, y
cropping = False # cropping is finished
refPoint = [(x_start, y_start), (x_end, y_end)]
if len(refPoint) == 2: #when two points were found
roi = oriImage[refPoint[0][1]:refPoint[1][1], refPoint[0][0]:refPoint[1][0]]
cv2.imshow("Cropped", roi)
cv2.namedWindow("image")
cv2.setMouseCallback("image", mouse_crop)
while True:
i = image.copy()
if not cropping:
cv2.imshow("image", image)
elif cropping:
cv2.rectangle(i, (x_start, y_start), (x_end, y_end), (255, 0, 0), 2)
cv2.imshow("image", i)
cv2.waitKey(1)
# close all open windows
cv2.destroyAllWindows()
This is the basic structure you could use to do this. Create an initial rectangle on first click, resize the rectangle by scrolling up or down, then finalize the cropping by clicking again. You would need to add some checks to limit the rectangle when it goes out of the image bounds and when it goes "smaller than zero".
# if the left mouse button was released
if event == cv2.EVENT_LBUTTONUP:
# create rectangle
if not cropping:
x_start, y_start = x-20, y-20
x_end, y_end = x+20, y+20
cropping = True # cropping is finished
# finish cropping
else:
refPoint = [(x_start, y_start), (x_end, y_end)]
if len(refPoint) == 2: #when two points were found
roi = oriImage[refPoint[0][1]:refPoint[1][1], refPoint[0][0]:refPoint[1][0]]
cv2.imshow("Cropped", roi)
elif event == cv2.EVENT_MOUSEWHEEL:
print(flags)
# scroll up
if flags > 0:
x_start, y_start = x_start-2, y_start-2
x_end, y_end = x_end+2, y_end+2
# scroll down
else:
x_start, y_start = x_start+2, y_start+2
x_end, y_end = x_end-2, y_end-2
Thank you :) but i have still one problem my code looks now like this:
import cv2
import numpy as np
cropping = False
x_start, y_start, x_end, y_end = 0, 0, 0, 0
image = cv2.imread('example.jpg')
oriImage = image.copy()
def mouse_crop(event, x, y, flags, param):
# grab references to the global variables
global x_start, y_start, x_end, y_end, cropping
# if the left mouse button was DOWN, start RECORDING
# (x, y) coordinates and indicate that cropping is being
if event == cv2.EVENT_MOUSEMOVE:
x_start, y_start, x_end, y_end = x, y, x+96, y+96
cropping = True
# Mouse is Moving
# elif event == cv2.EVENT_MOUSEMOVE:
# if cropping == True:
# x_end, y_end = x, y
# if the left mouse button was released
elif event == cv2.EVENT_LBUTTONUP:
# record the ending (x, y) coordinates
# x_end, y_end = x, y
cropping = False # cropping is finished
refPoint = [(x_start, y_start), (x_end, y_end)]
if len(refPoint) == 2: #when two points were found
roi = oriImage[refPoint[0][1]:refPoint[1][1], refPoint[0][0]:refPoint[1][0]]
cv2.imshow("Cropped", roi)
elif event == cv2.EVENT_MOUSEWHEEL:
print(flags)
# scroll up
if flags > 0:
x_start, y_start = x_start-15, y_start-15
x_end, y_end = x_end+15, y_end+15
# scroll down
else:
x_start, y_start = x_start+15, y_start+15
x_end, y_end = x_end-15, y_end-15
cv2.namedWindow("image")
cv2.setMouseCallback("image", mouse_crop)
while True:
i = image.copy()
if not cropping:
cv2.imshow("image", image)
elif cropping:
cv2.rectangle(i, (x_start, y_start), (x_end, y_end), (255, 0, 0), 2)
cv2.imshow("image", i)
cv2.waitKey(1)
# close all open windows
cv2.destroyAllWindows()
my problem is: the rectangle follows my mouse, I can make it bigger and smaller with the mousewheel but if I made it bigger and the mouse moves, it gets small again
I have written a code that draws circles over an image using mouse clicks. The radius and centre of the circles drawn are stored in a .txt file.
However, I want to add on another component into the text file;- the mean RGB value of the inside of the circles drawn. How do i go about doing that?
I've seen another qs where the author recommends using a mask. (RGB average of circles) Is it possible to do so without adding a mask?
import cv2
import numpy as np
import math
drawing = False
colours=[(255,0,0),(0,255,0),(0,0,255),(204,0,204),(0,155,255),(0,223,255),(238,220,130),
(255,79,79), (161,0,244),(32,178,170),(170,178,32)]
def draw_circle(event, x, y, flags, param):
global x1, y1, drawing, radius, num, img, img2,z,circleProps
if event == cv2.EVENT_LBUTTONDOWN:
drawing = True
x1, y1 = x, y
radius = int(math.hypot(x - x1, y - y1))
cv2.circle(img, (x1,y1), radius, (255, 0, 0), 1)
elif event == cv2.EVENT_MOUSEMOVE:
if drawing == True:
a, b = x, y
if a != x & b != y:
img = img2.copy()
radius = int(math.hypot(a - x1, b - y1))
cv2.circle(img, (x1,y1), radius, (255, 0, 0), 1)
elif event == cv2.EVENT_LBUTTONUP:
drawing = False
num += 1
radius = int(math.hypot(x - x1, y - y1))
z = (z + 1) % (len(colours) - 1)
cv2.circle(img, (x1,y1), radius, colours[z], 1) #just need outer circle colour to be different
print("rad:",radius)
circleProps += "%d "%num + "%d "%x1 + "%d "%y1 + "%d \n"%radius
cv2.circle(img, (x1, y1), 1, (0, 255, 0), -1) #draws centre of circle
#font = cv2.FONT_HERSHEY_SIMPLEX
font = cv2.FONT_HERSHEY_PLAIN
cv2.putText(img, str(num), (x1 + radius, y1 + radius), font, 1, (200, 255, 155), 1, cv2.LINE_AA)
img2 = img.copy()
elif event == cv2.EVENT_RBUTTONUP:
pass
def handleKeyboard():
if cv2.waitKey(20) == 38:
print ('38')
if __name__ == "__main__":
num = 0
z = 0
windowName = 'Drawing'
circleProps ="Num | x1 | y1 | r \n"
img = cv2.imread('image.jpg', cv2.IMREAD_COLOR)
img2 = img.copy()
cv2.namedWindow(windowName)
cv2.setMouseCallback(windowName, draw_circle)
while (True):
cv2.imshow(windowName, img)
# handleKeyboard()
if cv2.waitKey(20) == 27:
text_file = open("Output.txt", "w") # create txt file
text_file.write(circleProps)
text_file.close()
break
elif cv2.waitKey(20) == 38:
print ('38')
cv2.destroyAllWindows()
EDIT:
I have just added these lines into the function and I am getting RGB values. However I am not sure if the values printed are right? Could someone clarify?
elif event == cv2.EVENT_LBUTTONUP:
drawing = False
num += 1
radius = int(math.hypot(x - x1, y - y1))
z = (z + 1) % (len(colours) - 1)
circle_drawn=cv2.circle(img, (x1,y1), radius, colours[z], 1) #lines added
RGB=cv2.mean(circle_drawn) #lines added
print("RGB: ", RGB) #lines added
print("rad:",radius)
circleProps += "%d "%num + "%d "%x1 + "%d "%y1 + "%d \n"%radius
cv2.circle(img, (x1, y1), 1, (0, 255, 0), -1) #draws centre of circle
#font = cv2.FONT_HERSHEY_SIMPLEX
font = cv2.FONT_HERSHEY_PLAIN
cv2.putText(img, str(num), (x1 + radius, y1 + radius), font, 1, (200, 255, 155), 1, cv2.LINE_AA)
img2 = img.copy()