一、本期学习目标
1.理解图像像素级算术运算原理,区分 OpenCV 饱和加法与 NumPy 取模加法的差异,掌握图像加融合。
2.掌握图像边界填充的 5 种模式,理解填充在卷积运算中的实际意义。
3.理解图像阈值分割原理,掌握 5 种阈值处理模式,明白阈值处理的使用前提。
4.理解图像噪声、图像平滑、滤波的基本概念。
5.熟练掌握方框滤波、高斯滤波、中值滤波,对比三类滤波的原理、适用噪声类型、优缺点。
6.能够根据图像噪声类型,选择合适的滤波算法完成图像降噪。
二、图像的加法与加权运
1 .原理基础
数字图像本质是矩阵,彩色图像为三维矩阵(高度,宽度,通道数),灰度图为二维矩阵,矩阵中每一个元素取值范围 0‑255,代表像素亮度,0 最黑,255 最亮。
图像加法,就是两张完全相同尺寸、相同通道数图像对应位置像素值相加。
像素相加会出现溢出:两个像素相加结果大于 255。这里存在两种处理逻辑:
1.NumPy 直接相加 img1 + img2:取模运算

超过 255 时,对 256 取余数。例如:250+20 =270,270 mod256=14。
现象:高亮区域直接变成暗黑色,图像出现反常黑白翻转,图像处理中几乎不使用。
2.OpenCV 加法 cv2.add(img1,img2):饱和截断

