OPENCV复习第二期

发布于:2025-09-11 ⋅ 阅读:(17) ⋅ 点赞:(0)

主要算法说明

  1. 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 方向的边缘图像融合,得到完整边缘
  2. Scharr 算子

    • 是 Sobel 算子的改进版,在 3x3 卷积核下具有更高的精度,对边缘的检测更敏感
    • 使用方式与 Sobel 类似:cv2.Scharr(yt, cv2.CV_64F, dx=1, dy=0)
    • 适用于需要更精确边缘检测的场景
  3. Laplacian 算子

    • 基于图像的二阶导数,对图像中的快速变化(边缘)更敏感
    • cv2.Laplacian(yt, cv2.CV_64F):直接计算图像的拉普拉斯变换
    • 对噪声较敏感,通常需要先进行平滑处理
  4. Canny 边缘检测

    • 一种多阶段的边缘检测算法,能有效抑制噪声并检测出真正的边缘
    • cv2.Canny(yt, 80, 120):两个阈值分别为低阈值和高阈值
      • 低于低阈值的像素被丢弃
      • 高于高阈值的像素被保留为边缘
      • 介于两者之间的像素,若与边缘像素相连则保留

这段代码主要使用 OpenCV 对指纹图像(zw1.pngzw2.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)
  • 外接圆:包围轮廓的最小圆形,适合圆形或近圆形物体的定位。
  • 外接矩形:轴对齐的最小矩形(边与图像边框平行),适合快速定位物体的大致位置。

总结

  1. 图像预处理(灰度化→二值化);
  2. 轮廓提取与层级分析;
  3. 轮廓特征计算(面积、周长);
  4. 轮廓筛选(按面积)与可视化;
  5. 轮廓的几何拟合(外接圆、外接矩形)。

这段代码主要实现了对图像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].shapeapprox.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窗口,释放内存

关键原理与注意事项

  1. 匹配算法选择(cv2.TM_CCOEFF_NORMED)

    • 这是常用的 “归一化相关系数匹配”,结果归一化到 [-1, 1],无需手动调整阈值,max_val 接近 1 即表示可靠匹配;
    • 其他算法(如 TM_SQDIFF)是 “平方差匹配”,min_val 越小匹配越好,需根据算法类型调整极值选择。
  2. 匹配区域的坐标逻辑

    • 匹配结果矩阵 res 的尺寸 = (目标图像高 - 模板高 + 1, 目标图像宽 - 模板宽 + 1);
    • max_loc 是 res 中最大值的坐标,对应 “模板左上角在目标图像中的位置”,因此右下角需加上模板的宽高。


网站公告

今日签到

点亮在社区的每一天
去签到