一、什么是图像特征
在计算机视觉中,图片并不只是由颜色和像素组成。程序还需要从图片中找到一些具有代表性的内容,例如:
1,角点;
2,边缘;
3,纹理;
4,方向变化明显的位置;
5,具有独特形状的局部区域。
6,这些内容可以称为图像特征。
例如,一张建筑物图片中,窗户的角落、屋顶的交点、文字的边缘,都可能成为特征点。不同图片如果包含相同物体,即使图片大小、角度或位置不同,也可能拥有一些相同的特征。
因此,特征检测可以用于:
图片匹配;目标识别;图像拼接;物体定位;指纹识别;人脸识别。
二、Harris角点检测
角点是图像中灰度变化明显的点。简单来说,如果一个小区域向各个方向移动,像素变化都很明显,那么这个位置就可能是角点。
例如矩形的四个拐角,通常具有比较明显的灰度变化。
OpenCV 中可以使用 Harris 算法检测角点:
import cv2
image = cv2.imread(
"coca.png"
)
gray = cv2.cvtColor(
image,
cv2.COLOR_BGR2GRAY
)
dst = cv2.cornerHarris(
gray,
4,
3,
0.04
)
cornerHarris( )的参数包括:
gray:输入的灰度图像;
blockSize:计算角点时所考虑的邻域大小;
ksize:Sobel 算子的窗口大小;
k:Harris 算法中的参数。
检测结果 dst 是一个矩阵,其中每个位置都有一个角点响应值。响应值越大,表示这个位置越可能是角点。
可以设置阈值,将角点标记为红色:
image[
dst > 0.01 * dst.max()
] = [0, 0, 255]
最后显示图片:
cv2.imshow(
"corners",
image
)
cv2.waitKey(0)
cv2.destroyAllWindows()
需要注意,OpenCV 使用 BGR 颜色顺序,因此:
[0, 0, 255]
表示红色,而不是 RGB 中的蓝色。
Harris 角点检测适合寻找角落和交点,但它的特征表达能力比较有限。如果图片发生旋转、缩放或较大变化,单纯使用 Harris 角点可能不够稳定。

三、SIFT特征检测
SIFT 是一种比较经典的局部特征算法。它不仅可以找到关键点,还可以为每个关键点生成一个描述符。
关键点可以理解为:
图片中比较有代表性的位置
描述符则可以理解为:
描述这个关键点周围图像特征的一组数字
后面进行图片匹配时,程序并不是直接比较整张图片,而是比较这些关键点和描述符。
创建 SIFT 对象:
sift = cv2.SIFT_create()
读取图片并转为灰度图:
image = cv2.imread(
"coca.png"
)
gray = cv2.cvtColor(
image,
cv2.COLOR_BGR2GRAY
)
检测关键点:
keypoints = sift.detect(gray)
绘制关键点:
result = cv2.drawKeypoints(
image,
keypoints,
None,
flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS
)
cv2.imshow(
"sift",
result
)
cv2.waitKey(0)
cv2.destroyAllWindows()
在显示结果中,可以看到图片上出现许多圆圈或标记,这些位置就是 SIFT 检测到的关键点。

每个关键点都有一些属性:
pt:关键点坐标;
size:关键点所在区域的大小;
angle:关键点方向;
response:关键点响应值;
octave:关键点所在的尺度层级。
SIFT 能够在不同大小、不同方向的图片中寻找相似特征,因此比普通角点检测更加稳定。
四、计算特征描述符
只有关键点还不够,还需要计算描述符:
kp, des = sift.compute(gray,keypoints)
也可以直接使用:
keypoints, descriptors = sift.detectAndCompute(gray,None)
detectAndCompute() 会一次完成关键点检测和描述符计算。
查看结果:
print(
len(keypoints)
)
print(
descriptors.shape
)
descriptors 通常是一个二维数组:
关键点数量 × 描述符维度
SIFT 描述符一般是 128 维。也就是说,每个关键点都会对应一组数字,用来描述它附近的纹理和方向信息。
五、两张图片之间的特征匹配
有了两张图片的关键点和描述符后,就可以比较它们是否存在相同内容。
假设有两张指纹图片:
img1 = cv2.imread("src.BMP")
img2 = cv2.imread("model.BMP")
分别提取特征:
sift = cv2.SIFT_create()
kp1, des1 = sift.detectAndCompute(
img1,
None
)
kp2, des2 = sift.detectAndCompute(
img2,
None
)
然后创建 FLANN 匹配器:
flann = cv2.FlannBasedMatcher()
使用 K 近邻匹配:
matches = flann.knnMatch(
des1,
des2,
k=2
)
这里的 k=2 表示:对于第一张图片中的每一个描述符,在第二张图片中寻找最相近的两个描述符。
六、Lowe比例测试
仅仅找到距离最近的特征点还不够,因为有些特征可能会产生误匹配。
代码中使用了 Lowe 比率测试:
good_matches = []
for m, n in matches:
if m.distance < 0.8 * n.distance:
good_matches.append(m)
其中:
m 是距离最近的匹配;
n 是距离第二近的匹配;
m.distance 和 n.distance 表示特征描述符之间的距离。
判断条件:
m.distance < 0.8 * n.distance
意思是:最近的匹配必须明显优于第二近的匹配,才能认为它比较可靠。
如果两个距离非常接近,说明程序无法确定哪个才是真正的匹配,这种结果就会被舍弃。
最后统计有效匹配数量:
num = len(good_matches)
print("有效匹配点:", num)
匹配点越多,通常表示两张图片越相似。
七、特征匹配的实际意义
SIFT具有旋转不变性和尺度不变性
在普通图片中,程序可以通过 SIFT 找到物体表面的特征。例如,一张建筑图片经过旋转或缩放后,仍然可能与原图匹配。
在指纹识别中,指纹纹理中有很多特殊的分叉点、端点和纹路结构。SIFT 可以从指纹图片中提取这些局部特征,再通过描述符匹配判断两张指纹是否属于同一个人。
整个过程不需要逐个比较所有像素,而是比较更有代表性的特征点,因此效率和稳定性都更好。
八、编程时需要注意的问题
1. 图片读取失败
image = cv2.imread("coca.png")
if image is None:
print("图片读取失败")
如果图片路径错误,imread() 会返回 None,后续调用灰度转换时会报错。
2. 描述符可能为空
如果图片太简单,或者图片读取失败,SIFT 可能检测不到关键点:
if des1 is None or des2 is None:
print("没有检测到足够的特征")
进行匹配前最好先判断。
3. 匹配数量不是唯一标准
匹配点数量越多,不一定代表结果绝对正确。还需要考虑:
1,匹配点是否分布合理;
2,是否集中在一个小区域;
3,图片是否存在重复纹理;
4,是否受到噪声影响。
在更复杂的项目中,还可以结合单应性矩阵和 RANSAC 去除错误匹配。
总结
本篇学习了 OpenCV 中的特征检测和特征匹配。
Harris 算法可以检测图像中的角点,适合寻找局部灰度变化明显的位置。SIFT 不仅可以检测关键点,还可以计算特征描述符,使图像在发生缩放、旋转后仍然能够进行匹配。
两张图片的匹配流程可以概括为:
1,读取图片
2,检测关键点
3,计算描述符
4,使用FLANN寻找近邻
5,通过比例测试筛选
6,统计有效匹配点数量
这些知识为后面的指纹验证和指纹识别打下了基础。