像素和超过 255 直接限制最大值 255,不会发生颜色翻转,是图像处理标准做法。
2 图像加权融合 addWeighted
加权融合实现两张图片按权重混合,公式:
dst=α⋅img1+β⋅img2+γ
α:第一张图像权重
β:第二张图像权重
γ:亮度偏移量,可以整体提亮或者压暗图片
同样强制要求两张图像高、宽、通道完全一致,否则直接报错。
3 核心 API
饱和加法
cv2.add(src1, src2)
加权融合
cv2.addWeighted(src1, alpha, src2, beta, gamma)
4.代码
python
import cv2
# ===================== 图像加法运算:直接使用 + 号 =====================
# 对于 + 号运算,当对图像a,图像b进行加法求和时,遵循以下规则:
# 当某位置像素相加得到的数值小于255时,该位置数值为两图像该位置像素相加之和
# 当某位置像素相加得到的数值大于255时,该位置数值将截断结果并将其减去 256
# 例如:相加后是260,实际是260-256=4(模256取余,溢出会回绕)
a = cv2.imread('11.jpg') # 读取第一张图片
b = cv2.imread('22.jpg') # 读取第二张图片
c = a + 10 # 整张图每个像素全部+10,+号模式,溢出会回绕
cv2.imshow('yuantu', a) # 显示原图a
cv2.imshow('a+10', a+10) # 显示每个像素加10之后的图片
cv2.waitKey(0) # 等待按键,暂停窗口
# 切片截取两张图相同区域,做 + 加法运算
# a[高度范围,宽度范围]:50到450行,50到400列
m = a[50:450, 50:400] + b[50:450, 50:400]
cv2.imshow('a+b', m) # 显示切片直接相加结果(溢出回绕)
cv2.waitKey(0) # 等待按键
# ===================== 图像加法运算:cv2.add()函数 =====================
# 对于cv2.add()运算,当对图像a,图像b进行加法求和时,遵循以下规则:
# 当某位置像素相加得到的数值小于255时,该位置数值为两图像该位置像素相加之和
# 当某位置像素相加得到的数值大于255时,该位置数值直接取最大值255(饱和截断,不会回绕)
# 注意:add要求两张图片宽高通道数必须完全一致
a = cv2.imread('11.jpg')
b = cv2.imread('22.jpg')
b = cv2.resize(b, (400, 400)) # 将b缩放到400×400
a = cv2.resize(a, (400, 400)) # 将a缩放到400×400,保证两张图尺寸一样
c = cv2.add(a, b) # OpenCV官方加法,超过255直接置为255
cv2.imshow('a add b', c) # 显示add相加结果
cv2.waitKey(0)
cv2.destroyAllWindows() # 关闭所有窗口
# ===================== 加权混合 cv2.addWeighted() =====================
# addWeighted(图1,图1权重,图2,图2权重,亮度偏移值)
# 公式:输出 = a*alpha + b*beta + gamma
# a权重0.5,b权重0.8,整体再加10的亮度偏移
c = cv2.addWeighted(a, 0.5, b, 0.8, 10)
cv2.imshow('addweighted', c) # 显示两张图片加权融合效果
cv2.waitKey(0)
cv2.destroyAllWindows()
| 运算方式 | 溢出处理 | 现象 | 适用场景 |
|---|---|---|---|
numpy + |
模 256,溢出回绕 | 高光出现黑斑、色彩反转 | 几乎不用于图像处理 |
cv2.add() |
饱和截断到 255 | 高光正常变白,无黑斑 | 图像直接叠加 |
cv2.addWeighted() |
饱和截断 | 按权重混合两张图,可调节整体亮度 | 图片融合、淡入淡出 |
【1】numpy 的+是矩阵数学运算,不考虑图像像素 0‑255 的显示逻辑,溢出会回绕,不要直接用来做图像相加。
【2】OpenCV 的cv2.add()做饱和截断,符合图像显示逻辑,是图像加法的标准 API。
【3】cv2.add()、cv2.addWeighted()都强制要求输入图像尺寸、通道数完全一致,尺寸不同需要先 resize 缩放。
【4】addWeighted 的 gamma 参数可以整体调节输出图像的明暗。
三、图像边界填充 copyMakeBorder
1 为什么需要边界填充
当执行卷积、滤波操作的时候,卷积核滑动到图像边缘,边缘像素缺少邻域像素参与计算。
如果不做填充,卷积之后输出图像的宽高会缩小。
边界填充就是在原图四周扩充像素,保证卷积之后输出图像和原图尺寸保持一致。
cv2.copyMakeBorder(src, top, bottom, left, right, borderType, value)
top,bottom,left,right:上下左右需要填充多少像素
borderType:填充模式
value:仅 CONSTANT 模式生效,填充的颜色值
2 五种填充模式详解
1.cv2.BORDER_REPLICATE 复制填充
复制图像最边缘那一行 / 列像素向外延展。
特点:边缘过渡平缓,卷积经常使用。
2.cv2.BORDER_REFLECT 镜像反射填充
以图像边界为镜面,把图像内容镜像翻转复制出去,边界像素会重复。
3.cv2.BORDER_REFLECT_101 反射 101(也叫镜像,OpenCV 最常用)
镜像填充,边界像素不重复,相邻不会出现重复像素,卷积操作首选填充方式。
4.cv2.BORDER_WRAP 环绕平铺填充
把图像当作瓦片,循环平铺。右边缺的像素从图像左边拿,下边缺的像素从图像上边拿。视觉上会出现画面拼接割裂感。
5.cv2.BORDER_CONSTANT 常量纯色填充
四周全部填充固定颜色 value,比如黑色边框、白色边框。
3代码
python
import cv2
# 读取图片
ys = cv2.imread('11.jpg')
# 修改图片尺寸为400*400
ys = cv2.resize(ys, dsize=(400, 400))
# 设置上下左右需要填充的像素宽度
top,bottom,left,right = 50,50,50,50
# 1.常量填充:用固定颜色填充边界
constant = cv2.copyMakeBorder(ys,top,bottom,left,right,borderType=cv2.BORDER_CONSTANT)
# 2.反射填充:镜像复制,包含边缘像素
reflect = cv2.copyMakeBorder(ys,top,bottom,left,right,borderType=cv2.BORDER_REFLECT)
# 3.反射101填充:镜像复制,不重复边缘像素,项目最常用
reflect101_ = cv2.copyMakeBorder(ys,top,bottom,left,right,borderType=cv2.BORDER_REFLECT_101)
# 4.复制填充:直接复制图像最边缘那一行/列像素向外拉伸
replicate = cv2.copyMakeBorder(ys,top,bottom,left,right,borderType=cv2.BORDER_REPLICATE)
# 5.环绕填充:把图片整体平铺环绕,类似卷纸循环
wrap = cv2.copyMakeBorder(ys,top,bottom,left,right,borderType=cv2.BORDER_WRAP)
# 显示原图
cv2.imshow('yuantu',ys)
# 显示常量填充效果
cv2.imshow('constant',constant)
# 显示reflect镜像效果
cv2.imshow('reflect',reflect)
# 显示reflect101镜像效果,窗口标题是reflect101,传入处理好的图片变量reflect101_
cv2.imshow('reflect101',reflect101_)
# 显示复制边缘填充效果
cv2.imshow('replicate',replicate)
# 显示环绕平铺填充效果
cv2.imshow('wrap',wrap)
# 等待任意按键,0代表无限等待
cv2.waitKey(0)
# 关闭全部opencv窗口,释放资源
cv2.destroyAllWindows()
| 填充模式 | 视觉现象 | 是否出现明显边框 | 适用场景 |
|---|---|---|---|
| CONSTANT | 四周纯色边框 | 是 | 手动添加边框 |
| REFLECT | 镜像,边界像素重复 | 几乎无,但边缘有重复纹路 | 少用 |
| REFLECT_101 | 平滑镜像,无像素重复 | 无 | 卷积滤波首选,工业项目常用 |
| REPLICATE | 边缘像素向外拉伸 | 无 | 边缘颜色单一图像 |
| WRAP | 画面循环错位拼接 | 拼接割裂感 | 纹理平铺 |
【1】copyMakeBorder可以在图像上下左右扩充指定像素,输出图像宽高变大。
【2】REFLECT_101实际工程中使用最多,镜像不重复边界像素,卷积时可以有效减少边界伪影。
【3】CONSTANT产生人为纯色边界,会引入虚假像素,卷积运算尽量不要使用。
【4】WRAP会把图像另一端的内容搬运到边界,普通图像会出现内容错乱,只适合纹理素材。
四、图像阈值处理 threshold
1 基本概念
阈值处理一般针对灰度图像。设定一个阈值 T,将图像每一个像素和 T 比较,把像素划分两类,实现图像二值分割。
注意:彩色图不能直接做 threshold,需要先转灰度cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)。
函数:retval, dst = cv2.threshold(src, thresh, maxval, type)
src:输入灰度图
thresh:设定阈值 T
maxval:满足条件时赋予的最大值(一般填 255)
type:阈值模式
retval:返回实际使用的阈值;dst 输出处理后图像
2 五种阈值模式
1.cv2.THRESH_BINARY 二值化

