本文给出一个鼠标绘图的综合程序。程序由两部分组成:一是参数设置,二是画板显示。参数设置包括画笔颜色、画笔大小和绘制图形。绘制图像可以在直线、矩形、圆形、曲线中进行选择。画布默认是黑色的,初始画笔默认是白色的,绘制图形默认为直线。画笔大小默认为1px,最大画笔大小为5px。如果画笔大小设置为0,代表以填充方式绘制图形。
createTrackbar(trackbarName,windowName,value,count,onChange)
轨迹栏必须存在一个窗口中,所以要先创建一个窗口才能添加轨迹栏
cv.namedWindow('image')
# 轨迹栏发生变化时的回调函数 def nothing(x): pass # 创建颜色变化的轨迹栏 cv.createTrackbar('R','image',255,255,nothing) cv.createTrackbar('G','image',255,255,nothing) cv.createTrackbar('B','image',255,255,nothing) # 根据names生成选项 def get_choice_str(names): values = np.arange(0,len(names)) choices = dict(zip(names,values)) choice_str = [f'{key}:{value}' for (key,value) in choices.items()] choice_str = '\n'.join(choice_str) return choice_str # 创建绘制方式选择轨迹栏 shape_choice_str = get_choice_str(['line',"rectangle","circle","curve"]) cv.createTrackbar(shape_choice_str,'image',0,3,nothing) # 创建填充方式选择轨迹栏 size_choice_str = "pen size(0 is solid)" cv.createTrackbar(size_choice_str,'image',1,5,nothing)
# 创建黑色画布 img = np.zeros((300,512,3), np.uint8)
创建画布后不用程序员手动添加到窗口中,当调用 imshow 函数时,画布会自动添加到窗口中。
添加鼠标监听事件步骤
# 鼠标回调函数 def mouse_call(event,x,y,flags,param): # 鼠标左键按下 if event == cv.EVENT_LBUTTONDOWN: # do something1... pass # 鼠标移动 elif event == cv.EVENT_MOUSEMOVE: # do something2... pass # 鼠标左键抬起 elif event == cv.EVENT_LBUTTONUP: # do something3... pass # 监听鼠标事件 # image 为窗口名称 cv.setMouseCallback('image',mouse_call)
while True: cv.imshow('image',img) k = cv.waitKey(1) if k == 27: break r = cv.getTrackbarPos('R','image') g = cv.getTrackbarPos('G','image') b = cv.getTrackbarPos('B','image') mode = cv.getTrackbarPos(shape_choice_str,'image') pen_size = cv.getTrackbarPos(size_choice_str,'image') cv.destroyAllWindows()
(ix,iy)
,在鼠标移动时获取当前鼠标坐标(x,y)
作为直线终点,然后利用line
函数绘制直线;(ix,iy)
,在鼠标移动时获取当前鼠标坐标(x,y)
作为矩形右下角坐标,然后利用rectangle
函数绘制矩形;(ix,iy)
,在鼠标移动时获取当前鼠标坐标(x,y)
作为圆心坐标,然后利用circle
函数绘制圆形;(x,y)
作为圆心坐标,绘制以画笔大小pen_size
为半径的小圆,这些小圆连起来就是曲线。按照上面的绘图逻辑,在以填充模式绘制图形时没有问题,但是若以线框方式绘制图形时,会在鼠标移动过程中画出很多重复图形,如下图所示。
为了解决上述问题,本程序在鼠标事件回调函数中记录了上一次绘制终点(px,py)
这个变量。对于非填充模式的绘制,在绘制前,先用背景色将上一次绘制图形进行一次覆盖。以直线为例给出下列代码。
bg_color = (0,0,0) if pen_size >= 1: cv.line(img,(ix,iy),(px,py),bg_color,pen_size) cv.line(img,(ix,iy),(x,y),color,pen_size)
绘图逻辑完整代码
# 全局变量 drawing = False # 如果按下鼠标,则为真 mode = 3 # 默认为曲线模式 pen_size = 0 # 默认为实心 ix,iy = 0,0 # 直线或矩形的起始点坐标/圆的圆心 px,py = 0,0 # 直线、矩形或圆上一次绘图的终点(防止线框模式重复绘图) img = None # 图像 b,g,r = 255,255,255 # 画笔初始颜色 # 绘制图像 def draw(mode,color,pen_size,ix,iy,x,y,px,py): global img bg_color = (0,0,0) if mode == 0: if pen_size >= 1: cv.line(img,(ix,iy),(px,py),bg_color,pen_size) cv.line(img,(ix,iy),(x,y),color,pen_size) elif mode == 1: if pen_size >= 1: cv.rectangle(img,(ix,iy),(px,py),bg_color,pen_size) cv.rectangle(img,(ix,iy),(x,y),color,pen_size) elif mode == 2: if pen_size >= 1: radius = int(((px-ix)**2 + (py-iy)**2)**0.5) cv.circle(img,(px,py),radius,bg_color,pen_size) radius = int(((x-ix)**2 + (y-iy)**2)**0.5) cv.circle(img,(x,y),radius,color,pen_size) elif mode == 3: cv.circle(img,(x,y),pen_size,color,pen_size) # 鼠标回调函数 def mouse_call(event,x,y,flags,param): global ix,iy,px,py,drawing,mode,pen_size,img,b,g,r if event == cv.EVENT_LBUTTONDOWN: drawing = True ix,iy = x,y elif event == cv.EVENT_MOUSEMOVE: if drawing == True: brush = pen_size if pen_size <= 0: brush = -1 draw(mode,(b,g,r),brush,ix,iy,x,y,px,py) elif event == cv.EVENT_LBUTTONUP: drawing = False brush = pen_size if pen_size <= 0: brush = -1 draw(mode,(b,g,r),brush,ix,iy,x,y,px,py) px,py = x,y
import cv2 as cv import numpy as np drawing = False # 如果按下鼠标,则为真 mode = 3 # 默认为曲线模式 pen_size = 0 # 默认为实心 ix,iy = 0,0 # 直线或矩形的起始点坐标/圆的圆心 px,py = 0,0 # 直线、矩形或圆上一次绘图的终点(防止线框模式重复绘图) img = None # 图像 b,g,r = 255,255,255 # 画笔初始颜色 # 根据names生成选项 def get_choice_str(names): values = np.arange(0,len(names)) choices = dict(zip(names,values)) choice_str = [f'{key}:{value}' for (key,value) in choices.items()] choice_str = '\n'.join(choice_str) return choice_str # 轨迹栏发生变化时的回调函数 def nothing(x): pass # 绘制图像 def draw(mode,color,pen_size,ix,iy,x,y,px,py): global img bg_color = (0,0,0) if mode == 0: if pen_size >= 1: cv.line(img,(ix,iy),(px,py),bg_color,pen_size) cv.line(img,(ix,iy),(x,y),color,pen_size) elif mode == 1: if pen_size >= 1: cv.rectangle(img,(ix,iy),(px,py),bg_color,pen_size) cv.rectangle(img,(ix,iy),(x,y),color,pen_size) elif mode == 2: if pen_size >= 1: radius = int(((px-ix)**2 + (py-iy)**2)**0.5) cv.circle(img,(px,py),radius,bg_color,pen_size) radius = int(((x-ix)**2 + (y-iy)**2)**0.5) cv.circle(img,(x,y),radius,color,pen_size) elif mode == 3: cv.circle(img,(x,y),pen_size,color,pen_size) # 鼠标回调函数 def mouse_call(event,x,y,flags,param): global ix,iy,px,py,drawing,mode,pen_size,img,b,g,r if event == cv.EVENT_LBUTTONDOWN: drawing = True ix,iy = x,y elif event == cv.EVENT_MOUSEMOVE: if drawing == True: brush = pen_size if pen_size <= 0: brush = -1 draw(mode,(b,g,r),brush,ix,iy,x,y,px,py) elif event == cv.EVENT_LBUTTONUP: drawing = False brush = pen_size if pen_size <= 0: brush = -1 draw(mode,(b,g,r),brush,ix,iy,x,y,px,py) px,py = x,y # 绘图程序 def test(): global mode,pen_size,img,b,g,r # 创建一个窗口 cv.namedWindow('image') # 创建颜色变化的轨迹栏 cv.createTrackbar('R','image',255,255,nothing) cv.createTrackbar('G','image',255,255,nothing) cv.createTrackbar('B','image',255,255,nothing) # 创建绘制方式选择轨迹栏 shape_choice_str = get_choice_str(['line',"rectangle","circle","curve"]) cv.createTrackbar(shape_choice_str,'image',0,3,nothing) # 创建填充方式选择轨迹栏 size_choice_str = "pen size(0 is solid)" cv.createTrackbar(size_choice_str,'image',1,5,nothing) # 创建黑色画布 img = np.zeros((300,512,3), np.uint8) # 监听鼠标事件 cv.setMouseCallback('image',mouse_call) while True: cv.imshow('image',img) k = cv.waitKey(1) if k == 27: break r = cv.getTrackbarPos('R','image') g = cv.getTrackbarPos('G','image') b = cv.getTrackbarPos('B','image') mode = cv.getTrackbarPos(shape_choice_str,'image') pen_size = cv.getTrackbarPos(size_choice_str,'image') cv.destroyAllWindows() if __name__ == "__main__": test()