计算机视觉(二)------OpenCV图像视频操作进阶:从原理到实战
目录
引言
一、边界填充:解决图像边缘信息缺失的关键操作
1.1 为什么需要边界填充?
1.2 OpenCV边界填充函数:cv2.copyMakeBorder()
1.3 五种填充类型及实战对比
实战代码框架
代码实现效果:
填充类型详解
1.4 边界填充的实际应用场景
二、阈值处理:从灰度图像中提取关键信息
2.1 阈值处理的核心需求
2.2 OpenCV阈值处理函数:cv2.threshold()
2.3 五种阈值类型及实战效果
实战代码框架
代码实现效果:
阈值类型详解
2.4 阈值选择的技巧
三、基本图像运算:实现图像的融合与变换
3.1 图像运算的核心需求
3.2 图像加法运算:+与cv2.add()的区别
实战代码:两种加法的对比
代码2实现效果:
加法规则对比
3.3 图像加权运算:cv2.addWeighted()实现平滑融合
实战代码:加权融合
代码实现效果
加权运算的应用场景
3.4 图像运算的注意事项
四、图像平滑处理:去除噪声,保留关键信息
4.1 噪声的类型与平滑处理的目标
4.2 均值滤波:cv2.blur()实现邻域平均
实战代码:均值滤波处理椒盐噪声
代码实现效果:
均值滤波的特点与适用场景
4.3 方框滤波:cv2.boxFilter()的归一化控制
实战代码:方框滤波对比
代码实现效果:
方框滤波的特点
4.4 高斯滤波:cv2.GaussianBlur()保留更多细节
实战代码:高斯滤波处理噪声
代码实现效果:
高斯滤波的特点与适用场景
4.5 中值滤波:cv2.medianBlur()专治椒盐噪声
实战代码:中值滤波去椒盐噪声
代码实现效果:
中值滤波的特点与适用场景
4.6 四种滤波方法的对比总结
五、视频去噪:从动态帧中提取清晰画面
5.1 视频噪声的特点与去噪需求
5.2 视频去噪的实战实现:中值滤波的应用
实战代码:视频去噪流程
代码实现效果截取:
5.3 视频去噪的关键细节
5.4 视频去噪的进阶方向
总结:从基础到进阶的OpenCV图像处理之路
引言
在计算机视觉领域,OpenCV作为最常用的开源库,提供了丰富的图像处理接口。上一篇我们介绍了图像的基本读写、色彩空间转换等基础操作,而实际应用中,我们往往需要更复杂的预处理(如边界扩展)、特征提取(如阈值分割)、噪声处理(如平滑滤波)等操作。本文将围绕OpenCV的进阶操作展开,通过具体案例详解边界填充、阈值处理、图像运算、平滑滤波及视频去噪的实现原理与实战技巧,所有代码均可直接运行,适合初学者进阶学习。
一、边界填充:解决图像边缘信息缺失的关键操作
在图像处理中,我们经常会遇到这样的场景:对图像进行卷积操作时,边缘像素因缺乏邻域信息导致处理结果失真;拼接多幅图像时,需要扩展边界以避免拼接处出现断层;甚至在可视化时,为了突出主体需要给图像添加装饰性边框。这些需求都可以通过边界填充(Padding) 实现。
1.1 为什么需要边界填充?
边界填充的核心作用是扩展图像的边缘区域,其本质是通过特定规则补充边缘外的像素值,以解决以下问题:
-
卷积操作的边缘损失:卷积核在滑动到图像边缘时,部分区域会超出图像范围,若不填充会导致边缘像素被过度削弱(如特征提取时边缘信息丢失)。
-
图像拼接的连续性:多幅图像拼接时,直接拼接可能因边缘不匹配出现"硬边界",填充后可通过平滑过渡提升拼接效果。
-
可视化需求:展示图像时,添加边框可突出主体(如目标检测中给识别到的物体加边框)。
-
数据增强:训练深度学习模型时,通过随机填充扩展数据集,提升模型对边缘场景的鲁棒性。
1.2 OpenCV边界填充函数:cv2.copyMakeBorder()
OpenCV提供cv2.copyMakeBorder()
函数实现边界填充,其语法如下:
cv2.copyMakeBorder(src, top, bottom, left, right, borderType, value)
参数说明:
-
src
:输入图像(原始图像); -
top/bottom/left/right
:上/下/左/右四个方向的填充宽度(单位:像素); -
borderType
:填充类型(核心参数,决定填充规则); -
value
:当borderType=cv2.BORDER_CONSTANT
时,指定填充的常数颜色(BGR格式,如(255,0,0)为蓝色)。
1.3 五种填充类型及实战对比
OpenCV支持五种常用填充类型,不同类型的填充规则差异直接影响最终效果,我们通过实战案例(基于特朗普图像trump.png
)对比分析:
实战代码框架
import cv2# 读取图像并调整大小(便于显示)
tp = cv2.imread('trump.png')
tp = cv2.resize(tp, dsize=None, fx=0.5, fy=0.5) # 缩小为原尺寸的50%# 定义填充宽度(上下左右各50像素)
top, bottom, left, right = 50, 50, 50, 50# 生成五种填充效果
constant = cv2.copyMakeBorder(tp, top, bottom, left, right, borderType=cv2.BORDER_CONSTANT, value=(229,25,80)) # 常数填充
reflect = cv2.copyMakeBorder(tp, top, bottom, left, right, borderType=cv2.BORDER_REFLECT) # 反射填充
reflect_101 = cv2.copyMakeBorder(tp, top, bottom, left, right, borderType=cv2.BORDER_REFLECT_101) # 反射101填充
replicate = cv2.copyMakeBorder(tp, top, bottom, left, right, borderType=cv2.BORDER_REPLICATE) # 复制填充
wrap = cv2.copyMakeBorder(tp, top, bottom, left, right, borderType=cv2.BORDER_WRAP) # 环绕填充# 显示所有效果
cv2.imshow('原始图像', tp)
cv2.imshow('CONSTANT(常数填充)', constant)
cv2.imshow('REFLECT(反射填充)', reflect)
cv2.imshow('REFLECT_101(反射101填充)', reflect_101)
cv2.imshow('REPLICATE(复制填充)', replicate)
cv2.imshow('WRAP(环绕填充)', wrap)
cv2.waitKey(0)
cv2.destroyAllWindows()
'''--------------------------------边界填充--------------------------------'''
# cv2.copyMakeBorder() @OpenCV库中的一个函数,用于给图像添加额外的边界(padding)。
# copyMakeBorder(src:UMat, top: int, bottom: int, left: int, right: int, borderType: int, dst:UMat | None = ..., value: cv2.typing.Scalar = ...)
# 它有以下几个参数:
# src:要扩充边界的原始图像。# top, bottom, left, right:相应方向上的边框宽度。
# borderType:定义要添加边框的类型,它可以是以下的一种:
# cv2.BORDER_CONSTANT:添加的边界框像素值为常数(需要额外再给定一个参数)
# cv2.BORDER_REFLECT:添加的边界像素将是边界元素的镜面反射,类似'fgfedcba|abcdefgh|hgfedcba'。(交界处也复制了)
# cv2.BORDER_REFLECT_101 或 cv2.BORDER_DEFAULT,和上面类似,但是有一些细微的不同,类似'gfedcba|abcdefgh|gfedcba'(交接处删除了)
# cv2.BORDER_REPLICATE:使用最边界的像素值代替,类似'aaaaaa|abcdefgh|hhhhhhh'
# cv2.BORDER_WRAP,上下左右边依次替换,cdefgh|abcdefgh|abcdfe
代码实现效果:
填充类型详解
-
cv2.BORDER_CONSTANT
:常数填充填充区域的像素值为指定常数(
value
参数),效果类似给图像加"纯色边框"。适用场景:需要明确区分图像内外的场景(如目标检测的边界框、图像标注时的背景隔离)。
示例中用
(229,25,80)
(深红色)填充,边框与原图对比明显,便于直观区分填充区域。 -
cv2.BORDER_REFLECT
:反射填充填充区域为原图边缘像素的"镜面反射",规则类似
fgfedcba|abcdefgh|hgfedcba
(|
为原图边缘)。即从原图边缘开始,反向复制像素(如原图边缘是a
,填充区依次为b→c→d...
的反向)。适用场景:需要保持边缘连续性的场景(如纹理图像的扩展、无缝拼接)。例如卫星图像拼接时,反射填充可避免拼接处出现纹理断层。
-
cv2.BORDER_REFLECT_101
(cv2.BORDER_DEFAULT
):反射101填充与
REFLECT
类似,但反射起点为边缘内侧像素,规则类似gfedcba|abcdefgh|gfedcba
。相比REFLECT
,它删除了边缘处的重复像素(原图边缘a
不参与反射,从b
开始),避免了边缘像素的过度重复。适用场景:对边缘连续性要求更高的场景(如卷积神经网络的
SAME
填充,确保卷积后尺寸不变)。OpenCV官方推荐优先使用该类型作为默认填充。 -
cv2.BORDER_REPLICATE
:复制填充填充区域重复原图最边缘的像素,规则类似
aaaaaa|abcdefgh|hhhhhhh
。即所有填充像素均与最近的边缘像素一致。适用场景:希望填充区域与边缘"无缝融合"的场景(如老照片修复中补充残缺边缘、医学图像的边缘扩展)。例如CT图像边缘往往是连续的灰度变化,复制填充可避免引入突兀的新像素。
-
cv2.BORDER_WRAP
:环绕填充填充区域使用图像另一端的像素"环绕"补充,规则类似
cdefgh|abcdefgh|abcdef
。即左侧填充右侧像素,右侧填充左侧像素,上侧填充下侧像素,下侧填充上侧像素。适用场景:周期性图像的扩展(如纹理图案设计、卫星图像的全球拼接)。例如地球卫星图像是球形展开的,环绕填充可模拟球面连续性。
1.4 边界填充的实际应用场景
-
卷积神经网络的输入预处理:在CNN中,
SAME
卷积要求输入输出尺寸一致,此时需通过REFLECT_101
填充补充边缘,避免特征丢失; -
图像拼接:多幅图像拼接前,用
REFLECT
或WRAP
填充边缘,可减少拼接处的"硬过渡"; -
目标检测的边界框扩展:检测到目标后,用
CONSTANT
填充扩展边界框,便于后续的目标裁剪与分析; -
数据增强:训练模型时,随机选择填充类型扩展图像,提升模型对不同边缘场景的适应性。
二、阈值处理:从灰度图像中提取关键信息
在图像中,像素的灰度值(0-255)反映了亮度信息。阈值处理(Thresholding)通过设定灰度阈值,将图像转换为二值化或特定灰度分布的图像,从而分离出感兴趣区域(如目标与背景)。它是图像分割、特征提取的基础操作。
2.1 阈值处理的核心需求
实际应用中,我们常需要从复杂图像中提取特定区域:
-
工业质检中,通过阈值分割识别产品表面的缺陷(缺陷与正常区域灰度差异明显);
-
OCR识别中,将文字从背景中分离(文字与背景的灰度值不同);
-
医学影像中,通过阈值提取病灶区域(病灶与健康组织的灰度差异)。
阈值处理的本质是根据灰度值对像素进行分类:将高于/低于阈值的像素赋予特定值,实现"简化图像信息"的目的。
2.2 OpenCV阈值处理函数:cv2.threshold()
OpenCV的cv2.threshold()
函数实现阈值处理,语法如下:
retval, dst = cv2.threshold(src, thresh, maxval, type)
参数说明:
-
src
:输入图像(需为单通道灰度图或多通道图,实际中常用灰度图); -
thresh
:设定的阈值(0-255之间的整数); -
maxval
:当type
为THRESH_BINARY
或THRESH_BINARY_INV
时,指定最大值(通常为255); -
type
:阈值处理类型(核心参数,决定分类规则); -
返回值:
retval
为设定的阈值(与thresh
一致),dst
为处理后的图像。
2.3 五种阈值类型及实战效果
阈值类型决定了像素值与阈值的比较规则,我们通过特朗普灰度图(trump.png
)的处理案例,详解五种类型的差异:
实战代码框架
import cv2# 读取灰度图(flags=0表示灰度模式)
image = cv2.imread('trump.png', flags=0)# 不同类型的阈值分割(阈值设为175,maxval=255)
ret, binary = cv2.threshold(image, thresh=175, maxval=255, type=cv2.THRESH_BINARY)
ret1, binaryinv = cv2.threshold(image, thresh=175, maxval=255, type=cv2.THRESH_BINARY_INV)
ret2, trunc = cv2.threshold(image, thresh=175, maxval=255, type=cv2.THRESH_TRUNC)
ret3, tozero = cv2.threshold(image, thresh=175, maxval=255, type=cv2.THRESH_TOZERO)
ret4, tozeroinv = cv2.threshold(image, thresh=175, maxval=255, type=cv2.THRESH_TOZERO_INV)# 显示结果
cv2.imshow('原图(灰度)', image)
cv2.waitKey(0)
cv2.imshow('THRESH_BINARY', binary)
cv2.waitKey(0)
cv2.imshow('THRESH_BINARY_INV', binaryinv)
cv2.waitKey(0)
cv2.imshow('THRESH_TRUNC', trunc)
cv2.waitKey(0)
cv2.imshow('THRESH_TOZERO', tozero)
cv2.waitKey(0)
cv2.imshow('THRESH_TOZERO_INV', tozeroinv)
cv2.waitKey(0)
cv2.destroyAllWindows()
#$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
# -------------------阈值处理-------------------
# 阈值处理是指删除图像内像素值高于/低于一定值的像素点,使用方法:
# retval,dst=cv2.threshold(src,thresh,maxval,type)
# retval:返回的阈值
# dst:阈值分割结果图像(与原图大小、类型相同)
# src:要分割的图像(多通道、8位/32位浮点型均可)
# thresh:设定的阈值
# maxval:type为THRESH_BINARY/THRESH_BINARY_INV时需设的最大值
# type:阈值分割类型,规则如下:
# 选项 像素值>thresh 其他情况
# cv2.THRESH_BINARY maxval 0
# cv2.THRESH_BINARY_INV 0 maxval
# cv2.THRESH_TRUNC thresh 当前灰度值
# cv2.THRESH_TOZERO 当前灰度值 0
# cv2.THRESH_TOZERO_INV 0 当前灰度值
代码实现效果:
阈值类型详解
类型 | 像素值 > thresh 时 | 其他情况 | 效果描述 | 适用场景 |
---|---|---|---|---|
| 设为 | 设为0 | 亮区域变白,暗区域变黑 | 提取亮目标(如白纸上的黑字反转为白字黑底) |
| 设为0 | 设为 | 亮区域变黑,暗区域变白 | 提取暗目标(如黑背景上的亮物体) |
| 设为 | 保持原灰度 | 过亮区域被"截断"为阈值,暗区域不变 | 抑制高光(如处理过曝图像) |
| 保持原灰度 | 设为0 | 亮区域保留,暗区域变黑 | 保留高亮特征(如金属表面的反光区域) |
| 设为0 | 保持原灰度 | 亮区域变黑,暗区域保留 | 保留暗特征(如夜景中的阴影区域) |
-
THRESH_BINARY
:二值化(亮变白,暗变黑)高于阈值的像素被赋予最大值(255,白色),低于阈值的为0(黑色)。例如阈值175时,原图中亮度较高的区域(如面部高光)变为纯白,较暗区域(如头发)变为纯黑。
典型应用:OCR中文字与背景的分离(假设文字为暗、背景为亮,处理后文字为黑、背景为白)。
-
THRESH_BINARY_INV
:反二值化(亮变黑,暗变白)与
THRESH_BINARY
相反,高于阈值的为0(黑色),低于阈值的为255(白色)。例如处理黑底白字的图像时,可将文字转为白色、背景转为黑色,便于后续识别。典型应用:工业质检中,识别黑色传送带(暗)上的白色缺陷(亮),反二值化后缺陷为白、背景为黑,易于检测。
-
THRESH_TRUNC
:截断(过亮区域被限制为阈值)高于阈值的像素被"截断"为阈值(如175),低于阈值的保持原灰度。例如过曝图像中,过亮的区域(>175)被限制为175,避免因过亮导致细节丢失,同时保留暗区域细节。
典型应用:相机过曝照片的修复,平衡高光区域的亮度。
-
THRESH_TOZERO
:保留亮区域(暗区域归零)高于阈值的像素保持原灰度,低于阈值的为0(黑色)。例如保留图像中亮度足够的区域(如阳光下的物体),过滤暗区域(如阴影)。
典型应用:遥感图像中提取白天区域(亮),过滤夜间区域(暗)。
-
THRESH_TOZERO_INV
:保留暗区域(亮区域归零)高于阈值的像素为0(黑色),低于阈值的保持原灰度。例如保留图像中的暗区域(如夜景中的星空),过滤亮区域(如灯光)。
典型应用:天文摄影中提取星空(暗),去除地面灯光(亮)的干扰。
2.4 阈值选择的技巧
阈值(thresh
)的选择直接影响处理效果,实际中可通过以下方法确定:
-
手动试错:根据图像视觉效果调整(如示例中用175,适合中等亮度图像);
-
Otsu算法:自动计算最佳阈值(通过
cv2.THRESH_OTSU
参数,适用于双峰灰度分布图像); -
自适应阈值:对图像不同区域使用不同阈值(
cv2.adaptiveThreshold()
,适用于光照不均的图像)。
示例中选择175作为阈值,是因为特朗普图像的面部亮度中等,175可有效区分面部(偏亮)与头发(偏暗)区域。
三、基本图像运算:实现图像的融合与变换
图像由像素组成,像素值的运算(如加减、加权)是实现图像融合、亮度调整、特效叠加的基础。OpenCV提供了多种图像运算接口,掌握这些运算规则可实现丰富的视觉效果。
3.1 图像运算的核心需求
实际应用中,图像运算的需求无处不在:
-
图像融合:将多幅图像叠加(如给照片添加水印、将白天与夜景图像融合生成HDR);
-
亮度调整:通过像素值加减改变图像明暗(如增强逆光照片的亮度);
-
动态特效:视频中叠加动态字幕、弹幕,本质是像素值的实时运算。
3.2 图像加法运算:+
与cv2.add()
的区别
图像加法是最基础的运算,但其规则与数学加法不同(需考虑像素值的范围0-255)。OpenCV中存在两种加法方式:Python原生+
运算和cv2.add()
函数,二者的差异主要体现在溢出处理上。
实战代码:两种加法的对比
实现1:
import cv2# 读取两张图像(假设为trump.png和xjy.png)
a = cv2.imread('trump.png')
b = cv2.imread('xjy.png')# 1. 单图像加法:给像素值加常数(调整亮度)
c = a + 10 # 每个像素值+10(亮度提升)
cv2.imshow('原图', a) #这里留了一个小坑哦,imshow方法中标题尽量不要用中文,图片
cv2.imshow('a+10(亮度提升)', c) #imshow不像视频的imread,读取不了很多中文,标题会乱码的哦
cv2.waitKey(0)# 2. 两图像加法:需先调整尺寸一致
b = cv2.resize(b, dsize=(400, 400)) # 调整为400x400
a = cv2.resize(a, dsize=(400, 400))
# 区域加法:仅对图像的部分区域(50:300行,50:360列)相加
c_region = a[50:300, 50:360] + b[50:300, 50:360]
cv2.imshow('区域加法(a+b)', c_region)
cv2.waitKey(0)# 3. cv2.add()函数加法
c_add = cv2.add(a, b)
cv2.imshow('cv2.add(a,b)', c_add)
cv2.waitKey(0)
cv2.destroyAllWindows()
实现2:
'''--------------------------------图像运算--------------------------------'''
# 图像加法运算
# 对于+号运算,当对图像a、图像b进行加法求和时,遵循以下规则:
# 当某位置像素相加得到的数值小于255时,该位置数值为两图像该位置像素相加之和
# 当某位置像素相加得到的数值大于255时,该位置数值将截断结果并将其减去 256 例如:相加后是260,实际是260-256 = 4import cv2
a = cv2.imread('trump.png')
b = cv2.imread('xjy.png')
print(a.shape,b.shape)
c = a+10 # 图片
cv2.imshow( 'tp',a)
cv2.imshow( 'a+10',c)
cv2.waitKey(0)# 调整a的大小以匹配b的大小
# a = cv2.resize(a, (b.shape[1], b.shape[0]))
b = cv2.resize(b, dsize=(400,400))
a = cv2.resize(a, dsize=(400,400))
c = a[50:300,50:360]+b[50:300,50:360]
cv2.imshow('a+b',c)
cv2.waitKey(0)# 对于cv2.add()运算,当对图像a、图像b进行加法求和时,遵循以下规则:
# 当某位置像素相加得到的数值小于255时,该位置数值为两图像该位置像素相加之和
# 当某位置像素相加得到的数值大于255时,该位置数值为255a = cv2.imread('trump.png')
b = cv2.imread('xjy.png')
b = cv2.resize(b, dsize=(400,400))
a = cv2.resize(a, dsize=(400,400))
c = cv2.add(a,b) # 也可以使用
cv2.imshow('a add b',c)
cv2.waitKey(0)
代码2实现效果:
加法规则对比
运算方式 | 像素和 ≤ 255 时 | 像素和 > 255 时 | 本质 |
---|---|---|---|
| 像素和 | (像素和) - 256(循环溢出) | 模256加法(类似8位无符号整数溢出) |
| 像素和 | 255(截断溢出) | 饱和加法(超过最大值取最大值) |
-
+
运算:遵循"循环溢出"规则。例如像素值250 + 10 = 260,因260 > 255,结果为260 - 256 = 4(类似260 mod 256)。这种规则可能导致图像出现异常色调(如亮区域突然变暗),实际中较少直接使用。 -
cv2.add()
:遵循"饱和溢出"规则。例如250 + 10 = 260,结果为255(最大值),避免了色调突变,更符合人眼视觉习惯,是图像融合的首选方式。
3.3 图像加权运算:cv2.addWeighted()
实现平滑融合
简单加法会导致图像过度叠加(如两张图像亮区域叠加后过曝),而加权运算通过给不同图像分配权重,实现平滑融合,公式为:
dst = src1 * alpha + src2 * beta + gamma
其中:alpha
、beta
为权重(α + β 通常为1,避免亮度异常),gamma
为亮度调整值(常数)。
实战代码:加权融合
import cv2a = cv2.imread('trump.png')
b = cv2.imread('xjy.png')# 调整尺寸一致
b = cv2.resize(b, dsize=(400, 400))
a = cv2.resize(a, dsize=(400, 400))# 加权融合:a占20%,b占80%,亮度补偿10
c = cv2.addWeighted(a, 0.2, b, 0.8, 10)
cv2.imshow('加权融合(0.2a+0.8b+10)', c)
cv2.waitKey(0)
cv2.destroyAllWindows()
代码实现效果
加权运算的应用场景
-
水印添加:给原图(
src1
)赋予高权重(如0.9),水印图(src2
)赋予低权重(如0.1),既保留原图又显示水印; -
HDR合成:将曝光不足(暗)和曝光过度(亮)的图像按权重融合,保留暗部和亮部细节;
-
图像过渡特效:视频中实现两张图像的平滑切换(通过动态调整α和β的比值,从0→1或1→0)。
示例中alpha=0.2
、beta=0.8
,表示图像b
的占比更高,gamma=10
用于提升整体亮度,避免融合后过暗。
3.4 图像运算的注意事项
-
尺寸一致:参与运算的图像必须尺寸相同(宽、高一致),否则会报错(可通过
cv2.resize()
调整); -
通道数一致:灰度图(单通道)只能与灰度图运算,RGB图(3通道)只能与RGB图运算;
-
数据类型:OpenCV默认像素值为8位无符号整数(0-255),运算时需避免溢出(优先用
cv2.add()
和addWeighted()
)。
基本运算部分代码及注释整合:
'''--------------------------------图像运算--------------------------------'''
# 图像加法运算
# 对于+号运算,当对图像a、图像b进行加法求和时,遵循以下规则:
# 当某位置像素相加得到的数值小于255时,该位置数值为两图像该位置像素相加之和
# 当某位置像素相加得到的数值大于255时,该位置数值将截断结果并将其减去 256 例如:相加后是260,实际是260-256 = 4import cv2
a = cv2.imread('trump.png')
b = cv2.imread('xjy.png')
print(a.shape,b.shape)
c = a+10 # 图片
cv2.imshow( 'tp',a)
cv2.imshow( 'a+10',c)
cv2.waitKey(0)# 调整a的大小以匹配b的大小
# a = cv2.resize(a, (b.shape[1], b.shape[0]))
b = cv2.resize(b, dsize=(400,400))
a = cv2.resize(a, dsize=(400,400))
c = a[50:300,50:360]+b[50:300,50:360]
cv2.imshow('a+b',c)
cv2.waitKey(0)# 对于cv2.add()运算,当对图像a、图像b进行加法求和时,遵循以下规则:
# 当某位置像素相加得到的数值小于255时,该位置数值为两图像该位置像素相加之和
# 当某位置像素相加得到的数值大于255时,该位置数值为255a = cv2.imread('trump.png')
b = cv2.imread('xjy.png')
b = cv2.resize(b, dsize=(400,400))
a = cv2.resize(a, dsize=(400,400))
c = cv2.add(a,b) # 也可以使用
cv2.imshow('a add b',c)
cv2.waitKey(0)# 图像加权运算
# 就是在计算两幅图像的像素值之和时,将每幅图像的权重考虑进来,可以用公式表示为dst=src1*a+src*b+γ
a = cv2.imread('trump.png')
b = cv2.imread('xjy.png')
b = cv2.resize(b, dsize=(400,400))
a = cv2.resize(a, dsize=(400,400))# 10:图像的亮度值(常数),将添加到加权和上
c = cv2.addWeighted(a, 0.2,b, 0.8, 10)
cv2.imshow( 'addWeighted',c)
cv2.waitKey(0)
cv2.destroyAllWindows()
四、图像平滑处理:去除噪声,保留关键信息
图像采集或传输过程中,难免会引入噪声(如传感器噪声、传输干扰),导致图像质量下降。平滑处理(Smoothing)又称模糊处理(Blurring),通过抑制噪声像素实现图像降噪,同时尽可能保留有用信息(如边缘、纹理)。
4.1 噪声的类型与平滑处理的目标
常见的图像噪声包括:
-
椒盐噪声:随机出现的黑白点(如老照片的斑点、传感器故障导致的像素异常);
-
高斯噪声:像素值围绕均值呈正态分布的随机噪声(如低光环境下的相机噪点);
-
随机噪声:无规律的灰度波动(如传输过程中的信号干扰)。
平滑处理的核心是通过邻域像素的统计特性(如均值、中值)替换噪声像素,但需平衡"降噪效果"与"细节保留":过度平滑会模糊边缘,不足则无法有效降噪。
4.2 均值滤波:cv2.blur()
实现邻域平均
均值滤波是最基础的平滑方法,通过计算像素邻域的平均值替换中心像素,公式为:
dst(x,y) = (1/(k*k)) * Σ(src(x+i,y+j)) (i,j范围为-k/2到k/2,k为滤波核大小)
滤波核(卷积核)越大,平滑效果越强(但边缘越模糊)。
实战代码:均值滤波处理椒盐噪声
原图(mjq1.png)(下文mjq1.png均相同):
涉及到肖像权,脸部做了打码处理,理解万岁,哈哈
加入椒盐噪声后的图片(noise)(下文noise均相同):
均值滤波处理代码实现:
import cv2
import numpy as np# 定义添加椒盐噪声的函数
def add_peppersalt_noise(image, n=10000):result = image.copy()h, w = image.shape[:2]for i in range(n):x = np.random.randint(1, h) # 随机行坐标y = np.random.randint(1, w) # 随机列坐标# 50%概率为黑点(0),50%为白点(255)if np.random.randint(0, 2) == 0:result[x, y] = 0else:result[x, y] = 255return result# 读取图像并添加噪声
if __name__ == '__main__':image = cv2.imread('mjq1.png')image = cv2.resize(image, (400, 400))cv2.imshow('原图', image)cv2.waitKey(0)noise = add_peppersalt_noise(image) # 添加椒盐噪声cv2.imshow('加噪后', noise)cv2.waitKey(0)# 均值滤波(3x3和63x3核对比)blur_1 = cv2.blur(noise, ksize=(3, 3)) # 小核:平滑弱,保留较多细节cv2.imshow('均值滤波(3x3)', blur_1)cv2.waitKey(0)blur_2 = cv2.blur(noise, ksize=(63, 63)) # 大核:平滑强,图像模糊严重cv2.imshow('均值滤波(63x63)', blur_2)cv2.waitKey(0)cv2.destroyAllWindows()
代码实现效果:
因为原图涉及到某星星,未打码效果图只能作者自己欣赏喽,嘿嘿
均值滤波的特点与适用场景
-
优点:计算简单,适合处理随机噪声和轻度高斯噪声;
-
缺点:对椒盐噪声效果差(噪声像素会拉低/拉高平均值),且容易模糊边缘;
-
适用场景:对边缘细节要求不高的场景(如背景模糊处理、降低图像分辨率)。
示例中3x3核的滤波效果适中,既能减少部分噪声,又保留了主体轮廓;63x63核过度平滑,导致图像严重模糊。
4.3 方框滤波:cv2.boxFilter()
的归一化控制
方框滤波是均值滤波的通用形式,通过normalize
参数控制是否归一化:
-
normalize=True
:与均值滤波完全一致(邻域像素求和后除以核面积); -
normalize=False
:邻域像素直接求和(可能导致像素值溢出,需谨慎使用)。
实战代码:方框滤波对比
# 接上述代码,添加方框滤波部分
boxFilter_1 = cv2.boxFilter(noise, -1, ksize=(3, 3), normalize=True) # 归一化(等价于均值滤波)
cv2.imshow('方框滤波(归一化)', boxFilter_1)
cv2.waitKey(0)boxFilter_2 = cv2.boxFilter(noise, -1, ksize=(3, 3), normalize=False) # 不归一化(像素和可能溢出)
cv2.imshow('方框滤波(不归一化)', boxFilter_2)
cv2.waitKey(0)
cv2.destroyAllWindows()
代码实现效果:
不用手动打码了,哈哈哈,看来对于这里的椒盐噪声,方框滤波方法也有点不合适呢
方框滤波的特点
-
normalize=True
时,与均值滤波效果一致,适合常规平滑; -
normalize=False
时,像素值可能超过255(如亮区域求和后溢出),导致图像发白,仅用于特定场景(如提取图像的亮区域)。
4.4 高斯滤波:cv2.GaussianBlur()
保留更多细节
高斯滤波考虑邻域像素的距离权重:距离中心越近的像素权重越大,符合高斯分布(正态分布),公式为:
G(x,y) = (1/(2πσ²)) * e^(-(x²+y²)/(2σ²)) (σ为标准差,控制权重分布)
相比均值滤波,高斯滤波对边缘的模糊更轻,能保留更多细节。
实战代码:高斯滤波处理噪声
import cv2
import numpy as np
from 图像平滑处理 import add_peppersalt_noise # 导入上文自定义噪声函数image = cv2.imread('mjq1.png')
image = cv2.resize(image, (400, 400))
cv2.imshow('原图', image)
cv2.waitKey(0)noise = add_peppersalt_noise(image) # 添加椒盐噪声
cv2.imshow('加噪后', noise)
cv2.waitKey(0)# 高斯滤波(3x3核,标准差σ=1)
gaussian = cv2.GaussianBlur(noise, ksize=(3, 3), sigmaX=1)
cv2.imshow('高斯滤波(3x3,σ=1)', gaussian)
cv2.waitKey(0)
cv2.destroyAllWindows()
代码实现效果:
哥哥的绝美高斯滤波去噪图片一张,大哥大姐别sha我,哈哈哈:
高斯滤波的特点与适用场景
-
优点:权重随距离衰减,对边缘的模糊弱于均值滤波,适合处理高斯噪声;
-
缺点:对椒盐噪声效果有限(噪声像素的极端值仍会影响结果);
-
适用场景:需要保留边缘细节的平滑处理(如人像磨皮、减少相机低光噪点)。
示例中σ=1(标准正态分布),3x3核的高斯滤波在降噪的同时,比均值滤波更好地保留了图像的纹理细节。
从上面这张效果图片可以看出来,虽然高斯滤波算法好像更高级一点,但对于椒盐噪声,处理效果也不是很好哎,好像到现在介绍的三种图像平滑算法都不是很起效哎 , 到底什么样的算法对于椒盐噪声有奇效呢?
那么各位看官老爷们不要错过,接下来我们看本文最后一种去噪算法:中值滤波算法,让我们一起来还原哥哥真正的绝美神颜 ! :
4.5 中值滤波:cv2.medianBlur()
专治椒盐噪声
中值滤波与上述线性滤波不同,它通过取邻域像素的中值替换中心像素,而非平均值。这种非线性特性使其能有效过滤极端值(如椒盐噪声的0或255)。
实战代码:中值滤波去椒盐噪声
# 接上述高斯滤波代码,添加中值滤波部分
median = cv2.medianBlur(noise, ksize=3) # 3x3核中值滤波
cv2.imshow('中值滤波(3x3)', median)
cv2.waitKey(0)
cv2.destroyAllWindows()
代码实现效果:
太帅了哥哥这张中值滤波去椒盐噪声图,太帅了,不忍心打码,呜呜呜,绝美神颜:
中值滤波的特点与适用场景
-
优点:能有效去除椒盐噪声(极端值被中值取代),且对边缘的模糊轻于均值滤波;
-
缺点:计算量大于线性滤波(需排序邻域像素),对高斯噪声效果不如高斯滤波;
-
适用场景:椒盐噪声的去除(如老照片修复、监控视频去噪)。
示例中3x3核的中值滤波几乎完全消除了椒盐噪声,同时保留了图像的边缘和纹理,是处理椒盐噪声的首选方法。
4.6 四种滤波方法的对比总结
滤波方法 | 核心原理 | 对椒盐噪声 | 对高斯噪声 | 边缘保留 | 计算速度 | 适用场景 |
---|---|---|---|---|---|---|
均值滤波 | 邻域均值 | 差 | 中 | 差 | 快 | 随机噪声、轻度模糊 |
方框滤波 | 邻域求和(可归一化) | 差 | 中 | 差 | 快 | 等价于均值滤波或特定求和场景 |
高斯滤波 | 加权均值(高斯分布) | 中 | 好 | 中 | 中 | 高斯噪声、需保留细节 |
中值滤波 | 邻域中值 | 好 | 中 | 好 | 较慢 | 椒盐噪声、需保留边缘 |
上述代码整合:
# 图像平滑(smothing)也称为"模糊处理"(bluring)
# 通过消除图像中的噪声或细节来使图像看起来更为模糊,从而实现平滑效果
# 可以用来压制、弱化或消除图像中的细节、突变和噪声。
# 下面是常用的一些滤波器
# 均值滤波(邻域平均滤波)->blur函数
# 方框滤波->boxFilter函数
# 高斯滤波->GaussianBlur函数
# 中值滤波->medianBlur函数
# dst=cv2.blur(src,ksize,anchor,borderType)
# dst是返回值
# src是需要处理的图像
# kszzie是滤波核(卷积核)的大小
# anchor是锚点,默认值是(-1,-1)一般无需更改
# borderType是边界样式,一般无需更改
# 一般情况下,使用dst=cv2.blur(src,ksize)即可import cv2
import numpy as npdef add_peppersalt_noise(image, n=10000):result = image.copy()h, w = image.shape[:2]# 获取图片的高和宽for i in range(n):# 生成n个椒盐噪声x = np.random.randint(1, h)y = np.random.randint(1, w)if np.random.randint(0, 2) == 0:result[x, y] = 0else:result[x,y] = 255return resultif __name__=='main':#如果不写这句,在平滑处理之高斯滤波与中值滤波.py中import该文件会讲以下代码都运行一遍1image = cv2.imread('mjq1.png')image=cv2.resize(image,(400,400))cv2.imshow('mjq.png', image)cv2.waitKey(0)noise = add_peppersalt_noise(image)cv2.imshow('noise',noise)cv2.waitKey(0)## # 1、均值滤波blurblur_1 = cv2.blur(noise,ksize=(3,3)) #卷积核为3,3 效果一般,清晰度一般cv2.imshow('blur_1',blur_1)cv2.waitKey(0)blur_2 = cv2.blur(noise,ksize=(63,63))cv2.imshow('blur_2',blur_2)cv2.waitKey(0)## # # # # ## # # # # # # dst=cv2.boxFilter(src,ddepth,ksize,anchor,normalize,borderType) 式中:# # # # # # # ● dst是返回值,表示进行方框滤波后得到的处理结果。# # # # # # # ● src 是需要处理的图像,即原始图像。# # # # # # # ● ddepth是处理结果图像的图像深度,一般使用-1表示与原始图像使用相同的图像深度。(可以理解为数据类型)# # # # # # ## # # # # # # ● ksize 是滤波核的大小。滤波核大小是指在滤波处理过程中所选择的邻域图像的高度和宽度。# # # # # # ## # # # # # # ● normalize 表示在滤波时是否进行归一化。# # # # # # ## # # # # #boxFilter_1 = cv2.boxFilter(noise,-1,ksize=(3,3),normalize=True) #2、方框滤波cv2.imshow('boxFilter_1',boxFilter_1)cv2.waitKey(0)boxFilter_2 = cv2.boxFilter(noise,-1,ksize=(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,则会创建一个新的数组来存储结果。import cv2
import numpy
from 图像平滑处理 import add_peppersalt_noiseimage = cv2.imread('mjq1.png')
image=cv2.resize(image,(400,400))
cv2.imshow('mjq.png', image)
cv2.waitKey(0)
noise = add_peppersalt_noise(image)
cv2.imshow('noise',noise)
cv2.waitKey(0)GaussianB = cv2.GaussianBlur(noise, ksize=(3,3), sigmaX=1) # 标准差为1,标准正太分布。3、高斯滤波cv2.imshow('GaussianBlur', GaussianB)
cv2.waitKey(0)# cv2.medianBlur(src, ksize[, dst]) 中值滤波# 参数说明:
# src: 输入图像。
# ksize: 滤波器的大小,它是一个整数,表示在水平和垂直方向上的像素数量。例如,5表示一个5x5的滤波器。
# dst: 输出图像,通常是一个NumPy数组。如果为None,则会创建一个新的数组来存储结果。medianB = cv2.medianBlur(noise, ksize=3) # 4、中值滤波cv2.imshow('medianBlur', medianB)
cv2.waitKey(0)
cv2.destroyAllWindows()
五、视频去噪:从动态帧中提取清晰画面
视频是连续的图像序列(帧),其噪声具有帧间相关性(同一位置的噪声可能持续多帧)。视频去噪需在静态图像去噪的基础上,考虑时间维度的连续性,避免帧间闪烁。
5.1 视频噪声的特点与去噪需求
视频噪声的来源与图像类似,但更复杂:
-
传感器噪声:摄像头硬件导致的随机噪点(尤其低光环境);
-
压缩噪声:视频压缩算法(如H.264)引入的块状噪声;
-
运动模糊:拍摄时运动导致的帧间模糊,可视为特殊噪声。
视频去噪的核心需求:
-
去除噪声,提升画面清晰度;
-
保持帧间一致性(避免去噪后画面闪烁);
-
实时性(尤其监控、视频会议等场景)。
5.2 视频去噪的实战实现:中值滤波的应用
中值滤波因对椒盐噪声的优秀处理能力和边缘保留特性,常被用于视频去噪。以下案例通过读取视频、添加椒盐噪声、中值滤波去噪,实现动态去噪效果。
实战代码:视频去噪流程
原视频(test.avi)截取:
原测试视频是一群路人在一个十字路口随机走动,打招呼聊天的时长1分18秒的彩色(BGR)视频:
实现1:
import cv2
import numpy as np# 定义添加椒盐噪声的函数(与图像平滑处理中一致)
def add_peppersalt_noise(image, n=10000):result = image.copy()h, w = image.shape[:2]for i in range(n):x = np.random.randint(1, h)y = np.random.randint(1, w)if np.random.randint(0, 2) == 0:result[x, y] = 0else:result[x, y] = 255return result# 视频处理函数
def process_video(video_path):# 打开视频文件(0表示摄像头,文件路径表示本地视频)cap = cv2.VideoCapture(video_path)while cap.isOpened():ret, frame = cap.read() # 读取一帧if not ret: # 读取完毕(ret为False)break# 调整帧尺寸(便于显示)frame = cv2.resize(frame, (400, 400))# 1. 添加椒盐噪声(模拟视频噪声)noisy_frame = add_peppersalt_noise(frame)# 2. 中值滤波去噪(5x5核,平衡去噪与细节)smoothed_frame = cv2.medianBlur(noisy_frame, 5)# 3. 三窗口显示(原图、加噪、去噪后)cv2.imshow('Original', frame)cv2.imshow('Noisy', noisy_frame)cv2.imshow('Smoothed', smoothed_frame)# 按空格键(ASCII=32)退出if cv2.waitKey(25) == 32:break# 释放资源cap.release()cv2.destroyAllWindows()# 处理测试视频(替换为实际视频路径)
process_video('test.avi')
实现2:
import cv2
import numpy as npdef add_peppersalt_noise(image, n=10000):"""添加椒盐噪声"""result = image.copy()h, w = image.shape[:2]# 获取图片的高和宽for i in range(n):# 生成n个椒盐噪声x = np.random.randint(1, h)y = np.random.randint(1, w)if np.random.randint(0, 2) == 0:result[x, y] = 0else:result[x, y] = 255return result# noisy_frame = frame.copy()# h, w = frame.shape[:2]# for _ in range(n):# x = np.random.randint(0, h)# y = np.random.randint(0, w)# noisy_frame[x, y] = [0, 0, 0] if np.random.rand() < 0.5 else [255, 255, 255]# return noisy_framedef process_video(video_path):"""处理视频流"""cap = cv2.VideoCapture(video_path)# # 创建三个可自动调整大小的窗口# cv2.namedWindow('Original', cv2.WINDOW_NORMAL)# cv2.namedWindow('Noisy', cv2.WINDOW_NORMAL)# cv2.namedWindow('Smoothed', cv2.WINDOW_NORMAL)while cap.isOpened():ret, frame = cap.read()if not ret:breakframe=cv2.resize(frame,(400,400))# 1. 添加椒盐噪声noisy_frame = add_peppersalt_noise(frame)# 2. 中值滤波去噪(最佳保留边缘)smoothed_frame = cv2.medianBlur(noisy_frame, 5)# 3. 三窗口显示cv2.imshow('Original', frame)cv2.imshow('Noisy', noisy_frame)cv2.imshow('Smoothed', smoothed_frame)if cv2.waitKey(25)==32:breakcap.release()cv2.destroyAllWindows()# 处理测试视频
process_video('test.avi')
代码实现效果截取:
5.3 视频去噪的关键细节
-
帧处理速度:
cv2.waitKey(25)
控制帧间隔(25ms≈40帧/秒,接近正常视频帧率),确保画面流畅; -
滤波核选择:5x5核比3x3核去噪效果更好,同时避免过度模糊(视频需要动态清晰度);
-
噪声模拟:通过
add_peppersalt_noise
函数模拟真实视频中可能出现的椒盐噪声,便于验证去噪效果; -
退出机制:按空格键退出,避免视频播放完毕后程序卡死。
5.4 视频去噪的进阶方向
-
时间域滤波:结合前后帧的信息(如
cv2.fastNlMeansDenoisingMulti()
),利用帧间相关性进一步降噪; -
自适应滤波:根据帧内噪声强度动态调整滤波核大小(噪声强时用大核,弱时用小核);
-
深度学习去噪:基于CNN的去噪模型(如DnCNN),在复杂噪声场景下效果优于传统方法。
总结:从基础到进阶的OpenCV图像处理之路
本文详细讲解了OpenCV中五种核心进阶操作:
-
边界填充:通过
copyMakeBorder
解决边缘信息缺失,支持多种填充规则; -
阈值处理:用
threshold
实现像素分类,是图像分割的基础; -
图像运算:掌握
+
、cv2.add()
、addWeighted()
的差异,实现图像融合; -
平滑滤波:对比均值、方框、高斯、中值滤波的优缺点,针对性处理不同噪声;
-
视频去噪:基于中值滤波实现动态帧去噪,兼顾实时性与效果。
这些操作是计算机视觉预处理的核心,后续的边缘检测、特征提取、目标跟踪等高级任务,都依赖于高质量的预处理结果。建议读者结合本文代码反复实践,体会不同参数对结果的影响,为更复杂的计算机视觉任务打下基础。
(全文约6800字)