OpenCV基础教程——特征提取与描述(理解图像特征、Harris角点检测)
一、理解图像特征
目标:
本章节你需要学习以下内容:
*在本章中,我们将尝试了解哪些是图像的特征,理解为什么图像特征很重要,理解为什么角点很重要等等。
代码解读
解释
普遍认为拼图游戏是一种受欢迎的游戏形式。玩家会收到一堆看似无关紧要的碎片,在经过仔细思考和排列组合后才能完成一幅完整而有意义的画面。那么你又是如何达成这一壮举的呢?同样的理论能否被应用到电脑程序中?这样电脑是否也能进行类似的拼图操作呢?如果电脑能够完成类似的重组任务,请问为什么不直接把大量的真实自然照片交给电脑处理,并让其自动组合出一张更大的全景图片呢?同样的技术能否用来整合大量分散在空间中的图片并构建出复杂的三维建筑模型或其他结构呢?
想象空间与创意可以是无限广阔的,在这个问题上你拥有无限多的可能性。这都取决于最基础的问题:你又是如何操作拼图游戏?又该如何将大量零散、混乱的图像片段组合成一个完整的整体?你又是如何将这些大量零散的图像元素巧妙地整合在一起形成一个连贯的画面?
答案:
我们需要寻获独特的特定模式或功能,
以便于追踪和比较信息。
若寻获某项特征定义,
则难以用文字描述但能直观认知其特性。
若有人要求指出多个图像间可比较的良好功能,
请举例说明即可。
其原因在于这些能力本就具备于人类幼崽。
我们在图像中识别特定特征,
并在不同图像中定位相同特征以完成拼图任务。
所有这些能力都是我们天生所具备的。
因此可知我们面临的 core problem 不仅得到了进一步的发展和完善. 那么这些功能到底是什么呢? 其背后的技术实现对于计算机领域的人士同样应当具备足够的理解能力.
人类如何找到这些特征难以评说。它已经在我们的大脑中内化为固有的认知模式。然而如果深入研究一些图像并探索其中的规律性我们可能会意外地发现一些令人惊喜的现象例如请拍下以下图片:

该图像极为简单,在其顶端呈现了六个小补丁片段。你的问题是在原始图像中确定这些补丁的具体位置,并能准确识别出多少个正确的补片?
A和B是平坦的表面,它们分布在很多区域。很难找到这些补丁的确切位置。
C与D相比要简单得多;它们位于建筑的边线上;大致可以看出其位置;但确切的位置仍然难以确定;因为沿边界的模式在不同位置上是一致的;然而,在边界处;这里是不同的情况;因此与平面区域相比起来;边界更适合做特征点(尽管它无法单独作为最佳特征点;但在拼接图中连续性的表现较好)。
最后,在建筑中有一些关键点被称为E和F。这些关键点容易被识别出来。在这些关键点上移动覆盖物时会出现差异性变化。因此可以认为这些关键点具有重要的功能价值。随后我们将转向更为简洁明了的图像这一部分以助于我们更加清晰地理解整个概念

