OpenCV - 图像特征检测

2024-04-18

OpenCV系列第二集,图像特征检测。OpenCV被称作计算机的眼睛,是计算机视觉中最重要的部分。图像特征,其实就是指的图像的边缘、轮廓和棱角。如果想要绘制一张图像,首要做的就是绘制图像的轮廓,然后在轮廓的基础上绘制图像的线条(边缘)和角(角点),这样整张图像大体就展现出来了。

1. 边缘编辑与增强

图像的边缘通常指局部不连续的特征,即亮度变化最显著的区域。边缘检测在图像处理和计算机视觉任务中起着关键作用,有助于提取目标物体的轮廓信息,提高图像分析的准确性。

  • Canny 边缘检测简介

    Canny 边缘检测是一种经典且广泛应用的边缘检测算法,由 John F. Canny 于 1986 年提出。它采用多阶段处理流程,包括 噪声去除梯度计算非极大值抑制滞后阈值,以确保检测到清晰且可靠的边缘。

1. 噪声去除

由于边缘检测容易受到噪声影响,Canny 算法的第一步是对图像进行平滑处理,通常采用 高斯滤波(Gaussian Blur)。常见的滤波器尺寸为 5×5,通过卷积操作减少高频噪声,使边缘检测更稳定。

2. 计算图像梯度

图像梯度用于衡量像素灰度的变化情况。梯度的方向始终与边界垂直,可用于定位边缘。梯度计算通常使用 Sobel 算子,通过计算 x 方向(水平)和 y 方向(垂直)的导数,获得梯度幅值和方向:

\[G = \sqrt{G_x^2 + G_y^2}\\ \theta = \arctan\left(\frac{G_y}{G_x}\right)\]

其中,$G_x$ 和 $G_y$ 分别表示水平方向和垂直方向的梯度。

在实际应用中,梯度方向通常被归为四类:水平、垂直、左上-右下对角线、右上-左下对角线

3. 非极大值抑制

非极大值抑制(Non-Maximum Suppression, NMS)用于细化边缘,去除非边缘像素点。其核心思想是 仅保留局部梯度方向上最强的像素点,并抑制其他较弱的响应,以获得更精确的边缘信息。

具体方法是:

  • 计算像素点梯度值,并根据梯度方向,与相邻的两个像素进行比较。
  • 若该像素的梯度值 小于 其梯度方向上的邻居,则将其置为零(即非边缘)。
  • 若该像素的梯度值 大于等于 其邻居,则保留。

4. 滞后阈值(Hysteresis Thresholding)

由于光照等因素,部分边界的梯度值较低。Canny 算法使用 双阈值 机制,即 高阈值(maxVal)低阈值(minVal),来区分边缘像素:

  • 若像素梯度值 高于 maxVal,则被认为是 强边缘,直接保留。
  • 若像素梯度值 低于 minVal,则被认为是 非边缘,直接舍弃。
  • 若梯度值 介于 minVal 和 maxVal 之间,则进一步判断该像素是否与某个强边缘像素相连:
    • 若相连,则保留(弱边缘变为强边缘)
    • 若不相连,则舍弃

通过这种方式,可以有效地滤除伪边缘,并保证边缘的连贯性和准确性。

