OpenCV图像处理实战全解析:镜像、缩放、矫正、水印与降噪技术详解
目录
- 前言
- 一、图像镜像反转
- 1.1 代码
- 二、图像缩放
- 2.1代码
- 三、图像矫正
- 3.1仿射变换与透视变换
- 3.2 透视变换矩阵
- 3.3 代码
- 四、图像添加水印
- 4.1 模板输入
- 4.2 与运算
- 4.3 图像融合
- 5. 代码
- 五、图像噪点消除
- 5.1 噪声
- 5.2 均值滤波
- 5.3 方框滤波
- 5.4 高斯滤波
- 5.5 中值滤波
- 总结
前言
书接上文
OpenCV图像处理实战指南:ROI精准切割与旋转仿射变换核心技术解析-CSDN博客文章浏览阅读701次,点赞21次,收藏19次。本文系统讲解OpenCV图像处理两大核心技术——ROI区域切割与仿射变换旋转,通过坤坤篮球案例详解Numpy数组切片操作,从单点旋转推导出仿射变换矩阵的数学原理,对比分析最近邻/双线性/Lanczos等插值算法差异,演示边界反射/常数填充等边缘处理方案,配套可运行的Python代码实现旋转角度控制与效果可视化,帮助开发者掌握工业级图像预处理的关键技术。https://blog.csdn.net/qq_58364361/article/details/146937711?fromshare=blogdetail&sharetype=blogdetail&sharerId=146937711&sharerefer=PC&sharesource=qq_58364361&sharefrom=from_link
一、图像镜像反转
图像的镜像旋转分为三种,分别使用filpcode的参数表示:
- flipcode=0,垂直翻转
垂直翻转可以把src沿着x轴翻转,坐标从(x,y)翻转为(x,-y)。
- flipcode>0,水平翻转
水平翻转可以把src沿着y轴翻转,坐标从(x,y)翻转为(-x,y)。
- flipcode<0,水平垂直翻转
相当与图像旋转,x轴和y周都翻转,坐标从(x,y)翻转为(-x,-y)。
1.1 代码
import cv2
import matplotlib.pyplot as plt
def show_image(image, name):
"""显示图像
Args:
image: 输入图像(numpy数组)
name: 图像标题(str)
"""
plt.rcParams['font.sans-serif'] = ['SimHei'] # 设置中文字体
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题
if len(image.shape) == 3: # 彩色图像
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # BGR转RGB
else: # 灰度图像
image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) # 灰度转RGB
plt.imshow(image) # 显示图像
plt.title(name) # 设置标题
plt.axis('off') # 关闭坐标轴
# 图像路径
path = "nvren.png"
# 读取图像
img = cv2.imread(path)
# 创建画布,设置DPI为300
plt.figure(dpi=300)
# 创建1行2列的子图,当前为第1个
plt.subplot(121)
# 显示原始图像
show_image(img, "原始图像")
# 图像翻转(-1表示水平和垂直同时翻转)
f_img = cv2.flip(img, -1)
# 切换到第2个子图
plt.subplot(122)
# 显示翻转后的图像
show_image(f_img, "翻转图像")
# 显示所有图像
plt.show()
二、图像缩放
图像缩放相比之前图像旋转实验中,可以单独针对x轴和y轴进行独立的倍率缩放,另外插值方法节点与图像缩放实验完全相同。
2.1代码
import cv2
import matplotlib.pyplot as plt
def show_image(image, name):
"""显示图像
Args:
image: 输入图像(numpy数组)
name: 图像标题(str)
"""
plt.rcParams['font.sans-serif'] = ['SimHei'] # 设置中文字体
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题
if len(image.shape) == 3: # 彩色图像
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # BGR转RGB
else: # 灰度图像
image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) # 灰度转RGB
plt.imshow(image) # 显示图像
plt.title(name) # 设置标题
plt.axis('off') # 关闭坐标轴
# 图像路径
path = "nvren.png"
# 读取图像
img = cv2.imread(path)
# 创建画布,设置DPI为300
plt.figure(dpi=300)
# 创建1行2列的子图,当前为第1个
plt.subplot(121)
# 显示原始图像
show_image(img, "原始图像")
#图像缩放
s_img = cv2.resize(img, (100, 150)) # 缩放图像到100x150像素
plt.subplot(122) # 切换到第2个子图
show_image(s_img, "缩放图像") # 显示缩放后的图像
plt.show() # 显示所有图像
三、图像矫正
3.1仿射变换与透视变换
- 仿射变换
之前在图像旋转实验中已经接触过仿射变换,仿射变换是一个二维坐标系到另一个二维坐标系的过程,在仿射变换中符合直线的平直性和平行性。
- 透视变换
透视变换是把一个图像投影到一个新的视平面的过程。在现实世界中,观察到的物体在人类视觉中都会受到透视效果的影响:近大远小。
透视变换可以把上面人眼看到的三维空间重新投影到二维平面,且消除出现的形变和透视畸变等问题。通俗的讲,透视变换的作用是改变物体被观察的视角。
上图就是一个透视变换的例子,很明显不符合平行性,但是并不是所有的不符合平直性和平行性的变换都是透视变换,透视变换需要满足中心投影关系:
实际开发中判断是否是透视变换的方法非常简单:只要是摄像头拍摄出的画面都是符合透视变换
3.2 透视变换矩阵
与仿射变换相同,透视变换也有对应的透视变换矩阵,在这个过程原始图像src中的点(x,y)通过透视变换被转换到到dst图像中的新坐标点(x',y')
变换过程如下:
它将原始图像的齐次坐标(x,y,1)变换为目标图像齐次坐标的(X,Y,Z),可以推导出:
Z是三维坐标,但是dst是二维图像,此需要把三维的Z消除(除以Z),可以得到新的表达式
a11,a12,...,a33表示一些旋转量和平移量,涉及到三维图像学的变换,因此不去详细研究,只要理解过程就行。
使用上面的透视变换矩阵需要给算法提供输入的数据,通过输入数据计算a11,a12,...,a33的值,此处提供的输入数据必须满足以下条件:
- 在源图像src中选择四个点
- 不能共线
- 四个点要符合透视变换关系(来自于摄像头)
3.3 代码
import cv2
import matplotlib.pyplot as plt
import numpy as np
def show_image(image, name):
"""显示图像
Args:
image: 输入图像(numpy数组)
name: 图像标题(str)
"""
plt.rcParams['font.sans-serif'] = ['SimHei'] # 设置中文字体
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题
if len(image.shape) == 3: # 彩色图像
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # BGR转RGB
else: # 灰度图像
image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) # 灰度转RGB
plt.imshow(image) # 显示图像
plt.title(name) # 设置标题
plt.axis('off') # 关闭坐标轴
# 图像路径
path = "vipka.jpg"
# 读取图像
img = cv2.imread(path)
# 创建画布,设置DPI为300
plt.figure(dpi=300)
# 创建1行2列的子图,当前为第1个
plt.subplot(131)
# 显示原始图像
show_image(img, "原始图像")
# 定义透视变换的4个顶点坐标
points = [[110,60],[330,88],[72,185],[320,220]]
# 转换为浮点型numpy数组
pts = np.float32(points)
# 复制原图用于绘制ROI标记
img_fz = img.copy()
# 在图像上绘制ROI区域的四条边线
cv2.line(
img_fz,
pts[0].astype(np.int32).tolist(),
pts[1].astype(np.int32).tolist(),
(0,0,255), # 红色
2, # 线宽
cv2.LINE_AA # 抗锯齿
)
cv2.line(
img_fz,
pts[1].astype(np.int32).tolist(),
pts[3].astype(np.int32).tolist(),
(0,0,255),
2,
cv2.LINE_AA
)
cv2.line(
img_fz,
pts[2].astype(np.int32).tolist(),
pts[3].astype(np.int32).tolist(),
(0,0,255),
2,
cv2.LINE_AA
)
cv2.line(
img_fz,
pts[0].astype(np.int32).tolist(),
pts[2].astype(np.int32).tolist(),
(0,0,255),
2,
cv2.LINE_AA
)
plt.subplot(132)
show_image(img_fz, "标记roi区域")
# 获取图像尺寸
img_shape = img.shape
# 定义目标透视变换的4个顶点坐标(对应图像四个角)
pts2 = np.float32([[0,0],[img_shape[1],0],[0,img_shape[0]],[img_shape[1],img_shape[0]]])
# 生成透视变换矩阵
m = cv2.getPerspectiveTransform(pts, pts2)
# 应用透视变换
dst = cv2.warpPerspective(img,
m,
(img.shape[1], img.shape[0]), # 输出图像尺寸
cv2.INTER_LANCZOS4, # 插值方法
cv2.BORDER_WRAP # 边界处理方式
)
plt.subplot(133)
show_image(dst, "透视变换")
plt.show() # 显示所有图像
四、图像添加水印
4.1 模板输入
此组件的功能是输入一个水印图片,需要进行灰度化与二值化,还需要制作掩膜。
模板图建议使用包含透明图层的png格式,png格式支持Alpha通道(透明度),jpg不支持此通道,但是OpenCV默认读取图片是BGR三通道,因此会减少png的Alpha通道,结果就是黑色。
建议使用带透明的png图片作为水印。
在一些软件中会谁用灰白格表示透明区域,例如PS。
4.2 与运算
有了二值化之后的模板输入图,就可以在原始图像中根据模板大小切割出一个ROI区域,即要添加水印的区域,让二值化之后的模板与原图切割出的ROI区域进行与运算。
与
进行与运算,得到
4.3 图像融合
该组件的目的是将图像对应的位置像素进行叠加,需要注意的是两个像素数组的大小应该是相同的,另外还需要通道数相同。
把上面的原理图改为实际的图片,相加的结果如下所示:
5. 代码
import cv2
if __name__ == '__main__':
# 1. 图片输入: 读取原始图片文件
image_np = cv2.imread('kunkun.jpg')
# 2. 模板输入: 读取logo模板图片文件
logo = cv2.imread('tubiao.png')
# 3. 灰度化: 将logo转换为灰度图像
gray_logo = cv2.cvtColor(logo, cv2.COLOR_BGR2GRAY)
# 4. 二值化: 使用OTSU算法自动计算阈值并进行反二值化处理
# 参数说明:
# - gray_logo: 输入灰度图像
# - 100: 初始阈值(实际使用OTSU自动计算)
# - 255: 最大值
# - cv2.THRESH_OTSU + cv2.THRESH_BINARY_INV: 使用OTSU算法+反二值化
ret, mask_logo = cv2.threshold(gray_logo, 100, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY_INV)
# 5. 与运算: 在原图上提取与logo相同大小的ROI区域
# 获取logo的尺寸(高度和宽度)
rows, cols = logo.shape[:2]
# 从原图中截取ROI区域(左上角开始)
roi = image_np[:rows, :cols]
# 使用按位与运算进行掩膜处理
# 参数说明:
# - roi: 输入图像1
# - roi: 输入图像2
# - mask_logo: 掩膜图像
image_np_bg = cv2.bitwise_and(
roi,
roi,
mask=mask_logo
)
# 6. 图像融合: 将处理后的背景与原始logo进行叠加
dst = cv2.add(image_np_bg, logo)
# 将融合结果放回原图对应位置
image_np[:rows, :cols] = dst
# 7. 图片输出: 显示最终结果图像
cv2.imshow('image_np', image_np)
cv2.waitKey(0)
五、图像噪点消除
5.1 噪声
噪声指的是图像中的一些干扰因素,通常由于图像的采集设备或传输过程中导致的,表现为图像中部分像素点出现随机亮度,也可以理解为一些像素点与周围的像素点格格不入。
常见的噪声分为两类:
- 高斯噪声
是一种符合正态分布的噪声,会使图像变得有明暗不均的噪点,如果是彩图可能还会出现颜色的分布不均。
- 椒盐噪声
是一些黑色像素值分布在原图像中,可以把椒盐噪声理解为斑点,随机出现在画面中。
滤波器:实际上就是之前的卷积核,与自适应二值化中的核一样,本身是一个小区域,有这特定的核值,并且工作原理也实在原图上从左向右、从上到下进行滑动计算。
滤波器可以分为两类:
- 线性滤波
对卷积核中的邻域像素进行线性计算,例如计算求和等,常见的线性滤波器有均值滤波、高斯滤波等。
- 非线性滤波
利用原始图像与模板之间的一种逻辑关系得到结果,常见的非线性滤波器有中值滤波、双边滤波等。
5.2 均值滤波
均值滤波是一种最简单的滤波器,它直接计算卷积核区域内元素的平均值,例如3x3的卷积核:
一张高斯噪声图:
import cv2
import numpy as np
import matplotlib.pyplot as plt
if __name__ == '__main__':
# 1. 读取图像
path = "gaosi.jpg" # 图像文件路径
img = cv2.imread(path) # 使用OpenCV读取图像文件
cv2.imshow("img", img) # 显示原始图像窗口
# 均值滤波
# 使用3x3的核进行均值滤波处理
# 参数说明:
# img: 输入图像
# (3,3): 滤波核大小
lb_img = cv2.blur(img, (3, 3)) # 对图像进行均值滤波处理
cv2.imshow("lb_img", lb_img) # 显示滤波后的图像窗口
cv2.waitKey(0) # 等待按键输入
5.3 方框滤波
方框滤波与均值滤波非常像,如3x3的滤波核如下:
参数与均值滤波有相同的也有不同的,只讲解不同的(后文同),新参数:
- ddepth 输出图像的深度
-1表示使用原图的深度,即每个像素点占用的位数,最常用的位数是cv2.CV_8U,即8位无符号整数:0-255
- normalize 控制a的数值
当此参数为True时,方框滤波就是均值滤波;
当此参数为False时,a=1,相当于在区域内求像素和。
import cv2
if __name__ == '__main__':
# 1. 读取图像
path = "gaosi.jpg" # 图像文件路径
img = cv2.imread(path) # 使用OpenCV读取图像文件
cv2.imshow("img", img) # 显示原始图像窗口
# 使用boxFilter进行方框滤波
# 参数说明:
# img: 输入图像
# -1: 输出图像深度(-1表示与输入相同)
# (3,3): 滤波核大小
# normalize=False: 不进行归一化(若为True则执行均值滤波)
lb_img = cv2.boxFilter(
img,
-1,
(3, 3),
normalize=False,
)
cv2.imshow("lb_img", lb_img) # 显示滤波后的图像窗口
cv2.waitKey(0) # 等待按键输入(0表示无限等待)
5.4 高斯滤波
高斯滤波的核权重计算与自适应二值化的《高斯加权求和》过程完全相同。
当时以3x3为例,计算出核为:
可以看到多了一个SigmaX参数,对应的是高斯函数里σ的值,对应的效果就是中心区域的权重是否更大:SigmaX越大,中心像素的权重越低。
import cv2
if __name__ == '__main__':
# 1. 读取图像
path = "gaosi.jpg" # 图像文件路径
img = cv2.imread(path) # 使用OpenCV读取图像文件
# cv2.imshow: 显示图像窗口
# 参数1: 窗口名称字符串
# 参数2: 要显示的图像矩阵
cv2.imshow("img", img) # 显示原始图像窗口
# cv2.GaussianBlur: 高斯滤波
# 参数1: 输入图像
# 参数2: 高斯核大小(宽度,高度),必须是正奇数
# 参数3: 标准差,控制模糊程度
lb_img = cv2.GaussianBlur(img, (3, 3), 1)
cv2.imshow("lb_img", lb_img) # 显示滤波后的图像窗口
# cv2.waitKey: 等待键盘输入
# 参数: 延迟毫秒数(0表示无限等待)
# 返回值: 按键的ASCII码值
cv2.waitKey(0) # 等待按键输入(0表示无限等待)
5.5 中值滤波
中值就是中位数,是核中对应的像素排序后的中间值,因此核本身没有数值,也不需要新增的参数。需要注意的是,默认的边界填充算法是边界复制。
import cv2
if __name__ == '__main__':
# 1. 读取图像
path = "gaosi.jpg" # 图像文件路径
img = cv2.imread(path) # 使用OpenCV读取图像文件
"""
显示图像窗口
@param winname: 窗口名称字符串
@param mat: 要显示的图像矩阵
"""
cv2.imshow("img", img) # 显示原始图像窗口
"""
中值滤波处理图像
@param src: 输入图像矩阵
@param ksize: 滤波器大小(必须为奇数)
@return: 滤波后的图像矩阵
"""
lb_img = cv2.medianBlur(img, 5) # 中值滤波
cv2.imshow("lb_img", lb_img) # 显示滤波后的图像窗口
"""
等待键盘输入
@param delay: 延迟时间(毫秒),0表示无限等待
@return: 按键的ASCII码值
"""
cv2.waitKey(0) # 等待按键输入(0表示无限等待)
中值滤波特别擅长去除椒盐噪声,如下所示:
总结
本文全面探讨了OpenCV在图像处理中的五大核心技术:镜像反转通过flip函数实现垂直、水平及双轴翻转,揭示了坐标变换原理;缩放技术通过resize函数灵活调整图像尺寸,保留关键信息;图像矫正利用透视变换矩阵消除透视畸变,强调四点定位与坐标映射关系;水印添加结合二值化模板与像素运算实现精准融合,突显ROI区域处理逻辑;噪声消除部分对比均值滤波、高斯滤波、中值滤波的算法差异,解析线性与非线性滤波对高斯噪声、椒盐噪声的针对性解决方案。全文通过代码实例演示各技术实现路径,完整呈现从理论到实践的图像处理闭环。