小白学OpenCV系列3-图像算数运算
我们之前讲过,图像读取到计算机之后就是一个矩阵,那么它就可以做各种各样的运算,今天就先来讲讲最基础的加减乘除以及位运算,它们是其他高阶运算的基础。
加法
有两种方式可以进行常规的加法运算,一种是通过加法运算符“+”,一种是通过cv2.add()函数。但是由于灰度图的像素值范围通常是[0, 255],如果求和,得到的数值很可能会超过255,因此对超过255的数值,两者的处理方式是不一样的。
加号运算符
使用加法运算符的时候,超过255的数值会对256取模,以便让数值范围重新回到[0,255],我们可以通过下列例子来演示该过程。
import cv2 img1 = cv2.imread("lena.bmp", flags=0)
print("img1:", img1)
img2 = cv2.imread("lena.bmp", flags=0)
print("img2", img2)
print("img1+img2:", img1 + img2)
img1和img2都是下图的像素值矩阵。
最终计算的结果如下图所示:
可以看到图中第一个红框和第二个红框的数值加起来就超过了255,可是最后计算的结果却是68,实际上就是(162+162)mod 256 = 68。
cv2.add函数
使用add函数的时候,对于超过255的数值会截止到255,以便让数值范围仍然在[0,255],我们将上面的代码稍微改一改。
import cv2 img1 = cv2.imread("lena.bmp", flags=0)
print("img1:\n", img1)
img2 = cv2.imread("lena.bmp", flags=0)
print("img2:\n", img2)
print("cv2.add(img1,img2):\n", cv2.add(img1, img2))
计算的结果如下所示:
可以看到图中第一个红框和第二个红框的数值加起来就超过了255,可是最后计算的结果却被截止到了255。
加权和
除了上述两种常规加法外,实际上还可以计算图像的加权和,就是在计算两幅图像的像素值之和时,将每幅图像的权重考虑进来,如下式所示:
dst=saturate(src1∗α+scr2∗β+γ)
dst=saturate(src1 * \alpha + scr2 * \beta + \gamma)
dst=saturate(src1∗α+scr2∗β+γ)
最后计算出来的值如果超过了255,同样地会被截止到255,而且src1和src2的大小和类型必须相同才行。我们还是使用上述图片来演示该过程。
import cv2 img1 = cv2.imread("lena.bmp", flags=0)
print("img1:\n", img1)
img2 = cv2.imread("lena.bmp", flags=0)
print("img2:\n", img2)
print("cv2.addWeighted(img1,2,img2,3,4):\n", cv2.addWeighted(img1, 2, img2, 3, 4))
我们可以简单地计算下上述的数值是否满足加权求和的规律:45 * 2 + 45 * 3 + 4 = 229,说明加权成功了,而计算后超过255的数值就会被重置为255。
减法
和加法一样,同样有两种方式可以进行常规的减法运算,一种是通过减法运算符“-”,一种是通过cv2.subtract()函数。但是由于灰度图的像素值范围通常是[0, 255],如果求差,得到的数值很可能会小于0,因此对小于0的数值,两者的处理方式也是不一样的。
减法运算符
使用减法运算符的时候,小于0的数值会对256取模,以便让数值范围重新回到[0,255],我们可以通过下列例子来演示该过程。
import cv2
import numpy as np img1 = cv2.imread("lena.bmp", flags=0)
print("img1:\n", img1)
# ones_like的作用为创建一个形状和img1一样的单位矩阵
img2 = np.ones_like(img1) * 100
print("img2:\n", img2)
print("img1-img2:\n", img1 - img2)
最终计算的结果如下图所示,从中可以看到大于0的数值仍然保持原样,而小于0的数值则对256取模了。
cv2.subtract函数
使用subtract函数的时候,对于小于0的数值会截止到0,以便让数值范围仍然在[0,255],我们将上面的代码稍微改一改。
import cv2
import numpy as np img1 = cv2.imread("lena.bmp", flags=0)
print("img1:\n", img1)
# ones_like的作用为创建一个形状和img1一样的单位矩阵
img2 = np.ones_like(img1) * 100
print("img2:\n", img2)
print("cv2.subtract(img1, img2):\n", cv2.subtract(img1, img2))
最终计算的结果如下所示:
乘法
和加减法一样,同样有两种方式可以进行常规的乘法运算,一种是通过乘法运算符“*”,一种是通过cv2.multiply()函数。和加减法一样,前者对超过255的数值会对256取模,后者则会截止到255。
我们可以使用下列代码来感受二者的区别。
import cv2
import numpy as np img1 = cv2.imread("lena.bmp", flags=0)
print("img1:\n", img1)
# ones_like的作用为创建一个形状和img1一样的单位矩阵
img2 = np.ones_like(img1) * 2
print("img2:\n", img2)
print("img1 * img2:\n", img1 * img2)
print("cv2.multiply(img1, img2):\n", cv2.multiply(img1, img2))
最终计算的结果如下所示:
除法
和加减乘法一样,同样有两种方式可以进行常规的除法运算,一种是通过除法运算符“/”,一种是通过cv2.divide()函数。但是前者计算出来的结果会存在小数,而后者则会做相应处理,将结果四舍五入为整数。另外如果除数为0的时候,后者也会将结果置为0,因此一般情况下使用后者会更好一些。我们可以使用下列代码来感受二者的区别。
import cv2
import numpy as np img1 = cv2.imread("lena.bmp", flags=0)
print("img1:\n", img1)
# ones_like的作用为创建一个形状和img1一样的单位矩阵
img2 = np.ones_like(img1) * 2
# zeros_like的作用为创建一个形状和img1一样的零矩阵
# img2 = np.zeros_like(img1)
print("img2:\n", img2)
print("img1 / img2:\n", img1 / img2)
print("cv2.divide(img1, img2):\n", cv2.divide(img1, img2))
位运算
在OpenCV中,常见的位运算有以下四种:
- cv2.bitwise_and:按位与,当两个输入位都是1时,输出才是1,否则为0
- cv2.bitwise_or:按位或,只要有一个输入位是1,输出就是1,只有当两个都是0时,输出才是0。
- cv2.bitwise_xor:按位异或,当两个输入位不同时,输出是1,当相同时,输出是0。
- cv2.bitwise_not:按位取反,如果输入是1,输出是0,如果输入是0,输出是1。
下面我们以按位与为例,来演示一下位运算的用处。假如我们只想要最开始例图中的头部画面,那么就可以利用按位与的性质来实现,其代码如下所示:
import cv2
import numpy as np # 读取图片
img1 = cv2.imread("lena.bmp", flags=0)
# 建立与之进行按位与的模板
mask = np.zeros(img1.shape, dtype=np.uint8)
mask[50: 200, 100: 200] = 255
mask[50: 250, 50: 100] = 255
masked_img1 = cv2.bitwise_and(img1, mask) cv2.imshow("mask_lena", masked_img1)
cv2.waitKey()
cv2.destroyAllWindows()
最终我们可以得到如下结果:
总结
本篇博客主要学习了OpenCV中的算数运算操作,对于加减乘除操作而言,总体规律就是:如果使用运算符号操作,结果需要对256取模,若使用函数操作,结果则被0和255截断。对于位运算而言,很多时候可以用来构建掩膜,以找到我们感兴趣的区域。