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

相关推荐
乌恩大侠1 分钟前
【Spark】操作记录
人工智能·spark·usrp
一水鉴天5 分钟前
整体设计 全面梳理复盘 之27 九宫格框文法 Type 0~Ⅲ型文法和 bnf/abnf/ebnf 之1
人工智能·状态模式·公共逻辑
极客BIM工作室9 分钟前
GAN vs. VAE:生成对抗网络 vs. 变分自编码机
人工智能·神经网络·生成对抗网络
咋吃都不胖lyh11 分钟前
小白零基础教程:安装 Conda + VSCode 配置 Python 开发环境
人工智能·python·conda
minhuan12 分钟前
构建AI智能体:八十九、Encoder-only与Decoder-only模型架构:基于ModelScope小模型的实践解析
人工智能·模型架构·encoder-only架构·decoder-only架构
rit843249914 分钟前
基于MATLAB的PCA+SVM人脸识别系统实现
人工智能·算法
一 铭24 分钟前
Claude Agent Skills:一种基于 Prompt 扩展的元工具架构
人工智能·大模型·llm·prompt
连线Insight29 分钟前
小马智行港股上市:自动驾驶从“技术追跑”到“商业领跑”的里程碑
人工智能
xier_ran32 分钟前
深度学习:为什么不能将多层神经网络参数全部初始化为零以及如何进行随机初始化
人工智能·深度学习
扫地僧98532 分钟前
[特殊字符]用于糖尿病视网膜病变图像生成的生成对抗网络(GAN)
人工智能·神经网络·生成对抗网络