鼠标作为画笔
用鼠标绘制图形是很有用的,我们在后面的目标跟踪中会使用鼠标将需要跟踪的目标标注出来,之后对目标进行实时跟踪,所以今天我们来了解一下OpenCV中的一些基础知识—鼠标画笔。
首先需要了解一个函数:
cv2.setMouseCallback(windowName, MouseCallback , param=None)
其各个参数为:
windowName:窗口名称
MouseCallback:鼠标响应回调函数
param:响应函数传递的的参数
在这里,我们创建一个简单的应用程序,无论我们在哪里双击它,都可以在图像上绘制一个圆。
首先,我们创建一个鼠标回调函数,该函数在发生鼠标事件时执行。该函数原型为(此函数为我们自己所定义,OpenCV库中是没有此函数的,名称可以自己任意定义):
MouseCallback (int event, int x, int y, int flags, void *userdata)
其各个部分参数为:
event:一个MouseEventTypes 常量
x:鼠标的x坐标
y:鼠标的y坐标
flags:一个MouseEventFlags常量
userdata:可选参数
鼠标事件可以是与鼠标相关的任何东西,例如左键按下,右键按下,左键双击等。它为我们提供了每个鼠标事件的坐标(x,y)。 通过此活动和地点,我们可以做任何我们喜欢的事情。 要列出所有可用的可用参数,我们在pycharm终端中运行以下代码:
import cv2 as cv events = [i for i in dir(cv) if 'EVENT' in i] print( events )
我们查看输出,会发现一大堆参数名称:
['EVENT_FLAG_ALTKEY', 'EVENT_FLAG_CTRLKEY', 'EVENT_FLAG_LBUTTON', 'EVENT_FLAG_MBUTTON', 'EVENT_FLAG_RBUTTON', 'EVENT_FLAG_SHIFTKEY', 'EVENT_LBUTTONDBLCLK', 'EVENT_LBUTTONDOWN', 'EVENT_LBUTTONUP', 'EVENT_MBUTTONDBLCLK', 'EVENT_MBUTTONDOWN', 'EVENT_MBUTTONUP', 'EVENT_MOUSEHWHEEL', 'EVENT_MOUSEMOVE', 'EVENT_MOUSEWHEEL', 'EVENT_RBUTTONDBLCLK', 'EVENT_RBUTTONDOWN', 'EVENT_RBUTTONUP']
现在我们来看一下MouseCallback 函数中的event参数,其代表一个MouseEventFlags常量:
MouseEventFlags :
cv.EVENT_FLAG_LBUTTON = 1, 左键拖拽
cv.EVENT_FLAG_RBUTTON = 2, 右键拖拽
cv.EVENT_FLAG_MBUTTON = 4, 中键不放
cv.EVENT_FLAG_CTRLKEY = 8,按住ctrl不放
cv.EVENT_FLAG_SHIFTKEY = 16, 按住shift不放
cv.EVENT_FLAG_ALTKEY = 32 ,按住alt不放
继续看MouseCallback 函数中的flags参数,其代表一个MouseEventFlags常量:
cv.MouseEventTypes :
cv.EVENT_MOUSEMOVE = 0, 鼠标移动
cv.EVENT_LBUTTONDOWN = 1, 左键按下
cv.EVENT_RBUTTONDOWN = 2, 右键按下
cv.EVENT_MBUTTONDOWN = 3, 中键按下
cv.EVENT_LBUTTONUP = 4, 左键释放
cv.EVENT_RBUTTONUP = 5, 右键释放
cv.EVENT_MBUTTONUP = 6, 中键释放
cv.EVENT_LBUTTONDBLCLK = 7, 左键双击
cv.EVENT_RBUTTONDBLCLK = 8, 右键双击
cv.EVENT_MBUTTONDBLCLK = 9, 中健双击
cv.EVENT_MOUSEWHEEL = 10, 滚轮滑动
cv.EVENT_MOUSEHWHEEL = 11 横向滚轮滑动
接下来我们可以开始进行实验了,创建鼠标回调函数具有特定的格式,该格式在所有地方都是相同的。它仅在功能上有所不同。因此,现在我们用鼠标回调函数做一件事,在我们双击的地方绘制一个圆圈:
import numpy as np import cv2 as cv def draw_circle(event, x, y, flags, param): if event == cv.EVENT_LBUTTONDBLCLK: cv.circle(img, (x, y), 100, (255, 0, 0), -1) img = np.zeros((512, 512, 3), np.uint8) cv.namedWindow('image') cv.setMouseCallback('image', draw_circle) while (1): cv.imshow('image', img) if cv.waitKey(20) & 0xFF == 27: break cv.destroyAllWindows()
这里面我们调用的参数是EVENT_LBUTTONDBLCLK,意思就是鼠标双击触发。我们双击任意一个区域,都会以此为圆心创建一个圆形出来:
鼠标画图形
现在我们需要进行更高级的操作, 在这种情况下,我们像在“画图”应用程序中一样,通过拖动鼠标来绘制矩形或圆形(取决于我们选择的模式)。 因此,我们的鼠标回调函数有两个部分,一个用于绘制矩形,另一个用于绘制圆形。这个将会非常有帮助,后期进行手动目标跟踪的时候,我们就会用鼠标自己手动标注这些图形,从而完成实时的目标跟踪。
现在我们来实现一个综合的例子,这个实例会帮助你理解图像交互的一些思想:
在图像上用鼠标画图,可以画圆或矩形,按q键在两种模式下切换。左键按下时开始画图,移动到哪儿画到哪儿,左键释放时结束画图。听上去很复杂,是吗?一步步分析下:
· 用鼠标画图:需要定义鼠标的回调函数mouse_event
· 画圆或矩形:需要定义一个画图的模式mode
· 左键单击、移动、释放:需要捕获三个不同的事件
· 开始画图,结束画图:需要定义一个画图的标记位drawing
好,开始代码:
import numpy as np import cv2 as cv drawing = False # true if mouse is pressed mode = True # if True, draw rectangle. Press 'm' to toggle to curve ix, iy = -1, -1 # mouse callback function def draw_circle(event, x, y, flags, param): global ix, iy, drawing, mode if event == cv.EVENT_LBUTTONDOWN: drawing = True ix, iy = x, y elif event == cv.EVENT_MOUSEMOVE: if drawing: if mode: cv.rectangle(img, (ix, iy), (x, y), (0, 255, 0), -1) else: cv.circle(img, (x, y), 5, (0, 0, 255), -1) elif event == cv.EVENT_LBUTTONUP: drawing = False if mode: cv.rectangle(img, (ix, iy), (x, y), (0, 255, 0), -1) else: cv.circle(img, (x, y), 5, (0, 0, 255), -1) img = np.zeros((512, 512, 3), np.uint8) cv.namedWindow('image') cv.setMouseCallback('image', draw_circle) while 1: cv.imshow('image', img) k = cv.waitKey(1) & 0xFF if k == ord('q'): mode = not mode elif k == 27: break cv.destroyAllWindows()
效果:
ok,搞定。
但是假如我们想创建一个未填充的矩形怎么办呢?
那么我们需要修改一下代码,在上一节中就已经讲过,画矩形函数以及画圆函数等等函数,它们的最后一个参数当为-1时,代表为实心,当大于0时,即为空心,假如我们定义为1,那么画出的图形的轮廓的粗细程度就为1,我们来看代码:
import numpy as np import cv2 as cv drawing = False # true if mouse is pressed mode = True # if True, draw rectangle. Press 'm' to toggle to curve ix, iy = -1, -1 # mouse callback function def draw_circle(event, x, y, flags, param): global ix, iy, drawing, mode if event == cv.EVENT_LBUTTONDOWN: drawing = True ix, iy = x, y elif event == cv.EVENT_MOUSEMOVE: if drawing: if mode: tmp = img.copy() cv.rectangle(tmp, (ix, iy), (x, y), (0, 255, 0), 1) cv.imshow('image', tmp) elif event == cv.EVENT_LBUTTONUP: drawing = False if mode: cv.rectangle(img, (ix, iy), (x, y), (0, 255, 0), 1) img = np.zeros((512, 512, 3), np.uint8) cv.namedWindow('image') cv.setMouseCallback('image', draw_circle) cv.imshow("image",img) while 1: k = cv.waitKey(1) & 0xFF if k == 27: break cv.destroyAllWindows()
我们看一下效果:
现在我们还可以修改一下代码,我们将EVENT_LBUTTONUP的参数语句执行的功能取消,这样的话,我们在画下一个矩形时上一个就会被自动清除:
import numpy as np import cv2 as cv drawing = False # true if mouse is pressed mode = True # if True, draw rectangle. Press 'm' to toggle to curve ix, iy = -1, -1 # mouse callback function def draw_circle(event, x, y, flags, param): global ix, iy, drawing, mode if event == cv.EVENT_LBUTTONDOWN: drawing = True ix, iy = x, y elif event == cv.EVENT_MOUSEMOVE: if drawing: if mode: tmp = img.copy() cv.rectangle(tmp, (ix, iy), (x, y), (0, 255, 0), 1) cv.imshow('image', tmp) elif event == cv.EVENT_LBUTTONUP: drawing = False # if mode: # cv.rectangle(img, (ix, iy), (x, y), (0, 255, 0), 1) img = np.zeros((512, 512, 3), np.uint8) cv.namedWindow('image') cv.setMouseCallback('image', draw_circle) cv.imshow("image",img) while 1: k = cv.waitKey(1) & 0xFF if k == 27: break cv.destroyAllWindows()
效果:
至此,OpenCV的鼠标绘图部分就算结束了,大家要想熟练掌握,还是自己多多练习。