Python OpenCV 基础篇

105 阅读31分钟

绘制图形和文字

  • OpenCV提供了许多绘制图形的方法,包括绘制线段的line()方 法、绘制矩形的rectangle()方法、绘制圆形的circle()方法、绘制多边形 的polylines()方法和绘制文字的putText()方法。

image.png

  • 绘制图形
# 绘制图形
import cv2
import numpy as np
#线段的绘制
'''
img = cv2.line(img,pt1,pt2,color,thickness)
img:画布
pt1:起点坐标
pt2:终点坐标
color:颜色 线条颜色同样是BGR
thickness:线条粗细
'''
canvas1 = np.zeros((300,300,3),np.uint8)#OpenCV中的灰度图像和RGB图像都是以uint8存储的
canvas1 = cv2.line(canvas1,(50,50),(250,50),(255,0,0),5)
canvas1 = cv2.line(canvas1,(50,150),(250,150),(0,255,0),10)
canvas1 = cv2.line(canvas1,(50,250),(250,250),(0,0,255),15)
canvas1 = cv2.line(canvas1,(150,50),(150,250),(0,255,255),20)
cv2.imshow("Lines",canvas1)
cv2.waitKey()
#矩形的绘制
'''
img = cv2.rectangle(img,pt1,pt2,color,thickness)
img:画布
pt1:左上角坐标
pt2:右下角坐标
color:绘制矩形时线条的颜色
thickness:线条粗细
'''
canvas2 = np.zeros((300,300,3),np.uint8)
# canvas2 = cv2.rectangle(canvas2,(50,50),(200,150),(255,255,0),20)#绘制一个矩形边框
canvas2 = cv2.rectangle(canvas2,(50,50),(200,150),(255,255,0),-1)#绘制一个实心矩形,-1表示填充矩形
cv2.imshow("Rectangle",canvas2)
cv2.waitKey()
#绘制正方形
canvas3 = np.zeros((300,300,3),np.uint8)
canvas3 = cv2.rectangle(canvas3,(50,50),(250,250),(0,0,255),40)
canvas3 = cv2.rectangle(canvas3,(90,90),(210,210),(0,255,0),30)
canvas3 = cv2.rectangle(canvas3,(120,120),(180,180),(255,0,0),20)
canvas3 = cv2.rectangle(canvas3,(140,140),(160,160),(0,255,255),-1)
cv2.imshow("Square",canvas3)
cv2.waitKey()
#绘制圆形
'''
img = cv2.circle(img,center,radius,color,thickness)
img:画布
center:圆心坐标
radius:半径
color:颜色
thickness:线条粗细
'''
canvas4 = np.zeros((100,300,3),np.uint8)
canvas4 = cv2.circle(canvas4,(50,50),40,(0,0,255),-1)
canvas4 = cv2.circle(canvas4,(150,50),40,(0,255,255),-1)
canvas4 = cv2.circle(canvas4,(250,50),40,(0,255,0),-1)
cv2.imshow("TrafficLights",canvas4)
cv2.waitKey()
#绘制同心圆
canvas5 = np.zeros((300,300,3),np.uint8)
center_X = int(canvas5.shape[1]/2)#shape[1]表示1画布的宽度,center_X表示圆心的横坐标,圆心的横坐标等于画布宽度的一半
center_Y = int(canvas5.shape[0]/2)#shape[0]表示1画布的高度,center_Y表示圆心的纵坐标,圆心的纵坐标等于画布高度的一半
#r表示半径;其中,r的值分别为0,30,60,90和120
for r in range(0,150,30):
    canvas5 = cv2.circle(canvas5,(center_X,center_Y),r,(0,255,0),5)
cv2.imshow("Circles",canvas5)
cv2.waitKey()
#绘制27个随机实心圆
canvas6 = np.zeros((300,300,3),np.uint8)
for numbers in range(0,28):
    center_x = np.random.randint(0,high=300)
    center_y = np.random.randint(0,high=300)
    radius = np.random.randint(11,high=71)
    color = np.random.randint(0,high=256,size=(3,)).tolist()#OpenCV颜色值是一个列表,tolist()不能省略
    cv2.circle(canvas6,(center_x,center_y),radius,color,-1)
    cv2.imshow("RandomCircles",canvas6)
cv2.waitKey()
#多边形的绘制
'''
img = cv2.polylines(img,pts,isClosed,color,thickness)
img:画布
pts:由多边形的各个顶点组成的列表,这个列表是一个numpy的数组类型
isClosed:如果值为True,表示一个闭合的多边形;如果值为False,表示一个不闭合的多边形
color:颜色
thickness:线条粗细
'''
#绘制一个等腰梯形边框
canvas7 = np.zeros((300,300,3),np.uint8)
'''
按顺时针给出等腰梯形4个顶点坐标,一定要按顺时针或逆时针
这四个顶点的坐标构成了一个大小等于“顶点个数*1*2”的数组
这个数组的数据类型为np.int32
'''
pts = np.array([[100,50],[200,50],[250,250],[50,250]],np.int32)
#在画布上根据4个顶点的坐标,绘制一个闭合的、红色的、粗细为5的等腰梯形边框
canvas7 = cv2.polylines(canvas7,[pts],True,(0,0,255),5)
cv2.imshow("Trapezoid",canvas7)
cv2.waitKey()
cv2.destroyAllWindows()
  • 绘制文字