类似于上文所述,在图像处理中存在两种类型的区域:一种是蓝色区域(蓝区),这些区域具有平坦的表面特性,在任何位置都无法实现可靠的追踪;另一种是黑色区域(黑区),它们具有明显的边缘特征。当沿垂直方向(即沿着渐变梯度的方向)移动黑区时会发生变化;而平行于边缘移动时视觉效果一致。对于红色区域(红区),它们被识别为角点特征:无论怎样调整位置都会显示出显著的不同特征。因此,在图像分析中通常将这些位置标记为关键点或角点特征。(此外,在某些特定场景下也可以使用blob等结构作为特征提取的对象)
因此我们解答了"这些功能是什么?"这个核心问题。然而接下来的问题随之而来:我们如何定位它们呢?或者角点是如何被识别的?为了直观起见,请问我们可以采用如下方式:即通过在图像中寻找那些在其周围发生显著变化且仅发生微小移动的区域来实现。在后续章节中我们将探讨这一方法如何被编程实现。这样我们就实现了对图像特征的检测。
我们成功识别出了一系列功能位于图像中的不同位置。每当检测到某个特定的元素时,在另一幅图像中也能相应发现其对应的存在位置信息。这种现象是如何实现的?为了更好地定位该特征的位置信息,在周围设置一个明确的边界范围即可。例如,在上方设置天空区域,在下方设定建筑主体部分,并确保建筑表面包含玻璃等细节信息;随后在另一处搜索对应的区域图即可完成匹配过程。同样地,在计算机系统内部也应具备类似的处理机制以便完成自动匹配任务。这种用于表示特性和位置关系的方式被称为特征特性和位置信息提取模型或简称为特征描述模型。通过这一方法体系可以系统化地获取各类关键特性及其存在的位置信息从而实现对所有测试样本的一致性分析与分类工作。
在此过程中,在本单元的内容中, 我们致力于探索利用多种 OpenCV 算法进行特征提取, 识别和分类, 并与这些项建立关联。这一过程有助于提升系统的整体性能。
二、Harris角点检测
目标:
本章节你需要学习以下内容:
*我们将了解Harris Corner Detection背后的概念。
*我们将看到函数:cv.cornerHarris(),cv.cornerSubPix()
代码解读
1、理论
在上一节中,我们已经掌握了角点的一个显著特征:无论向哪个方向移动变化都会产生显著的效果。Chris_Harris与Mike_Stephens在1988年发表的经典论文《A CombinedCorner and Edge Detector》中首次提出了基于焦点检测的角点检测方法,这一方法即被称为Harris角点检测法。他将这一核心概念成功地转化为数学表达式。将窗口在(u, v)的所有可能方向上滑动,并计算各方向上的差异总和。具体公式如下:
能量函数E(u,v)被定义为所有像素(x,y)上加权窗口函数\omega(x,y)与平移后的强度I(x+u, y+v)减去原强度I(x, y)的平方差之和。
窗口函数包括标准矩形窗以及对每个像素分配不同加权系数的高斯窗
在角点检测过程中,为了实现E(µ,ν)的最大化目标。这实际上要求我们让方程右边第二项取得最大的数值。随后对上述公式进行泰勒级数展开,并通过若干数学推导(如参考相关教材),最终得到了以下表达式:
E(u,v) \approx \begin{bmatrix} u & v \end{bmatrix} M \begin{bmatrix} u \\ v \end{bmatrix}
其中:
M = \sum_{x,y} w(x,y) \begin{bmatrix}I_x I_x & I_x I_y \\ I_x I_y & I_y I_y \end{bmatrix}
在当前场景中,请注意:I_x和I_y分别表示x方向和y方向上的图像梯度。(该功能可通过调用函数cv.Sobel()来实现)
接下来是关键部分。随后, 他们构建了一个分数, 实质上相当于一个方程式, 它将用来判断一个窗口是否能够容纳一个拐点
R = det(M) - k(trace(M))^2
其中:
- R = det(M) - k(trace(M))^2
- trace(M) = \lambda_1 + \lambda_2
- \lambda_1和\lambda_2是M的本征值
因此,这些特征值的值决定区域是角点,边缘还是平坦。
- 若|R|非常小,则该区域呈现平坦状态。
- 当R为负值且λ₁远大于λ₂时,则出现这一现象;反之亦然。
- 若R数值极大,并且λ₁与λ₂大致相当,则形成拐角区域。
- 它可以用下面这张很好理解的图片表示:

该方法Harris角点检测的输出是一个由角点分数组成的灰度图像。选择合适的阈值对结果图像进行二值化处理,我们就能检测到图像中的角点。我们计划使用一个直观的例子来演示这一过程。
2、OpenCV中的Harris角点探测器
为此,OpenCV具有函数cv.cornerHarris()。它的参数是:
- img - 输入图像应具有灰度色调并采用float32数据类型的特征矩阵。
- blockSize - 用于角点检测的局部区域大小参数。
- ksize - 基于Sobel算子的设计参数。
- k - 用于调节角点感知灵敏度的关键调节参数。
请参阅以下示例:
import numpy as np
import cv2 as cv
filename = 'chessboard.png'
img = cv.imread(filename)
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
gray = np.float32(gray)
dst = cv.cornerHarris(gray,2,3,0.04)
#result is dilated for marking the corners, not important
dst = cv.dilate(dst,None)
# Threshold for an optimal value, it may vary depending on the image.
img[dst>0.01*dst.max()]=[0,0,255]
cv.imshow('dst',img)
if cv.waitKey(0) & 0xff == 27:
cv.destroyAllWindows()
代码解读
以下是三个结果:

3、具有亚像素精度的角点
在某些情况下,你可能需要采用最精确的方式找到一个角落的位置. OpenCV提供了函数cv.cornerSubPix(), 这个函数能够对已经被初步检测出具有亚像素级别的精确度的角度特征进行进一步优化. 举个例子来说, 首先定位Harris检测到的所有候选角落位置. 然后将这些候选区域(通常是一堆连续 pixels)所对应的质心位置提供给该算法进行进一步优化. 这些被标记为红色 pixel 的就是原始Harris角度, 而经过优化后的精细角度则被标记为绿色 pixel. 对于这个过程, 我们必须明确何时终止迭代运算的标准. 在指定的最大迭代次数或者达到预期收敛程度时, 应优先选择前者作为终止条件. 此外, 我们还需要设定该算法搜索角度特征区域的具体范围大小.
import numpy as np
import cv2 as cv
filename = 'chessboard2.jpg'
img = cv.imread(filename)
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
# find Harris corners
gray = np.float32(gray)
dst = cv.cornerHarris(gray,2,3,0.04)
dst = cv.dilate(dst,None)
ret, dst = cv.threshold(dst,0.01*dst.max(),255,0)
dst = np.uint8(dst)
# find centroids
ret, labels, stats, centroids = cv.connectedComponentsWithStats(dst)
# define the criteria to stop and refine the corners
criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 100, 0.001)
corners = cv.cornerSubPix(gray,np.float32(centroids),(5,5),(-1,-1),criteria)
# Now draw them
res = np.hstack((centroids,corners))
res = np.int0(res)
img[res[:,1],res[:,0]]=[0,0,255]
img[res[:,3],res[:,2]] = [0,255,0]
cv.imwrite('subpixel5.png',img)
代码解读
下面是结果,其中一些重要位置显示在缩放窗口中以显示:

