本实例将使用OpenCV和Numpy这两个工具包给图1所示的目标图像的任意区域打马赛克。通过鼠标交互,指定打马赛克的区域,并用蓝色的矩形边框把这个区域圈出来,如图2所示。确定打马赛克的区域后,按照每一块马赛克的边长,把每一块马赛克从打马赛克区域中分隔出来,并用随机的颜色填充每一块马赛克,每一块马赛克填充颜色后的效果如图3所示。
图1 目标图像
图2 用矩形边框圈出打马赛克的区域
图3 对已选择区域打马赛克后的效果
本实例的实现过程可以被分为5个部分,它们分别是公共工具模块、对已选择区域打马赛克的方法、鼠标交互、选择打马赛克的区域和融合打马赛克的区域。下面将依次对这5个部分进行讲解。
1 公共工具模块
本实例包含如下4个公共工具模块
R 导入OpenCV工具包和Numpy工具包;
R 当按下鼠标左键时,初始化鼠标所在像素的横、纵坐标;
R 当抬起鼠标左键时,初始化鼠标所在像素的横、纵坐标;
R 初始化是否选择打马赛克的区域。
下面将编写与上述4个公共工具模块对应的代码。
(1 导入OpenCV工具包和Numpy工具包
OpenCV工具包和Numpy工具包是本实例需要使用的两个工具包。在使用它们对如图1所示的目标图像执行形态学操作之前,要使用import关键字把它们导入到当前的.py文件。代码如下所示
01 import cv2
02 import numpy as np
本实例通过鼠标交互的方式,选择打马赛克的区域。当按下鼠标左键时,记录鼠标所在像素的横、纵坐标;当抬起鼠标左键时,记录鼠标所在像素的横、纵坐标。通过这两个像素的横、纵坐标,就能够确定打马赛克的区域。Python语言规定在使用标签之前,要定义标签。也就是说,在尚未选择打马赛克的区域之前,要初始化这两个像素的横、纵坐标。
在Python语言中,字典是一系列的键值对。在字典中,每个键都对应着一个值。如果把像素的横、纵坐标作为键(即x和y ,把横、纵坐标的数值作为对应键的值,那么就可以用字典初始化上述两个像素的横、纵坐标。代码如下所示
01 start = {'x': 0, 'y': 0}
02 end = {'x': 0, 'y': 0}
参数说明
start 标签名,表示的是当按下鼠标左键时,记录鼠标所在像素的横、纵坐标;
end 标签名,表示的是当抬起鼠标左键时,记录鼠标所在像素的横、纵坐标。
(4 初始化是否选择打马赛克的区域
不论是在程序运行前,还是在程序运行后,只要没有进行鼠标交互,就尚未选择打马赛克的区域。因此,要对“是否选择打马赛克的区域”执行初始化操作。如果用“True”和“False”表示“是否选择打马赛克的区域”,那么与“尚未选择打马赛克的区域”对应的就是“False”,与“已经选择打马赛克的区域”对应的就是“True”。代码如下所示
selected = False
参数说明
selected 标签名,表示的是“是否选择打马赛克的区域”。
2 对已选择区域打马赛克的方法
本实例的目的是根据按下、抬起鼠标左键时鼠标所在像素的横、纵坐标,先在目标图像中确定被选择的区域,再对这个区域打马赛克。因此,在编码时,需要定义了一个用于对已选择区域打马赛克的mosaic()方法。mosaic()方法是一个自定义的、有参且有返回值的方法。mosaic()方法的语法格式如下所示
defmosaic(selected_img, block_size = 9):
……# 省略方法体中的代码
returndist
参数说明
selected_img 通过鼠标交互,在目标图像中选择的某个区域;
block_size = 9 设置每一块马赛克的边长为9。
返回值说明
dist 对复制后的已选择区域打马赛克的效果图像。
下面将逐步对被省略的代码是什么和它们各自发挥的作用进行讲解。
调用已选择区域的shape属性,获得已选择区域中像素的行数、列数和已选择区域的通道数。代码如下所示
rows, cols, _ = selected_img.shape
调用Python中用于复制列表的copy()方法,对已选择区域进行复制。代码如下所示
dist = selected_img.copy()
使用嵌套for循环,按照每一块马赛克的边长,把每一块马赛克从打马赛克区域中分隔出来。代码如下所示
01 fory inrange(0, rows block_size, block_size):
02 forx inrange(0, cols block_size, block_size):
让每一块马赛克中所有像素的像素值都等于一个随机的颜色。为了得到随机的颜色,需要使用NumPy提供的random.randint()方法创建随机数组,并且将随机值的取值范围设置在0 255之间。代码如下所示
01 dist[y:y + block_size, x:x + block_size] = \
02 (np.random.randint(0, 255), np.random.randint(0, 255), np.random.randint(0, 255))
3 鼠标交互
鼠标交互在本实例中的作用是根据按下、抬起鼠标左键时鼠标所在像素的横、纵坐标,在目标图像中确定被选择的区域。因此,需要创建一个鼠标事件的响应函数,函数名为“mouse_handler”。mouse_handler()函数是一个自定义的、含有5个参数的、没有返回值的方法,其语法格式如下所示
def mouse_handler(event, x, y, flag, param):
参数说明
event 触发的某一个鼠标事件;
x, y 当某一个鼠标事件被触发时,鼠标在窗口中的坐标,即“(x, y)”;
flags 是否触发了鼠标拖曳事件 者键盘鼠标联合事件;
param 用于标识响应函数的ID。
下面将逐步对mouse_handler()函数中的代码进行讲解。
在第1部分中,通过定义标签selected,已经完成了“是否选择打马赛克的区域”的初始化工作。由于mouse_handler()函数要修改标签selected的值(标签selected不在mouse_handler()函数内 ,所以需要借助global语句完成这个修改的过程。代码如下所示
globalselected
在第1部分中,定义了一个字典start,用于记录按下鼠标时鼠标所在像素的横、纵坐标。因此,当在目标图像中触发“按下左键”这一鼠标事件时,需要重新记录鼠标所在像素的横、纵坐标。代码如下所示
01 ifevent == cv2.EVENT_LBUTTONDOWN:
02 start['x'] = x
03 start['y'] = y
在第1部分中,定义一个字典end,用于记录抬起鼠标时鼠标所在像素的横、纵坐标。因此,当在目标图像中触发“抬起左键”这一鼠标事件时,需要重新记录鼠标所在像素的横、纵坐标。在触发“按下左键”和“抬起左键”这两个鼠标事件后,即可确定打马赛克的区域,这时要把标签selected的值由False修改为True。代码如下所示
01 ifevent == cv2.EVENT_LBUTTONUP:
02 end['x'] = x
03 end['y'] = y
04 selected = True
4 选择打马赛克的区域
在讲解“选择打马赛克的区域”的内容前,需要着手一些准备工作,具体如下所示
调用imread()方法读取在当前项目目录下的目标图像。代码如下所示
img = cv2.imread("faces.png")
调用Python中用于复制列表的copy()方法,对目标图像进行复制。代码如下所示
调用imread()方法读取在当前项目目录下的目标图像。代码如下所示
img_copy = img.copy()
先调用namedWindow()方法命名一个窗口,再调用setMouseCallback()方法把响应函数mouse_handler和这个窗口绑定在一起,代码如下所示
01 cv2.namedWindow("img")
02 cv2.setMouseCallback("img", mouse_handler)
如果尚未在目标图像中选择打马赛克的区域,那么在上一步已经命名的窗口里显示目标图像。代码如下所示
01 while not selected:
02 cv2.imshow("img", img)
03 key = cv2.waitKey(10)
下面开始对“选择打马赛克的区域”进行讲解。
因为打马赛克的区域是一个矩形,所以定义一个字典rect,用于记录这个矩形的左上角坐标、宽度和高度。定义一个字典rect的代码如下所示
rect = {}
如果按下鼠标时鼠标所在像素的横、纵坐标均小于抬起鼠标时鼠标所在像素的横、纵坐标,那么打马赛克的区域的左上角的横、纵坐标就是按下鼠标时鼠标所在像素的横、纵坐标。代码如下所示
01 if start['x'] < end['x'] and start['y'] < end['y']:
02 rect['x'] = start['x']
03 rect['y'] = start['y']
如果按下鼠标时鼠标所在像素的横坐标小于抬起鼠标时鼠标所在像素的横坐标、按下鼠标时鼠标所在像素的纵坐标大于抬起鼠标时鼠标所在像素的纵坐标,那么打马赛克的区域的左上角的横坐标就是按下鼠标时鼠标所在像素的横坐标、打马赛克的区域的左上角的纵坐标就是抬起鼠标时鼠标所在像素的纵坐标。代码如下所示
01 ifstart['x'] < end['x']andstart['y'] > end['y']:
02 rect['x'] = start['x']
03 rect['y'] = end['y']
如果按下鼠标时鼠标所在像素的横坐标大于抬起鼠标时鼠标所在像素的横坐标、按下鼠标时鼠标所在像素的纵坐标小于抬起鼠标时鼠标所在像素的纵坐标,那么打马赛克的区域的左上角的横坐标就是抬起鼠标时鼠标所在像素的横坐标、打马赛克的区域的左上角的纵坐标就是按下鼠标时鼠标所在像素的纵坐标。代码如下所示
01 ifstart['x'] > end['x']andstart['y'] < end['y']:
02 rect['x'] = end['x']
03 rect['y'] = start['y']
如果按下鼠标时鼠标所在像素的横、纵坐标均大于抬起鼠标时鼠标所在像素的横、纵坐标,那么打马赛克的区域的左上角的横、纵坐标就是抬起鼠标时鼠标所在像素的横、纵坐标。代码如下所示
01 ifstart['x'] > end['x']andstart['y'] > end['y']:
02 rect['x'] = end['x']
03 rect['y'] = end['y']
根据按下、抬起鼠标左键时鼠标所在像素的横、纵坐标,调用rectangle()方法把打马赛克的区域用蓝色的矩形边框标记出来。代码如下所示
01 cv2.rectangle(img, (start['x'], start['y']),
02 (end['x'], end['y']), (255, 0, 0), 3)
调用imshow()方法,在一个窗口里显示用蓝色的矩形边框标记打马赛克的区域的结果图像。代码如下所示
cv2.imshow("draw_rectangle", img)
调用Numpy中的abs()方法,根据按下、抬起鼠标左键时鼠标所在像素的横、纵坐标,计算出打马赛克的区域的宽度和高度。代码如下所示
01 rect['width'] = np.abs(end['x'] start['x'])
02 rect['height'] = np.abs(end['y'] start['y'])
根据打马赛克的区域的左上角坐标、宽度和高度,通过操作复制后的目标图像,对打马赛克的区域进行编码。代码如下所示
01 selected_img = img_copy[rect['y']:rect['y'] + rect['height'],
02 rect['x']:rect['x'] + rect['width']]
5 融合打马赛克的区域
通过第4部分的内容,已经成功地通过编码表示了打马赛克的区域。下面将操作这个打马赛克的区域,完成本实例的尾声部分。
在mosaic()方法中,有一个参数selected_img,其含义是“通过鼠标交互,在目标图像中选择的某个区域”。也就是说,参数selected_img实质上就是打马赛克的区域。因此,调用mosaic()方法,对指定区域打马赛克的代码如下所示
result = mosaic(selected_img)
获得对指定区域打马赛克的图像后,需要将其与打马赛克的区域进行融合。为了能够在尽量保留原有图像信息的基础上把这两幅图像融合到一起,需要借助OpenCV中用于计算图像加权和的addWeighted()方法。获取融合到一起的图像后,将其赋值给复制后的目标图像中的相应位置上的像素。代码如下所示
01 img_copy[rect['y']:rect['y'] + rect['height'], rect['x']:rect['x'] + rect['width']] = \
02 cv2.addWeighted(result, 0.65, selected_img, 0.35, 2.0)
调用imshow()方法,在另一个窗口里显示给目标图像中的指定区域打马赛克的结果图像。代码如下所示
cv2.imshow('result', img_copy)
调用waitKey()方法等待键盘上的按键指令,当键盘上的某一个按键被按下时,使用destroyAllWindows()方法销毁以上两个正在显示图像的窗口。代码如下所示
01 cv2.waitKey()
02 cv2.destroyAllWindows()
如果你对相关代码有疑问 者对结果有疑问,可以扫二维码加v信,你将受邀 本 的编程讨论群,并有机会获得相关 哟!
相关参考文献
Powered by 小羊羔外链网 8.3.12
©2015 - 2024 小羊羔外链网
您的IP:223.85.231.247,2024-04-25 20:08:13,Processed in 0.05061 second(s).