实验

  1. 导入模块

     import cv2
     import matplotlib.pyplot as plt
     # 绘图文字使用黑体显示(显示中文,默认不支持中文)
     plt.rcParams['font.sans-serifil=['SimHei']
    
  2. 读取图像,将彩色图转换为灰度图

     # 读取图像
     img=cv2.imread( img/0401.jpg')
     # 将彩色图转换为灰度图
     img= cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    
  3. 使用 Canny 边缘检测算法,配合滞后值处理图像

     # 使用Canny边缘检测算法,将滞后闻值分别设定为200和300
     edges1 = cv2.Canny(img, 200, 300)
    
  4. 编写可视化代码

     plt.subplot(121)  # 会制第一张子图,总共为1行2列
     plt.title('原图')
     plt.imshow(img)
     # 去除图像的坐标尺
     plt.xticks([])
     plt.yticks([])
     plt.subplot(122) # 给制第二张子图,总共为2行2列
     plt.title('轮廓处理 1')
     plt.imshow(edges1, cmap='gray')
     plt.xticks([])
     plt.yticks([])
        
     # 显示图像效果
     plt.show()
    

    img1

2. 图像轮廓检测

轮廓(Contour)是指由相邻像素点连接而成的曲线,这些像素点具有相同的颜色或灰度值。轮廓检测广泛应用于目标检测、物体分割和形状分析等计算机视觉任务。

1. 轮廓检测流程

轮廓检测通常需要以下步骤:

  1. 二值化处理

    在查找轮廓之前,需要对图像进行 二值化(Thresholding)或 Canny 边缘检测,以减少图像复杂度,使轮廓更容易识别。

  2. 查找轮廓

    OpenCV 提供的 cv2.findContours() 函数用于提取图像中的轮廓。需要注意的是,该函数会 修改原始图像,因此如果需要保留原图,应先创建副本进行处理。

  3. 轮廓的工作原理

    在 OpenCV 中,轮廓检测类似于在黑色背景上寻找白色物体,因此建议在处理前确保前景为白色,背景为黑色。

2. 轮廓查找函数

在 OpenCV 中,cv2.findContours() 是常用的轮廓检测函数,其典型调用格式如下:

contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

各参数说明:

  • thresh二值化后的图像,可以通过 cv2.threshold()cv2.Canny() 生成。
  • cv2.RETR_TREE轮廓检索模式,用于指定轮廓的层级关系:
    • cv2.RETR_EXTERNAL:仅检测最外层轮廓。
    • cv2.RETR_LIST:检测所有轮廓,但不建立层级关系。
    • cv2.RETR_CCOMP:检测所有轮廓,并将它们组织成两个层级(外轮廓和内洞)。
    • cv2.RETR_TREE:检测所有轮廓,并建立完整的层级结构。
  • cv2.CHAIN_APPROX_SIMPLE轮廓近似方式
    • cv2.CHAIN_APPROX_NONE:存储轮廓上的所有点,不进行压缩。
    • cv2.CHAIN_APPROX_SIMPLE仅存储直线段的端点,减少内存使用。例如,矩形轮廓仅存储四个角点,而非所有边上的像素点。

返回值:

  • contours:包含所有轮廓的列表,每个轮廓由一系列坐标点表示。
  • hierarchy:包含轮廓的层级信息,可用于分析轮廓的嵌套关系。

3. 轮廓绘制函数

OpenCV 提供了 cv2.drawContours() 函数用于绘制检测到的轮廓。典型调用格式如下:

img = cv2.drawContours(img, contours, -1, (0, 0, 255), 2)

参数说明:

  • img原始图像,绘制轮廓的目标图像。
  • contours轮廓列表,由 cv2.findContours() 生成。
  • 1:指定绘制的轮廓索引,1 表示绘制 所有轮廓。如果只想绘制特定轮廓,可以传入对应索引。
  • (0, 0, 255)轮廓颜色,采用 BGR 格式,此处表示 红色
  • 2线条宽度,表示轮廓线的像素宽度。

返回值:

  • img:绘制轮廓后的图像,可用于可视化分析。

实验

  1. 导入模块 & 读取图像,并将其转换为灰度图

     import cv2
        
     img=cv2.imread('img/0402.jpg')
     img_gray=cv2.cvtColor(img, CV2.COLOR_BGR2GRAY)
    
  2. 采用二值化方式处理图像

     # 采用二值化方式处理图像。像素值在182和255之间的数据为1,小于182的数据为0,ret, thresh=cv2.threshold(img_gray, 182, 255, 0)
    
  3. 查找轮廓

     # 使用简易方式英取全部轮廓
     Contours, hierarchy= cv2.findContours(thresh, cV2.RETR TREE, cV2.CHAIN_APPROX_SIMPLE)
    
  4. 绘制轮廓

     # 传入的参数:图像、轮廓坐标、全部轮廓、轮廓颜色(红色),线宽
     img = cv2.drawContours(img, contours, -1, (0,0,255), 2)
    
  5. 可视化图像

     cv2.imshow('gray',  img_gray)
     cv2.imshow(bin',  thresh)
     cv2.imshow('contour', img)
     # 按任意键退出图像显示,结束程序
     # 灰度图效果
     # 二值化图效果
     # 轮廓图效果
     cv2.waitKey(0)
     cv2.destroyAllWindows()
    

    img2

小结

  • 通过轮廓检测的应用,可以得到以下结论:
    • 二值化取值不一定是按照灰度值 182 作为值切分的,可以根据每张图像的特点进行调整。
    • 轮廓查找是二值化图像黑色和白色交界处的像素点位置。

3. 图像角点与线条检测

角点和线条检测是计算机视觉中的重要技术,用于特征提取、图像匹配和目标识别。角点通常对应于物体的拐角,例如 道路的交叉口、建筑物的顶点 等。在图像分析中,角点具有独特的局部特征,能够用于稳定地跟踪物体或进行几何变换估计。

1. 角点的定义

在计算机视觉领域,角点可以从两个角度定义:

  1. 角点是两个边缘的交点,例如十字路口或物体的棱角。
  2. 角点是邻域内具有两个主要方向的特征点,即当在不同方向移动窗口时,图像灰度值发生显著变化。

这些特性使角点在图像匹配、运动检测和三维重建等任务中尤为重要。

2. Harris 角点检测简介

Harris 角点检测Chris Harris 和 Mike Stephens 在论文 “A Combined Corner and Edge Detector” 中提出,主要用于检测图像中的角点。

  • Harris 角点检测的基本原理

    人眼识别角点的方式通常是在 一个局部窗口内 观察图像的灰度变化:

    • 如果窗口在所有方向上移动,灰度值发生明显变化,则判定为角点。
    • 如果窗口移动时,灰度值几乎不变,则窗口区域是平坦区域,不包含角点。
    • 如果窗口仅在某个方向上变化,而在其他方向上不变,则该区域可能是一条直线。

Harris 角点检测基于 自相关矩阵(Second Moment Matrix),通过计算窗口内像素的梯度变化,判断该窗口是否包含角点。其核心公式为:

\[R = det(M) - k \cdot (trace(M))^2\]

其中:

  • $M$ 是图像窗口内的 自相关矩阵

    \[M = \begin{bmatrix} I_x^2 & I_x I_y \\ I_x I_y & I_y^2 \end{bmatrix}\]
  • $I_x, I_y$ 是 Sobel 算子计算得到的 x 和 y 方向上的梯度
  • $det(M)$ 计算矩阵的行列式,衡量梯度的散布情况。
  • $trace(M)$ 计算矩阵的迹,即特征值之和。
  • $k$ 是经验系数,通常取 0.04 - 0.06

当 $R$ 值较大时,像素点被认为是角点。

3. Harris 角点检测函数

OpenCV 提供了 cv2.cornerHarris() 进行角点检测,其典型调用格式如下:

dst = cv2.cornerHarris(gray, 2, 3, 0.04)

参数说明

  • gray:输入的 灰度图像(必须是 float32 类型)。
  • 2:窗口大小,即检测角点时考虑的邻域范围。值越小,检测更加局部化;值过大会影响计算效率和准确性。
  • 3:Sobel 算子计算梯度时的窗口大小,较小的值能获得更细节的梯度信息,较大的值会导致角点检测不稳定。
  • 0.04:Harris 角点检测公式中的自由参数 ,通常设定在 [0.04, 0.06] 之间。
  • 返回值 dst:表示每个像素点的角点响应值,值越大,角点特征越明显。

实验

  1. 导入模块

     import cv2
     import numpy as np
    
  2. 图像预处理

     #  读取图像
     img=cv2.imread('img/0403.jpg')
     # 将图像转化成灰度图
     gray=cv2.cvtColor(img, cV2.COLOR_BGR2GRAY)
     gray=np.float32(grey)
    
  3. 角点处理

     # gray: 输入的float类型的灰度图
     # 2:检测过程中考虑的领域大小
     # 3:使用Scbel算法在求导时使用的窗口大小
     # 0.04:Harris角点检测方程中的自由参数,取值范围为[0.04,  0.06]
     dst= cv2.cornerHarris(gray, 2, 3, 0.04)
     # 这里设定一个阈值,只要大于等于这个阈值就可认判定为角点
     img[dst>0.01*dst.max()]=[0, 0, 255] #[0, 0, 255]为红色  
    
  4. 可视化处理

     cv2.imshow(dst ,img)
     if cv2.waitKey(0) & 0xff==27:
         cv2.destroyAllWindows()小结
    

    img3

小结

  • 通过角点检测的使用,可以得到以下结论。
    • 在查找角点之前,需要先将图像转换为 float 类型。
    • 检测框和 Sobel 算法的计算窗口不能设置得太大,否则计算量会增加,并且检测效果会变差。