番外4. Python OpenCV 中鼠标事件相关处理与常见问题解决方案

本系列专栏写作方式

本系列专栏写作将采用首创的问答式写作形式,快速让你学习到 OpenCV 的初级、中级、高级知识。

4. Python OpenCV 中鼠标事件相关处理与常见问题解决方案

本篇博客主要分析 <kbd>cv2.setMouseCallback</kbd> 函数,以及该函数在日常编码中出现问题是如何进行解决。

本函数主要是 OpenCV 中用来处理鼠标相关事件的函数,通过它可以捕获到数据触发的事件,并对其进行处理。

使用该函数前,可以先通过 <kbd>help</kbd> 函数查阅基本用法。

该函数原型如下:

setMouseCallback(windowName, onMouse [, param]) -> None

可以看到该函数有两个参数,其一是窗口的名称,其二是回调函数,窗口名称与<kbd>cv2.imshow</kbd> 中的名称保持一致即可。

通过函数原型,可以看出 <kbd>cv2.setMouseCallback</kbd> 函数是在给窗口设置一个回调函数。

OpenCV 中鼠标都有哪些事件?

查看事件的代码如下,通过内置函数 <kbd>dir</kbd> 可以进行查阅。

import cv2

def show_event():
    events = [i for i in dir(cv2) if 'EVENT' in i]
    print(events)

if __name__ == "__main__":
    show_event()

运行结果如下,所有与 <kbd>event</kbd>(事件相关的函数,都罗列了出来)

['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']

以上事件中,最常用的为 <kbd>EVENT_LBUTTONDOWN</kbd>,<kbd>EVENT_LBUTTONUP</kbd>,我们接下来就重点掌握。

<kbd>EVENT_LBUTTONDOWN</kbd> 鼠标左键按下事件。

先通过以下代码呈现一个窗体,测试一下鼠标左键按下。

import cv2


def show_event():
    events = [i for i in dir(cv2) if 'EVENT' in i]
    print(events)


def mouse_handler(event, x, y, flags, userdata):
    if event == 1:   # cv2.EVENT_LBUTTONDOWN
        print("鼠标左键按下")


if __name__ == "__main__":

    image = cv2.imread("./tt.jpg")

    cv2.namedWindow("mouse_event")
    cv2.imshow("mouse_event", image)
    cv2.setMouseCallback("mouse_event", mouse_handler)
    cv2.waitKey()

代码运行效果如下,在图片上点击鼠标左键,会在控制台进行数据的输入,输出内容如截图红框位置所示。

e573c4c4fed549768c96f85edc43ec30[1].png

此时需要注意的问题是,即使你没有加载任何图片,只是使用 nameWindow 命名了一下窗体,对应的 setMouseCallback 函数也会绑定成功,具体测试代码如下。

# image = cv2.imread("./tt.jpg")

cv2.namedWindow("mouse_event")
# cv2.imshow("mouse_event", image)
cv2.setMouseCallback("mouse_event", mouse_handler)

由上面的案例,我们还能得到下述推论,<kbd>cv2.setMouseCallback</kbd> 函数中第二个参数回调函数<kbd>onMouse</kbd> ,具备某种格式,因为在上述代码中出现了这样一段内容:

def mouse_handler(event, x, y, flags, userdata):

这里其实对于所有鼠标事件,回调函数格式都是统一的,只是函数内部的具体实现不同。

参数说明如下:

  • <kbd>event</kbd>:鼠标事件名称,通过该值可以获取鼠标进行的何种事件操作;
  • <kbd>x, y</kbd>:鼠标进行事件操作一瞬间,所在的坐标位置;
  • <kbd>flags</kbd>:指的是与 <kbd>event</kbd> 相关的实践中包含 <kbd>FLAG</kbd> 的事件;
  • <kbd>userdata</kbd>:鼠标回调函数触发时传递进来的参数。

以上参数都非常容易理解,但是目前网络上很多内容对 <kbd>flags</kbd> 参数都一带而过,没有进行说明。

该参数其实就是我们在上文获取到的所有事件的一个子集,在看一下之前获取到的所有事件。

['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']

在其中你重点寻找带 <kbd>flag</kbd> 的值,检索如下:

'EVENT_FLAG_ALTKEY',
'EVENT_FLAG_CTRLKEY',
'EVENT_FLAG_LBUTTON',
'EVENT_FLAG_MBUTTON',
'EVENT_FLAG_RBUTTON',
'EVENT_FLAG_SHIFTKEY'

稍微对英文进行一下翻译,就能了解 <kbd>flags</kbd> 参数,例如我们想要实现按住鼠标左键的同时进行拖动,那核心代码为:

event == cv2.EVENT_MOUSEMOVE and flags == cv2.EVENT_FLAG_LBUTTON

