主要算法说明
Sobel 算子
- 用于计算图像的梯度(边缘),分别检测 x 方向(水平)和 y 方向(垂直)的边缘
cv2.Sobel(yt, cv2.CV_64F, dx=1, dy=0)
:计算 x 方向梯度(dx=1, dy=0)cv2.Sobel(yt, cv2.CV_64F, dx=0, dy=1)
:计算 y 方向梯度(dx=0, dy=1)- 使用
cv2.CV_64F
是为了保留梯度的正负值(边缘可能是亮到暗或暗到亮) cv2.convertScaleAbs()
:将梯度值转换为绝对值并转为 8 位图像(便于显示)cv2.addWeighted()
:将 x 和 y 方向的边缘图像融合,得到完整边缘
Scharr 算子
- 是 Sobel 算子的改进版,在 3x3 卷积核下具有更高的精度,对边缘的检测更敏感
- 使用方式与 Sobel 类似:
cv2.Scharr(yt, cv2.CV_64F, dx=1, dy=0)
- 适用于需要更精确边缘检测的场景
Laplacian 算子
- 基于图像的二阶导数,对图像中的快速变化(边缘)更敏感
cv2.Laplacian(yt, cv2.CV_64F)
:直接计算图像的拉普拉斯变换- 对噪声较敏感,通常需要先进行平滑处理
Canny 边缘检测
- 一种多阶段的边缘检测算法,能有效抑制噪声并检测出真正的边缘
cv2.Canny(yt, 80, 120)
:两个阈值分别为低阈值和高阈值- 低于低阈值的像素被丢弃
- 高于高阈值的像素被保留为边缘
- 介于两者之间的像素,若与边缘像素相连则保留
这段代码主要使用 OpenCV 对指纹图像(zw1.png
和zw2.png
)进行了三种形态学操作:开运算、闭运算和梯度运算,用于图像预处理或特征提取。下面是逐部分的详细解释:
开运算(MORPH_OPEN)
zhiwen1 = cv2.imread('zw1.png') # 读取第一张指纹图像
cv2.imshow('zw1', zhiwen1) # 显示原始图像
cv2.waitKey(0) # 等待按键(按任意键继续)
# 定义2×2的结构元素(卷积核),类型为无符号整数
kernel = np.ones((2,2), np.uint8)
# 对图像执行开运算:先腐蚀后膨胀
zhiwen1_new = cv2.morphologyEx(zhiwen1, cv2.MORPH_OPEN, kernel)
cv2.imshow('zhiwen1_new', zhiwen1_new) # 显示开运算结果
cv2.waitKey(0)
- 开运算作用:先腐蚀后膨胀,主要用于去除图像中的小噪点、孤立的小区域,同时保留较大区域的形状和大小基本不变。
- 适合场景:如果
zw1.png
中有细小的噪声点(如指纹图像中的斑点),开运算可以有效清除这些噪声。
闭运算(MORPH_CLOSE)
zhiwen2 = cv2.imread('zw2.png') # 读取第二张指纹图像
cv2.imshow('zw2', zhiwen2) # 显示原始图像
cv2.waitKey(0)
# 定义4×4的结构元素(比开运算的核更大)
kernel = np.ones((4,4), np.uint8)
# 对图像执行闭运算:先膨胀后腐蚀
zhiwen2_new = cv2.morphologyEx(zhiwen2, cv2.MORPH_CLOSE, kernel)
cv2.imshow('zhiwen2_new', zhiwen2_new) # 显示闭运算结果
cv2.waitKey(0)
- 闭运算作用:先膨胀后腐蚀,主要用于填充图像中的小空洞、连接断裂的区域,适合修复图像中因噪声导致的不连续部分。
- 适合场景:如果
zw2.png
的指纹纹路有断裂或小缺口,闭运算可以连接这些纹路,使结构更完整(核尺寸越大,填充 / 连接效果越强)。
梯度运算(MORPH_GRADIENT)
zhiwen1 = cv2.imread('zw1.png') # 重新读取第一张图像
cv2.imshow('zhiwen1', zhiwen1)
cv2.waitKey(0)
kernel = np.ones((2,2), np.uint8)
# 单独执行膨胀操作(扩大图像中的亮区域)
pz_zhiwen1 = cv2.dilate(zhiwen1, kernel, iterations=1)
cv2.imshow('pz_zhiwen1', pz_zhiwen1)
cv2.waitKey(0)
# 单独执行腐蚀操作(缩小图像中的亮区域)
fs_zhiwen1 = cv2.erode(zhiwen1, kernel, iterations=1)
cv2.imshow('fs_zhiwen1', fs_zhiwen1)
cv2.waitKey(0)
# 执行形态学梯度运算:膨胀结果 - 腐蚀结果
td_zhiwen1 = cv2.morphologyEx(zhiwen1, cv2.MORPH_GRADIENT, kernel) # 注:原代码参数有误,修正为直接用原图
cv2.imshow("td_zhiwen1", td_zhiwen1)
cv2.waitKey(0)
cv2.destroyAllWindows() # 关闭所有窗口
- 梯度运算作用:通过计算 “膨胀结果 - 腐蚀结果”,提取图像中物体的边缘轮廓(因为边缘是膨胀和腐蚀变化最明显的区域)。
- 代码修正说明:原代码中
cv2.morphologyEx(fs_zhiwen1, ...)
参数有误,梯度运算应基于原图计算(正确应为cv2.morphologyEx(zhiwen1, ...)
),否则结果会是腐蚀后图像的梯度,而非原图梯度。 - 适合场景:在指纹识别中,梯度运算可用于提取指纹的纹路边缘,突出轮廓特征。
总结
这段代码通过三种形态学操作对指纹图像进行处理:
- 开运算:去噪、消除小干扰;
- 闭运算:修复断裂、填充空洞;
- 梯度运算:提取边缘轮廓。
图像读取与灰度化
phone = cv2.imread('zzd02.png') # 读取彩色图像(默认BGR格式)
phone_grey = cv2.cvtColor(phone, cv2.COLOR_BGR2GRAY) # 转为灰度图
cv2.imshow('phone_grey', phone_grey)
cv2.waitKey(0)
- 轮廓检测通常基于灰度图进行,灰度化可以简化计算(从 3 通道变为 1 通道)。
# 将灰度图转为二值图(黑白图像)
ret, phone_b = cv2.threshold(phone_grey, 120, 255, cv2.THRESH_BINARY)
cv2.imshow('phone_b', phone_b)
cv2.waitKey(0)
- 参数说明:
threshold
函数将灰度值大于 120 的像素设为 255(白色),小于等于 120 的设为 0(黑色)。 - 作用:二值化是轮廓检测的前提,能突出物体的轮廓边界(黑白对比明显)。
# 寻找图像中的所有轮廓
_, contours, hierarchy = cv2.findContours(phone_b, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
print(hierarchy) # 打印轮廓的层级关系
- 参数说明:
phone_b
:输入的二值图像(必须是单通道)。cv2.RETR_TREE
:轮廓检索模式,返回所有轮廓及其完整的层级关系(父子轮廓关系)。cv2.CHAIN_APPROX_SIMPLE
:轮廓近似方法,只保留轮廓的关键顶点(如矩形只保留 4 个角点),节省内存。
- 返回值:
contours
:轮廓列表,每个轮廓是由顶点坐标组成的数组。hierarchy
:轮廓的层级信息,描述轮廓之间的包含关系。
image_copy = phone.copy() # 复制原图(避免直接修改原图)
# 绘制所有轮廓(contourIdx=-1表示绘制全部)
cv2.drawContours(image=image_copy, contours=contours, contourIdx=-1, color=(0,255,0), thickness=2)
cv2.imshow('image_copy', image_copy)
cv2.waitKey(0)
- 参数说明:
color=(0,255,0)
是绿色(BGR 格式),thickness=2
是线宽。 - 作用:直观展示检测到的所有轮廓。
# 计算第1个轮廓的面积
area_0 = cv2.contourArea(contours[0])
print(area_0)
# 计算第2个轮廓的面积
area_1 = cv2.contourArea(contours[1])
print(area_1)
# 计算第1个轮廓的周长(closed=True表示轮廓是闭合的)
length = cv2.arcLength(contours[0], closed=True)
print(length)
cv2.contourArea
:返回轮廓包围的面积(像素单位)。cv2.arcLength
:返回轮廓的周长(像素单位),closed=True
表示将轮廓视为闭合曲线。
筛选大轮廓(面积大于 10000)
a_list = []
for i in contours:
# 只保留面积大于10000的轮廓(过滤小噪声轮廓)
if cv2.contourArea(i) > 10000:
a_list.append(i)
# 绘制筛选后的轮廓
image_copy = phone.copy()
cv2.drawContours(image=image_copy, contours=a_list, contourIdx=-1, color=(0,255,0), thickness=3)
cv2.imshow('contours_show_10000', image_copy)
cv2.waitKey(0)
- 应用场景:如果图像中只有一个主要物体(如手机、证件),可通过此方法快速定位其轮廓。
cnt = contours[2] # 选择第3个轮廓(可根据实际需求修改索引)
# 计算并绘制最小外接圆
(x, y), r = cv2.minEnclosingCircle(cnt) # (x,y)是圆心,r是半径
phone_circle = cv2.circle(phone, (int(x), int(y)), int(r), (0,255,0), 2)
cv2.imshow('phone_circle', phone_circle)
cv2.waitKey(0)
# 计算并绘制最小外接矩形(轴对齐)
x, y, w, h = cv2.boundingRect(cnt) # x,y是左上角坐标,w,h是宽和高
phone_rectangle = cv2.rectangle(phone, (x,y), (x+w, y+h), (0,255,0), 2)
cv2.imshow('phone rectangle', phone_rectangle)
cv2.waitKey(0)
- 外接圆:包围轮廓的最小圆形,适合圆形或近圆形物体的定位。
- 外接矩形:轴对齐的最小矩形(边与图像边框平行),适合快速定位物体的大致位置。
总结
- 图像预处理(灰度化→二值化);
- 轮廓提取与层级分析;
- 轮廓特征计算(面积、周长);
- 轮廓筛选(按面积)与可视化;
- 轮廓的几何拟合(外接圆、外接矩形)。
这段代码主要实现了对图像ipthon.png
的轮廓检测和轮廓近似(多边形拟合)操作,核心是通过cv2.approxPolyDP()
函数将复杂轮廓简化为近似的多边形。下面是详细解释:
phone = cv2.imread('ipthon.png') # 读取彩色图像
phone_gray = cv2.cvtColor(phone, cv2.COLOR_BGR2GRAY) # 转为灰度图
# 二值化:将灰度值>120的像素设为255(白),≤120设为0(黑)
ret, phone_thresh = cv2.threshold(phone_gray, 120, 255, cv2.THRESH_BINARY)
- 轮廓检测依赖二值图像(黑白对比),因此先将原图转为灰度图再二值化,突出物体轮廓。
# 提取所有轮廓,取返回值的倒数第二个(兼容不同OpenCV版本)
contours = cv2.findContours(phone_thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)[-2]
cv2.findContours()
返回轮廓列表contours
,这里用[-2]
确保在不同 OpenCV 版本中都能正确获取轮廓(避开可能的冗余返回值)。cv2.RETR_TREE
:保留轮廓的层级关系;cv2.CHAIN_APPROX_NONE
:保存轮廓所有顶点(不近似),适合后续精确拟合。
轮廓近似(多边形拟合)
# 计算轮廓的周长(contours[0]是第一个轮廓,假设是主要物体的轮廓)
epsilon = 0.01 * cv2.arcLength(contours[0], True)
# 用Douglas-Peucker算法近似轮廓为多边形
approx = cv2.approxPolyDP(contours[0], epsilon, True
核心参数解析:
contours[0]
:要近似的原始轮廓(假设是图像中最主要的轮廓)。epsilon
:近似精度(控制简化程度),这里设为轮廓周长的 1%(值越小,近似结果越接近原始轮廓)。True
:表示原始轮廓是闭合的,近似后的多边形也保持闭合。
作用:将复杂的轮廓(可能包含大量顶点)简化为顶点较少的多边形,例如将圆形轮廓近似为正多边形,将矩形轮廓简化为 4 个顶点的多边形。
# 打印原始轮廓和近似轮廓的顶点数量(shape[0]表示顶点数)
print(contours[0].shape) # 原始轮廓:(N, 1, 2),N是大量顶点
print(approx.shape) # 近似轮廓:(M, 1, 2),M通常远小于N
# 绘制近似后的多边形轮廓
phone_new = phone.copy() # 复制原图避免修改
image_contours = cv2.drawContours(phone_new, [approx], -1, (0,255,0), 3)
# 显示原图和近似轮廓结果
cv2.imshow('phone', phone)
cv2.waitKey(0)
cv2.imshow('image_contours', image_contours)
cv2.waitKey(0)
- 通过对比
contours[0].shape
和approx.shape
,可以直观看到顶点数量的减少(例如原始轮廓有 1000 个顶点,近似后可能只有 4 个,说明拟合为矩形)。 - 绘制的绿色轮廓是简化后的多边形,更清晰地展示物体的几何形状。
关键原理:Douglas-Peucker 算法
轮廓近似的核心是 Douglas-Peucker 算法,它通过不断迭代保留轮廓上的 “关键顶点”(距离轮廓线最远的点),删除冗余顶点,最终用最少的顶点表示轮廓形状。epsilon
值决定了简化程度:
epsilon
越小:保留的顶点越多,近似结果越接近原始轮廓;epsilon
越大:简化程度越高,顶点越少(可能过度简化导致形状失真)。
核心概念回顾
在看代码前,先明确两个操作的核心作用(均基于 “结构元素 /kernel” 对图像进行卷积):
- 腐蚀(Erode):缩小图像中的亮区域(白色),扩大暗区域(黑色),可用于去除小噪点、细化物体边缘。
- 膨胀(Dilate):扩大图像中的亮区域(白色),缩小暗区域(黑色),可用于填充物体内部小空洞、连接断裂边缘。
import cv2
import numpy as np
from cv2 import erode # 导入腐蚀函数(可省略,直接用cv2.erode)
# 1. 读取图像并显示原图
sun = cv2.imread("tp1.png") # 读取彩色图像(默认BGR格式)
cv2.imshow("yuantu", sun) # 显示原始图像
cv2.waitKey(0) # 等待按键(按任意键继续)
# 2. 定义结构元素(卷积核)
kernel = np.ones((3, 3), np.uint8) # 3×3的全1核,类型为无符号整数
# 结构元素是形态学操作的“工具”,3×3核表示每次以3×3的范围扫描图像
# 3. 执行腐蚀操作
erosion_1 = cv2.erode(
src=sun, # 输入图像
kernel=kernel, # 结构元素
iterations=2 # 腐蚀次数(次数越多,腐蚀效果越强)
)
# 4. 显示腐蚀结果
cv2.imshow('erosion_1', erosion_1)
cv2.waitKey(0)
效果分析:
假设tp1.png
中有白色的小噪点或较粗的物体边缘,经过 2 次 3×3 核的腐蚀后:
- 小噪点会被 “磨掉”(暗区域吞噬亮区域);
- 物体的边缘会变细(亮区域范围缩小);
- 图像整体亮度会略微降低(亮区域减少)
对 tp2.png
执行膨胀操作
# 1. 读取第二张图像并显示原图
sun1 = cv2.imread("tp2.png") # 读取第二张彩色图像
cv2.imshow('src1', sun1) # 显示原始图像
cv2.waitKey(0)
# 2. 定义结构元素
kernel = np.ones((2, 2), np.uint8) # 2×2的全1核(比腐蚀的核小)
# 2×2核的“作用范围”更小,膨胀效果会比3×3核更细腻
# 3. 执行膨胀操作
sun1_1 = cv2.dilate(
src=sun1, # 输入图像
kernel=kernel, # 结构元素
iterations=2 # 膨胀次数(次数越多,膨胀效果越强)
)
# 4. 显示膨胀结果并关闭窗口
cv2.imshow('sun1_1', sun1_1)
cv2.waitKey() # 等待按键(按任意键关闭)
cv2.destroyAllWindows() # 关闭所有OpenCV窗口,避免内存占用
- 效果分析:
假设tp2.png
中有黑色的小空洞(如物体内部的小黑点)或断裂的白色边缘,经过 2 次 2×2 核的膨胀后:- 小空洞会被 “填充”(亮区域吞噬暗区域);
- 断裂的边缘会被 “连接”(亮区域范围扩大);
- 图像整体亮度会略微升高(亮区域增加)。
图像读取与显示
import cv2
import numpy as np
sun = cv2.imread('tp1.png') # 读取彩色图像(默认BGR格式)
cv2.imshow('sun_yuantu', sun) # 显示原始图像
cv2.waitKey(0) # 等待按键(按任意键继续)
定义结构元素
kernel = np.ones((2, 2), np.uint8) # 2×2的全1结构元素(卷积核)
- 结构元素是形态学操作的 “工具”,2×2 核表示每次以 2×2 的范围扫描图像,核越小,操作对细节的敏感度越高(效果越细腻)。
顶帽运算(MORPH_TOPHAT)
tophat = cv2.morphologyEx(sun, cv2.MORPH_TOPHAT, kernel) # 执行顶帽运算
cv2.imshow('TOPHAT', tophat) # 显示顶帽运算结果
cv2.waitKey(0)
1)顶帽运算的定义
顶帽运算 = 原始图像 - 图像的开运算
其中,开运算 = 先腐蚀后膨胀(作用是去除亮区域中的小噪点,保留大亮区域的形状)。
(2)核心作用
突出原始图像中比周围背景亮、但尺寸小于结构元素的区域(这些区域会被开运算 “过滤掉”,用原图减去开运算结果后,就能单独提取出这些高亮小区域)。
(3)效果举例
如果 tp1.png
是一张 “暗背景 + 亮小物体” 的图像(如黑纸上的白色小点、夜景中的灯光),顶帽运算会让这些亮小物体更加突出,背景则会变暗甚至接近黑色。
黑帽运算(MORPH_BLACKHAT)
blackhat = cv2.morphologyEx(sun, cv2.MORPH_BLACKHAT, kernel) # 执行黑帽运算
cv2.imshow('BLACKHAT', blackhat) # 显示黑帽运算结果
cv2.waitKey(0)
1)黑帽运算的定义
黑帽运算 = 图像的闭运算 - 原始图像
其中,闭运算 = 先膨胀后腐蚀(作用是填充暗区域中的小空洞,保留大暗区域的形状)。
(2)核心作用
突出原始图像中比周围背景暗、但尺寸小于结构元素的区域(这些区域会被闭运算 “填充掉”,用闭运算结果减去原图后,就能单独提取出这些暗小区域)。
(3)效果举例
如果 tp1.png
是一张 “亮背景 + 暗小物体” 的图像(如白纸上的黑色小孔、人脸中的黑痣),黑帽运算会让这些暗小物体更加突出,背景则会变亮甚至接近白色。
import cv2
# 读取模板图像和目标图像
yanjing = cv2.imread('moban.png') # 模板图像(要寻找的“目标模板”,如某个小区域)
beiliya = cv2.imread('zzd02.png') # 目标图像(要在其中寻找模板的大图像)
# 显示原始图像,方便对比
cv2.imshow('yanjing', yanjing) # 显示模板
cv2.imshow('beiliya', beiliya) # 显示目标图像
cv2.waitKey(0) # 按任意键继续
- 关键前提:模板匹配要求 模板图像的尺寸 ≤ 目标图像的尺寸(否则无法在目标图像中滑动匹配),若模板比目标大,会直接匹配失败。
模板匹配核心步骤
# 1. 获取模板图像的高和宽(h=高,w=宽)
h, w = yanjing.shape[:2] # shape[:2] 取图像的前两个维度(高度、宽度),忽略通道数
# 2. 执行模板匹配
# 参数说明:
# - 输入1(yanjing):模板图像
# - 输入2(beiliya):目标图像
# - 输入3(cv2.TM_CCOEFF_NORMED):匹配算法(归一化相关系数匹配,结果范围[-1,1],1表示完全匹配)
# - 输出(res):匹配结果矩阵,每个元素代表“模板在目标图像对应位置的匹配程度”
res = cv2.matchTemplate(beiliya, yanjing, cv2.TM_CCOEFF_NORMED) # 注意:原代码参数顺序颠倒,已修正!
# 3. 从匹配结果中提取极值(匹配最好和最差的位置)
# min_val:最小匹配值(最差匹配,接近-1)
# max_val:最大匹配值(最好匹配,接近1)
# min_loc:最小匹配值对应的位置(左上角坐标)
# max_loc:最大匹配值对应的位置(左上角坐标)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
# 4. 确定匹配区域的矩形框(模板匹配的核心是“滑动模板”,匹配位置对应模板左上角)
top_left = max_loc # 匹配区域的左上角坐标(对应模板的左上角)
bottom_right = (top_left[0] + w, top_left[1] + h) # 右下角坐标(左上角+模板宽高)
在模板图像和目标图像上绘制匹配框
# 1. 在模板图像上绘制矩形(实际意义不大,更多是演示)
# 注意:模板图像本身就是要寻找的区域,这里绘制的矩形会覆盖整个模板
yanjing_template = cv2.rectangle(
img=yanjing, # 要绘制的图像(模板图像)
pt1=top_left, # 矩形左上角
pt2=bottom_right, # 矩形右下角
color=(0, 0, 0), # 颜色(黑色,BGR格式)
thickness=2 # 线宽
)
cv2.imshow('yanjing_template', yanjing_template)
cv2.waitKey(0)
# 2. 在目标图像上绘制矩形(核心:标记出找到的匹配区域)
beiliya_rectangle = cv2.rectangle(
img=beiliya, # 要绘制的图像(目标图像)
pt1=top_left, # 匹配区域左上角(来自max_loc)
pt2=bottom_right, # 匹配区域右下角(左上角+模板宽高)
color=(255, 255, 255),# 颜色(白色,BGR格式)
thickness=2 # 线宽
)
cv2.imshow('beiliya_rectangle', beiliya_rectangle)
cv2.waitKey(0)
- 目标图像上的白色矩形框,就是算法找到的 “与模板最相似的区域”,
max_val
越接近 1,匹配越准确。
关闭窗口
cv2.destroyAllWindows() # 关闭所有OpenCV窗口,释放内存
关键原理与注意事项
匹配算法选择(cv2.TM_CCOEFF_NORMED):
- 这是常用的 “归一化相关系数匹配”,结果归一化到 [-1, 1],无需手动调整阈值,
max_val
接近 1 即表示可靠匹配; - 其他算法(如
TM_SQDIFF
)是 “平方差匹配”,min_val
越小匹配越好,需根据算法类型调整极值选择。
- 这是常用的 “归一化相关系数匹配”,结果归一化到 [-1, 1],无需手动调整阈值,
匹配区域的坐标逻辑:
- 匹配结果矩阵
res
的尺寸 = (目标图像高 - 模板高 + 1, 目标图像宽 - 模板宽 + 1); max_loc
是res
中最大值的坐标,对应 “模板左上角在目标图像中的位置”,因此右下角需加上模板的宽高。
- 匹配结果矩阵