回调函数,这个概念确实碰到很多次了,乘着今天在做项目的时候,抓住机会记录下学习心得。
一、基本概念
》回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
》钩子函数
钩子函数是Windows消息处理机制的一部分,通过设置“钩子”,应用程序可以在系统级对所有消息、事件进行过滤,访问在正常情况下无法访问的消息。钩子的本质是一段用以处理系统消息的程序,通过系统调用,把它挂入系统。一般认为,钩子函数就是回调函数的一种,其实还是有差异的,差异地方就是:触发的时机不同。
》注册函数
注册函数所在的类,是调用回调函数的决定者,决定何时何种条件下去调用回调函数。而真正实现回调函数功能的类,不会直接调用该回调函数。一般来说,会把回调函数通过注册函数,保存在注册函数所在类的成员变量中,在适当的时候,通过该成员变量去访问注册过的回调函数。
二、使用示例
所谓回调,就是客户程序C调用服务程序S中的某个函数A,然后S又在某个时候反过来调用C中的某个函数B,对于C来说,这个B便叫做回调函数。例如Win32下的窗口过程函数就是一个典型的回调函数。
一般说来,C不会自己调用B,C提供B的目的就是让S来调用它,而且是C不得不提供。由于S并不知道C提供的B叫甚名谁,所以S会约定B的接口规范(函数原型),然后由C提前通过S的一个函数R告诉S自己将要使用B函数,这个过程称为回调函数的注册,R称为注册函数。
下面举个通俗的例子:
》 某天,我打电话向你请教问题,当然是个难题,:),你一时想不出解决方法,我又不能拿着电话在那里傻等,于是我们约定:等你想出办法后打手机通知我,这样,我就挂掉电话办其它事情去了。过了XX分钟,我的手机响了,你兴高采烈的说问题已经搞定,应该如此这般处理。故事到此结束。
这个例子说明了“异步+回调”的编程模式。其中,你后来打手机告诉我结果便是一个“回调”过程;我的手机号码必须在以前告诉你,这便是注册回调函数;我的手机号码应该有效并且手机能够接收到你的呼叫,这是回调函数必须符合接口规范。
》常见的利用回调函数实现鼠标操作
#####利用鼠标截取图上roi区域
import cv2
import numpy as np
global img
global point1, point2
global g_rect
def on_mouse(event, x, y, flags, param):
global img, point1, point2, g_rect
img2 = img.copy()
if event == cv2.EVENT_LBUTTONDOWN: # 左键点击,则在原图打点
print("1-EVENT_LBUTTONDOWN")
point1 = (x, y)
cv2.circle(img2, point1, 10, (0, 255, 0), 5)
cv2.imshow('image', img2)
elif event == cv2.EVENT_MOUSEMOVE and (flags & cv2.EVENT_FLAG_LBUTTON): # 按住左键拖曳,画框
print("2-EVENT_FLAG_LBUTTON")
cv2.rectangle(img2, point1, (x, y), (255, 0, 0), thickness=2)
cv2.imshow('image', img2)
elif event == cv2.EVENT_LBUTTONUP: # 左键释放,显示
print("3-EVENT_LBUTTONUP")
point2 = (x, y)
cv2.rectangle(img2, point1, point2, (0, 0, 255), thickness=2)
cv2.imshow('image', img2)
if point1 != point2:
min_x = min(point1[0], point2[0])
min_y = min(point1[1], point2[1])
width = abs(point1[0] - point2[0])
height = abs(point1[1] - point2[1])
g_rect = [min_x, min_y, width, height]
print("截取的位置",g_rect)
cut_img = img[min_y:min_y + height, min_x:min_x + width]
cv2.imshow('ROI', cut_img)
def get_image_roi(rgb_image):
'''
获得用户ROI区域的rect=[x,y,w,h]
:param rgb_image:
:return:
'''
bgr_image = cv2.cvtColor(rgb_image, cv2.COLOR_RGB2BGR)
global img
img = bgr_image
cv2.namedWindow('image')
while True:
cv2.setMouseCallback('image', on_mouse)
# cv2.startWindowThread() # 加在这个位置
cv2.imshow('image', img)
key = cv2.waitKey(0)
if key == 13 or key == 32: # 按空格和回车键退出
break
cv2.destroyAllWindows()
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
return g_rect
if __name__ == '__main__':
image_path = "1.jpg"
src=cv2.imread(image_path)
rect=get_image_roi(src)
print(rect)
三、使用心得
回调函数就是那些自己写的,但是不是自己来调,而是给别人来掉用的函数。消息响应函数就可以看成是回调函数,因为是让系统在合适的时候去调用。这不过消息响应函数就是为了处理消息的,所以就拿出来单做一类了。其实本质上就是回调函数。但是回调函数不是只有消息响应函数一种,比如在内核编程中,驱动程序就要提供一些回调函数,当一个设备的数据读写完成后,让系统调用这些回调函数来执行一些后续工作。回调函数赋予程序员这样一种能力,让自己编写的代码能够跳出正常的程序控制流,适应具体的运行环境在正确的时间执行。回调函数,重点就是可以当成参数来使用,如果说普通的参数是传入数据,那回调函数就是传入功能。
#!/usr/bin/env python
#-*- coding:utf-8 -*-
#回调函数1
#生成一个2k形式的偶数
def double(x):
return x * 2
#回调函数2
#生成一个4k形式的偶数
def quadruple(x):
return x * 4
#中间函数
#接受一个生成偶数的函数作为参数
#返回一个奇数
def getOddNumber(k, getEvenNumber):
return 1 + getEvenNumber(k)
#起始函数,这里是程序的主函数
def main():
k = 1
#当需要生成一个2k+1形式的奇数时
i = getOddNumber(k, double)
print(i)
#当需要一个4k+1形式的奇数时
i = getOddNumber(k, quadruple)
print(i)
#当需要一个8k+1形式的奇数时
i = getOddNumber(k, lambda x: x * 8)
print(i)
if __name__ == "__main__":
main()
参考链接:
3、回调函数的作用