按住键盘 <kbd>CTRL</kbd> 的同时,按下鼠标左键,代码如下

event == cv2.EVENT_LBUTTONUP and flags == cv2.EVENT_FLAG_CTRLKEY

接下来我们就实现一下如何按住鼠标左键并进行拖动,绘制一个矩形。

import cv2

image = cv2.imread("./tt.jpg")
cv2.namedWindow("mouse_event")
x1, y1 = 0, 0


def show_event():
    events = [i for i in dir(cv2) if 'EVENT' in i]
    print(events)


def mouse_handler(event, x, y, flags, userdata):
    global x1, y1
    if event == cv2.EVENT_LBUTTONDOWN:
        print("左键点击")
        x1, y1 = x, y

    if event == cv2.EVENT_MOUSEMOVE and flags == cv2.EVENT_FLAG_LBUTTON:
        # print("鼠标左键按下拖动")
        cv2.rectangle(image, (x1, y1), (x, y), (0, 255, 0), -1)


if __name__ == "__main__":
    cv2.setMouseCallback("mouse_event", mouse_handler)
    while True:
        cv2.imshow("mouse_event", image)
        k = cv2.waitKey(1) & 0xFF
        if k == 27:
            break

    cv2.destroyAllWindows()

上述代码,并未用到最后一个参数 <kbd>userdata</kbd>,接下来我们通过传参的方式应用一下该参数。

核心修改一个地方即可,在 <kbd>cv2.setMouseCallback</kbd> 函数部分将读取的图像 image 传递到回调的函数中去,具体如下

cv2.setMouseCallback("mouse_event", mouse_handler, image)

OpenCV 在视频中捕获鼠标事件的解决方案

上文已经实现了在图片中捕获鼠标事件,接下来我们看一下如何去在视频中进行相同的操作。

由以前的知识已经知道,视频处理就是对视频的每一帧进行相应的操作,那可以按照下述代码进行。

import cv2


def mouse_handler(event, x, y, flags, frame):

    if frame is not None:
        # 获取坐标,测试用
        # print(x, y)
        if event == cv2.EVENT_MOUSEMOVE:
            cv2.putText(frame, "Hello OpenCV", (x, y),
                        cv2.FONT_HERSHEY_COMPLEX, 1, (255, 0, 0))
            cv2.imshow("video", frame)


if __name__ == "__main__":

    cap = cv2.VideoCapture("./test.mp4")
    while cap.isOpened():
        ret, frame = cap.read()
        if ret:
            cv2.imshow("video", frame)
            cv2.setMouseCallback("video", mouse_handler, frame)
            if cv2.waitKey(25) & 0xFF == 27:
                break

    cap.release()
    cv2.destroyAllWindows()

以上代码因为绑定在 <kbd>EVENT_MOUSEMOVE</kbd> 事件上,所以当鼠标移动的时候,会出现一个 Hello OpenCV 的字样,但是该方式会导致视频不断重复渲染,效率不好,频繁刷新几次之后,页面就会崩溃掉。

如果你单纯为了测试,可以将 <kbd>cv2.waitKey(25)</kbd> 中的数字设置到 1000,这样视频播放速度就会变慢,即可抓取到最终效果。

07257d9eb7054da99700c8c3284ef1d4[1].png

对于鼠标回调函数的学习,重点要掌握的依旧是各种事件,还有一个需要注意的是组合按键与鼠标位置的计算,你可以基于此实现一个简单的 OpenCV 画板,当然前提是你对之前学的图形绘制函数已经十分熟悉。

补充知识,OpenCV 绘制多边形

上一篇博客 中,我们缺少了一个函数,绘制多边形,这里进行一下补充,该函数为 <kbd>cv2.polylines</kbd>,函数原型如下:

polylines(img, pts, isClosed, color[, thickness[, lineType[, shift]]]) -> img

其中最重要的参数 <kbd>pts</kbd>,该参数表示待绘制多边形的折线数组,也可以理解为多边形的顶点顺序坐标。

例如下述代码:

image = np.zeros((400, 400, 3), np.uint8)
points = np.array(
    [[50, 50], [170, 100], [200, 150], [300, 320]], np.int32)
cv2.polylines(image, [points], True, (255, 0, 0))
cv2.imshow('image',image)
cv2.waitKey()

绘制的多边形如下:

6eb34d2f32e74245a24b9c3388d127f2[1].png

最后,你可以结合本文学到的 <kbd>cv2.setMouseCallback</kbd> 函数,加上绘制直线函数,实现一个多边形手动绘制工具。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,817评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,329评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,354评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,498评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,600评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,829评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,979评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,722评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,189评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,519评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,654评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,940评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,762评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,993评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,382评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,543评论 2 349

推荐阅读更多精彩内容