大于阈值→变白;小于阈值→变黑。
2.cv2.THRESH_BINARY_INV 反向二值化

大于阈值变黑,小于阈值变白,和上面完全反转。
3.cv2.THRESH_TRUNC 截断模式
大于阈值的像素强制等于阈值;小于阈值保持原值。画面不会直接变成纯黑纯白,只是把高光截断。
4.cv2.THRESH_TOZERO 置零模式
大于阈值保持原值;小于阈值直接置 0 变黑。
5.cv2.THRESH_TOZERO_INV 反向置零
小于阈值保持原值;大于阈值置 0 变黑。
3. 应用场景
阈值是图像分割的基础,把目标物体和背景分离开。
例如:文档扫描文字提取、物体轮廓提取。
4.代码
python
import cv2
# 读取图片,第二个参数0代表直接读取为灰度图像(单通道)
image = cv2.imread('11.jpg',0)
# 将图片缩放为400×400大小
image = cv2.resize(image,(400,400))
# threshold(输入灰度图,阈值,最大值,阈值模式)
# 返回值:ret 代表使用的阈值;binary 处理之后的图像
# 模式1:THRESH_BINARY 二值化:大于阈值→255白色;小于阈值→0黑色
ret,binary= cv2.threshold(image,175,255,cv2.THRESH_BINARY)
# 模式2:THRESH_BINARY_INV 反向二值化:大于阈值→0黑色;小于阈值→255白色
ret1,binaryinv= cv2.threshold(image,175,255,cv2.THRESH_BINARY_INV)
# 模式3:THRESH_TRUNC 截断:大于阈值就等于阈值,小于阈值保持原值不变
ret2,trunc= cv2.threshold(image,175,255,cv2.THRESH_TRUNC)
# 模式4:THRESH_TOZERO 置零:大于阈值保持原值;小于阈值置为0黑色
ret3,tozero= cv2.threshold(image,175,255,cv2.THRESH_TOZERO)
# 模式5:THRESH_TOZERO_INV 反向置零:大于阈值置0黑色;小于阈值保持原来像素值
ret4,tozeroinv= cv2.threshold(image,175,255,cv2.THRESH_TOZERO_INV)
# 显示灰度原图
cv2.imshow('gray',image)
cv2.waitKey(0) # 等待按键,按任意键切换下一张
# 显示BINARY正向二值化结果
cv2.imshow('binary',binary)
cv2.waitKey(0)
# 显示BINARY_INV反向二值化结果(注意代码里窗口名拼写binrayinv是笔误)
cv2.imshow('binrayinv',binaryinv)
cv2.waitKey(0)
# 显示TRUNC截断模式结果
cv2.imshow('trunc',trunc)
# 显示TOZERO置零模式结果
cv2.imshow('tozero',tozero)
cv2.waitKey(0)
# 显示TOZERO_INV反向置零结果
cv2.imshow('tozeroinv',tozeroinv)
cv2.waitKey(0)
# 全部窗口关闭释放(建议加上,实验好习惯)
cv2.destroyAllWindows()
| 阈值模式 | 输出特点 | 像素规则总结 |
|---|---|---|
| THRESH_BINARY | 二值图,只有 0 和 255 | 大于阈值变白,小于变黑 |
| THRESH_BINARY_INV | 二值图,只有 0 和 255 | 大于阈值变黑,小于变白 |
| THRESH_TRUNC | 保留灰度,无纯白 | 大于阈值就限制为阈值,暗部不变 |
| THRESH_TOZERO | 保留高于阈值的灰度 | 小于阈值全部变黑 |
| THRESH_TOZERO_INV | 保留低于阈值的灰度 | 大于阈值全部变黑 |
【1】cv2.threshold()要求输入必须是灰度单通道图像;如果传入彩色三通道图像,代码不会报错,但结果完全没有意义。
【2】THRESH_BINARY与THRESH_BINARY_INV输出严格二值图像,只有 0、255,多用于前景背景分割。
【3】TRUNC、TOZERO、TOZERO_INV 不会生成二值图,会保留一部分灰度信息,用于亮度筛选,而不是分割。
【4】阈值 T 的取值会直接改变分割效果:阈值调大,更多像素被判定为暗;阈值调小,更多像素判定为亮。
五、 图像平滑与滤波基础概念
1 图像噪声
图像在采集传输过程引入的错误像素点,主要两类:
高斯噪声 :整体画面分布细小噪点,符合高斯正态分布。
椒盐噪声:画面随机出现孤立白点(盐)、黑点(胡椒)。
2 图像平滑(低通滤波)
图像的像素突变(边缘、噪点)属于图像高频信息;大面积缓慢变化属于低频信息。
低通滤波:保留低频,抑制高频。结果就是图像变模糊,同时消除噪声。
注意:滤波降噪是有代价的:消除噪声同时,图像物体边缘、细节会被模糊。
核 (kernel) 尺寸越大,降噪越强,图像模糊越严重。
3 方框滤波 BoxFilter
cv2.boxFilter(src, ddepth, ksize, normalize)
ksize:滤波核大小,例如 (3,3)、(5,5),必须奇数
normalize=True:邻域全部像素求平均值,就是均值滤波。
normalize=False:邻域像素直接求和,很容易像素溢出大面积变白。
原理:滑动窗口内所有像素同等权重,相加求平均。
缺点:把噪声像素也参与平均,降噪同时边缘模糊严重。
4 高斯滤波 GaussianBlur(工程最常用)
cv2.GaussianBlur(src,ksize,sigmaX)
高斯核,窗口内像素权重不是均等;卷积核中心权重最高,越往四周权重逐渐变小。
sigmaX:X 方向高斯标准差;sigmaY 不填默认等于 sigmaX。
适合:高斯噪声。
优点:相比方框滤波,边缘保留效果更好,现实项目最常用平滑算法。
5 中值滤波 medianBlur
cv2.medianBlur(src, ksize)
ksize 必须是大于 1 的奇数。
非线性滤波,不做加权求和。把窗口内所有像素排序,取中间值作为输出像素。
噪点(孤立白点黑点)一般是极值,排序后会被直接丢弃。
特别适合 椒盐噪声。
对高斯噪声效果一般;会抹除细小纹理细节。
6 三类滤波横向对比表
| 滤波类型 | 原理 | 适合噪声 | 缺点 |
|---|---|---|---|
| 方框滤波 | 邻域像素求平均 | 轻微高斯噪声 | 边缘模糊严重 |
| 高斯滤波 | 高斯加权平均 | 高斯噪声 | 不能完美去除椒盐噪点 |
| 中值滤波 | 邻域取像素中值 | 椒盐噪声 | 丢失细小纹理细节 |
7.代码
python
import cv2
import numpy as np
# 定义函数:给图片添加椒盐噪声(胡椒盐噪声,随机白点、黑点)
def add_peppersalt_noise(image,n = 10000):
result = image.copy() #复制图片,对图片的副本进行处理,以防修改了原图
h,w = image.shape[:2] #选取图片的高和宽,彩色图片image.shape返回三个值[高,宽,通道数],灰度图只返回[高,宽]
# 循环n次,生成n个噪声点
for i in range(n):
# 随机生成x行坐标,范围0~图片高度
x = np.random.randint(0 , h)
# 随机生成y列坐标,范围0~图片宽度
y = np.random.randint(0 , w)
# 随机0或1
if np.random.randint(0,2) == 0:
result[x,y] = 0 # 0为黑色,胡椒噪声点
else:
result[x,y] = 255 # 255为白色,盐噪声点
return result
# 读取原始图片
image = cv2.imread('11.jpg')
# 修改图片尺寸为400×400
image =cv2.resize(image,(400,400))
# 显示原图
cv2.imshow('yuantu',image)
cv2.waitKey(0)
# 调用函数,生成带椒盐噪声的图片
noise = add_peppersalt_noise(image)
cv2.imshow('noise',noise)
cv2.waitKey(0)
# ==========均值滤波 cv2.blur()==========
# (3,3)代表3×3的卷积核,邻域内所有像素求平均值作为输出,去除噪声
blur_1 = cv2.blur(noise,(3,3))
cv2.imshow('blur_1',blur_1)
cv2.waitKey(0)
# (63,63)很大的卷积核,模糊效果极强,图片会变得非常糊
blur_2 = cv2.blur(noise,(63,63))
cv2.imshow('blur_2',blur_2)
cv2.waitKey(0)
cv2.destroyAllWindows()
python
import cv2
import numpy as np
def add_peppersalt_noise(image,n = 10000):
result = image.copy()#复制图片,对图片的副本进行处理,以防修改了原图
h,w = image.shape[:2]#选取图片的高和宽,彩色图片image.shape返回三个值[高,宽,通道数],灰度图只返回[高,宽]
for i in range(n):
x = np.random.randint(0 , h)
y = np.random.randint(0 , w)
if np.random.randint(0,2) == 0:
result[x,y] = 0
else:
result[x,y] = 255
return result
image = cv2.imread('11.jpg')
image =cv2.resize(image,(400,400))
noise = add_peppersalt_noise(image)
cv2.imshow('yuantu',image)
cv2.waitKey(0)
#放框滤波
###src 是需要处理的图像,即原始图像。
# # # # # ddepth是处理结果图像的图像深度,一般使用-1表示与原始图像使用相同的图像深度。(可以理解为数据类型)
######ksize 是滤波核的大小。滤波核大小是指在滤波处理过程中所选择的邻域图像的高 度和宽度。
###### anchor 是锚点,(指对应哪个区域)
# #####normalize 表示在滤波时是否进行归一化。
####1.当值为True时,归一化,用邻域像素值的和除以面积。此时方框滤波与 均值滤波 效果相同
####2.当值为False时,不归一化,直接使用邻域像素值的和。和>255时使用255
boxFilter_1 = cv2.boxFilter(noise,-1,(3,3),normalize= True)#
cv2.imshow('boxFilter_1',boxFilter_1)
cv2.waitKey(0)
boxFilter_2 = cv2.boxFilter(noise,-1,(3,3),normalize= False)
cv2.imshow('boxFilter_2',boxFilter_2)
cv2.waitKey(0)
#高斯滤波
### cv2.GaussianBlur(src,ksize[,sigmaX[,sigmaY[,dst]]])高斯滤波
# # # 参数说明:
# # # src:输入图像,通常是一个NumPy数组。
# # # ksize:滤波器的大小,它是一个元组,表示在水平和垂直方向上的像素数量。例如,(5,5)表示一个5x5的滤波器。
# ### sigmaX和sigmay:分别表示在X轴和Y轴方向上的标准差。这些值与滤波器大小相同。默认情况下,它们都等于0,这意味着没有高斯模糊。# # # dst:输出图像,通常是一个NumPy数组。如果为None,则会创建一个新的数组来存储结果。
GaussianB = cv2.GaussianBlur(noise,(3,3),1)
cv2.imshow('GaussianBlur',GaussianB)
cv2.waitKey(0)
#中值滤波
# cv2.medianBlur(src,ksizel,dst])中值滤波
# 参数说明:
# src:输入图像。
# ksize:滤波器的大小,它是一个整数,表示在水平和垂直方向上的像素数量。例如,5表示一个5x5的滤波器。
# dst:输出图像,通常是一个NumPy数组。如果为None,则会创建一个新的数组来存储结果。
mediamB = cv2.medianBlur(noise,3)
cv2.imshow('medianBlur',mediamB)
cv2.waitKey(0)
cv2.destroyAllWindows()
#中值滤波效果最好
六、本期完整核心 API 汇总
python
# =========图像运算=========
cv2.add()
cv2.addWeighted()
# =========边界填充=========
cv2.copyMakeBorder()
# =========阈值处理=========
cv2.threshold()
# =========滤波平滑=========
cv2.boxFilter() #方框滤波
cv2.GaussianBlur() #高斯滤波
cv2.medianBlur() #中值滤波
七、本期总结
图像像素相加区分 NumPy 取模加法 和 OpenCV 饱和加法,图像处理优先使用 cv2.add。加权融合需要两张图像尺寸通道完全一致。
copyMakeBorder 五种填充模式,REFLECT_101 镜像填充最适合卷积扩边;CONSTANT 用于人为增加纯色边框。填充的目的是解决卷积后图像尺寸收缩。
threshold 阈值处理输入必须是灰度图像,5 种阈值模式,二值化模式最常用于图像分割。
图像平滑本质低通滤波,牺牲图像细节换取噪声抑制。
噪声类型决定滤波选择:高斯噪声选高斯滤波;椒盐噪声优先中值滤波;方框滤波使用较少。
滤波核越大降噪越强,但图像细节丢失越严重,实际项目需要权衡核大小。