# 绘制文字
import cv2
import numpy as np
'''
img = cv2.putText(img,text,org,fontFace,fontScale,color,thickness,lineType,bottomLeftOrigin)
img:画布
text:要显示的文字
org:文字左下角的坐标
fontFace:字体样式,如cv2.FONT_HERSHEY_SIMPLEX
fontScale:字体大小
color:字体颜色
thickness:字体粗细
lineType:线条类型
bottomLeftOrigin:是否以左下角为原点,默认是False,即以左上角为原点
'''
canvas1 = np.zeros((600,600,3),np.uint8)
cv2.putText(canvas1,"Hello World!",(20,70),cv2.FONT_HERSHEY_TRIPLEX,2,(0,255,0),5)
#文字的斜体效果
fontStyle = cv2.FONT_HERSHEY_TRIPLEX + cv2.FONT_ITALIC
cv2.putText(canvas1,"Hello World!",(20,210),fontStyle,2,(0,255,0),5)
#文字的垂直镜像效果
fontStyle1 = cv2.FONT_HERSHEY_TRIPLEX
cv2.putText(canvas1,"Hello World!",(20,80),fontStyle1,2,(0,255,0),5,8,True)
cv2.imshow("Text",canvas1)
cv2.waitKey()

#在图像上绘制文字
image = cv2.imread("elysia.png")
cv2.putText(image,"Elysia",(150,100),fontStyle1,3,(0,255,255))
cv2.imshow("ImageText",image)
cv2.waitKey()
cv2.destroyAllWindows()
  • 动态绘制图形

在一个宽、高都为200像素的纯白色图像中,绘制一个半径为20像 素的纯蓝色小球。让小球做匀速直线运动,一旦圆形碰触到图像边界 则开始反弹(反弹不损失动能)。想要实现这个功能需要解决两个问 题:如何计算运动轨迹和如何实现动画。下面分别介绍这两个问题的 解决思路。小球在运动的过程中可以把移动速度划分为上、下、左、右4个方 向。左右为横坐标移动速度,上下为纵坐标移动速度。小球向右移动 时横坐标不断变大,向左移动时横坐标不断变小,由此可以认为:小 球向右的移动速度为正,向左的移动速度为负。纵坐标同理,因为图 像坐标系的原点为背景左上角顶点,越往下延伸纵坐标越大,所以小 球向上的移动速度为负,向下的移动速度为正。

#动态绘制图形
import cv2
import numpy as np
import time
#弹球动画
'''
time.sleep(seconds)
seconds:休眠时间,单位为s,可以是小数
'''
width,heigth=200,200#画面的宽高
r = 20#圆半径
x = r+20#圆心横坐标起始坐标
y = r+100#圆心纵坐标起始坐标
x_offer = y_offer = 4#每一帧的移动速度

while cv2.waitKey(1) == -1:
    if x > width-r or x < r:#如果圆的横坐标超出画面范围,则反方向移动
        x_offer *= -1#横坐标速度取相反值
    if y > heigth-r or y < r:#如果圆的纵坐标超出画面范围,则反方向移动
        y_offer *= -1#纵坐标速度取相反值
    x += x_offer#圆心横坐标移动
    y += y_offer#圆心纵坐标移动
    img = np.zeros((width,heigth,3),np.uint8)*255
    cv2.circle(img,(x,y),r,(255,0,0),-1)
    cv2.imshow('img',img)
    time.sleep(1/60)#休眠1/60s,每秒60帧
cv2.destroyAllWindows()
  • 小结

不论是绘制图形,还是绘制文字,都需要创建画布,这个画布可 以是一幅图像。需要确定线条的颜色时,要特别注意颜色的表示方 式,即(B, G, R)。当绘制矩形、圆形和多边形时,通过设置线条宽 度,既可以绘制图形的边框,又可以绘制被填充的图形。但是,在绘 制多边形的过程中,要按照顺时针或者逆时针的方向,标记多边形各 个顶点的坐标。此外,OpenCV提供的用于绘制图形的方法,不仅可 以绘制静态的图形,还可以绘制动态的图形。

图像的几何变换

# 图像的几何变换
import cv2
import numpy as np
#缩放
'''
dst = cv2.resize(src,dsize,fx,fy,interpolation)
src: 原始图像
dsize: 输出图像的大小,格式为(width,height),单位为像素
fx: 水平方向的缩放因子
fy: 垂直方向的缩放因子
interpolation: 插值方法,默认为cv2.INTER_LINEAR,可选cv2.INTER_CUBIC、cv2.INTER_AREA、cv2.INTER_NEAREST

dst: 缩放后的图像
'''
#dsize参数实现缩放
img = cv2.imread("elysia.png")
dst1 = cv2.resize(img,(250,140))
dst2 = cv2.resize(img,(500,280))
cv2.imshow("original",img)
cv2.imshow("dst1",dst1)
cv2.imshow("dst2",dst2)
cv2.waitKey(0)
#fx和fy参数实现缩放
'''
新图像宽度 = round(fx * 原图像宽度)
新图像高度 = round(fy * 原图像高度)
'''
dst3 = cv2.resize(img,None,fx=1/3,fy=1/2)
dst4 = cv2.resize(img,None,fx=4/3,fy=4/3)
cv2.imshow("dst3",dst3)
cv2.imshow("dst4",dst4)
cv2.waitKey()

