关键点检测和匹配流水线四个阶段
① 特征检测/提取(feature detection):
特征检测/提取的过程指从每一幅图像中寻找那些能在其他图像中较好匹配的位置。
另外需要注意的是,这里也涉及图像分割的内容。分割将图像细分为构成它的子区域或物体。细分的程度取决于要解决的问题。也就是说,在应用中,当感兴趣的物体或区域已经被检测出来时,就停止分割。
② 特征描述(feature description):
特征描述即把检测到的关键点周围的每一个区域转化成一个更紧凑和稳定(不变的)的描述子。
使用图像分割的方法将一幅图像分割成多个区域后,分割后的像素集合经常需要以一种适合于计算机进一步处理的形式来表示和描述,此时我们可以使用特征描述子对其进行描述。
③ 特征匹配(feature matching):
特征匹配即在其他图像中高效地搜索可能的匹配候选。一旦我们从两幅或多幅图像中提取特征及其描述子,下一步就是要在这些图像之间建立一些初始化特征之间的匹配。
④ 特征跟踪(feature tracking):
特征跟踪是第三个阶段的另一种替代方法,它只在检测到的特征点周围一个小的领域内寻找匹配,因此更适合视频处理。
以上介绍将贯彻接下来要介绍的特征检测/提取、图像分割、特征描述、特征匹配、特征跟踪。
一、背景知识
1. 图像特征
本文主要介绍灰度空间的特征检测,所讨论的特征检测跟特征提取有相同的概念,特征被检测后它可以从图像中被提取出来。我们感兴趣的三种图像特征是孤立点、线、边缘。
边缘像素是图像中灰度突变的那些像素,而边缘是相连边缘像素的集合。边缘检测器是设计用来检测边缘像素的局部图像处理方法。
一条线可视为一条边缘线段,该线两侧的背景灰度要么远亮于该线的像素的灰度,要么远暗于该线的像素的灰度。
类似的,孤立点可视为一条线,只是其长度和宽度都为一个像素。
2. 数字函数的一阶导数和二阶导数
我们在空间域滤波器中介绍过一阶微分和二阶微分的差分形式。若将函数 展开为关于 的泰勒级数,令 ,且只保留该级数的线性项,结果数字差分是:
对上式关于 微分,我们得到一个二阶导数表达式:
这一展开是关于点 的,我们的兴趣是关于点 的二阶导数,故将上式中的变量减1后,得到
3. 导数与图像特征关系
① 一阶导数通常在图像中产生较粗的边缘。
② 二阶导数对精细细节,如细线、孤立点和噪声有较强的响应。
③ 二阶导数在灰度斜坡和灰度台阶过渡处会产生双边缘响应。
④ 二阶导数的符号可用于确定边缘的过渡是从亮到暗还是从暗到亮。
二、孤立点的检测
孤立点的检测以二阶导数为基础,这意味着使用拉普拉斯算子(详细可见空间域滤波器):
输出是使用如下表达式得到的:
式中, 是输出图像, 是一个非负的阈值, 由上式的拉普拉斯算子给出。从直观上看,这一概念是一个孤立点的灰度将完全不同于其周围像素的灰度,因而,使用这种类型的模板可很容易地检测出这个孤立点。
三、线检测
复杂度更高的检测是线检测。对于线检测,可以预期二阶导数将导致更强的响应,并产生比一阶导数更细的线。我们可以使用拉普拉斯算子,另外,必须适当处理二阶导数的双线效应,这里可以使用拉普拉斯的正值实现。
通常,我们的兴趣在于检测特定方向的线,对于不同方向的线,我们可以使用以下模板(其中角度是相对于正x轴度量的角度):
四、边缘检测
1. 边缘模型
边缘检测基于灰度突变。边缘的三种模型分别为:台阶模型、斜坡模型、屋顶边缘模型,如图1所示:
一阶导数的幅度可用于检测图像中的某个点处是否存在一个边缘(存在边缘则导数不为0);二阶导数的符号可用于确定一个边缘像素是位于该边缘的暗测还是位于该边缘的亮侧(正为暗、负为亮)。
围绕一条边缘的二阶导数有两个附加性质:① 对图像中的每条边缘,二阶导数生成两个值(一个不希望的特点);② 二阶导数的零交叉点可用于定义粗边缘的中心,具体见图2。
执行边缘检测有以下三个步骤:
① 为降噪对图像进行平滑处理。
② 边缘点的检测。这是一个局部操作,从一幅图像中提取所有的点,这些点是变为边缘点的潜在候选者。
③ 边缘定位。这一步的目的是从候选边缘点中选择组成边缘点集合的真实成员。
以下讨论实现这三个步骤的一些技术。
2. 基本边缘检测
以下边缘检测使用一阶导数。
① Roberts算子
Roberts算子以求对角像素之差为基础,该算子用于识别对角线方向的边缘:
模板如下图所示:
② Prewitt算子
Prewitt算子使用以 为中心的 邻域对 和 的近似如下式所示:
模板如下图所示:
③ Sobel算子
Sobel算子使用以 为中心的 邻域对 和 的近似如下式所示:
Sobel模板能较好地抑制(平滑)噪声地特性使得它更为可取,因为在处理导数时噪声抑制是一个重要地问题。模板如下图所示:
④ 检测对角边缘的Prewitt和Sobel模板
3. Marr-Hildreth边缘检测器
① 概念
Marr and Hildreth[1980] 最早成功尝试了在边缘检测处理中结合更高级的分析。Marr and Hildreth 证明了:
a. 灰度变化与图像尺寸无关,因此它们的检测要求使用不用尺寸的算子;
b. 灰度的突然变化会在一阶导数中导致波峰或波谷,或在二阶导数中等效地引起零交叉。
这些概念表明,用于边缘检测的算子应有两个明显的特点:
a. 它应该是一个能计算图像中每一点处的一阶导数或二阶导数的数字近似的微分算子;
b. 它应能被“调整”以便在任何期望的尺寸上起作用,因此大算子也可用于检测模糊边缘,小算子可用于检测锐度集中的精细细节。
② 高斯拉普拉斯(LoG)
Marr and Hildreth 证明,满足这些条件的最令人满意的算子是滤波器 , 是拉普拉斯算子,而 是标准差为 的二维高斯函数
为求 的表达式,我们执行如下微分:
整理各项后给出如下最终表达式:
该表达式称为高斯拉普拉斯(LoG)。图像及相关用法如图4所示,注意(d)的模板并不是唯一的,其目的是获取LoG函数的基本形状。
③ Marr-Hildreth 算法
Marr-Hildreth 算法由LoG滤波器与一副图像 卷积组成,即
然后寻找 的零交叉来确定 中边缘的位置。因为这些都是线性操作,故上式也可写为
它指出我们先用一个高斯滤波器平滑图像,然后计算该结果的拉普拉斯。
Marr-Hildreth 边缘检测算法可小结如下:
a. 用式(1)取样得到一个 高斯低通滤波器对输入图像滤波
b. 计算由第a步得到的图像的拉普拉斯
c. 找到第b步所得图像的零交叉
④ 寻找零交叉
在滤波后的图像 的任意像素 处,寻找零交叉的一种方法是,使用以 为中心的一个3×3邻域。 点处的零交叉意味着至少有两个相对的邻域像素的符号不同。有4种要测试的情况:左/右、上/下和两个对角。如果 的值与一个阈值比较(一种常用方法),那么不仅相对邻域的符号不同,而且它们的差值的绝对值还必须超过这个阈值。这时,我们称 为一个零交叉像素。
⑤ 高斯差分(DoG)
Marr and Hildreth 指出,使用高斯差分(DoG)来近似式(2)中的LoG滤波器是有可能的:
式子,。
4. 坎尼边缘检测器(Canny)
① 概念
坎尼检测器(Canny)是迄今为止讨论过的边缘检测器中最优秀的。坎尼方法基于三个基本目标:
a. 低错误率。所有边缘都应被找到,并且应该没有伪响应。即检测到的边缘必须尽可能是真实的边缘。
b. 边缘点应被更好地定位。已定位边缘必须尽可能接近真实边缘。即由检测器标记为边缘的点和真实边缘的中心之间的距离应该最小。
c. 单一的边缘点响应。对于真实的边缘点,检测器仅应返回一个点。即真实边缘周围的局部最大数应该是最小的。这意味着在仅存一个单一边缘点的位置,检测器不应指出多个边缘像素。
② 求最优数值解
坎尼的工作的本质是,从数学上表达前面的三个准则,并试图找到这些表达式的最佳解,通常,这是很困难的(或不可能的)。我们可以使用高斯近似出最优解:首先使用一个环形二维高斯函数平滑图像,计算结果的梯度,然后使用梯度幅度和方向来估计每一点处的边缘强度与方向。
A. 第一步
令 表示输入图像, 表示高斯函数:
我们用 和 卷积形成一幅平滑后的图像 :
B. 第二步
接下来计算结果的梯度幅度和方向:
C. 第三步
下一步是细化那些边缘,一种方法是使用非最大抑制,我们给出的方案如下:
a. 寻找最接近 的方向
b. 若 的值至少小于沿 的两个邻居之一,则令 (抑制);否则,令 。得到非最大抑制后的图像 。
D. 第四步
最后的操作是对 进行阈值处理,以便减少伪边缘点。坎尼算法使用两个阈值:低阈值 和高阈值 。坎尼建议高低阈值比率应为2:1或3:1。令
和 的非零像素可分别视为“强”和“弱”边缘像素。其中 为边缘点, 为候选点。对于候选点,如果与边缘点邻接,就标记为边缘点。
具体步骤如下:
a. 在 中定位下一个未被访问的边缘像素 。
b. 在 中与 是8邻接的像素标记为有效边缘像素。
c. 若 中的所有非零像素已被访问,则跳到步骤d,否则返回步骤a。
d. 将 中未标记为有效边缘像素的所有像素置零。
在这一过程的末尾,将来自 的所有非零像素附近到 ,用坎尼算子形成最终的输出图像。
③ 坎尼边缘检测算法
坎尼边缘检测算法的基本步骤总结如下:
a. 用一个高斯滤波器平滑输入图像
b. 计算梯度幅度图像和角度图像
c. 对梯度幅度图像应用非最大抑制
d. 用双阈值处理和连接分析来检测并连接边缘
5. 边缘连接和边界检测
理想情况下,边缘检测应该仅产生位于边缘上的像素集合。实际上,由于噪声、不均匀照明引起的边缘间断,以及其他引入灰度值虚假的不连续的影响,这些像素并不能完全描述边缘特性。因此,一般的边缘检测后紧跟连接算法,将边缘像素组成有意义的边缘或区域边界。
以下讨论三种基本的边缘连接方法,它们是实际中使用的代表性技术。
① 局部处理
连接边缘点最简单的方法之一是,在每个点 处的一个小邻域内分析像素的特点,根据预定的准则,将所有相似点连接起来,以形成根据指定准则满足相同特性像素的一条边缘。
用于确定边缘像素相似性的两个主要性质是:梯度向量的强度(幅度)与梯度向量的方向。步骤如下:
a. 计算输入图像 的梯度幅度阵列 和梯度角度阵列 。
b. 形成一幅二值图像 ,任何坐标对 处的值由下式给出:
式中, 是一个阈值,A是一个指定的角的方向, 定义了一个关于 的可接受方向“带宽”。
c. 扫描 的行,并在不超过指定长度 的每一行中填充(置1)所有缝隙(0的集合)。缝隙一定要限制在一个1或多个1的两端。分别处理各行。
d. 在任何其他方向 上检测缝隙,以该角度旋转 ,并应用步骤3中的水平扫描过程。然后,将结果以 旋转回来。
② 区域处理
对区域处理的一种方法是函数近似,即对已知点拟合一条二维曲线。算法描述如下:
a. 令 是一个已排序序列,显然,这些点是一幅二值图像中的1值点。指定两个起始点 和 。它们是多边形的两个起始顶点。
b. 指定一个阈值 ,以及两个空堆栈“开”(OPEN)和“闭”(CLOSED)。
c. 把 放到“开”中末尾。若 中的点对应于一条闭合曲线,把 放到“开”和“闭”中;若点对应于一条开放曲线,把 放到“闭”中。
d. 计算从“闭”中最后一个顶点到“开”中最后一个顶点的线的参数。
e. 计算步骤d所得的直线至 中所有点的距离,序列把它们放到步骤d所得的两个顶点之间。选择具有最大距离 的点 (解决任意性问题)。
f. 若 ,则把 作为一个新顶点放到“开”的末尾。转到步骤d。
g. 否则,从“开”中移除最后一个顶点,并把它作为“闭”的最后一个顶点插入。
h. 若“开”非空,转到步骤d。
i. 否则,退出。“闭”中顶点就是拟合 中的点的多边形的顶点。
③ 使用Hough变换的全局处理
A. 直线
给定一幅图像中的n个点,假设我们希望找到这些点中一个位于直线上的子集。一种可行的解决方法是,先找到所有由每对点确定的直线,然后寻找靠近特定直线的点的所有子集。这种方法因为计算量太大而没有应用价值,Hough提出了一种替代方法,称为Hough变换。
使用一条直线的法线表示 平面的点:
从图5(a)可以看出,通过点 的直线有无数条。但若考虑 平面,则通过点 的线是唯一的。图5©中的交点 对应于图5(b)中通过点 和 的直线。
Hough变换计算上的魅力在于可将 参数空间划分为所谓的累加单元。如图5(d)所示,其中 和 是所期望的参数值范围: 和 , 是图像中对角之间的最大距离。坐标 处的单元具有累加值 ,对平面上每个点计算 和 ,并将其四舍五入到沿相应轴的最接近的允许单元值。若选择的 值得到解 ,则令 。在这一过程结束后, 中的值 意味着平面中有 个点位于直线 上。 平面中的细分数量决定了这些点的共线精度。
B. 圆
Hough变换也适用于形如 的任何函数,其中 是坐标向量, 是系数向量。例如,位于圆:
可使用上述方法检测,区别在于存在3个参数(、和),在三维参数空间中,这三个参数导致了类似立体的单元和形如 的累加器。很明显,Hough变换的复杂性取决于给定函数表达式中的坐标和系数数量。
C. 边缘连接问题
基于Hough变换的连接方法如下:
a. 使用特征提取的任何技术得到一幅二值图像
b. 指定 平面中的细分
c. 对像素高度集中的地方检验其累加单元的数量
d. 检验选中单元中像素间的关系(主要针对连续性),若缝隙的长度比指定的阈值小,则连接与给定单元相关联的一条直线中的缝隙。
五、代码实现(Python+OpenCV)
OpenCV提供了许多边缘检测滤波函数,包括Laplacian()、Sobel()、Schar()以及Canny()。这些滤波函数都会将非边缘区域转为黑色,将边缘区域转为白色或其他饱和颜色。但是这些函数都很容易将噪声错误地识别为边缘。缓解这个问题的方法是在找到边缘之前对图像进行模糊处理。OpenCV也提供了许多模糊滤波函数,包括blur()(简单的算术平均)、medianBlur()以及GaussianBlur()。边缘检测滤波函数和模糊滤波函数的参数有很多,但总会有一个ksize参数,它是一个奇数,表示滤波核的宽和高(以像素为单位)。
模糊图像(去除噪声):
cv2.blur()
cv2.medianBlur()
cv2.GaussianBlur()
直线检测:
cv2.HoughLines():使用标准的Hough变换
cv2.HoughLinesP():使用概率Hough变换(它只通过分析点的子集并估计这些点都属于一条直线的概率,这是标准Hough变换的优化版本,该函数的计算代价会少一些,执行会变得更快。)
圆检测:
cv2.HoughCircles()
锐化图像(勾勒边缘):
cv2.Laplacian()
cv2.Sobel()
cv2.Schar()
cv2.Canny()
边界检测:
cv2.findContours():三个参数,分别是输入图像、层次类型、轮廓逼近方法;三个返回值,分别是修改后的图像、图像的轮廓以及它们的层次。
cv2.approxPolyDP()
import cv2
import numpy as np
img = cv2.imread('img.jpg', 0)
# 使用Sobel算子
x = cv2.Sobel(img,cv2.CV_16S,1,0) # 计算x方向
y = cv2.Sobel(img,cv2.CV_16S,0,1) # 计算y方向
sobelX = cv2.convertScaleAbs(x) # 转回uint8
sobelY = cv2.convertScaleAbs(y)
sobel = cv2.addWeighted(sobelX,0.5,sobelY,0.5,0) # 组合得到最终结果
# 使用laplacian算子
laplacian = cv2.Laplacian(img,cv2.CV_64F,ksize = 3)
laplacian = cv2.convertScaleAbs(laplacian)
# 使用Canny算子
canny = cv2.Canny(img, 50, 120)
# 图像轮廓
ret, thresh = cv2.threshold(img,127,255,0) # 阈值处理,将图像转变为二值图
image, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
bg = np.zeros(img.shape, dtype = np.uint8)
contours = cv2.drawContours(bg, contours, -1, (255,255,255), 1) #给轮廓填充颜色
cv2.imwrite('sobelX.jpg', sobelX)
cv2.imwrite('sobelY.jpg', sobelY)
cv2.imwrite('sobel.jpg', sobel)
cv2.imwrite('laplacian.jpg', laplacian)
cv2.imwrite('canny.jpg', canny)
cv2.imwrite('contours.jpg', contours)
以上全部内容参考书籍如下:
冈萨雷斯《数字图像处理(第三版)》
Joe Minichino、Joseph Howse《OpenCV 3计算机视觉Python语言实现(原书第2版)》
Richard Szeliski《计算机视觉 - 算法与应用》