跳转至

第3章 特征提取与描述

特征提取与描述图

📚 章节概述

本章介绍计算机视觉中的特征提取与描述技术,包括边缘检测、角点检测、特征描述符(SIFT、SURF、ORB等)以及特征匹配方法。这些技术是传统计算机视觉的核心,也是理解深度学习特征的基础。

学习时间:5-7天 难度等级:⭐⭐⭐⭐ 前置知识:第1-2章、线性代数

🎯 学习目标

完成本章后,你将能够: - 理解特征提取的重要性和基本概念 - 掌握边缘检测和角点检测算法 - 熟练使用SIFT、SURF、ORB等特征描述符 - 理解特征匹配的原理和方法 - 能够实现图像配准和拼接 - 完成图像拼接项目


3.1 特征提取基础

3.1.1 什么是特征?

定义:特征是图像中具有辨识性的局部区域,能够在不同视角、光照下保持稳定。

好的特征应该具备: - 可重复性:同一物体在不同图像中能检测到 - 独特性:能够区分不同的物体 - 不变性:对旋转、缩放、光照变化鲁棒 - 高效性:计算速度快

特征层次

Text Only
低层特征:边缘、角点、斑点
中层特征:特征描述符(SIFT、HOG)
高层特征:语义特征(深度学习)

3.1.2 边缘检测

Canny边缘检测回顾

Python
import cv2
import numpy as np

image = cv2.imread('image.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# Canny边缘检测
edges = cv2.Canny(gray, 100, 200)

# 查找轮廓
contours, hierarchy = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# 绘制轮廓
result = image.copy()
cv2.drawContours(result, contours, -1, (0, 255, 0), 2)

Laplacian of Gaussian (LoG)

Python
# LoG边缘检测
gray_float = np.float32(gray)
log = cv2.Laplacian(gray_float, cv2.CV_32F, ksize=5)
log = cv2.convertScaleAbs(log)

3.1.3 角点检测

Harris角点检测

Python
# Harris角点检测
gray = np.float32(gray)
harris = cv2.cornerHarris(gray, blockSize=2, ksize=3, k=0.04)

# 阈值处理
harris = cv2.dilate(harris, None)
threshold = 0.01 * harris.max()
corner_img = image.copy()
corner_img[harris > threshold] = [0, 0, 255]

# Shi-Tomasi角点检测(更好的方法)
corners = cv2.goodFeaturesToTrack(gray, maxCorners=100, qualityLevel=0.01, minDistance=10)
corners = np.int32(corners)

for corner in corners:
    x, y = corner.ravel()
    cv2.circle(corner_img, (x, y), 3, (0, 255, 0), -1)


3.2 特征描述符

3.2.1 SIFT (Scale-Invariant Feature Transform)

SIFT特点: - 尺度不变性 - 旋转不变性 - 光照不变性 - 部分视角不变性

代码实现

Python
import cv2
import numpy as np

# 读取图像
img1 = cv2.imread('image1.jpg')
img2 = cv2.imread('image2.jpg')

# 转换为灰度图
gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)

# 创建SIFT检测器
sift = cv2.SIFT_create()

# 检测关键点和计算描述符
kp1, des1 = sift.detectAndCompute(gray1, None)
kp2, des2 = sift.detectAndCompute(gray2, None)

