小白学OpenCV系列3-图像算数运算

我们之前讲过,图像读取到计算机之后就是一个矩阵,那么它就可以做各种各样的运算,今天就先来讲讲最基础的加减乘除以及位运算,它们是其他高阶运算的基础。

加法

有两种方式可以进行常规的加法运算,一种是通过加法运算符"+",一种是通过cv2.add()函数。但是由于灰度图的像素值范围通常是[0, 255],如果求和,得到的数值很可能会超过255,因此对超过255的数值,两者的处理方式是不一样的。

加号运算符

使用加法运算符的时候,超过255的数值会对256取模,以便让数值范围重新回到[0,255],我们可以通过下列例子来演示该过程。

python 复制代码
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],我们将上面的代码稍微改一改。

python 复制代码
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的大小和类型必须相同才行。我们还是使用上述图片来演示该过程。

python 复制代码
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],我们可以通过下列例子来演示该过程。

python 复制代码
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],我们将上面的代码稍微改一改。

python 复制代码
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。

我们可以使用下列代码来感受二者的区别。

python 复制代码
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,因此一般情况下使用后者会更好一些。我们可以使用下列代码来感受二者的区别。

python 复制代码
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。

下面我们以按位与为例,来演示一下位运算的用处。假如我们只想要最开始例图中的头部画面,那么就可以利用按位与的性质来实现,其代码如下所示:

python 复制代码
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截断。对于位运算而言,很多时候可以用来构建掩膜,以找到我们感兴趣的区域。

相关推荐
张小九994 小时前
ThermoSeek:热稳定蛋白数据库
人工智能
wzy-6664 小时前
DINOv3 新颖角度解释
人工智能
jie*4 小时前
小杰机器学习(two)——导数、损失函数、斜率极值最值、微分规则、切平面与偏导数、梯度。
人工智能·机器学习
Niuguangshuo4 小时前
深度学习:归一化技术
人工智能·深度学习
302AI4 小时前
Claude 断供中国之际,Kimi-K2-0905 低调上线:时势造英雄
人工智能·llm·ai编程
却道天凉_好个秋5 小时前
计算机视觉(九):图像轮廓
人工智能·opencv·计算机视觉·图像轮廓
爱读源码的大都督5 小时前
Java已死?别慌,看我如何用Java手写一个Qwen Code Agent,拯救Java
java·人工智能·后端
机器之心5 小时前
国内外AI大厂重押,初创梭哈,谁能凭「记忆」成为下一个「DeepSeek」?
人工智能·openai
时序之心5 小时前
覆盖Transformer、GAN:掩码重建正在重塑时间序列领域!
人工智能·深度学习·生成对抗网络·transformer·时间序列