Python OpenCV入门到精通学习日记:图像的运算
前言
图像怎么运算呢?首先,图像是由像素组成的,像素又是由具体的正整数表示的,所以图像也是可以进行一系列的数学运算的。通过运算我们可以获得截取,合并图像等效果。Opencv提供了许多图像运算方法。
图像的运算
1 掩模
这个词听起来可能有点抽象,不像之前的名词那么好懂,举个例子:当医生要给病人做手术的时候,通常会给病人身上盖上一块布,布上有个洞,只把要动手术的部位暴露出来,这样方便医生定位患处。
同样的,当计算机处理图像时,有些内容不需要处理,有些内容需要处理,但通常情况下,计算机处理图像时会把图像所有的像素全部处理一遍。但如果想只处理某一块区域,那就需要能够覆盖原始图像,只暴露要处理的那一部分的模板图像就叫做掩模
Opencv处理图像时,我们通常使用numpy库提供的方法创建掩模图像。
在这里举个例子:利用numpy库的zeros()方法创建一幅掩模图像,感兴趣区域为在该图像中横坐标为20、纵坐标为50、宽为60、高为50的矩形,展示该掩模图像。调换该掩模图像的感兴趣区域和不感兴趣区域之后,再次展示掩模图像。
import cv2
import numpy as np
mask = np.zeros((150,150,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()
cv2.destroyAllWindows()
运行结果如下:
2 图像的加法运算
书上是这么说的:图像中每一个像素都有用整数表示的像素值,2幅图像相加就是让相同位置像素值相加,最后将计算结果按照原位置重新组成一幅新图像。
这不就是将两幅图像重合吗,这也太有意思了吧。
但是在开发程序时我们不用“+”运算符对图像进行加法运算,而是使用add()
方法。
dst = cv2.add(src1, src2, mask, dtype)
参数说明:
src1:第一幅图像。
src2:第二幅图像。
mask:可选参数,掩模,建议使用默认值。
dtype:可选参数,图像深度,建议使用默认值。
dst:相加之后的图像。如果相加之后值的结果大于255,则取255。
示例:读取一幅图像,让该图像自己对自己做加法运算,分别使用“+”运算符和add()方法,观查两者相加结果的不同。
import cv2
img = cv2.imread("img.png")
sum1 = img + img
sum2 = cv2.add(img,img)
cv2.imshow("img",img)
cv2.imshow("sum1",sum1)
cv2.imshow("sum2",sum2)
cv2.waitKey()
cv2.destroyAllWindows()
运行结果如下:
如果使用“+”运算符,图像颜色变得似乎更深了,也就是说相加后获得的像素值反而更小了,这是为什么呢?
原来,“+”运算符计算结果如果超出了255,就会取得相加和除以255的余数,也就是取模运算,而add()
方法计算结果超过255,则会取值255,一些浅颜色的像素彻底变成了纯白色了。
示例:颜料中的三原色为红、黄、蓝,这3种颜色混在一起变成黑色,而光学中的三原色为红、绿、蓝,这3种颜色混在一起变成白色。现在分别创建纯蓝、纯绿、纯红3种图像,取3幅图像的相加和,查看结果是黑色还是白色。
import cv2
import numpy as np
img1 = np.zeros((250,250,3),np.uint8)
img1[:,:,0] = 255
img2 = np.zeros((250,250,3),np.uint8)
img2[:,:,1] = 255
img3 = np.zeros((250,250,3),np.uint8)
img3[:,:,2] = 255
cv2.imshow("1",img1)
cv2.imshow("2",img2)
cv2.imshow("3",img3)
img = cv2.add(img1,img2)
img = cv2.add(img,img3)
cv2.imshow("1+2+3",img)
cv2.waitKey()
cv2.destroyAllWindows()
运行结果如下:
如果要添加掩模怎么办呢?我们可以在代码中添加功能,以下是关键代码:
m = np.zeros((250,250,1),np.uint8)
m[50:100,50:100,:] = 255
然后在add()
中修改掩模参数为m即可。
这里有个点要注意,那就是为什么在前面设置掩模时可以设置掩模为三通道而在这里如果设置三通道就会发生报错,原因是:如果你尝试将掩码设置为三通道(例如使用(250,250,3)),那么在调用cv2.add函数时会出现问题。这是因为cv2.add函数在处理掩码时期望掩码是一个单通道的图像。掩码的目的是控制哪些区域应该被添加,因此它不需要颜色通道。
运行结果如下:
3 图像的位运算
位运算是二进制独有的运算操作。图像是由像素组成的,而每个像素是由十进制整数表示的,而十进制整数又可以转化为二进制数,如此一来,图像也可以做位运算。
OpenCV提供了几种常用的位运算方法:
方法 | 含义 |
---|---|
cv2.bitwise_and() | 按位与 |
cv2.bitwise_or() | 按位或 |
cv2.bitwise_not() | 按位取反 |
cv2.bitwise_xor() | 按位异或 |
3.1 按位与运算
与运算就是按照二进制位进行判断,如果同一位的数字都是1,则运算结果的相同位数字取1,否则取0。
Opencv提供了bitwise_and()
来对图像做与运算:
dst = cv2.bitwise_and(src1,src2,mask)
参数说明:
src1:第一幅图像。
src2:第二幅图像。
mask:可选参数,掩模。
dst:与运算之后的图像。
图像做与运算时,会把每一个像素值都转化为二进制数,然后会让两幅图像相同位置的两个像素值做与运算,最后把运算结果保存在新图像的相同位置上。
与运算有两个特点:
- 如果某个像素与纯白像素做与运算,结果仍然是某像素的原值。
- 如果某个像素与纯黑图像做与运算,结果为纯黑像素。
由此我们可知,如果原图像与掩模进行与运算,原图像仅保留掩模白色区域覆盖的内容,其他区域全部变成黑色。
示例:创建一个掩模,在掩模中央保留一个十字形的白色区域,让掩模与图像做与运算。
import cv2
import numpy as np
img = cv2.imread("liuhui.png")
mask = np.zeros(img.shape,np.uint8)
mask[120:180,:,:] = 255
mask[:,80:180,:] = 255
img = cv2.bitwise_and(img,mask)
cv2.imshow("mask",mask)
cv2.imshow("img",img)
cv2.waitKey()
cv2.destroyAllWindows()
运行结果如下:
3.2 按位或运算
或运算也是按照二进制位进行判断,如果同一位的数字都是0,则运算结果就取0,否则取1。下面是bitwise_or()
的语法:
dst = cv2.bitwise_or(src1, src2, mask)
参数说明:
src1:第一幅图像。
src2:第二幅图像。
mask:可选参数,掩模。
dst:或运算之后的图像。
或运算的特点正好和与运算相反,这里不多赘述了。
3.3 按位取反运算
取反运算是一种单目运算,只需要一个数字参与运算就可以得到结论。取反运算也是按照二进制位进行判断,如果运算数某位上数字是0,则运算结果的相同位的数字就取1,反之同理。
Opencv中提供了bitwise_not()
方法来对图像做取反运算。
dst = cv2.bitwise_not(src, mask)
参数说明:
src:参与运算的图像。
mask:可选参数,掩模。
dst:取反运算之后的图像。
图像经过取反运算后呈现于原图颜色完全相反的效果。
import cv2
img = cv2.imread("liuhui.png")
img_not = cv2.bitwise_not(img)
cv2.imshow("img_not",img_not)
cv2.waitKey()
cv2.destroyAllWindows()
运行结果如下:
3.4 按位异或运算
如果两个运算数同一位的数字相同,则运算结果的相同位数字取0,否则取1。
Opencv提供了bitwise_xor()
对图像进行异或运算:
dst = cv2.bitwise_xor(src, mask)
参数说明:
src:参与运算的图像。
mask:可选参数,掩模。
dst:异或运算之后的图像。
这让异或运算有两个特点:
- 如果某像素与纯白像素做异或运算,结果为原像素的取反结果。
- 如果某像素与纯黑像素做异或运算,结果为原值。
由此可以得出结论:如果原图像与掩模进行异或运算,掩模白色区域呈现取反结果,黑色区域保持不变。
import cv2
import numpy as np
img = cv2.imread("liuhui.png")
mask = np.zeros(img.shape,np.uint8)
mask[120:180,:,:] = 255
mask[:,80:180,:] = 255
img_xor = cv2.bitwise_xor(img,mask)
cv2.imshow("img_xor",img_xor)
cv2.waitKey()
cv2.destroyAllWindows()
运行结果如下:
异或运算还有一个特点:执行一次异或运算得到一个结果,再次进行一次可以还原为最初的值。利用这个特点可以实现对图像的加密和解密。
我们可以利用np.random.randint()
创建一个随机像素值图像作为秘钥图像,试试看:
import cv2
import numpy as np
# 加密解密函数
def encode(img,img_key):
result = cv2.bitwise_xor(img,img_key)
return result
img_i = cv2.imread("liuhui.png")
y,x,channel = img_i.shape
img_key = np.random.randint(0,256,(y,x,3),np.uint8)
result = encode(img_i,img_key)
cv2.imshow("jiami",result)
result = encode(result,img_key)
cv2.imshow("jiemi",result)
cv2.waitKey()
cv2.destroyAllWindows()
运行结果如下:
4 合并图像
在处理图像时经常会遇到需要将两幅图像合并为一副图像,合并图像也分为两种情况:1.两种图像融合在一起;2.每幅图像提供一部分内容,将这些内容拼接成一副图像。Opencv分别用加权和和覆盖两种方法来满足。
4.1 加权和
多次曝光技术是指在一副胶片上拍摄几个影像,最后冲印的相片同时具有多个影像的信息。
加权和会按照不同的权重取两幅图像的像素之和,最后组成新图像。但是加权和不会像纯加法运算那样让图像丢失信息,而是在尽量保留原有图像信息的基础上把两幅图像融合在一起。这是addWeighted()
和图像加法运算cv2.add()
最大的区别。
dst = cv2.addWeighted(src1, alpha, src2, beta, gamma)
参数说明:
src1:第一幅图像。
alpha:第一幅图像的权重。
src2:第二幅图像。
beta:第二幅图像的权重。
gamma:在和结果上添加的标量。该值越大,结果图像越亮,相反则越暗。可以是负数。
dst:加权和后的图像。
4.2 覆盖
覆盖图像就是直接把前景图像显示在背景图像中,前景图像挡住背景图像。覆盖之后背景图像会丢失信息,不会出现加权和那样的“多次曝光”效果。
OpenCV没有提供覆盖操作的方法,开发者可以直接用修改图像像素值的方式实现图像的覆盖、拼接效果:从A图像中取像素值,直接赋值给B图像的像素,这样就能在B图像中看到A图像的信息了。
如果前景图像是4通道(含alpha通道)图像,就不能使用上面例子中直接替换整个区域的方式覆盖背景图像了。因为前景图像中有透明的像素,透明的像素不应该挡住背景,所以在给背景图像像素赋值时应排除所有透明的前景像素。
小结
今天主要学习掩模,图像位运算,合并图像。
明天学习模版匹配。