# 绘制关键点
img1_kp = cv2.drawKeypoints(gray1, kp1, None, flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
img2_kp = cv2.drawKeypoints(gray2, kp2, None, flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)

# 特征匹配
bf = cv2.BFMatcher()
matches = bf.knnMatch(des1, des2, k=2)

# Lowe's ratio test
good_matches = []
for m, n in matches:
    if m.distance < 0.75 * n.distance:
        good_matches.append(m)

# 绘制匹配结果
result = cv2.drawMatches(img1, kp1, img2, kp2, good_matches, None, flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)

print(f"检测到 {len(kp1)}{len(kp2)} 个关键点")
print(f"匹配到 {len(good_matches)} 对匹配点")

SIFT算法步骤: 1. 尺度空间极值检测:使用DoG(Difference of Gaussian) 2. 关键点定位:精确定位关键点位置 3. 方向分配:计算主方向 4. 描述符生成:生成128维描述符

3.2.2 SURF (Speeded-Up Robust Features)

SURF特点: - 比SIFT快 - 使用积分图像加速 - 类似SIFT的不变性

代码实现

Python
# 创建SURF检测器
surf = cv2.xfeatures2d.SURF_create(hessianThreshold=400)

# 检测关键点和计算描述符
kp1, des1 = surf.detectAndCompute(gray1, None)
kp2, des2 = surf.detectAndCompute(gray2, None)

# 特征匹配(与SIFT相同)

3.2.3 ORB (Oriented FAST and Rotated BRIEF)

ORB特点: - 速度快(实时) - 免费专利 - 旋转不变性 - 尺度不变性(金字塔)

代码实现

Python
# 创建ORB检测器
orb = cv2.ORB_create(nfeatures=1000)

# 检测关键点和计算描述符
kp1, des1 = orb.detectAndCompute(gray1, None)
kp2, des2 = orb.detectAndCompute(gray2, None)

# 使用汉明距离匹配
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
matches = bf.match(des1, des2)

# 按距离排序
matches = sorted(matches, key=lambda x: x.distance)  # lambda匿名函数

# 绘制前50个匹配
result = cv2.drawMatches(img1, kp1, img2, kp2, matches[:50], None, flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)  # 切片操作,取前n个元素

ORB算法组成: - FAST:快速角点检测 - BRIEF:二进制描述符 - 方向:计算主方向 - 金字塔:多尺度检测

3.2.4 其他特征描述符

HOG (Histogram of Oriented Gradients)

Python
# HOG特征提取
hog = cv2.HOGDescriptor()
hog.compute(gray1)

BRISK

Python
brisk = cv2.BRISK_create()
kp1, des1 = brisk.detectAndCompute(gray1, None)

AKAZE

Python
akaze = cv2.AKAZE_create()
kp1, des1 = akaze.detectAndCompute(gray1, None)


3.3 特征匹配

3.3.1 暴力匹配

Python
# 暴力匹配
bf = cv2.BFMatcher(cv2.NORM_L2, crossCheck=False)
matches = bf.match(des1, des2)

# 按距离排序
matches = sorted(matches, key=lambda x: x.distance)

3.3.2 FLANN匹配

Python
# FLANN匹配(更快)
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)
search_params = dict(checks=50)

flann = cv2.FlannBasedMatcher(index_params, search_params)
matches = flann.knnMatch(des1, des2, k=2)

# Lowe's ratio test
good_matches = []
for m, n in matches:
    if m.distance < 0.7 * n.distance:
        good_matches.append(m)

3.3.3 RANSAC(随机抽样一致)

Python
# 提取匹配点坐标
src_pts = np.float32([kp1[m.queryIdx].pt for m in good_matches]).reshape(-1, 1, 2)  # 重塑张量形状
dst_pts = np.float32([kp2[m.trainIdx].pt for m in good_matches]).reshape(-1, 1, 2)

# 使用RANSAC计算单应性矩阵
M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)

# 应用透视变换
h, w = img1.shape[:2]
result = cv2.warpPerspective(img1, M, (w, h))

3.4 实战案例:图像拼接

项目概述

实现全景图像拼接功能,使用特征检测和匹配技术。

完整代码

Python
import cv2
import numpy as np