#翻转
'''
dst = cv2.flip(src,filpCode)
src: 原始图像
flipCode: 翻转方向,取值为0、1、2、3,分别表示沿x轴、y轴、x轴+y轴、x轴-y轴进行翻转
dst: 翻转后的图像
'''
image = cv2.imread("ely.png")
dst5 = cv2.flip(image,0)
dst6 = cv2.flip(image,1)
dst7 = cv2.flip(image,2)
dst8 = cv2.flip(image,3)
dst9 = cv2.flip(image,-1)
cv2.imshow("original",image)
cv2.imshow("dst5",dst5)
cv2.imshow("dst6",dst6)
cv2.imshow("dst7",dst7)
cv2.imshow("dst8",dst8)
cv2.imshow("dst9",dst9)
cv2.waitKey()

#仿射变换
'''
dst = cv2.warpAffine(src,M,dsize,flags,borderMode,borderValue)
src: 原始图像
M: 仿射变换矩阵,2x3的矩阵 M = [[a,b,c],[d,e,f]]
    新x = 原x*a + 原y*b + c
    新y = 原x*d + 原y*e + f
dsize: 输出图像的大小,格式为(width,height),单位为像素
flags: 插值方式,默认为cv2.INTER_LINEAR,可选cv2.INTER_CUBIC、cv2.INTER_AREA、cv2.INTER_NEAREST
borderMode: 边界填充方式,默认为cv2.BORDER_CONSTANT,可选cv2.BORDER_REPLICATE、cv2.BORDER_REFLECT、cv2.BORDER_WRAP
borderValue: 边界填充值,默认为0
dst: 变换后的图像

M矩阵中的数字采用32位浮点格式
创建一个全0得数组
M = np.zeros((2,3),np.float32)
创建M的同时赋予具体值
M = np.float32([[1,2,3],[4,5,6]])
'''
#平移
'''
M = [[1,0,水平移动的距离],[0,1,垂直移动的距离]]
新x = 原x*1 + 原y*0 + 水平移动的距离 = 原x + 水平移动的距离
新y = 原x*0 + 原y*1 + 垂直移动的距离 = 原y + 垂直移动的距离
'''
rows = len(image)
cols = len(image[0])
M = np.float32([[1,0,50],[0,1,100]])
dst10 = cv2.warpAffine(image,M,(cols,rows))
cv2.imshow("dst10",dst10)
cv2.waitKey()
#旋转
'''
M = cv2.getRotationMatrix2D(center,angle,scale)
center: 旋转中心,格式为(x,y)
angle: 旋转角度(非弧度制),正数表示逆时针旋转,负数表示顺时针旋转
scale: 缩放比例,浮点类型。1表示不缩放
M: getRotationMatrix2D函数返回的仿射矩阵
'''
rows1 = len(image)
cols1 = len(image[0])
center = (rows1/2,rows1/2)
M = cv2.getRotationMatrix2D(center,30,0.8)
dst11 = cv2.warpAffine(image,M,(cols1,rows1))
cv2.imshow("dst11",dst11)
cv2.waitKey()
#倾斜
'''
M = cv2.getAffineTransform(src,dst)
src:原图像3个点坐标,格式为3行2列的32位浮点列表,[[x1,y1],[x2,y2],[x3,y3]]
dst:倾斜图像的3个点坐标,格式与src相同
M: getAffineTransform()方法返回的仿射矩阵
'''
rows2 = len(image)#图像的行数
cols2 = len(image[0])#图像的列数
p1 = np.zeros((3,2),np.float32)#原图像3个点坐标
p1[0] = [0,0]#原图像左上角
p1[1] = [cols2-1,0]#原图像右上角
p1[2] = [0,rows2-1]#原图像左下角
p2 = np.zeros((3,2),np.float32)#倾斜图像3个点坐标
p2[0] = [50,0]#倾斜图像左上角
p2[1] = [cols2-1,0]#倾斜图像右上角
p2[2] = [0,rows2-1]#倾斜图像左下角
M = cv2.getAffineTransform(p1,p2)#获取仿射矩阵
dst12 = cv2.warpAffine(image,M,(cols2,rows2))#进行仿射变换
cv2.imshow("dst12",dst12)
cv2.waitKey()
#透视变换
'''
dst = cv2.warpPerspective(src,M,dsize,flags,borderMode,borderValue)
src: 原始图像
M: 透视变换矩阵,3x3的矩阵 M = [[a,b,c],[d,e,f],[g,h,i]]
    新x = 原x*a + 原y*b + c + 原z*g
    新y = 原x*d + 原y*e + f + 原z*h
    新z = 原x*g + 原y*h + i + 原z*i
dsize: 输出图像的大小,格式为(width,height),单位为像素
flags: 插值方式,默认为cv2.INTER_LINEAR,可选cv2.INTER_CUBIC、cv2.INTER_AREA、cv2.INTER_NEAREST
borderMode: 边界填充方式,默认为cv2.BORDER_CONSTANT,可选cv2.BORDER_REPLICATE、cv2.BORDER_REFLECT、cv2.BORDER_WRAP
borderValue: 边界填充值,默认为0
dst: 变换后的图像

warpPerspective()方法也需要通过M矩阵计算透视效果,但得出这
个 矩 阵 需 要 做 很 复 杂 的 运 算 , 于 是 OpenCV 提 供 了
getPerspectiveTransform() 方 法 自 动 计 算 M 矩 阵 。
M = cv2.getPerspectiveTransform(src,dst)
src:原图像4个点坐标,格式为4行2列的32位浮点列表,[[x1,y1],[x2,y2],[x3,y3],[x4,y4]]
dst:透视图像的4个点坐标,格式与src相同
M: getPerspectiveTransform()方法返回的透视矩阵
'''
rows3 = len(image)#图像的行数
cols3 = len(image[0])#图像的列数
p1 = np.zeros((4,2),np.float32)#原图像4个点坐标
p1[0] = [0,0]#原图像左上角
p1[1] = [cols3-1,0]#原图像右上角
p1[2] = [0,rows3-1]#原图像左下角
p1[3] = [cols3-1,rows3-1]#原图像右下角
p2 = np.zeros((4,2),np.float32)#透视图像4个点坐标
p2[0] = [90,0]#透视图像左上角
p2[1] = [cols3-90,0]#透视图像右上角
p2[2] = [0,rows3-1]#透视图像左下角
p2[3] = [cols3-1,rows3-1]#透视图像右下角
M = cv2.getPerspectiveTransform(p1,p2)#获取透视矩阵
dst13 = cv2.warpPerspective(image,M,(cols3,rows3))#进行透视变换
cv2.imshow("dst13",dst13)
cv2.waitKey()
cv2.destroyAllWindows()
  • 小结

