OpenCV提供了许多绘制图形的方法,包括绘制线段的line()方法、绘制矩形的rectangle()方法、绘制圆形的circle()方法、绘制多边形的polylines()方法和绘制文字的putText()方法。本章将依次对上述各个方法进行讲解,并使用上述方法绘制相应的图形。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8TWsuuc6-1639056447707)(PythonOpenCV基础篇01.assets/image-20211118141415260.png)]
OpenCV提供了用于绘制线段的line()方法,使用这个方法即可绘制长短不一、粗细各异、五颜六色的线段。line()方法的语法格式如下:
img = cv2.line(img, pt1, pt2, color, thickness)
参数说明:
img:画布。
pt1:线段的起点坐标。
pt2:线段的终点坐标。
color:绘制线段时的线条颜色。
thickness:绘制线段时的线条宽度。
注意
线条颜色是RGB格式的,例如红色的RGB值是(255, 0, 0)。但是在OpenCV中,RGB图像的通道顺序被转换成B→G→R,因此(0, 0, 255)代表的是红色。
【实例6.1】 绘制线段并拼成一个“王”字。
编写一个程序,使用line()方法分别绘制颜色为蓝色、绿色、红色和黄色,线条宽度为5、10、15和20的4条线段,并且这4条线段能够拼成一个“王”字如图6.1所示,把其主体部分放在图6.2所示的坐标系中,即可确定每条线段的起点坐标和终点坐标,代码如下:
import numpy as np # 导入Python中的numpy模块
import cv2
# np.zeros():创建了一个画布
# (300, 300, 3):一个300 x 300,具有3个颜色空间(即Red、Green和Blue)的画布
# np.uint8:OpenCV 中的灰度图像和RGB图像都是以uint8存储的,因此这里的类型也是uint8
canvas = np.zeros((300, 300, 3), np.uint8)
# 在画布上,绘制一条起点坐标为(50, 50)、终点坐标为(250, 50)、蓝色的、线条宽度为5的线段
canvas = cv2.line(canvas, (50, 50), (250, 50), (255, 0, 0), 5)
# 在画布上,绘制一条起点坐标为(50, 150)、终点坐标为(250, 150)、绿色的、线条宽度为10的线段
canvas = cv2.line(canvas, (50, 150), (250, 150), (0, 255, 0), 10)
# 在画布上,绘制一条起点坐标为(50, 250)、终点坐标为(250, 250)、红色的、线条宽度为15的线段
canvas = cv2.line(canvas, (50, 250), (250, 250), (0, 0, 255), 15)
# 在画布上,绘制一条起点坐标为(150, 50)、终点坐标为(150, 250)、黄色的、线条宽度为20的线段
canvas = cv2.line(canvas, (150, 50), (150, 250), (0, 255, 255), 20)
cv2.imshow("Lines", canvas) # 显示画布
cv2.waitKey()
cv2.destroyAllWindows()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kFeJsnX6-1639056447708)(PythonOpenCV基础篇01.assets/image-20211118142121651.png)]
图6.1 绘制线段并拼成一个“王”字
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xYlpPAUB-1639056447709)(PythonOpenCV基础篇01.assets/image-20211118142054962.png)]
图6.2 每条线段的起点坐标和终点坐标
此外,如果想把图6.1中的黑色背景替换为白色背景,应该如何操作呢?
这时,只需将实例6.1的第7行代码替换成如下代码即可:
canvas = np.ones((300, 300, 3), np.uint8) * 255
运行修改后的代码,得到如图6.3所示的结果。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pCnwx3bM-1639056447709)(PythonOpenCV基础篇01.assets/image-20211118142429945.png)]
图6.3 把图6.1中的黑色背景替换为白色背景
OpenCV提供了用于绘制矩形的rectangle()方法,使用这个方法既可以绘制矩形边框,也可以绘制实心矩形。rectangle()方法的语法格式如下:
img = cv2.rectangle(img, pt1, pt2, color, thickness)
参数说明:
img:画布。
pt1:矩形的左上角坐标。
pt2:矩形的右下角坐标。
color:绘制矩形时的线条颜色。
thickness:绘制矩形时的线条宽度。
【实例6.2】 绘制一个矩形边框。
编写一个程序,使用rectangle()方法绘制一个青色的、线条宽度为20的矩形边框。绘制矩形时,矩形的左上角坐标为(50, 50),矩形的右下角坐标为(200, 150),代码如下:
import numpy as np # 导入Python中的numpy模块
import cv2
# np.zeros():创建了一个画布
# (300, 300, 3):一个300 x 300,具有3个颜色空间(即Red、Green和Blue)的画布
# np.uint8:OpenCV中的灰度图像和RGB图像都是以uint8存储的,因此这里的类型也是uint8
canvas = np.zeros((300, 300, 3), np.uint8)
# 在画布上绘制一个左上角坐标为(50,50)、右下角坐标为(200,150)、青色的、线条宽度为20的矩形边框
canvas = cv2.rectangle(canvas, (50, 50), (200, 150), (255, 255, 0), 20)
cv2.imshow("Rectangle", canvas) # 显示画布
cv2.waitKey()
cv2.destroyAllWindows()
上述代码的运行结果如图6.4所示
说明
可参照图6.2所示的坐标系,了解矩形的左上角坐标和矩形的右下角坐标是如何确定的。
如果想要填充图6.4中的矩形边框,使之变成实心矩形,应该如何修改上述代码呢?
在rectangle()方法的语法格式中,thickness表示绘制矩
形时的线条宽度。当thickness的值为-1时,即可绘制一个实心矩形。也就是说,只需要把实例6.2的第9行代码中的最后一个参数20修改为-1,就能够绘制一个实心矩形,关键代码如下:
canvas = cv2.rectangle(canvas, (50, 50), (200, 150), (255, 255, 0), -1) # 绘制一个实心矩形
运行修改后的代码,得到如图6.5所示的结果。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-meaiTM8x-1639056447710)(PythonOpenCV基础篇01.assets/image-20211118142643073.png)]
图6.4 绘制一个矩形边框
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g3kShSjJ-1639056447711)(PythonOpenCV基础篇01.assets/image-20211118142722661.png)]
图6.5 绘制一个实心矩形
正方形是特殊的矩形,因此使用rectangle()方法不仅能绘制矩形,还能绘制正方形。
【实例6.3】 绘制正方形。
编写一个程序,使用rectangle()方法分别绘制3个正方形边框和1个实心正方形,具体要求如下。
(1)左上角坐标为(50, 50)、右下角坐标为(250, 250)、红色的、线条宽度为40的正方形边框。
(2)左上角坐标为(90, 90)、右下角坐标为(210, 210)、绿色的、线条宽度为30的正方形边框。
(3)左上角坐标为(120, 120)、右下角坐标为(180, 180)、蓝色的、线条宽度为20的正方形边框。
(4)左上角坐标为(140, 140)、右下角坐标为(160, 160)、黄色的实心正方形。
代码如下:
import numpy as np # 导入Python中的numpy模块
import cv2
# np.zeros():创建了一个画布
# (300, 300, 3):一个300 x 300,具有3个颜色空间(即Red、Green和Blue)的画布
# np.uint8:OpenCV中的灰度图像和RGB图像都是以uint8存储的,因此这里的类型也是uint8
canvas = np.zeros((300, 300, 3), np.uint8)
# 绘制一个左上角坐标为(50,50)、右下角坐标为(250,250)、红色的、线条宽度为40的正方形边框
canvas = cv2.rectangle(canvas, (50, 50), (250, 250), (0, 0, 255), 40)
# 绘制一个左上角坐标为(90,90)、右下角坐标为(210,210)、绿色的、线条宽度为30的正方形边框
canvas = cv2.rectangle(canvas, (90, 90), (210, 210), (0, 255, 0), 30)
# 绘制一个左上角坐标为(120,120)、右下角坐标为(180,180)、蓝色的、线条宽度为20的正方形边框
canvas = cv2.rectangle(canvas, (120, 120), (180, 180), (255, 0, 0), 20)
# 绘制一个左上角坐标为(140,140)、右下角坐标为(160,160)、黄色的实心正方形
canvas = cv2.rectangle(canvas, (140, 140), (160, 160), (0, 255, 255), -1)
cv2.imshow("Square", canvas) # 显示画布
cv2.waitKey()
cv2.destroyAllWindows()
上述代码的运行结果如图6.6所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FbQmtOZH-1639056447711)(PythonOpenCV基础篇01.assets/image-20211118143019442.png)]
图6.6 绘制正方形
OpenCV提供了用于绘制圆形的circle()方法,这个方法与rectangle()方法的功能相同,既可以绘制圆形边框,也可以绘制实心圆形。circle()方法的语法格式如下:
img = cv2.circle(img, center, radius, color, thickness)
参数说明: img:画布。 center:圆形的圆心坐标。 radius:圆形的半径。 color:绘制圆形时的线条颜色。 thickness:绘制圆形时的线条宽度。【实例6.4】 绘制“交通灯”。(实例位置:资源包\TM\sl\6\04)
编写一个程序,使用circle()方法分别绘制红色的、黄色的和绿色的3个实心圆形,用于模拟交通灯。这3个实心圆形的半径均为40,并且呈水平方向放置,代码如下:
import numpy as np # 导入Python中的numpy模块
import cv2
# np.zeros():创建了一个画布
# (100, 300, 3):一个100 x 300,具有3个颜色空间(即Red、Green和Blue)的画布
# np.uint8:OpenCV中的灰度图像和RGB图像都是以uint8存储的,因此这里的类型也是uint8
canvas = np.zeros((100, 300, 3), np.uint8)
在画布上,绘制一个圆心坐标为(50, 50)、半径为40、红色的实心圆形
canvas = cv2.circle(canvas, (50, 50), 40, (0, 0, 255), -1)
# 在画布上,绘制一个圆心坐标为(150, 50)、半径为40、黄色的实心圆形
canvas = cv2.circle(canvas, (150, 50), 40, (0, 255, 255), -1)
# 在画布上,绘制一个圆心坐标为(250, 50)、半径为40、绿色的实心圆形
canvas = cv2.circle(canvas, (250, 50), 40, (0, 255, 0), -1)
cv2.imshow("TrafficLights", canvas) # 显示画布
cv2.waitKey()
cv2.destroyAllWindows()
上述代码的运行结果如图6.7所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yAtczgV0-1639056447712)(PythonOpenCV基础篇01.assets/image-20211118143049435.png)]
图6.7 绘制“交通灯”
绘制圆形和绘制线段或者矩形一样容易,但是绘制圆形要比绘制线段或者矩形多一些趣味。例如,绘制同心圆、绘制随机圆等。
【实例6.5】 绘制同心圆。(实例位置:资源包\TM\sl\6\05)
编写一个程序,使用circle()方法和for循环绘制5个同心圆,这些圆形的圆心坐标均为画布的中心,半径的值分别为0,30,60,90和120,线条颜色均为绿色,线条宽度均为5,代码如下:
import numpy as np # 导入Python中的numpy模块
import cv2
# np.zeros():创建了一个画布
# (300, 300, 3):一个300 x 300,具有3个颜色空间(即Red、Green和Blue)的画布
# np.uint8:OpenCV中的灰度图像和RGB图像都是以u
#uint8存储的,因此这里的类型也是uint8
canvas = np.zeros((300, 300, 3), np.uint8)
#shape[1]表示画布的宽度,center_X表示圆心的横坐标
#圆心的横坐标等于画布的宽度的一半
center_X = int(canvas.shape[1] / 2)
#shape[0]表示画布的高度,center_X表示圆心的纵坐标
#圆心的纵坐标等于画布的高度的一半
center_Y = int(canvas.shape[0] / 2)
#r表示半径;其中,r的值分别为0,30,60,90和120
for r in range(0, 150, 30):
#绘制一个圆心坐标为(center_X, center_Y)、半径为r、绿色的、线条宽度为5的圆形
cv2.circle(canvas, (center_X, center_Y), r, (0, 255, 0), 5)
cv2.imshow("Circles", canvas) # 显示画布
cv2.waitKey()
cv2.destroyAllWindows()
上述代码的运行结果如图6.8所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nLDk0APl-1639056447712)(PythonOpenCV基础篇01.assets/image-20211118143455707.png)]
图6.8 绘制同心圆
【实例6.6】 绘制27个随机实心圆。(实例位置:资源包\TM\sl\6\06)
编写一个程序,使用circle()方法和for循环随机绘制27个实心圆。其中,圆心的横、纵坐标在[0, 299]内取值,半径在[11, 70]内取值,线条颜色由3个在[0, 255]内的随机数组成的列表表示,代码如下:
import numpy as np # 导入Python中的numpy模块
import cv2
# np.zeros():创建了一个画布
# (300, 300, 3):一个300 x 300,具有3个颜色空间(即Red、Green和Blue)的画布
# np.uint8:OpenCV中的灰度图像和RGB图像都是以uint8存储的,因此这里的类型也是uint8
canvas = np.zeros((300, 300, 3), np.uint8)
# 通过循环绘制27个实心圆
for numbers in range(0, 28):
# 获得随机的圆心横坐标,这个横坐标在[0, 299]范围内取值
center_X = np.random.randint(0, high = 300)
# 获得随机的圆心纵坐标,这个纵坐标在[0, 299]范围内取值
center_Y = np.random.randint(0, high = 300)
# 获得随机的半径,这个半径在[11, 70]范围内取值
radius = np.random.randint(11, high = 71)
# 获得随机的线条颜色,这个颜色由3个在[0, 255]范围内的随机数组成的列表表示
color = np.random.randint(0, high = 256, size = (3,)).tolist()
# 绘制一个圆心坐标为(center_X, center_Y)、半径为radius、颜色为color的实心圆形
cv2.circle(canvas, (center_X, center_Y), radius, color, -1)
cv2.imshow("Circles", canvas) # 显示画布
cv2.waitKey()
cv2.destroyAllWindows()
上述代码的运行结果如图6.9所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LKYsBafj-1639056447712)(PythonOpenCV基础篇01.assets/image-20211118143627408.png)]
图6.9 绘制27个随机实心圆注意
因为OpenCV中的颜色值是一个列表(例如(0, 0, 255))等),所以color=np.random.randint(0, high =256, size=(3,)).tolist()中的.tolist()不能忽略,否则运行程序时会发生错误。
OpenCV提供了绘制多边形的polylines()方法,使用这个方法绘制的多边形既可以是闭合的,也可以是不闭合的。polylines()方法的语法格式如下:
img = cv2.polylines(img, pts, isClosed, color, thickness)
参数说明:
img:画布。
pts:由多边形各个顶点的坐标组成的一个列表,这个列表是一个numpy的数组类型。
isClosed:如果值为True,表示一个闭合的多边形;如果值为False,表示一个不闭合的多边形。
color:绘制多边形时的线条颜色。
thickness:绘制多边形时的线条宽度。
【实例6.7】 绘制一个等腰梯形边框。
编写一个程序,按顺时针给出等腰梯形4个顶点的坐标,即(100, 50),(200, 50),(250, 250)和(50, 250)。在画布上根据4个顶点的坐标,绘制一个闭合的、红色的、线条宽度为5的等腰梯形边框,代码如下:
import numpy as np # 导入Python中的numpy模块
import cv2
# np.zeros():创建了一个画布
# (300, 300, 3):一个300 x 300,具有3个颜色空间(即Red、Green和Blue)的画布
# np.uint8:OpenCV中的灰度图像和RGB图像都是以uuint8存储的,因此这里的类型也是uint8
canvas = np.zeros((300, 300, 3), np.uint8)
# 按顺时针给出等腰梯形4个顶点的坐标
# 这4个顶点的坐标构成了一个大小等于“顶点个数 * 1 * 2”的数组
# 这个数组的数据类型为np.int32
pts = np.array([[100, 50], [200, 50], [250, 250], [50, 250]], np.int32)
# 在画布上根据4个顶点的坐标,绘制一个闭合的、红色的、线条宽度为5的等腰梯形边框
canvas = cv2.polylines(canvas, [pts], True, (0, 0, 255), 5)
cv2.imshow("Polylines", canvas) # 显示画布
cv2.waitKey()
cv2.destroyAllWindows()
上述代码的运行结果如图6.10所示。注意
在绘制一个等腰梯形边框时,需按顺时针(即(100, 50),(200, 50),(250, 250)和(50, 250))或者逆时针(即(100, 50),(50, 250),(250, 250)和(200, 50))给出等腰梯形4个顶点的坐标,否则无法绘制一个等腰梯形边框。
例如,把实例6.7的第11行代码做如下修改:
pts = np.array([[100, 50], [200, 50], [50, 250], [250, 250]], np.int32)
运行修改后的代码,得到如图6.11所示的结果。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lDz9C1Ta-1639056447717)(PythonOpenCV基础篇01.assets/image-20211118183008718.png)]
图6.10 绘制一个等腰梯形边框
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rN5BZeTm-1639056447717)(PythonOpenCV基础篇01.assets/image-20211118183106168.png)]
图6.11 不按顺时针或逆时针给出等腰梯形4个顶点的坐标的运行结果
再如,把实例6.7的第13行代码中的True修改为False,那么将绘制出一个不闭合的等腰梯形边框,代码如下:
canvas = cv2.polylines(canvas, [pts], False, (0, 0, 255), 5) # 绘制一个不闭合的等腰梯形边框
运行修改后的代码,得到如图6.12所示的结果。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-apbXua2V-1639056447718)(PythonOpenCV基础篇01.assets/image-20211118183128142.png)]
图6.12 绘制一个不闭合的等腰梯形边框
OpenCV提供了用于绘制文字的putText()方法,使用这个方法不仅能够设置字体的样式、大小和颜色,而且能够使字体呈现斜体的效果,还能够控制文字的方向,进而使文字呈现垂直镜像的效果。putText()方法的语法格式如下:
img = cv2.putText(img, text, org, fontFace, fontScale, color, thickness, lineType, bottomLeftOrigin)
参数说明:
img:画布。
text:要绘制的文字内容。
org:文字在画布中的左下角坐标。
fontFace:字体样式,可选参数如表6.1所示。
表6.1 字体样式及其含义
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-grZeU8aF-1639056447718)(PythonOpenCV基础篇01.assets/image-20211118190722929.png)]
fontScale:字体大小。
color:绘制文字时的线条颜色。
thickness:绘制文字时的线条宽度。
lineType:线型。(线型指的是线的产生算法,有4和8两个值,默认值为8)
bottomLeftOrigin:绘制文字时的方向。(有True和False两个值,默认值为False)
说明
使用putText()方法时,thickness、lineType和bottomLeftOrigin是可选参数,有无均可。
【实例6.8】 绘制文字“mrsoft”。(实例位置:资源包\TM\sl\6\08)
编写一个程序,在画布上绘制文字“mrsoft”。其中,文字左下角的坐标为(20, 70),字体样式为FONT_HERSHEY_TRIPLEX,字体大小为2,线条颜色是绿色,线条宽度为5,代码如下:
import numpy as np # 导入Python中的numpy模块
import cv2
# np.zeros():创建了一个画布
# (100, 300, 3):一个100 x 300,具有3个颜色空间(即Red、Green和Blue)的画布
# np.uint8:OpenCV中的灰度图像和RGB图像都是以uint8存储的,因此这里的类型也是uint8
canvas = np.zeros((100, 300, 3), np.uint8)
# 在画布上绘制文字“mrsoft”,文字左下角的坐标为(20, 70)
#字体样式为FONT_HERSHEY_TRIPLEX
#字体大小为2,线条颜色是绿色,线条宽度为5
cv2.putText(canvas, "mrsoft", (20, 70), cv2.FONT_HERSHEY_TRIPLEX, 2, (0, 255, 0), 5)
cv2.imshow("Text", canvas) # 显示画布
cv2.waitKey()
cv2.destroyAllWindows()
上述代码的运行结果如图6.13所示。说明
不借助其他库或者模块,使用putText()方法绘制中文时,即把实例6.8的第11行代码中的mrsoft修改为“您好”,代码如下:
cv2.putText(canvas, "您好", (20, 70), cv2.FONT_HERSHEY_TRIPLEX, 2, (0, 255, 0), 5)
运行上述代码会出现如图6.14所示的乱码。
如果把实例6.8的第11行代码中的字体样式由“cv2.FONT_HERSHEY_TRIPLEX”修改为“cv2.FONT_HERSHEY_DUPLEX”,那么将改变图6.13中的字体样式,关键代码如下:
cv2.putText(canvas, "mrsoft", (20, 70), cv2.FONT_HERSHEY_DUPLEX, 2, (0, 255, 0), 5)
运行修改后的代码,得到如图6.15所示的效果(图6.15(a)是FONT_HERSHEY_TRIPLEX呈现的效果)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ur074ocr-1639056447718)(PythonOpenCV基础篇01.assets/image-20211118191113526.png)]
图6.15 字体样式变化效果
根据上述修改方法,读者朋友可以把实例6.8的第11行代码中的字体样式依次修改为表6.1中的各个字体样式,这样就能够查看每一个字体样式所呈现的效果。
FONT_ITALIC可以与其他文字类型一起使用,使字体在呈现指定字体样式效果的同时,也呈现斜体效果。
【实例6.9】 绘制指定字体样式的文字并呈现斜体效果。(实例位置:资源包\TM\sl\6\09)
编写一个程序,在图6.13呈现的文字效果的基础上,使字体呈现斜体效果,代码如下:
import numpy as np # 导入Python中的numpy模块
import cv2
# np.zeros():创建了一个画布
# (100, 300, 3):一个100 x 300,具有3个颜色空间(即Red、Green和Blue)的画布
# np.uint8:OpenCV中的灰度图像和RGB图像都是以uint8存储的,因此这里的类型也是uint8
canvas = np.zeros((100, 300, 3), np.uint8)
# 字体样式为FONT_HERSHEY_TRIPLEX和FONT_ITALIC
fontStyle = cv2.FONT_HERSHEY_TRIPLEX + cv2.FONT_ITALIC
# 在画布上绘制文字“mrsoft”,文字左下角的坐标为(20, 70)
# 字体样式为fontStyle,字体大小为2,线条颜色是绿色,线条宽度为5
cv2.putText(canvas, "mrsoft", (20, 70), fontStyle, 2, (0, 255, 0), 5)
cv2.imshow("Text", canvas) # 显示画布
cv2.waitKey()
cv2.destroyAllWindows()
上述代码的运行效果如图6.16所示(图6.16(a)是原图像,即图6.13)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HYvPSzlY-1639056447719)(PythonOpenCV基础篇01.assets/image-20211118191251684.png)]
图6.16 文字的斜体效果
在putText()方法的语法格式中,有一个控制绘制文字时的方向的参数,即bottomLeftOrigin,其默认值为False。当bottomLeftOrigin为True时,文字将呈现垂直镜像效果。
【实例6.10】 绘制呈现垂直镜像效果的“mrsoft”。(实例位置:资源包\TM\sl\6\10)
编写一个程序,首先在画布上绘制文字“mrsoft”。其中,文字左下角的坐标为(20, 70),字体样式为FONT_HERSHEY_TRIPLEX,字体大小为2,线条颜色是绿色,线条宽度为5。然后在该画布上绘制相同的字体样式、字体大小、线条颜色和线条宽度,而且呈现垂直镜像效果的“mrsoft”,代码如下:
import numpy as np # 导入Python中的numpy模块
import cv2
# np.zeros():创建了一个画布
# (200, 300, 3):一个200 x 300,具有3个颜色空间(即Red、Green和Blue)的画布
# np.uint8:OpenCV中的灰度图像和RGB图像都是以uint8存储的,因此这里的类型也是uint8
canvas = np.zeros((200, 300, 3), np.uint8)
# 字体样式为FONT_HERSHEY_TRIPLEX
fontStyle = cv2.FONT_HERSHEY_TRIPLEX
# 在画布上绘制文字“mrsoft”,文字左下角的坐标为(20, 70)
# 字体样式为fontStyle,字体大小为2,线条颜色是绿色,线条宽度为5
cv2.putText(canvas, "mrsoft", (20, 70), fontStyle, 2, (0, 255, 0), 5)
# 使文字“mrsoft”呈现垂直镜像效果,这时lineType和bottomLeftOrigin变成了必须参数
# 其中,lineType取默认值8,bottomLeftOrigin的值为True
cv2.putText(canvas, "mrsoft", (20, 100), fontStyle, 2, (0, 255, 0), 5, 8, True)
cv2.imshow("Text", canvas) # 显示画布
cv2.waitKey()
cv2.destroyAllWindows()
上述代码的运行效果如图6.17所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f5SUJDQx-1639056447719)(PythonOpenCV基础篇01.assets/image-20211118191411682.png)]
图6.17 绘制呈现垂直镜像效果的“mrsoft”
OpenCV除了可以在np.zeros()创建的画布上绘制文字外,还能够在图像上绘制文字。区别是当在图像上绘制文字时,不再需要导入Python中的numpy模块。
【实例6.11】 在图像上绘制文字。(实例位置:资源包\TM\sl\6\11)
编写一个程序,在D盘根目录下的2.1.jpg上绘制文字“Flower”。其中,文字左下角的坐标为(20, 90),字体样式为FONT_HERSHEY_TRIPLEX,字体大小为1,线条颜色是黄色,代码如下:
import cv2
image = cv2.imread("D:/2.1.jpg") # 读取D盘根目录下的2.1.jpg
#字体样式为FONT_HERSHEY_TRIPLEX
fontStyle = cv2.FONT_HERSHEY_TRIPLEX
# 在image上绘制文字“mrsoft”,文字左下角的坐标为(20, 90),
# 字体样式为fontStyle,字体大小为1,线条颜色是黄色
cv2.putText(image, "Flower", (20, 90), fontStyle, 1, (0, 255, 255))
cv2.imshow("Text", image) # 显示画布
cv2.waitKey()
cv2.destroyAllWindows()
上述代码的运行结果如图6.18所示。
说明
借助Python中的PIL(Python Imaging Library)模块,OpenCV能够在图像上输出中文,需要做的是对图像进行OpenCV格式和PIL格式的相互转换。这部分内容较为复杂,本书不做介绍,读者可以自学相关内容。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L1iu68eN-1639056447719)(PythonOpenCV基础篇01.assets/image-20211118191653484.png)]
图6.18 在图像上绘制文字
前面主要讲解的是如何在画布上绘制静态的图形,如线段、矩形、正方形、圆形、多边形和文字等。那么,能不能让这些静态的图形动起来呢?如果能,又该怎么做呢?
【实例6.12】 弹球动画。(实例位置:资源包\TM\sl\6\12)
在一个宽、高都为200像素的纯白色图像中,绘制一个半径为20像素的纯蓝色小球。让小球做匀速直线运动,一旦圆形碰触到图像边界则开始反弹(反弹不损失动能)。想要实现这个功能需要解决两个问题:如何计算运动轨迹和如何实现动画。下面分别介绍这两个问题的解决思路。1.通过图像坐标系计算运动轨迹
小球在运动的过程中可以把移动速度划分为上、下、左、右4个方向。左右为横坐标移动速度,上下为纵坐标移动速度。小球向右移动时横坐标不断变大,向左移动时横坐标不断变小,由此可以认为:小球向右的移动速度为正,向左的移动速度为负。纵坐标同理,因为图像坐标系的原点为背景左上角顶点,越往下延伸纵坐标越大,所以小球向上的移动速度为负,向下的移动速度为正。4个方向的速度如图6.19所示。
假设小球移动一段时间后,移动的轨迹如图6.20所示,小球分别达到了4个位置,2号位置和3号位置发生了反弹,也就是移动速度发生变化,导致移动方向发生变化。整个过程中,4个位置的速度分别如下:
❶:右下方向移动,横坐标向右,横坐标速度为+vx,纵坐标向下,纵坐标速度为+vy。
❷:右上方向移动,横坐标向右,横坐标速度为+vx,纵坐标向上,纵坐标速度为-vy。
❸:左上方向2移动,横坐标向左,横坐标速度为-vx,纵坐标向上,纵坐标速度为-vy。
❹:左上方移动,没有碰到边界,依然保持着与3号位置相同移动速度。
由此可以得出,只需要改变速度的正负号小球就可以改变移动方向,所以在程序中可以将小球的横坐标速度和纵坐标速度设定成一个不变的值,每次小球碰到左右边界,就更改横坐标速度的正负号,碰到上下边界,就更改纵坐标速度的正负号。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xa3RDG32-1639056447720)(PythonOpenCV基础篇01.assets/image-20211118191737740.png)]
图6.19 小球在4个方向的速度
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4A2xA1dg-1639056447720)(PythonOpenCV基础篇01.assets/image-20211118191824935.png)]
图6.20 小球的移动轨迹
2.通过time模块实现动画效果
Python自带一个time时间模块,该模块提供了一个sleep()方法可以让当前线程休眠一段时间,其语法格式如下:
time.sleep(seconds)
参数说明:
seconds:休眠时间,单位为s,可以是小数,如1/10表示(1/10)s。
例如,让当前线程休眠1s,代码如下:
import time
time.sleep(1) # 休眠1s
动画实际上是由多幅画面在短时间内交替放映实现的视觉效果。每一幅画面被称为一帧,所谓的60帧就是指1s放映了60幅画面。使用time模块每(1/60)s计算一次小球的移动轨迹,并将移动后的结果绘制到图像上,这样1s有60幅图像交替放映,就可以看到弹球的动画效果了。
弹球动画的具体代码如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PDtqWENi-1639056447720)(PythonOpenCV基础篇01.assets/image-20211118191858592.png)]
运行结果如图6.21所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EMUeWLCr-1639056447721)(PythonOpenCV基础篇01.assets/image-20211118191931672.png)]
图6.21 小球运动轨迹
不论是绘制图形,还是绘制文字,都需要创建画布,这个画布可以是一幅图像。需要确定线条的颜色时,要特别注意颜色的表示方式,即(B, G, R)。当绘制矩形、圆形和多边形时,通过设置线条宽度,既可以绘制图形的边框,又可以绘制被填充的图形。但是,在绘制多边形的过程中,要按照顺时针或者逆时针的方向,标记多边形各个顶点的坐标。此外,OpenCV提供的用于绘制图形的方法,不仅可以绘制静态的图形,还可以绘制动态的图形。
几何变换是指改变图像的几何结构,例如大小、角度和形状等,让图像呈现出缩放、翻转、映射和透视效果。这些几何变换操作都涉及复杂、精密的计算,OpenCV将这些计算过程封装成非常灵活的方法,开发者只需修改一些参数,就能实现图像的变换效果。本章将介绍几种常见的几何变换效果及其实现方法。
“缩”表示缩小,“放”表示放大,通过OpenCV提供的resize()方法可以随意更改图像的大小比例,其语法如下:
dst = cv2.resize(src, dsize, fx, fy, interpolation)
参数说明:
src:原始图像。
dsize:输出图像的大小,格式为(宽,高),单位为像素。
fx:可选参数。水平方向的缩放比例。
fy:可选参数。垂直方向的缩放比例。
interpolation:可选参数。缩放的插值方式。在图像缩小或放大时需要删减或补充像素,该参数可以指定使用哪种算法对像素进行增减。建议使用默认值。
返回值说明:
dst:缩值之后的图像。
resize()方法有两种使用方式,一种是通过dsize参数实现缩放,另一种是通过fx和fy参数实现缩放,下面分别介绍。
dsize参数的格式是一个元组,例如(100, 200),表示将图像按照宽100像素、高200像素的大小进行缩放。如果使用dsize参数,就可以不写fx和fy参数。
【实例7.1】 将图像按照指定宽高进行缩放。(实例位置:资源包\TM\sl\7\01)
将一个图像按照宽100像素、高100像素的大小进行缩小,再按照宽400像素、高400像素的大小进行放大,代码如下:
上述代码的运行结果如图7.1所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-80RYOFQi-1639056447721)(PythonOpenCV基础篇01.assets/image-20211118192105062.png)]
图7.1 dsize参数缩放图像效果
使用fx参数和fy参数控制缩放时,dsize参数值必须使用None,否则fx和fy失效。
fx参数和fy参数可以使用浮点值,小于1的值表示缩小,大于1的值表示放大。其计算公式为:
新图像宽度 = round(fx × 原图像宽度)
新图像高度 = round(fy × 原图像高度)
【实例7.2】 将图像按照指定比例进行缩放
将一个图像宽缩小到原来的1/3、高缩小到原来的1/2,再将图像宽放大2倍,高也放大2倍,代码如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GLD6TbGo-1639056447721)(PythonOpenCV基础篇01.assets/image-20211118192221463.png)]
上述代码的运行结果如图7.2所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6RDdlsF5-1639056447722)(PythonOpenCV基础篇01.assets/image-20211118192248191.png)]
图7.2 fx和fy参数缩放图像效果
水平方向被称为X轴,垂直方向被称为Y轴。图像沿着X轴或Y轴翻转之后,可以呈现出镜面或倒影的效果,如图7.3和图7.4所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GPHXLwiU-1639056447722)(PythonOpenCV基础篇01.assets/image-20211118192311942.png)]
图7.3 沿X轴翻转的效果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IUZ2QSmI-1639056447722)(PythonOpenCV基础篇01.assets/image-20211118192332597.png)]
图7.4 沿Y轴翻转的效果
OpenCV通过cv2.flip()方法实现翻转效果,其语法如下:
dst = cv2.flip(src, flipCode)
参数说明:
src:原始图像。
flipCode:翻转类型,类型值及含义如表7.1所示。
返回值说明:
dst:翻转之后的图像。
表7.1 flipCode类型值及含义
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wOsqrQzL-1639056447722)(PythonOpenCV基础篇01.assets/image-20211118192448155.png)]
【实例7.3】 同时实现3种翻转效果。
分别让图像沿X轴翻转,沿Y轴翻转,同时沿X轴、Y轴翻转,查看翻转的效果,代码如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-quwBCved-1639056447723)(PythonOpenCV基础篇01.assets/image-20211118192557425.png)]
上述代码的运行结果如图7.5所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ILPuSG2c-1639056447723)(PythonOpenCV基础篇01.assets/image-20211118192614947.png)]
图7.5 图像实现3种翻转效果
仿射变换是一种仅在二维平面中发生的几何变形,变换之后的图像仍然可以保持直线的“平直性”和“平行性”,也就是说原来的直线变换之后还是直线,平行线变换之后还是平行线。常见的仿射变换效果如图7.6所示,包含平移、旋转和倾斜。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ekz091B1-1639056447723)(PythonOpenCV基础篇01.assets/image-20211118192719998.png)]
图7.6 3种常见的仿射变换效果
OpenCV通过cv2. warpAffine()方法实现仿射变换效果,其语法如下:
dst = cv2.warpAffine(src, M, dsize, flags, borderMode, borderValue)
参数说明:
src:原始图像。
M:一个2行3列的矩阵,根据此矩阵的值变换原图中的像素位置。
dsize:输出图像的尺寸大小。
flags:可选参数,插值方式,建议使用默认值。
borderMode:可选参数,边界类型,建议使用默认值。
borderValue:可选参数,边界值,默认为0,建议使用默认值。
返回值说明:
dst:经过反射变换后输出图像。M也被叫作仿射矩阵,实际上就是一个2×3的列表,其格式如下:
M = [[a, b, c],[d, e, f]]
图像做何种仿射变换,完全取决于M的值,仿射变换输出的图像按照以下公式进行计算:
新x = 原x × a + 原y × b + c
新y = 原x × d + 原y × e + f
原x和原y表示原始图像中像素的横坐标和纵坐标,新x与新y表示同一个像素经过仿射变换后在新图像中的横坐标和纵坐标。M矩阵中的数字采用32位浮点格式,可以采用两种方式创建M。
(1)创建一个全是0的M,代码如下:
import numpy as np
M = np.zeros((2, 3), np.float32)
(2)创建M的同时赋予具体值,代码如下:
import numpy as np
M = np.float32([[1, 2, 3], [4, 5, 6]])
通过设定M的值就可以实现多种仿射效果,下面分别介绍如何实现图像的平移、旋转和倾斜。
平移就是让图像中的所有像素同时沿着水平或垂直方向移动。实现这种效果只需要将M的值按照以下格式进行设置:
M = [[1, 0, 水平移动的距离],[0, 1, 垂直移动的距离]]
原始图像的像素就会按照以下公式进行变换:
新x = 原x × 1 + 原y × 0 + 水平移动的距离 = 原x + 水平移动的距离
新y = 原x × 0 + 原y × 1 + 垂直移动的距离 = 原y + 垂直移动的距离
若水平移动的距离为正数,图像向右移动,若为负数,图像向左移动;若垂直移动的距离为正数,图像向下移动,若为负数,图像向上移动;若水平移动的距离和垂直移动的距离的值为0,图像不发生移动。
【实例7.4】 让图像向右下方平移。(实例位置:资源包\TM\sl\7\04)
例如,将图像向右移动50像素、向下移动100像素,代码如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oWSJE637-1639056447724)(PythonOpenCV基础篇01.assets/image-20211118192856809.png)]
上述代码的运行结果如图7.7所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qiNxefbr-1639056447724)(PythonOpenCV基础篇01.assets/image-20211118192839832.png)]
图7.7 图像向右下方平移效果
通过修改M的值可以实现其他平移效果。例如,横坐标不变,纵坐标向上移动50像素,M的值如下:
M = np.float32([[1, 0, 0], # 横坐标不变
[0, 1, -50]]) # 纵坐标向上移动50像素
移动效果如图7.8所示。
纵坐标不变,横坐标向左移动200像素,M的值如下:
M = np.float32([[1, 0, -200], # 横坐标向左移动200像素
[0, 1, 0]]) # 纵坐标不变
移动效果如图7.9所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3R16nVei-1639056447724)(PythonOpenCV基础篇01.assets/image-20211118192940668.png)]
图7.8 横坐标不变、纵坐标向上移动50像素的效果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jkmTuQ3R-1639056447724)(PythonOpenCV基础篇01.assets/image-20211118193002091.png)]
图7.9 纵坐标不变、横坐标向左移动200像素的效果
让图像旋转也是通过M矩阵实现的,但得出这个矩阵需要做很复杂的运算,于是OpenCV提供了getRotationMatrix2D()方法自动计算旋转图像的M矩阵。getRotationMatrix2D()方法的语法如下:
M = cv2.getRotationMatrix2D(center, angle, scale)
参数说明:
center:旋转的中心点坐标。
angle:旋转的角度(不是弧度)。正数表示逆时针旋转,负数表示顺时针旋转。
scale:缩放比例,浮点类型。如果取值1,表示图像保持原来的比例。
返回值说明:
M:getRotationMatrix2D()方法计算出的仿射矩阵。
【实例7.5】 让图像逆时针旋转。
让图像逆时针旋转30°的同时缩小到原来的80%,代码如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MZLgEPaZ-1639056447725)(PythonOpenCV基础篇01.assets/image-20211118193039585.png)]
上述代码的运行效果如图7.10所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nPr72Ncr-1639056447725)(PythonOpenCV基础篇01.assets/image-20211118193113385.png)]
7.10 图像逆时针旋转效果
OpenCV需要定位图像的3个点来计算倾斜效果,3个点的位置如图7.11所示,这3个点分别是“左上角”点A、“右上角”点B和“左下角”点C。OpenCV会根据这3个点的位置变化来计算其他像素的位置变化。因为要保证图像的“平直性”和“平行性”,所以不需要“右下角”的点做第4个参数,右下角这个点的位置根据A、B、C 3点的变化自动计算得出。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vjK8FNF3-1639056447725)(PythonOpenCV基础篇01.assets/image-20211118193145154.png)]
图7.11 通过3个点定位图像的仿射变换效果说明
“平直性”是指图像中的直线在经过仿射变换之后仍然是直线。“平行性”是指图像中的平行线在经过仿射变换之后仍然是平行线。
让图像倾斜也是需要通过M矩阵实现的,但得出这个矩阵需要做很复杂的运算,于是OpenCV提供了getAffineTransform()方法来自动计算倾斜图像的M矩阵。getRotationMatrix2D()方法的语法如下:
M = cv2.getAffineTransform(src, dst)
参数说明:
src:原图3个点坐标,格式为3行2列的32位浮点数列表,例如:[[0, 1], [1, 0], [1, 1]]。
dst:倾斜图像的3个点坐标,格式与src一样。
返回值说明:
M:getAffineTransform()方法计算出的仿射矩阵。
【实例7.6】 让图像向右倾斜。
让图像向右倾斜,代码如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-shMAEKmw-1639056447726)(PythonOpenCV基础篇01.assets/image-20211119061021581.png)]
上述代码的运行结果如图7.12所示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KbWkK42e-1639056447726)(PythonOpenCV基础篇01.assets/image-20211119061045205.png)]
图7.12 图像向右倾斜效果
如果让图像向左倾斜,不能只通过移动点A来实现,还需要通过移动点B和点C来实现,3个点的修改方式如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-17y741e0-1639056447726)(PythonOpenCV基础篇01.assets/image-20211119061108326.png)]
使用这两组数据计算出的M矩阵可以实现如图7.13所示的向左倾斜效果。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ppHluDTp-1639056447726)(PythonOpenCV基础篇01.assets/image-20211119061130763.png)]
图7.13 向左倾斜效果
如果说仿射是让图像在二维平面中变形,那么透视就是让图像在三维空间中变形。从不同的角度观察物体,会看到不同的变形画面,例如,矩形会变成不规则的四边形,直角会变成锐角或钝角,圆形会变成椭圆,等等。这种变形之后的画面就是透视图。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FLGJakuk-1639056447727)(PythonOpenCV基础篇01.assets/image-20211119061322272.png)]
图7.14 从图像的底部观察图像
如图7.14所示从图像的底部观察图7.15(a),眼睛距离图像底部较近,所以图像底部宽度不变,但眼睛距离图像顶部较远,图像顶部宽度就会等比缩小,于是观察者就会看到如图7.15(b)所示的透视效果。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-seb7RRvM-1639056447727)(PythonOpenCV基础篇01.assets/image-20211119061347353.png)]
图7.15 人眼观察图像透视效果
OpenCV中需要通过定位图像的4个点计算透视效果,4个点的位置如图7.16所示。OpenCV根据这4个点的位置变化来计算其他像素的位置变化。透视效果不能保证图像的“平直性”和“平行性”。
OpenCV通过warpPerspective()方法来实现透视效果,其语法如下:
dst = cv2.warpPerspective(src, M, dsize, flags, borderMode, borderValue)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HG2ynRSz-1639056447727)(PythonOpenCV基础篇01.assets/image-20211119061419756.png)]
图7.16 通过4个点定位图像的透视效果
参数说明:
src:原始图像。
M:一个3行3列的矩阵,根据此矩阵的值变换原图中的像素位置。
dsize:输出图像的尺寸大小。
flags:可选参数,插值方式,建议使用默认值。
borderMode:可选参数,边界类型,建议使用默认值。
borderValue:可选参数,边界值,默认为0,建议使用默认值。
返回值说明:
dst:经过透视变换后输出图像。
warpPerspective()方法也需要通过M矩阵计算透视效果,但得出这个矩阵需要做很复杂的运算,于是OpenCV提供了getPerspectiveTransform()方法自动计算M矩阵。getPerspectiveTransform()方法的语法如下:
M = cv2.getPerspectiveTransform(src, dst,)
参数说明:
src:原图4个点坐标,格式为4行2列的32位浮点数列表,例如:[[0, 0], [1, 0], [0, 1],[1, 1]]。
dst:透视图的4个点坐标,格式与src一样。
返回值说明:
M:getPerspectiveTransform()方法计算出的仿射矩阵。
【实例7.7】 模拟从底部观察图像得到的透视效果。
模拟从底部观察图像得到的透视效果,将图像顶部边缘收窄,底部边缘保持不变,代码如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T0sldbdJ-1639056447728)(PythonOpenCV基础篇01.assets/image-20211119061533770.png)]
上述代码的运行结果如图7.17所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t1FYSX0v-1639056447728)(PythonOpenCV基础篇01.assets/image-20211119061620244.png)]
图7.17 图像透视效果
图像的缩放有2种方式:一种是设置dsize参数,另一种是设置fx参数和fy参数。图像的翻转有3种方式,沿X轴翻转、沿Y轴翻转和同时沿X轴、Y轴翻转,这3种方式均由flipCode参数的值决定。图像的仿射变换取决于仿射矩阵,采用不同的仿射矩阵(M),就会使图像呈现不同的仿射效果。此外,图像的透视仍然要依靠M矩阵实现。因此,只要熟练掌握并灵活运用M矩阵,就能够得心应手地对图像进行几何变换操作。
阈值是图像处理中一个很重要的概念,类似一个“像素值的标准线”。所有像素值都与这条“标准线”进行比较,最后得到3种结果:像素值比阈值大、像素值比阈值小或像素值等于阈值。程序根据这些结果将所有像素进行分组,然后对某一组像素进行“加深”或“变淡”操作,使得整个图像的轮廓更加鲜明,更容易被计算机或肉眼识别。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p9ONbCX4-1639056447728)(PythonOpenCV基础篇01.assets/image-20211119061719129.png)]
在图像处理的过程中,阈值的使用使得图像的像素值更单一,进而使得图像的效果更简单。首先,把一幅彩色图像转换为灰度图像,这样图像的像素值的取值范围即可简化为0~255。然后,通过阈值使得转换后的灰度图像呈现出只有纯黑色和纯白色的视觉效果。例如,当阈值为127时,把小于127的所有像素值都转换为0(即纯黑色),把大于127的所有像素值都转换为255(即纯白色)。虽然会丢失一些灰度细节,但是会更明显地保留灰度图像主体的轮廓。
OpenCV提供的threshold()方法用于对图像进行阈值处理,threshold()方法的语法如下:
retval, dst = cv2.threshold(src, thresh, maxval, type)
参数说明:
src:被处理的图像,可以是多通道图像。
thresh:阈值,阈值在125~150取值的效果最好。
maxval:阈值处理采用的最大值。
type:阈值处理类型。常用类型和含义如表8.1所示。
表8.1 阈值处理类型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yvdhSOhI-1639056447728)(PythonOpenCV基础篇01.assets/image-20211119061937771.png)]
返回值说明:
retval:处理时采用的阈值。
dst:经过阈值处理后的图像。
二值化处理和反二值化处理使得灰度图像的像素值两极分化,灰度图像呈现出只有纯黑色和纯白色的视觉效果。
二值化处理也叫二值化阈值处理,该处理让图像仅保留两种像素值,或者说所有像素都只能从两种值中取值。
进行二值化处理时,每一个像素值都会与阈值进行比较,将大于阈值的像素值变为最大值,将小于或等于阈值的像素值变为0,计算公式如下:
if 像素值 <= 阈值: 像素值 = 0
if 像素值 > 阈值: 像素值 = 最大值
通常二值化处理是使用255作为最大值,因为灰度图像中255表示纯白色,能够很清晰地与纯黑色进行区分,所以灰度图像经过二值化处理后呈现“非黑即白”的效果。
例如,图8.1是一个由白到黑的渐变图,最左侧的像素值为255(表现为纯白色),右侧的像素值逐渐递减,直到最右侧的像素值为0(表现为纯黑色)。像素值的变化如图8.2所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bWLgNyAM-1639056447729)(PythonOpenCV基础篇01.assets/image-20211119062048670.png)]
图8.1 由白到黑的渐变图像
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XfC6P7JJ-1639056447729)(PythonOpenCV基础篇01.assets/image-20211119062107944.png)]
图8.2 渐变图像像素值变化示意图
【实例8.1】 二值化处理白黑渐变图。
将图8.1进行二值化处理,取0~255的中间值127作为阈值,将255作为最大值,代码如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FDy5hjfV-1639056447729)(PythonOpenCV基础篇01.assets/image-20211119062149699.png)]
上述代码的运行结果如图8.3和图8.4所示,图像中凡是大于127的像素值都变成了255(纯白色),小于或等于127的像素值都变成了0(纯黑色)。原图从白黑渐变图像变成了白黑拼接图像,可以看到非常清晰的黑白交界。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eFwuvv37-1639056447729)(PythonOpenCV基础篇01.assets/image-20211119062216440.png)]
图8.3 原图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AeCLgXhY-1639056447730)(PythonOpenCV基础篇01.assets/image-20211119062238704.png)]
图8.4 二值化处理效果
【实例8.2】 观察不同阈值的处理效果。
通过修改阈值大小可以调整黑白交界的位置。例如,分别采用127和210作为阈值,对比处理结果,代码如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3Ln0Hps3-1639056447730)(PythonOpenCV基础篇01.assets/image-20211119062337018.png)]
上述代码的运行结果如图8.5所示。因为原图中大部分像素值都大于127,所以阈值为127时,大部分像素都变成了255(纯白色);但原图中大于210的像素值并不多,所以阈值为210时,大部分像素都变成了0(纯黑色)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qZZkenTM-1639056447730)(PythonOpenCV基础篇01.assets/image-20211119062354861.png)]
图8.5 不同阈值处理效果
【实例8.3】 观察不同最大值的处理效果。(实例位置:资源包\TM\sl\8\03)
像素值的最小值默认为0,但最大值可以由开发者设定。如果最大值不是255(纯白色),那么“非黑”的像素就不一定是纯白色了。例如,灰度值150表现为“灰色”,查看将150作为最大值处理的效果,代码如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vbNlixdi-1639056447730)(PythonOpenCV基础篇01.assets/image-20211119062420756.png)]
上述代码的运行结果如图8.6所示。当最大值设为150时,凡是大于127的像素值都被改为150,呈现灰色。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5MSIhTgo-1639056447731)(PythonOpenCV基础篇01.assets/image-20211119062503592.png)]
图8.6 不同最大值处理效果
彩色图像也可以进行二值化处理,处理之后会将颜色夸张化,对比效果如图8.7和图8.8所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EKWk99XV-1639056447731)(PythonOpenCV基础篇01.assets/image-20211119062737249.png)]
图8.7 彩色图像原图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lx9FgBwQ-1639056447731)(PythonOpenCV基础篇01.assets/image-20211119062804127.png)]
图8.8 彩色图像进行二值化处理的效果
反二值化处理也叫反二值化阈值处理,其结果为二值化处理的相反结果。将大于阈值的像素值变为0,将小于或等于阈值的像素值变为最大值。原图像中白色的部分变成黑色,黑色的部分变成白色。计算公式如下:
if 像素值 <= 阈值: 像素值 = 最大值
if 像素值 > 阈值: 像素值 = 0
【实例8.4】 对图像进行反二值化处理。
分别将图8.1进行二值化处理和反二值化处理,对比处理结果,代码如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U7rXH33j-1639056447732)(PythonOpenCV基础篇01.assets/image-20211119062916394.png)]
上述代码的运行效果如图8.9所示,可以明显地看出二值化处理效果和反二值化处理效果是完全相反的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1ChHh0FO-1639056447732)(PythonOpenCV基础篇01.assets/image-20211119062935052.png)]
图8.9 二值化处理和反二值化处理效果
彩色图像经过反二值化处理后,因为各通道的颜色分量值不同,会呈现“混乱”的效果,对比效果如图8.10所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GjN8qsRp-1639056447732)(PythonOpenCV基础篇01.assets/image-20211119062958289.png)]
图8.10 彩色图像反二值化处理效果
零处理会将某一个范围内的像素值变为0,并允许范围之外的像素保留原值。零处理包括低于阈值零处理和超出阈值零处理。
低于阈值零处理也叫低阈值零处理,该处理将低于或等于阈值的像素值变为0,大于阈值的像素值保持原值,计算公式如下:
if 像素值 <= 阈值: 像素值 = 0
if 像素值 > 阈值: 像素值 = 原值
【实例8.5】 对图像进行低于阈值零处理。
将图8.1进行低于阈值零处理,阈值设为127,代码如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CV7sij6g-1639056447732)(PythonOpenCV基础篇01.assets/image-20211119063054958.png)]
上述代码的运行结果如图8.11所示,像素值低于或等于127的区域彻底变黑,像素值高于127的区域仍然保持渐变效果。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f29gBS5o-1639056447733)(PythonOpenCV基础篇01.assets/image-20211119063110806.png)]
图8.11 图像低于阈值零处理效果
图像经过低于阈值零处理后,颜色深的位置会彻底变黑,颜色浅的位置不受影响。彩色图像经过低于阈值零处理后,会让深颜色区域的颜色变得更深,甚至变黑,对比效果如图8.12所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7KTaxs5L-1639056447733)(PythonOpenCV基础篇01.assets/image-20211119063132937.png)]
图8.12 彩色图像低于阈值零处理效果
超出阈值零处理也叫超阈值零处理,该处理将大于阈值的像素值变为0,小于或等于阈值的像素值保持原值。计算公式如下:
if 像素值 <= 阈值: 像素值 = 原值
if 像素值 > 阈值: 像素值 = 0
【实例8.6】 对图像进行超出阈值零处理。
将图8.1进行超出阈值零处理,阈值设为127,代码如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-exMhTIfC-1639056447733)(PythonOpenCV基础篇01.assets/image-20211119063244471.png)]
上述代码的运行结果如图8.13所示,像素值高于127的区域彻底变黑,像素值低于或等于127的区域仍然保持渐变效果。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h4m0sOMS-1639056447733)(PythonOpenCV基础篇01.assets/image-20211119063259837.png)]
图8.13 图像超出阈值零处理效果
图像经过超出阈值零处理后浅颜色区域彻底变黑,深颜色区域则不受影响。但彩色图像经过超出阈值零处理后,浅颜色区域的颜色分量取相反的极值,也呈现出一种“混乱”的效果,对比效果如图8.14所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1KnQjMSp-1639056447734)(PythonOpenCV基础篇01.assets/image-20211119063324538.png)]
图8.14 彩色图像超出阈值零处理效果
截断处理也叫截断阈值处理,该处理将图像中大于阈值的像素值变为和阈值一样的值,小于或等于阈值的像素保持原值,其公式如下:
if 像素 <= 阈值: 像素 = 原值
if 像素 > 阈值: 像素 = 阈值
【实例8.7】 对图像进行截断处理。
将图8.1进行截断处理,取127作为阈值,代码如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8QLUfRgf-1639056447734)(PythonOpenCV基础篇01.assets/image-20211119063441020.png)]
上述代码的运行结果如图8.15所示,浅颜色区域都变成了灰色,但深颜色区域仍然是渐变效果。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gmv8a6nu-1639056447734)(PythonOpenCV基础篇01.assets/image-20211119063457401.png)]
图8.15 图像截断处理效果
图像经过截断处理后,整体颜色都会变暗。彩色图像经过截断处理后,在降低亮度的同时还会让浅颜色区域的颜色变得更浅,对比效果如图8.16所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pYTX1N7R-1639056447734)(PythonOpenCV基础篇01.assets/image-20211119063611119.png)]
图8.16 彩色图像截断处理效果
前面已经依次对cv2.THRESH_BINARY、cv2.THRESH_BINARY_INV、cv2.THRESH_TOZERO、cv2.THRESH_TOZERO_INV和cv2.THRESH_TRUNC这5种阈值处理类型进行了详解。因为图8.1是一幅色彩均衡的图像,所以直接使用一种阈值处理类型就能够对图像进行阈值处理。很多时候图像的色彩是不均衡的,如果只使用一种阈值处理类型,就无法得到清晰有效的结果。
【实例8.8】 使用常用的5种阈值处理类型对色彩不均衡的图像
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oR5nUpHU-1639056447735)(PythonOpenCV基础篇01.assets/4.27.png)]
图8.17 色彩不均衡的图像
先将图8.17转换为灰度图像,再依次使用cv2.THRESH_BINARY、cv2.THRESH_BINARY_INV、cv2.THRESH_TOZERO、cv2.THRESH_TOZERO_INV和cv2.THRESH_TRUNC这5种阈值处理类型对转换后的灰度图像进行阈值处理,代码如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s2rpMxYb-1639056447735)(PythonOpenCV基础篇01.assets/image-20211119063800302.png)]
上述代码的运行结果如图8.18~图8.22所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a1zsDV0h-1639056447735)(PythonOpenCV基础篇01.assets/image-20211119063819692.png)]
图8.18 二值化处理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wiOb82UH-1639056447736)(PythonOpenCV基础篇01.assets/image-20211119063905724.png)]
图8.19 反二值化处理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u1Ho37tq-1639056447736)(PythonOpenCV基础篇01.assets/image-20211119063924931.png)]
图8.20 低于阈值零处理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YcBeCPu6-1639056447736)(PythonOpenCV基础篇01.assets/image-20211119063944429.png)]
图8.21 超出阈值零处理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MpLINho5-1639056447736)(PythonOpenCV基础篇01.assets/image-20211119064005583.png)]
图8.22 截断处理
从视觉上看,对于色彩不均衡的图像,虽然使用截断处理的效果是5种阈值处理类型中效果比较好的,但是有些轮廓依然模糊不清(例如,图8.22中的手部轮廓),使用程序继续对其进行处理仍然很困难。这时,需要进一步简化图像。
OpenCV提供了一种改进的阈值处理技术:图像中的不同区域使用不同的阈值。把这种改进的阈值处理技术称作自适应阈值处理也称自适应处理,自适应阈值是根据图像中某一正方形区域内的所有像素值按照指定的算法计算得到的。与前面讲解的5种阈值处理类型相比,自适应处理能更好地处理明暗分布不均的图像,获得更简单的图像效果。
OpenCV提供了adaptiveThresHold()方法对图像进行自适应处理,adaptiveThresHold()方法的语法如下:
dst = cv2.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C)
参数说明:
src:被处理的图像。需要注意的是,该图像需是灰度图像。
maxValue:阈值处理采用的最大值。
adaptiveMethod:自适应阈值的计算方法。自适应阈值的计算方法及其含义如表8.2所示。
表8.2 自适应阈值的计算方法及其含义
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y9HwFAuu-1639056447737)(PythonOpenCV基础篇01.assets/image-20211119064121242.png)]
thresholdType:阈值处理类型;需要注意的是,阈值处理类型需是cv2.THRESH_BINARY或cv2.THRESH_BINARY_INV中的一个。
blockSize:一个正方形区域的大小。例如,5指的是5×5的区域。
C:常量。阈值等于均值或者加权值减去这个常量。
返回值说明:
dst:经过阈值处理后的图像。
【实例8.9】 使用自适应处理的效果。(实例位置:资源包\TM\sl\8\09)
先将图8.17转换为灰度图像,再分别使用cv2.ADAPTIVE_THRESH_MEAN_C和cv2.ADAPTIVE_THRESH_GAUSSIAN_C这两种自适应阈值的计算方法对转换后的灰度图像进行阈值处理,代码如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M9gjVuJI-1639056447737)(PythonOpenCV基础篇01.assets/image-20211119064147759.png)]
上述代码的运行结果如图8.23和图8.24所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cn9R4qBL-1639056447737)(PythonOpenCV基础篇01.assets/image-20211119064354181.png)]
图8.23 ADAPTIVE_THRESH_MEAN_C的处理结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GnSdaQsS-1639056447737)(PythonOpenCV基础篇01.assets/image-20211119064417014.png)]
图8.24 ADAPTIVE_THRESH_GAUSSIAN_C的处理结果
与前面讲解的5种阈值处理类型的处理结果相比,自适应处理保留了图像中更多的细节信息,更明显地保留了灰度图像主体的轮廓。注意
使用自适应阈值处理图像时,如果图像是彩色图像,那么需要先将彩色图像转换为灰度图像;否则,运行程序时会出现如图8.25所示的错误提示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H1GW2zEh-1639056447738)(PythonOpenCV基础篇01.assets/image-20211119064438784.png)]
图8.25 运行程序时出现的错误
前面在讲解5种阈值处理类型的过程中,每个实例设置的阈值都是127,这个127是笔者设置的,并不是通过算法计算得到的。对于有些图像,当阈值被设置为127时,得到的效果并不好,这时就需要一个个去尝试,直到找到最合适的阈值。
逐个寻找最合适的阈值不仅工作量大,而且效率低。为此,OpenCV提供了Otsu方法。Otsu方法能够遍历所有可能的阈值,从中找到最合适的阈值。
Otsu方法的语法与threshold()方法的语法基本一致,只不过在为type传递参数时,要多传递一个参数,即cv2.THRESH_OTSU。cv2.THRESH_OTSU的作用就是实现Otsu方法的阈值处理。Otsu方法的语法如下:
retval, dst = cv2.threshold(src, thresh, maxval, type)
参数说明:
src:被处理的图像。需要注意的是,该图像需是灰度图像。
thresh:阈值,且要把阈值设置为0。
maxval:阈值处理采用的最大值,即255。
type:阈值处理类型。除在表8.1中选择一种阈值处理类型外,还要多传递一个参数,即cv2.THRESH_OTSU。例如,cv2.THRESH_BINARY+cv2.THRESH_OTSU。
返回值说明:
retval:由Otsu方法计算得到并使用的最合适的阈值。
dst:经过阈值处理后的图像。
【实例8.10】 在图8.26上实现Otsu方法的阈值处理。(实例位置:资源包\TM\sl\8\10)
图8.26是一幅亮度较高的图像,分别对这幅图像进行二值化处理和实现Otsu方法的阈值处理,对比处理后图像的差异,代码如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cKPveI4H-1639056447738)(PythonOpenCV基础篇01.assets/image-20211119064609735.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-77Yyerxn-1639056447738)(PythonOpenCV基础篇01.assets/image-20211119064647798.png)]
图8.26 一幅亮度较高的图像
上述代码的运行结果如图8.27和图8.28所示。
对比图8.27和图8.28后能够发现,由于图8.26的亮度较高,使用阈值为127进行二值化阈值处理的结果没有很好地保留图像主体的轮廓,并出现了大量的白色区域。但是,通过实现Otsu方法的阈值处理,不仅找到了最合适的阈值(即184),还将图像主体的轮廓很好地保留了下来,获得了比较好的处理结果。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CcpyVeCl-1639056447738)(PythonOpenCV基础篇01.assets/image-20211119064729264.png)]
图8.27 二值化处理的结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rvwoBL8O-1639056447739)(PythonOpenCV基础篇01.assets/image-20211119064754376.png)]
图8.28 实现Otsu方法的阈值处理的结果
阈值处理在计算机视觉技术中占有十分重要的位置,它是很多高级算法的底层处理逻辑之一。因为二值图像会忽略细节,放大特征,而很多高级算法要根据物体的轮廓来分析物体特征,所以二值图像非常适合做复杂的识别运算。在进行识别运算之前,应先将图像转为灰度图像,再进行二值化处理,这样就得到了算法所需要的物体(大致)轮廓图像。
下面通过一个实例来演示通过阈值处理获取物体轮廓的方法。
【实例8.11】 利用阈值处理勾勒楼房和汽车的轮廓。
读取一幅图像,先将图像转为灰度图像,再将图像分别进行二值化处理和反二值化处理,具体代码如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Dvn0tBAr-1639056447739)(PythonOpenCV基础篇01.assets/image-20211119064833488.png)]
上述代码的运行结果如图8.29~图8.32所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mg4Q4IiJ-1639056447739)(PythonOpenCV基础篇01.assets/image-20211119064852964.png)]
图8.29 原始图像
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HiuRjcZa-1639056447741)(PythonOpenCV基础篇01.assets/image-20211119065010880.png)]
图8.30 灰度图像
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WkkKoBWl-1639056447741)(PythonOpenCV基础篇01.assets/image-20211119065032490.png)]
图8.31 二值化处理效果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QqLHFVgG-1639056447741)(PythonOpenCV基础篇01.assets/image-20211119065054225.png)]
图8.32 反二值化处理效果
从后面两幅图像可以看到,二值化处理后,图片只有纯黑和纯白两种颜色,图像中的楼房边缘变得更加鲜明,更容易被识别。地面因为颜色较深,所以大面积被涂黑,这样白色的汽车就与地面形成了鲜明的反差。二值化处理后的汽车轮廓在肉眼看来可能还不够明显,但反二值化处理后的汽车轮廓与地面的反差就非常大。高级图像识别算法可以根据这种鲜明的像素变化来搜寻特征,最后达到识别物体分类的目的。
OpenCV提供了一个可以快速抠出图像主体线条的工具,这个工具就是阈值。在阈值的作用下,一幅彩色图像被转换为只有纯黑和纯白的二值图像。然而,灰度图像经5种阈值处理类型处理后,都无法得到图像主体的线条。为此,OpenCV提供了一种改进的阈值处理技术,即自适应处理,其关键在于对图像中的不同区域使用不同的阈值。有了这种改进的阈值处理技术,得到图像主体的线条就不再是一件难以实现的事情了。
图像是由像素组成的,像素又是由具体的正整数表示的,因此图像也可以进行一系列数学运算,通过运算可以获得截取、合并图像等效果。OpenCV提供了很多图像运算方法,经过运算的图像可以呈现出很多有趣的视觉效果。下面将对OpenCV中一些常用的图像运算方法进行介绍。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mo6V3WJY-1639056447741)(PythonOpenCV基础篇01.assets/image-20211120060956203.png)]
前面的章节出现过“掩模”这个参数,当时建议大家不使用这个参数。掩模到底有什么用呢?这一节将介绍掩模的概念。
外科医生在给患者做手术时,会为患者盖上手术洞巾,类似图9.1,这样医生就只在这个预设好的孔洞部位进行手术。手术洞巾不仅有利于医生定位患处、暴露手术视野,还可以对非患处起到隔离、防污的作用。
同样,当计算机处理图像时,图像也如同一名“患者”一样,有些内容需要处理,有些内容不需要处理。通常计算机处理图像时会把所有像素都处理一遍,但如果想让计算机像外科大夫那样仅处理某一小块区域,那就要为图像盖上一张仅暴露一小块区域的“手术洞巾”。像“手术洞巾”那样能够覆盖原始图像、仅暴露原始图像“感兴趣区域”(ROI)的模板图像就被叫作掩模。
掩模,也叫作掩码,英文为mask,在程序中用二值图像来表示:0值(纯黑)区域表示被遮盖的部分,255值(纯白)区域表示暴露的部分(某些场景下也会用0和1当作掩模的值)。
例如,图9.2是一幅小猫的原始图像,图9.3是原始图像的掩模,掩模覆盖原始图像之后,可以得到如图9.4所示的结果。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6PV5M1Iu-1639056447742)(PythonOpenCV基础篇01.assets/image-20211120061039170.png)]
图9.1 外科手术给患者使用的手术洞巾
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6d6WFB2M-1639056447742)(PythonOpenCV基础篇01.assets/image-20211120061106906.png)]
图9.2 原始图像
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aq0EsDKY-1639056447742)(PythonOpenCV基础篇01.assets/image-20211120061128635.png)]
图9.3 掩模
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eikVbRYc-1639056447743)(PythonOpenCV基础篇01.assets/image-20211120061150893.png)]
图9.4 被掩模覆盖后得到的图像
如果调换了掩模中黑白区域,如图9.5所示,掩模覆盖原始图像之后得到的结果如图9.6所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4Lpjdx6b-1639056447743)(PythonOpenCV基础篇01.assets/image-20211120061213880.png)]
图9.5 调换黑白区域的新掩模
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YPCdKVNs-1639056447743)(PythonOpenCV基础篇01.assets/image-20211120061336692.png)]
图9.6 被新掩模覆盖后得到的图像
在使用OpenCV处理图像时,通常使用numpy库提供的方法创建掩模图像,下面通过一个实例演示如何创建掩模图像。
【实例9.1】 创建3通道掩模图像。(实例位置:资源包\TM\sl\9\01)
利用numpy库的zeros()方法创建一幅掩模图像,感兴趣区域为在该图像中横坐标为20、纵坐标为50、宽为60、高为50的矩形,展示该掩模图像。调换该掩模图像的感兴趣区域和不感兴趣区域之后,再次展示掩模图像,具体代码如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mfDxKbkV-1639056447743)(PythonOpenCV基础篇01.assets/image-20211120061301526.png)]
运行结果如图9.7和图9.8所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-siUCGS6Z-1639056447744)(PythonOpenCV基础篇01.assets/image-20211120061400048.png)]
图9.7 掩模图像
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-742mvPro-1639056447744)(PythonOpenCV基础篇01.assets/image-20211120061420875.png)]
图9.8 调换之后的掩模图像
掩模在图像运算过程中充当了重要角色,通过掩模才能看到最直观的运算结果,接下来将详细介绍图像运算的相关内容。
图像中每一个像素都有用整数表示的像素值,2幅图像相加就是让相同位置像素值相加,最后将计算结果按照原位置重新组成一幅新图像。原理如图9.9所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v04PglxA-1639056447744)(PythonOpenCV基础篇01.assets/image-20211120061517289.png)]
图9.9 图像相加生成新像素
图9.9中2幅图像的左上角像素值相加的结果就是新图像左上角的像素值,计算过程如下:
152 + 35 = 187
在开发程序时通常不会使用“+”运算符对图像做加法运算,而是用OpenCV提供的add()方法,该方法的语法如下:
dst = cv2.add(src1, src2, mask, dtype)
参数说明:
src1:第一幅图像。
src2:第二幅图像。
mask:可选参数,掩模,建议使用默认值。
dtype:可选参数,图像深度,建议使用默认值。
返回值说明:
dst:相加之后的图像。如果相加之后值的结果大于255,则取255。
下面通过一个实例演示“+”运算符和add()方法处理结果的不同。
【实例9.2】 分别使用“+”和add()方法计算图像和。(实例位置:资源包\TM\sl\9\02)
读取一幅图像,让该图像自己对自己做加法运算,分别使用“+”运算符和add()方法,观查两者相加结果的不同,具体代码如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GJ3lmZ3O-1639056447744)(PythonOpenCV基础篇01.assets/image-20211120061614335.png)]
上述代码的运行结果如图9.10所示。从结果可以看出,“+”运算符的计算结果如果超出了255,就会取相加和除以255的余数,也就是取模运算,像素值相加后反而变得更小,由浅色变成了深色;而add()方法的计算结果如果超过了255,就取值255,很多浅颜色像素彻底变成了纯白色。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N7pIO5VR-1639056447745)(PythonOpenCV基础篇01.assets/image-20211120061639378.png)]
图9.10 图像的加法运算效果
下面通过一个实例演示如何使用加运算修改图像颜色。
【实例9.3】 模拟三色光叠加得白光。
颜料中的三原色为红、黄、蓝,这3种颜色混在一起变成黑色,而光学中的三原色为红、绿、蓝,这3种颜色混在一起变成白色。现在分别创建纯蓝、纯绿、纯红3种图像,取3幅图像的相加和,查看结果是黑色还是白色,具体代码如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zkP0bhRT-1639056447745)(PythonOpenCV基础篇01.assets/image-20211120061705351.png)]
蓝色加上绿色等于青色,青色再加上红色就等于白色,结果符合光学三原色的叠加原理。
图像的加法运算中也可以使用掩模,下面通过一个实例介绍掩模的使用方法。
【实例9.4】 利用掩模遮盖相加结果。
创建纯蓝和纯红2幅图像,使用add()方法对2幅图像进行加法运算,并在方法中添加一个掩模,具体代码如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1YK0gida-1639056447745)(PythonOpenCV基础篇01.assets/image-20211120061833765.png)]
上述代码的运行结果如图9.16~图9.18所示,从结果可以看出,add()方法中如果使用了掩模参数,相加的结果只会保留掩模中白色覆盖的区域。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2qAWl3JQ-1639056447745)(PythonOpenCV基础篇01.assets/image-20211120061859770.png)]
图9.16 蓝色和红色相加的结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vAxNtd7g-1639056447746)(PythonOpenCV基础篇01.assets/image-20211120061924099.png)]
图9.17 掩模
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AnZIKqfD-1639056447746)(PythonOpenCV基础篇01.assets/image-20211120061948929.png)]
图9.18 通过掩模相加的结果
位运算是二进制数特有的运算操作。图像由像素组成,每个像素可以用十进制整数表示,十进制整数又可以转化为二进制数,所以图像也可以做位运算,并且位运算是图像数字化技术中一项重要的运算操作。
OpenCV提供了几种常用的位运算方法,具体如表9.1所示。
表9.1 OpenCV提供的位运算方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i74Ymylv-1639056447746)(PythonOpenCV基础篇01.assets/image-20211120062045946.png)]
接下来将详细介绍这些方法的含义及使用方式。
与运算就是按照二进制位进行判断,如果同一位的数字都是1,则运算结果的相同位数字取1,否则取0。
OpenCV提供bitwise_and()方法来对图像做与运算,该方法的语法如下:
dst = cv2.bitwise_and(src1, src2, mask)
参数说明:
src1:第一幅图像。
src2:第二幅图像。
mask:可选参数,掩模。
返回值说明:
dst:与运算之后的图像。
图像做与运算时,会把每一个像素值都转为二进制数,然后让两幅图像相同位置的两个像素值做与运算,最后把运算结果保存在新图像的相同位置上,运算过程如图9.19所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rMRaj53V-1639056447747)(PythonOpenCV基础篇01.assets/image-20211120062122565.png)]
图9.19 图像做与运算的过程
与运算有两个特点。
(1)如果某像素与纯白像素做与运算,结果仍然是某像素的原值,计算过程如下:
00101011 & 11111111 = 00101011
(2)如果某像素与纯黑像素做与运算,结果为纯黑像素,计算过程如下:
00101011 & 00000000 = 00000000
由此可以得出:如果原图像与掩模进行与运算,原图像仅保留掩模中白色区域覆盖的内容,其他区域全部变成黑色。下面通过一个实例演示掩模在与运算过程的作用。
【实例9.5】 花图像与十字掩模做与运算。
创建一个掩模,在掩模中央保留一个十字形的白色区域,让掩模与花图像做与运算,具体代码如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GlJmUebv-1639056447747)(PythonOpenCV基础篇01.assets/image-20211120062312995.png)]
上述代码的运行结果如图9.20~图9.22所示,经过与运算之后,花图像仅保留了掩模中白色区域覆盖的内容,其他区域都变成了黑色。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TkRnG8jC-1639056447747)(PythonOpenCV基础篇01.assets/image-20211120062336698.png)]
图9.20 花图像
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TpPl25TY-1639056447747)(PythonOpenCV基础篇01.assets/image-20211120062357566.png)]
图9.21 掩模图像
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DySSx5Wm-1639056447748)(PythonOpenCV基础篇01.assets/image-20211120062502072.png)]
图9.22 花图像与掩模图像与运算的效果
或运算也是按照二进制位进行判断,如果同一位的数字都是0,则运算结果的相同位数字取0,否则取1。
OpenCV提供bitwise_or()方法来对图像做或运算,该方法的语法如下:
dst = cv2.bitwise_or(src1, src2, mask)
参数说明:
src1:第一幅图像。
src2:第二幅图像。
mask:可选参数,掩模。
返回值说明:
dst:或运算之后的图像。
图像做或运算时的运算过程如图9.23所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xB9H6Zeo-1639056447748)(PythonOpenCV基础篇01.assets/image-20211120062536061.png)]
图9.23 图像做或运算的过程
或运算有以下两个特点。
(1)如果某像素与纯白像素做或运算,结果为纯白像素,计算过程如下:
00101011 | 11111111 = 11111111
(2)如果某像素与纯黑像素做或运算,结果仍然是某像素的原值,过程如下:
00101011 | 00000000 = 00101011
由此可以得出:如果原图像与掩模进行或运算,原图像仅保留掩模中黑色区域覆盖的内容,其他区域全部变成白色。下面通过一个实例演示掩模在或运算过程中的作用。
【实例9.6】 花图像与十字掩模做或运算。
创建一个掩模,在掩模中央保留一个十字形的白色区域,让掩模与花图像做或运算,具体代码如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bvJpOWat-1639056447748)(PythonOpenCV基础篇01.assets/image-20211120062638230.png)]上述代码的运行结果如图9.24所示,经过或运算后,花图像仅保留了掩模中黑色区域覆盖的内容,其他区域都变成了白色。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J2CP3iXs-1639056447748)(PythonOpenCV基础篇01.assets/image-20211120062715613.png)]
图9.24 图像或运算效果
取反运算是一种单目运算,仅需一个数字参与运算就可以得出结果。取反运算也是按照二进制位进行判断,如果运算数某位上数字是0,则运算结果的相同位的数字就取1,如果这一位的数字是1,则运算结果的相同位的数字就取0。
OpenCV提供bitwise_not()方法来对图像做取反运算,该方法的语法如下:
dst = cv2.bitwise_not(src, mask)
参数说明:
src:参与运算的图像。
mask:可选参数,掩模。
返回值说明:
dst:取反运算之后的图像。
图像做取反运算的过程如图9.25所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-doruRBpa-1639056447749)(PythonOpenCV基础篇01.assets/image-20211120062822407.png)]
图9.25 图像做取反运算的过程
图像经过取反运算后呈现与原图颜色完全相反的效果,下面通过一个实例演示掩膜在取反运算过程中的作用。
【实例9.7】 对花图像进行取反运算。
对花图像进行取反运算,具体代码如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uMFFswsx-1639056447749)(PythonOpenCV基础篇01.assets/image-20211120062846036.png)]
上述代码的运行结果如图9.26所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Fc8j2CCJ-1639056447749)(PythonOpenCV基础篇01.assets/image-20211120062904656.png)]
图9.26 图像取反运算的效果
异或运算也是按照二进制位进行判断,如果两个运算数同一位上的数字相同,则运算结果的相同位数字取0,否则取1。OpenCV提供bitwise_xor()方法对图像做异或运算,该方法的语法如下:
dst = cv2.bitwise_xor(src, mask)
参数说明:
src:参与运算的图像。
mask:可选参数,掩模。
返回值说明:
dst:异或运算之后的图像。
图像做异或运算的过程如图9.27所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3yYWCTkC-1639056447750)(PythonOpenCV基础篇01.assets/image-20211120063005147.png)]
图9.27 图像做异或运算的过程
异或运算有两个特点。
(1)如果某像素与纯白像素做异或运算,结果为原像素的取反结果,计算过程如下:
00101011 ^ 11111111 = 11010100
(2)如果某像素与纯黑像素做异或运算,结果仍然是某像素的原值,计算过程如下:
00101011 ^ 00000000 = 00101011
由此可以得出:如果原图像与掩模进行异或运算,掩模白色区域覆盖的内容呈现取反效果,黑色区域覆盖的内容保持不变。下面通过一个实例演示掩模在异或运算过程的作用。
【实例9.8】 花图像与十字掩模做异或运算。
创建一个掩模,在掩模中央保留一个十字形的白色区域,让掩模与花图像做异或运算,具体代码如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pRMMjfzf-1639056447750)(PythonOpenCV基础篇01.assets/image-20211120063056227.png)]
运算结果如图9.28所示,掩模白色区域覆盖的内容与原图像做取反运算的结果一致,掩模黑色区域覆盖的内容保持不变。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sUJ8rnvm-1639056447750)(PythonOpenCV基础篇01.assets/image-20211120063113955.png)]
图9.28 图像异或运算效果
异或运算还有一个特点:执行一次异或运算得到一个结果,再对这个结果执行第二次异或运算,则还原成最初的值。利用这个特点可以实现对图像内容的加密和解密。下面通过一个实例,利用异或运算的特点对图像数据进行加密和解密。
【实例9.9】 对图像进行加密、解密。
利用numpy.random.randint()方法创建一个随机像素值图像作为密钥图像,让密钥图像与原始图像做异或运算得出加密图像,再使用密钥图像对加密图像进行解密,具体代码如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7YvZkJ3C-1639056447751)(PythonOpenCV基础篇01.assets/image-20211120063142267.png)]
上述代码的运行结果如图9.29所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kFZvtAZh-1639056447751)(PythonOpenCV基础篇01.assets/image-20211120063219114.png)]
图9.29 图像加密、解密效果
在处理图像时经常会遇到需要将两幅图像合并成一幅图像,合并图像也分2种情况:①两幅图像融合在一起;②每幅图像提供一部分内容,将这些内容拼接成一幅图像。OpenCV分别用加权和和覆盖两种方式来满足上述需求。本节将分别介绍如何利用代码实现加权和和覆盖效果。
多次曝光技术是指在一幅胶片上拍摄几个影像,最后冲印出的相片同时具有多个影像的信息。
OpenCV通过计算加权和的方式,按照不同的权重取两幅图像的像素之和,最后组成新图像。加权和不会像纯加法运算那样让图像丢失信息,而是在尽量保留原有图像信息的基础上把两幅图像融合到一起。
OpenCV通过addWeighted()方法计算图像的加权和,该方法语法如下:
dst = cv2.addWeighted(src1, alpha, src2, beta, gamma)
参数说明:
src1:第一幅图像。
alpha:第一幅图像的权重。
src2:第二幅图像。
beta:第二幅图像的权重。
gamma:在和结果上添加的标量。该值越大,结果图像越亮,相反则越暗。可以是负数。
返回值说明:
dst:加权和后的图像。
下面通过一个实例演示addWeighted()方法的效果。
【实例9.10】 利用计算加权和的方式实现多次曝光效果。
读取两幅不同的风景照片,使用addWeighted()方法计算两幅图像的加权和,两幅图像的权重都为0.6,标量为0,查看处理之后的图像是否为多次曝光效果,具体代码如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dU3aTOGn-1639056447751)(PythonOpenCV基础篇01.assets/image-20211120063330427.png)]
上述代码的运行结果如图9.30~图9.32所示,可以看出最后得到的图像中同时包含两幅图像的信息。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qzoWre4i-1639056447751)(PythonOpenCV基础篇01.assets/image-20211120063347197.png)]
图9.30 日落图像
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fSDWxuI3-1639056447752)(PythonOpenCV基础篇01.assets/image-20211120063409727.png)]
图9.31 沙滩图像
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2vPd4IMh-1639056447752)(PythonOpenCV基础篇01.assets/image-20211120063438319.png)]
图9.32 两幅图像加权和的结果
覆盖图像就是直接把前景图像显示在背景图像中,前景图像挡住背景图像。覆盖之后背景图像会丢失信息,不会出现加权和那样的“多次曝光”效果。
OpenCV没有提供覆盖操作的方法,开发者可以直接用修改图像像素值的方式实现图像的覆盖、拼接效果:从A图像中取像素值,直接赋值给B图像的像素,这样就能在B图像中看到A图像的信息了。
下面通过一个实例来演示如何从前景图像中抠图,再将抠出的图像覆盖在背景图像中。
【实例9.11】 将小猫图像覆盖到沙滩图像上。
读取小猫原始图像,将原始图像中75~400行、120~260列的像素单独保存成一幅小猫图像,并将小猫图像缩放成70×160大小。读取沙滩图像,将小猫图像覆盖到沙滩图像(100, 200)的坐标位置。覆盖过程中将小猫图像的像素逐个赋值给沙滩图像中对应位置的像素,具体代码如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8uIAqkMC-1639056447752)(PythonOpenCV基础篇01.assets/image-20211120063525987.png)]
运行结果如图9.33所示,沙滩图像中的像素被替换成小猫之后,就可实现类似拼接图像的效果。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HzKSLj8j-1639056447752)(PythonOpenCV基础篇01.assets/image-20211120063608529.png)]
图9.33 覆盖图像效果
如果前景图像是4通道(含alpha通道)图像,就不能使用上面例子中直接替换整个区域的方式覆盖背景图像了。因为前景图像中有透明的像素,透明的像素不应该挡住背景,所以在给背景图像像素赋值时应排除所有透明的前景像素。下面通过一个实例来演示如何在覆盖过程中排除4通道图像的透明区域。
【实例9.12】 拼接禁止吸烟图像。
禁止图像由一个红圈和一个斜杠组成,这个图像是4通道图像,格式为PNG。将禁止图像覆盖到吸烟图像上时要注意:不要把前景图像的透明像素覆盖到背景图像上。覆盖之前要遍历前景图像中的每一个像素,如果像素的alpha通道值为0,表示该像素是透明像素,就要停止操作该像素,实现的具体代码如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VpwA8KlP-1639056447753)(PythonOpenCV基础篇01.assets/image-20211120063635612.png)]
上述代码的运行结果如图9.34~图9.36所示,禁止图像的透明位置没有挡住吸烟图像。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZEeaosfI-1639056447753)(PythonOpenCV基础篇01.assets/image-20211120063725775.png)]
图9.34 禁止图像
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UPb7ZFWF-1639056447754)(PythonOpenCV基础篇01.assets/image-20211120063745746.png)]
图9.35 吸烟图像
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mqudOO1Y-1639056447755)(PythonOpenCV基础篇01.assets/image-20211120063807277.png)]
图9.36 2幅图像拼接之后的禁止吸烟图像
明确关于掩模的3个问题:0和255这2个值在掩模中各自发挥的作用;通过这2个值,掩模的作用又是什么;如何创建一个掩模。掌握了掩模后,就能够利用掩模遮盖图像相加后的结果。掩模除了应用于图像的加法运算外,还应用于图像的位运算。一个掩模应用于图像的位运算的典型实例就是对图像进行加密、解密。本章除了上述内容,还讲解了合并图像的2种方式:加权和、覆盖。