class ImageStitcher:
    """图像拼接类"""

    def __init__(self, feature_type='sift'):
        """
        初始化拼接器
        feature_type: 'sift', 'surf', 'orb'
        """
        self.feature_type = feature_type
        self.detector = self._create_detector()

    def _create_detector(self):
        """创建特征检测器"""
        if self.feature_type == 'sift':
            return cv2.SIFT_create()
        elif self.feature_type == 'surf':
            return cv2.xfeatures2d.SURF_create()
        elif self.feature_type == 'orb':
            return cv2.ORB_create(nfeatures=2000)
        else:
            raise ValueError(f"不支持的特征类型: {self.feature_type}")

    def detect_and_match(self, img1, img2):
        """检测特征并匹配"""
        # 转换为灰度图
        gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
        gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)

        # 检测关键点和计算描述符
        kp1, des1 = self.detector.detectAndCompute(gray1, None)
        kp2, des2 = self.detector.detectAndCompute(gray2, None)

        # 特征匹配
        if self.feature_type == 'orb':
            # ORB使用汉明距离
            bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=False)
            matches = bf.knnMatch(des1, des2, k=2)
        else:
            # SIFT/SURF使用L2距离
            bf = cv2.BFMatcher(cv2.NORM_L2, crossCheck=False)
            matches = bf.knnMatch(des1, des2, k=2)

        # Lowe's ratio test
        good_matches = []
        for m, n in matches:
            if m.distance < 0.75 * n.distance:
                good_matches.append(m)

        print(f"检测到 {len(kp1)}{len(kp2)} 个关键点")
        print(f"匹配到 {len(good_matches)} 对匹配点")

        return kp1, kp2, good_matches

    def find_homography(self, kp1, kp2, matches):
        """计算单应性矩阵"""
        # 提取匹配点坐标
        src_pts = np.float32([kp1[m.queryIdx].pt for m in matches]).reshape(-1, 1, 2)
        dst_pts = np.float32([kp2[m.trainIdx].pt for m in matches]).reshape(-1, 1, 2)

        # 使用RANSAC计算单应性矩阵
        M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)

        # 计算内点数量
        inliers = sum(mask)
        print(f"内点数量: {inliers}/{len(matches)}")

        return M, mask

    def stitch_images(self, img1, img2):
        """拼接两张图像"""
        # 检测特征并匹配
        kp1, kp2, matches = self.detect_and_match(img1, img2)

        # 计算单应性矩阵
        M, mask = self.find_homography(kp1, kp2, matches)

        # 获取图像尺寸
        h1, w1 = img1.shape[:2]
        h2, w2 = img2.shape[:2]

        # 计算拼接后的画布大小
        corners1 = np.float32([[0, 0], [0, h1], [w1, h1], [w1, 0]]).reshape(-1, 1, 2)
        corners2 = np.float32([[0, 0], [0, h2], [w2, h2], [w2, 0]]).reshape(-1, 1, 2)

        corners1_transformed = cv2.perspectiveTransform(corners1, M)
        all_corners = np.concatenate((corners2, corners1_transformed), axis=0)

        [x_min, y_min] = np.int32(all_corners.min(axis=0).ravel() - 0.5)
        [x_max, y_max] = np.int32(all_corners.max(axis=0).ravel() + 0.5)

        # 平移矩阵
        translation = np.array([[1, 0, -x_min], [0, 1, -y_min], [0, 0, 1]])  # np.array创建NumPy数组

        # 应用变换
        result = cv2.warpPerspective(img1, translation.dot(M), (x_max - x_min, y_max - y_min))
        result[-y_min:h2-y_min, -x_min:w2-x_min] = img2

        return result

    def draw_matches(self, img1, img2, kp1, kp2, matches):
        """绘制匹配结果"""
        result = cv2.drawMatches(img1, kp1, img2, kp2, matches, None,
                                flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
        return result

# 使用示例
if __name__ == '__main__':
    # 创建测试图像(实际使用时替换为真实图像)
    img1 = np.random.randint(0, 256, (480, 640, 3), dtype=np.uint8)
    img2 = np.random.randint(0, 256, (480, 640, 3), dtype=np.uint8)

    # 保存测试图像
    cv2.imwrite('image1.jpg', img1)
    cv2.imwrite('image2.jpg', img2)

    # 创建拼接器
    stitcher = ImageStitcher(feature_type='orb')

    # 读取图像
    img1 = cv2.imread('image1.jpg')
    img2 = cv2.imread('image2.jpg')

    # 拼接图像
    result = stitcher.stitch_images(img1, img2)

    # 保存结果
    cv2.imwrite('stitched_result.jpg', result)
    print("拼接完成!结果已保存为 stitched_result.jpg")

3.5 练习题

基础题

  1. 简答题
  2. 什么是好的特征?应该具备哪些特性?

    好的特征应具备:①可重复性(同一物体在不同视角/光照下能被检测到);②独特性(能区分不同对象);③不变性(对旋转、缩放、光照变化鲁棒);④高效性(计算和匹配速度快)。

  3. SIFT和ORB有什么区别?

    SIFT生成128维浮点描述符,精度高但计算慢且有专利限制;ORB基于FAST关键点+BRIEF二进制描述符,速度约为SIFT的100倍、免费开源,但大尺度变化下精度略低。

  4. 编程题

  5. 使用Harris角点检测检测图像中的角点。
  6. 实现一个简单的特征匹配程序。

进阶题

  1. 编程题
  2. 实现从零开始计算Harris角点响应函数。
  3. 比较不同特征描述符的性能。

  4. 思考题

  5. 为什么需要特征描述符?

    仅有关键点位置不足以进行匹配,需要描述符编码关键点周围的局部图像信息,使同一物体上的对应点描述符相似、不同物体上的点差异大,从而实现可靠的特征匹配。

  6. RANSAC的作用是什么?

    RANSAC(随机抽样一致)用于从含有大量外点(误匹配)的数据中鲁棒地估计模型参数。通过随机采样最小点集拟合模型、统计内点数并迭代多次,选择内点最多的模型,有效剔除误匹配。

挑战题

  1. 项目题
  2. 实现一个完整的图像拼接系统,支持多张图像拼接。

3.6 面试准备

大厂面试题

Q1: 什么是特征?好的特征应该具备哪些特性?

参考答案: - 定义:图像中具有辨识性的局部区域 - 特性: - 可重复性:同一物体在不同图像中能检测到 - 独特性:能够区分不同的物体 - 不变性:对旋转、缩放、光照变化鲁棒 - 高效性:计算速度快

Q2: SIFT算法的步骤是什么?

参考答案: 1. 尺度空间极值检测(DoG) 2. 关键点定位 3. 方向分配 4. 描述符生成(128维)

Q3: ORB相比SIFT有什么优势?

参考答案: - 速度:ORB比SIFT快很多 - 专利:ORB免费,SIFT有专利 - 描述符:ORB使用二进制描述符,匹配更快 - 缺点:ORB的精度略低于SIFT

Q4: 什么是RANSAC?它的作用是什么?

参考答案: - 定义:随机抽样一致算法 - 作用:从包含噪声的数据中估计模型参数 - 步骤: 1. 随机选择最小样本集 2. 估计模型参数 3. 计算内点数量 4. 重复多次,选择内点最多的模型 - 应用:特征匹配、图像拼接、3D重建


3.7 本章小结

核心知识点

  1. 特征提取:边缘、角点、斑点
  2. 特征描述符:SIFT、SURF、ORB
  3. 特征匹配:暴力匹配、FLANN
  4. RANSAC:去除误匹配
  5. 图像拼接:特征检测、匹配、变换

下一步

完成本章后,你应该: - [ ] 理解特征提取的重要性 - [ ] 掌握常用的特征描述符 - [ ] 能够实现特征匹配 - [ ] 完成图像拼接项目

下一章04-传统计算机视觉算法.md - 学习传统CV算法


恭喜完成第3章! 🎉