图像的缩放有2种方式:一种是设置dsize参数,另一种是设置fx参 数和fy参数。图像的翻转有3种方式,沿X轴翻转、沿Y轴翻转和同时沿 X轴、Y轴翻转,这3种方式均由flipCode参数的值决定。图像的仿射变 换取决于仿射矩阵,采用不同的仿射矩阵(M),就会使图像呈现不 同的仿射效果。此外,图像的透视仍然要依靠M矩阵实现。因此,读 者只要熟练掌握并灵活运用M矩阵,就能够得心应手地对图像进行几 何变换操作。

图像的阈值处理

  • 阈值

阈值是图像处理中一个很重要的概念,类似一个“像素值的标准 线”。所有像素值都与这条“标准线”进行比较,最后得到3种结果:像 素值比阈值大、像素值比阈值小或像素值等于阈值。程序根据这些结 果将所有像素进行分组,然后对某一组像素进行“加深”或“变淡”操 作,使得整个图像的轮廓更加鲜明,更容易被计算机或肉眼识别。

image.png

在图像处理的过程中,阈值的使用使得图像的像素值更单一,进 而使得图像的效果更简单。首先,把一幅彩色图像转换为灰度图像, 这样图像的像素值的取值范围即可简化为0~255。然后,通过阈值使 得转换后的灰度图像呈现出只有纯黑色和纯白色的视觉效果。例如, 当阈值为127时,把小于127的所有像素值都转换为0(即纯黑色),把 大于127的所有像素值都转换为255(即纯白色)。虽然会丢失一些灰 度细节,但是会更明显地保留灰度图像主体的轮廓。

  • 阈值处理类型

image.png

  • 二值化处理

二值化处理也叫二值化阈值处理,该处理让图像仅保留两种像素值,或者说所有像素都只能从两种值中取值。通常二值化处理是使用255作为最大值,因为灰度图像中255表示纯白色,能够很清晰地与纯黑色进行区分,所以灰度图像经过二值化 处理后呈现“非黑即白”的效果。

  • 反二值化处理

反二值化处理也叫反二值化阈值处理,其结果为二值化处理的相 反结果。将大于阈值的像素值变为0,将小于或等于阈值的像素值变为 最大值。原图像中白色的部分变成黑色,黑色的部分变成白色。

  • 零处理

零处理会将某一个范围内的像素值变为0,并允许范围之外的像素 保留原值。零处理包括低于阈值零处理和超出阈值零处理。

  • 截断处理

截断处理也叫截断阈值处理,该处理将图像中大于阈值的像素值 变为和阈值一样的值,小于或等于阈值的像素保持原值。

  • 自适应处理

OpenCV提供了一种改进的阈值处理技术:图像中的不同区域使 用不同的阈值。把这种改进的阈值处理技术称作自适应阈值处理也称 自适应处理,自适应阈值是根据图像中某一正方形区域内的所有像素 值按照指定的算法计算得到的。与前面讲解的5种阈值处理类型相比,自适应处理能更好地处理明暗分布不均的图像,获得更简单的图像效果。

注意:使用自适应阈值处理图像时,如果图像是彩色图像,那么需要先将彩色图像转换为灰度图像。

  • Otsu方法

逐个寻找最合适的阈值不仅工作量大,而且效率低。为此, OpenCV提供了Otsu方法。Otsu方法能够遍历所有可能的阈值,从中找 到最合适的阈值。 Otsu方法的语法与threshold()方法的语法基本一致,只不过在为 type 传 递 参 数 时 , 要 多 传 递 一 个 参 数 , 即 cv2.THRESH_OTSU 。 cv2.THRESH_OTSU的作用就是实现Otsu方法的阈值处理。

  • 阈值处理的作用

二值化处理后,图片只有纯黑和纯白两种颜色,图像中的边缘变得更加鲜明,更容易被识别。颜色较深,所以大面积被涂黑,形成了鲜明的反差。二值化处理后的局部图像在肉眼看来可能还不够明显,但反二值化处理后的局部图像轮廓与地面的反差就非常大。高级图像识别算法可以根据这种鲜明的像素变化来搜寻特征,最后达到识别物体分类的目的。

  • 小结

OpenCV提供了一个可以快速抠出图像主体线条的工具,这个工 具就是阈值。在阈值的作用下,一幅彩色图像被转换为只有纯黑和纯 白的二值图像。然而,灰度图像经5种阈值处理类型处理后,都无法得 到图像主体的线条。为此,OpenCV提供了一种改进的阈值处理技 术,即自适应处理,其关键在于对图像中的不同区域使用不同的阈 值。有了这种改进的阈值处理技术,得到图像主体的线条就不再是一件难以实现的事情了。

# 图像的阈值处理
import cv2
#阈值处理函数
'''
retval,dst = cv2.threshold(src,thresh,maxval,type)
src:被处理的图像,可以是多通道图像
thresh:阈值,阈值在125~150取值的效果最好
maxval:最大值,图像的像素值大于thresh时,被设为maxval,否则被设为0
type:阈值处理类型
retval:处理时采用的阈值
dst:处理后的图像
'''
# 1. 二值化处理
'''
if 像素值 <= 阈值: 像素值 = 0
if 像素值 > 阈值: 像素值 = 最大值(255)
'''
img = cv2.imread("elysia_gray.png",0)#读取灰度图或彩图,参数0表示读取为灰度图
t1,dst1 = cv2.threshold(img,172,255,cv2.THRESH_BINARY)#二值化处理
cv2.imshow("img",img)
cv2.imshow("THRESH_BINARY1",dst1)
cv2.waitKey()

t2,dst2 = cv2.threshold(img,127,255,cv2.THRESH_BINARY)
t3,dst3 = cv2.threshold(img,210,255,cv2.THRESH_BINARY)
# t3,dst3 = cv2.threshold(img,127,150,cv2.THRESH_BINARY)#呈黑色和灰色
cv2.imshow("THRESH_BINARY2",dst2)
cv2.imshow("THRESH_BINARY3",dst3)
cv2.waitKey()

# 2. 反二值化处理
'''
if 像素值 <= 阈值: 像素值 = 最大值
if 像素值 > 阈值: 像素值 = 0
'''
t4,dst4 = cv2.threshold(img,127,255,cv2.THRESH_BINARY_INV)#反二值化处理
cv2.imshow("THRESH_BINARY_INV",dst4)
cv2.waitKey()

# 3. 零值处理
# 低于阈值零处理
'''
if 像素值 <= 阈值: 像素值 = 0
if 像素值 > 阈值: 像素值 = 原像素值
'''
t5,dst5 = cv2.threshold(img,127,255,cv2.THRESH_TOZERO)#低于阈值零处理
cv2.imshow("THRESH_TOZERO",dst5)
cv2.waitKey()
# 高于阈值零处理
'''
if 像素值 <= 阈值: 像素值 = 原像素值
if 像素值 > 阈值: 像素值 = 0
'''
t6,dst6 = cv2.threshold(img,127,255,cv2.THRESH_TOZERO_INV)#超出阈值零处理
cv2.imshow("THRESH_TOZERO_INV",dst6)
cv2.waitKey()

# 4. 截断处理
# 图像经过截断处理后,整体颜色都会变暗。彩色图像经过截断处理后,在降低亮度的同时还会让浅颜色区域的颜色变得更浅
t7,dst7 = cv2.threshold(img,127,255,cv2.THRESH_TRUNC)#截断处理
cv2.imshow("THRESH_TRUNC",dst7)
cv2.waitKey()

# 5. 自适应处理
'''
dst = cv2.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C)
src:被处理的图像,是灰度图像
maxValue:最大值,图像的像素值大于阈值时,被设为maxValue,否则被设为0
adaptiveMethod:自适应方法,有三种:cv2.ADAPTIVE_THRESH_MEAN_C、cv2.ADAPTIVE_THRESH_GAUSSIAN_C、cv2.ADAPTIVE_THRESH_MEAN_C+cv2.THRESH_BINARY
thresholdType:阈值类型,有两种:cv2.THRESH_BINARY、cv2.THRESH_BINARY_INV
blockSize:邻域大小,一般取3、5、7
C:常量,一般取0。阈值等于均值或者加权值减去这个常量。
dst:阈值处理后的图像
'''
athdMEAM = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY,5,3)
athdGAUSS = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY,5,3)
cv2.imshow("ADAPTIVE_THRESH_MEAN_C",athdMEAM)
cv2.imshow("ADAPTIVE_THRESH_GAUSSIAN_C",athdGAUSS)
cv2.waitKey()

# 6. Otsu方法
'''
Otsu方法是一种基于最大类间方差的阈值确定方法,遍历寻找最合适的阈值
Otsu方法的语法与threshold()方法的语法基本一致,只不过在为type传递参数时,要多传递一个参数,即cv2.THRESH_OTSU。
retval,dst = cv2.threshold(src,thresh,maxval,type)
src:被处理的图像,是灰度图像
thresh:阈值,且要把阈值设置为0。
maxval:最大值,图像的像素值大于阈值时,被设为maxval,否则被设为0
type:阈值处理类型,除选择一种阈值处理方法外,还要多传递Otsu方法,即cv2.THRESH_BINARY+cv2.THRESH_OTSU。
retval:由Otsu方法计算得到并使用的最合适的阈值
dst:阈值处理后的图像
'''
t8,dst8 = cv2.threshold(img,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
# 显示最佳阈值
cv2.putText(dst8,"best threshold = "+str(t8),(0,30),cv2.FONT_HERSHEY_SIMPLEX,1,(0,0,0),2)
cv2.imshow("THRESH_BINARY+cv2.THRESH_OTSU",dst8)
cv2.waitKey()
cv2.destroyAllWindows()

图像的运算

  • 掩膜

掩模,也叫作掩码,英文为mask,在程序中用二值图像来表示: 0值(纯黑)区域表示被遮盖的部分,255值(纯白)区域表示暴露的 部分(某些场景下也会用0和1当作掩模的值)。

  • 图像加法

图像中每一个像素都有用整数表示的像素值,2幅图像相加就是让相同位置像素值相加,最后将计算结果按照原位置重新组成一幅新图像。“+”运算符 的计算结果如果超出了255,就会取相加和除以255的余数,也就是取模运算,像素值相加后反而变得更小,由浅色变成了深色;而add()方法的计算结果如果超过了255,就取值255,很多浅颜色像素彻底变成了纯白色。

  • 图像减法

减法运算符(-

当使用Python的减法运算符(-)对两幅图像的相同位置像素进行减法运算时,结果将直接计算两像素值之差。如果结果小于0,它将被截断为0(因为像素值不能为负)。这意味着,如果第一幅图像的某个像素值小于第二幅图像对应位置的像素值,那么结果图像在该位置将显示为黑色(或接近黑色的深色,取决于差异的大小)。如果结果大于255,则它实际上会被截断为255,但这在8位图像中通常不会发生,因为两个有效像素值(0-255)之间的差不会超过255。

cv2.subtract() 函数

与减法运算符不同,cv2.subtract() 函数在执行减法时,如果结果小于0,则不会简单地将结果截断为0,而是允许结果保持为负数(在内部,这些值可能以某种方式存储,但在显示时,负值通常会被解释为0或接近0的深色)。然而,在将结果图像转换为8位无符号整数(这是最常见的存储方式)时,任何小于0的值都将被截断为0。如果结果大于255,则cv2.subtract() 也会将其截断为255(尽管这在常规8位图像减法中不常见)。但是,重要的是要注意,在大多数情况下,由于像素值的限制,这种截断到255的情况不会发生。

# 图像的运算
import cv2
import numpy as np
# 掩膜
# 创建3通道掩膜图像
mask = np.zeros((500, 500, 3), np.uint8)
mask[50:100,20:80,:] = 255
cv2.imshow("mask1", mask)
mask[:,:,:] = 255
mask[50:100,20:80,:] = 0
cv2.imshow("mask2", mask)
cv2.waitKey()

# 图像的加法
'''
dst = cv2.add(src1,src2,mask,dtype)
src1:第一幅图
src2:第二幅图
mask:掩膜
dtype:图像深度
dst:相加之后的图像
'''
img = cv2.imread("ely.png")
sum1 = img + img# 使用“+”运算符相加
sum2 = cv2.add(img,img)# 使用cv2.add()函数相加
cv2.imshow("img", img)
cv2.imshow("sum1", sum1)
cv2.imshow("sum2", sum2)
cv2.waitKey()
'''
从结果可以看出,“+”运算符
的计算结果如果超出了255,就会取相加和除以255的余数,也就是取
模运算,像素值相加后反而变得更小,由浅色变成了深色;而add()方
法的计算结果如果超过了255,就取值255,很多浅颜色像素彻底变成
了纯白色。
'''
# 模拟三色光叠加得白光
img1 = np.zeros((150,150,3),np.uint8)
img1[:,:,0] = 255
img2 = np.zeros((150,150,3),np.uint8)
img2[:,:,1] = 255
img3 = np.zeros((150,150,3),np.uint8)
img3[:,:,2] = 255
cv2.imshow("1", img1)
cv2.imshow("2", img2)
cv2.imshow("3", img3)
cv2.waitKey()
img4 = cv2.add(img1,img2)
cv2.imshow("1+2", img4)
img5 = cv2.add(img4,img3)
cv2.imshow("1+2+3", img5)
cv2.waitKey()
# 利用掩膜遮盖相加结果
img6 = cv2.add(img1,img3)# 相加前先将img1和img3叠加,无掩膜
cv2.imshow("no mask",img6)

m = np.zeros((150,150,1),np.uint8)# 创建掩膜图像
m[50:100,50:100,:]=255
cv2.imshow("mask",m)

img6M = cv2.add(img1,img3,mask=m)# 利用掩膜进行相加
cv2.imshow("use mask",img6M)
cv2.waitKey()

# 图像的减法
'''
dst = cv2.subtract(src1,src2,mask,dtype)
src1:第一幅图
src2:第二幅图
mask:掩膜
dtype:图像深度
dst:相减之后的图像
'''
diff1 = img6 - img6M# 使用“-”运算符相减
diff2 = cv2.subtract(img6,img6M)# 使用cv2.subtract()函数相减
cv2.imshow("diff1", diff1)
cv2.imshow("diff2", diff2)
cv2.waitKey()
'''
图像减法的结果与图像相加的结果相反,图像减法的结果是
图像的亮度减小,图像的暗部变得更暗,图像的亮部变得更亮。
OpenCV图像减法在图像处理和分析中具有广泛的应用,它不仅
可以检测图像变化、运动目标检测与跟踪、图像背景的消除和混
合图像的分离,还可以用于噪声提取等方面。这些功能使得图像
减法成为图像处理领域不可或缺的一部分。
'''
cv2.destroyAllWindows()

图像的位运算

位运算是二进制数特有的运算操作。图像由像素组成,每个像素 可以用十进制整数表示,十进制整数又可以转化为二进制数,所以图像也可以做位运算,并且位运算是图像数字化技术中一项重要的运算操作。

  • 常见位运算方法

image.png

  • 按位与

与运算有两个特点。

  1. 如果某像素与纯白像素做与运算,结果仍然是某像素的原 值,计算过程如下:

00101011 & 11111111 = 00101011

  1. 如果某像素与纯黑像素做与运算,结果为纯黑像素,计算过 程如下:

00101011 & 00000000 = 00000000

由此可以得出:如果原图像与掩模进行与运算,原图像仅保留掩 模中白色区域覆盖的内容,其他区域全部变成黑色。

  • 按位或

或运算有以下两个特点。

  1. 如果某像素与纯白像素做或运算,结果为纯白像素,计算过 程如下:

00101011 | 11111111 = 11111111

  1. 如果某像素与纯黑像素做或运算,结果仍然是某像素的原 值,计算过程如下:

00101011 | 00000000 = 00101011

由此可以得出:如果原图像与掩模进行或运算,原图像仅保留掩 模中黑色区域覆盖的内容,其他区域全部变成白色。

  • 按位取反

image.png

  • 按位异或

image.png

异或运算有两个特点。

  1. 如果某像素与纯白像素做异或运算,结果为原像素的取反结 果,计算过程如下:

00101011 ^ 11111111 = 11010100

  1. 如果某像素与纯黑像素做异或运算,结果仍然是某像素的原 值,计算过程如下:

00101011 ^ 00000000 = 00101011

由此可以得出:如果原图像与掩模进行异或运算,掩模白色区域 覆盖的内容呈现取反效果,黑色区域覆盖的内容保持不变。

# 图像的位运算
import cv2
import numpy as np
#与运算
'''
与运算就是按照二进制位进行判断,如果同一位的数字都是1,
则运算结果的相同位数字取1,否则取0。
dst = cv2.bitwise_and(src1,src2,mask)
src1:第一幅图像
src2:第二幅图像
mask:掩膜
dst:与运算之后的图像
'''
ely = cv2.imread("ely.png")
mask = np.zeros(ely.shape,np.uint8)
mask[120:180,:,:] = 255
mask[:,80:180,:] = 255
img1 = cv2.bitwise_and(ely,mask)
cv2.imshow("ely",ely)
cv2.imshow("mask",mask)
cv2.imshow("img1",img1)
cv2.waitKey()
'''
由此可以得出:如果原图像与掩模进行与运算,原图像仅保留掩
模中白色区域覆盖的内容,其他区域全部变成黑色。
'''
#按位或运算
'''
或运算也是按照二进制位进行判断,如果同一位的数字都是0,则
运算结果的相同位数字取0,否则取1。
dst = cv2.bitwise_or(src1,src2,mask)
src1:第一幅图像
src2:第二幅图像
mask:掩膜
dst:或运算之后的图像
'''
img2 = cv2.bitwise_or(ely,mask)
cv2.imshow("img2",img2)
cv2.waitKey()
'''
由此可以得出:如果原图像与掩模进行或运算,原图像仅保留掩
模中黑色区域覆盖的内容,其他区域全部变成白色。
'''
#按位取反运算
'''
取反运算是一种单目运算,仅需一个数字参与运算就可以得出结
果。取反运算也是按照二进制位进行判断,如果运算数某位上数字是
0,则运算结果的相同位的数字就取1,如果这一位的数字是1,则运算
结果的相同位的数字就取0。
dst = cv2.bitwise_not(src,mask)
src:参与运算的图像
mask:掩膜
dst:取反运算之后的图像
'''
img3 = cv2.bitwise_not(ely)
cv2.imshow("img3",img3)
cv2.waitKey()
'''
图像经过取反运算后呈现与原图颜色完全相反的效果。
'''
#按位异或运算
'''
异或运算也是按照二进制位进行判断,如果两个运算数同一位上
的数字相同,则运算结果的相同位数字取0,否则取1。
dst = cv2.bitwise_xor(src,mask)
src:参与运算的图像
mask:掩膜
dst:异或运算之后的图像
'''
img4 = cv2.bitwise_xor(ely,mask)
cv2.imshow("img4",img4)
cv2.waitKey()
'''
由此可以得出:如果原图像与掩模进行异或运算,掩模白色区域
覆盖的内容呈现取反效果,黑色区域覆盖的内容保持不变。
'''
#对图像进行加密解密
'''
利用numpy.random.randint()方法创建一个随机像素值图像作为密
钥图像,让密钥图像与原始图像做异或运算得出加密图像,再使用密
钥图像对加密图像进行解密。
'''
def encode(img,img_key):#加密、解密方法
    result = img = cv2.bitwise_xor(img,img_key)#两图像做异或运算
    return result
ely = cv2.imread("ely.png")
rows,colmns,channel = ely.shape#获取原图像的行列数和通道数
#创建与原图像大小相同的随机像素值图像作为密钥图像
img_key = np.random.randint(0,256,(rows,colmns,channel),np.uint8)
cv2.imshow("img_key",img_key)
result = encode(ely,img_key)#加密
cv2.imshow("encryption",result)
result = encode(result,img_key)#解密
cv2.imshow("decrypt",result)
cv2.waitKey()
cv2.destroyAllWindows()

合并运算

在处理图像时经常会遇到需要将两幅图像合并成一幅图像,合并 图像也分2种情况:①两幅图像融合在一起;②每幅图像提供一部分内 容,将这些内容拼接成一幅图像。OpenCV分别用加权和和覆盖两种 方式来满足上述需求。

  • 加权和

多次曝光技术是指在一幅胶片上拍摄几个影像,最后冲印出的相 片同时具有多个影像的信息。 OpenCV通过计算加权和的方式,按照不同的权重取两幅图像的 像素之和,最后组成新图像。加权和不会像纯加法运算那样让图像丢失信息,而是在尽量保留原有图像信息的基础上把两幅图像融合到一 起。 OpenCV通过addWeighted()方法计算图像的加权和。

  • 覆盖

覆盖图像就是直接把前景图像显示在背景图像中,前景图像挡住 背景图像。覆盖之后背景图像会丢失信息,不会出现加权和那样的“多 次曝光”效果。 OpenCV没有提供覆盖操作的方法,开发者可以直接用修改图像 像素值的方式实现图像的覆盖、拼接效果:从A图像中取像素值,直 接赋值给B图像的像素,这样就能在B图像中看到A图像的信息了。

# 合并图像
import cv2
import numpy as np
# 加权和
'''
dst = cv2.addWeighted(src1,alpha,src2,beta,gamma)
src1:第一幅图像
alpha:第一幅图像的权重
src2:第二幅图像
beta:第二幅图像的权重
gamma:在和结果上添加的标量。该值越大,结果图像越亮,相反则越暗。可以是负数。
dst: 加权和后的图像
'''
ely = cv2.imread("ely.png")
elysia = cv2.imread("elysia.png")
rows,colmns,channel = elysia.shape
ely = cv2.resize(ely,(colmns,rows))
img = cv2.addWeighted(ely,0.6,elysia,0.6,0)
cv2.imshow("addWeighted",img)
cv2.waitKey()
# 覆盖
'''
覆盖图像就是直接把前景图像显示在背景图像中,前景图像挡住
背景图像。覆盖之后背景图像会丢失信息,不会出现加权和那样的“多
次曝光”效果。
OpenCV没有提供覆盖操作的方法,开发者可以直接用修改图像
像素值的方式实现图像的覆盖、拼接效果:从A图像中取像素值,直
接赋值给B图像的像素,这样就能在B图像中看到A图像的信息了。
'''
newely = ely[:,100:600,:]
cv2.imshow("newely1",newely)
newely = cv2.resize(newely,(300,300))
cv2.imshow("newely2",newely)
rows1,colmns1,channel1 = newely.shape
# 将沙滩中一部分像素改成截取之后的图像
elysia[100:100+rows1,260:260+colmns1,:] = newely
cv2.imshow("elysia",elysia)
cv2.waitKey()
def overlay_img(img,img_over,img_over_x,img_over_y):
    img_h,img_w,img_c = img.shape             # 背景图像宽、高、通道数
    img_over_h,img_over_w,img_over_c = img_over.shape#覆盖图像宽、高、通道数
    if img_over_c <= 3:                       # 通道数小于等于3,直接覆盖
        img_over = cv2.cvtColor(img_over,cv2.COLOR_BGR2BGRA)# 转换成4通道图像
    for w in range(0,img_over_w):            # 遍历列
        for h in range(0,img_over_h):         # 遍历行
            if img_over[h,w,3]!= 0:         # 透明度不为0,覆盖
                for c in range(0,3):         # 遍历3个通道
                    x = img_over_x + w          # 覆盖像素的横坐标
                    y = img_over_y + h          # 覆盖像素的纵坐标
                    if x >= img_w or y >= img_h:# 如果坐标超出最大宽、高
                        break
                    img[y,x,c] = img_over[h,w,c]# 赋值覆盖像素值
    return img
elysia = cv2.imread("elysia.png",cv2.IMREAD_UNCHANGED)
ely = cv2.imread("ely.png",cv2.IMREAD_UNCHANGED)
img_cover = overlay_img(elysia,ely,95,90)
cv2.imshow("img_cover",img_cover)
cv2.waitKey()
cv2.destroyAllWindows()

注意:如果前景图像是4通道(含alpha通道)图像,就不能使用上面例 子中直接替换整个区域的方式覆盖背景图像了。因为前景图像中有透 明的像素,透明的像素不应该挡住背景,所以在给背景图像像素赋值 时应排除所有透明的前景像素。

  • 小结

读者朋友要明确关于掩模的3个问题:0和255这2个值在掩模中各 自发挥的作用;通过这2个值,掩模的作用又是什么;如何创建一个掩 模。掌握了掩模后,就能够利用掩模遮盖图像相加后的结果。掩模除 了应用于图像的加法运算外,还应用于图像的位运算。一个掩模应用 于图像的位运算的典型实例就是对图像进行加密、解密。本章除了上 述内容,还讲解了合并图像的2种方式:加权和、覆盖。读者朋友要熟 练掌握这两种方式。

以上内容摘自明日科技《Python OpenCV从入门到精通》一书