OpenCV 图像特征与匹配:SIFT 特征检测与 BFMatcher 暴力匹配

OpenCV 图像特征与匹配:SIFT 特征检测与 BFMatcher 暴力匹配

OpenCV 图像处理课程 · 第 8 章「Image Feature and Matcher」学习笔记

环境:Python 3.10 + opencv-python 4.5.5.64 + PyCharm(chapter8 目录)

包含 2 课:SIFT 特征检测 / 特征匹配


章节总览

主题 核心函数 文件
01 SIFT 特征检测 cv2.SIFT_create() / sift.detectAndCompute() / cv2.drawKeypoints() sift_algo.py
02 特征匹配 cv2.BFMatcher() / bf.match() / cv2.drawMatches() feature_matching.py

本章说明 :图像是一系列局部特征的集合(角点、边缘等)。特征检测用于识别图像中的关键点,特征匹配用于把两张图像里的相似特征配对。


第 1 课 · SIFT 特征检测

概念

  • SIFT(Scale-Invariant Feature Transform,尺度不变特征变换)
  • 开发于约 1999 年 ,不列颠哥伦比亚大学专利
  • 商业应用需授权,但 2020-03-07 专利到期,现可免费商用
  • 能检测图像中的局部特征(关键点),用于物体识别、跟踪、手势识别
  • 特征丰富的区域(如马的身体)检测到更多特征,背景等变化小的区域检测少

关键点 + 描述符

输出 含义
keypoints 特征位置(x,y 坐标),每个特征一个 KeyPoint 对象
descriptors 每个特征的128 维唯一向量,描述该特征

函数签名

python 复制代码
sift = cv2.SIFT_create()
keypoints, descriptors = sift.detectAndCompute(gray_image, mask)
  • gray_image:输入灰度图
  • mask:掩码(None = 整图检测;传二值图可限定区域)
python 复制代码
image_features = cv2.drawKeypoints(image, keypoints, None, color)
  • color:关键点颜色(如 (255,0,255) 粉色)

完整代码(sift_algo.py)

python 复制代码
import cv2

image_path = "../data/horse.jpg"
image = cv2.imread(image_path)

image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

sift = cv2.SIFT_create()
keypoints, descriptors = sift.detectAndCompute(image_gray, None)

print("length of keypoints:", len(keypoints))   # 约40个特征
print("descriptors shape:", descriptors.shape)  # (N, 128)

image_features = cv2.drawKeypoints(image, keypoints, None, (255, 0, 255))

cv2.imshow("image features", image_features)
cv2.waitKey(0)
cv2.destroyAllWindows()

流程示意

#mermaid-svg-qlkztqsXFyjIvqY3{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-qlkztqsXFyjIvqY3 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-qlkztqsXFyjIvqY3 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-qlkztqsXFyjIvqY3 .error-icon{fill:#552222;}#mermaid-svg-qlkztqsXFyjIvqY3 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-qlkztqsXFyjIvqY3 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-qlkztqsXFyjIvqY3 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-qlkztqsXFyjIvqY3 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-qlkztqsXFyjIvqY3 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-qlkztqsXFyjIvqY3 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-qlkztqsXFyjIvqY3 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-qlkztqsXFyjIvqY3 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-qlkztqsXFyjIvqY3 .marker.cross{stroke:#333333;}#mermaid-svg-qlkztqsXFyjIvqY3 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-qlkztqsXFyjIvqY3 p{margin:0;}#mermaid-svg-qlkztqsXFyjIvqY3 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-qlkztqsXFyjIvqY3 .cluster-label text{fill:#333;}#mermaid-svg-qlkztqsXFyjIvqY3 .cluster-label span{color:#333;}#mermaid-svg-qlkztqsXFyjIvqY3 .cluster-label span p{background-color:transparent;}#mermaid-svg-qlkztqsXFyjIvqY3 .label text,#mermaid-svg-qlkztqsXFyjIvqY3 span{fill:#333;color:#333;}#mermaid-svg-qlkztqsXFyjIvqY3 .node rect,#mermaid-svg-qlkztqsXFyjIvqY3 .node circle,#mermaid-svg-qlkztqsXFyjIvqY3 .node ellipse,#mermaid-svg-qlkztqsXFyjIvqY3 .node polygon,#mermaid-svg-qlkztqsXFyjIvqY3 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-qlkztqsXFyjIvqY3 .rough-node .label text,#mermaid-svg-qlkztqsXFyjIvqY3 .node .label text,#mermaid-svg-qlkztqsXFyjIvqY3 .image-shape .label,#mermaid-svg-qlkztqsXFyjIvqY3 .icon-shape .label{text-anchor:middle;}#mermaid-svg-qlkztqsXFyjIvqY3 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-qlkztqsXFyjIvqY3 .rough-node .label,#mermaid-svg-qlkztqsXFyjIvqY3 .node .label,#mermaid-svg-qlkztqsXFyjIvqY3 .image-shape .label,#mermaid-svg-qlkztqsXFyjIvqY3 .icon-shape .label{text-align:center;}#mermaid-svg-qlkztqsXFyjIvqY3 .node.clickable{cursor:pointer;}#mermaid-svg-qlkztqsXFyjIvqY3 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-qlkztqsXFyjIvqY3 .arrowheadPath{fill:#333333;}#mermaid-svg-qlkztqsXFyjIvqY3 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-qlkztqsXFyjIvqY3 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-qlkztqsXFyjIvqY3 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qlkztqsXFyjIvqY3 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-qlkztqsXFyjIvqY3 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qlkztqsXFyjIvqY3 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-qlkztqsXFyjIvqY3 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-qlkztqsXFyjIvqY3 .cluster text{fill:#333;}#mermaid-svg-qlkztqsXFyjIvqY3 .cluster span{color:#333;}#mermaid-svg-qlkztqsXFyjIvqY3 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-qlkztqsXFyjIvqY3 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-qlkztqsXFyjIvqY3 rect.text{fill:none;stroke-width:0;}#mermaid-svg-qlkztqsXFyjIvqY3 .icon-shape,#mermaid-svg-qlkztqsXFyjIvqY3 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qlkztqsXFyjIvqY3 .icon-shape p,#mermaid-svg-qlkztqsXFyjIvqY3 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-qlkztqsXFyjIvqY3 .icon-shape .label rect,#mermaid-svg-qlkztqsXFyjIvqY3 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qlkztqsXFyjIvqY3 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-qlkztqsXFyjIvqY3 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-qlkztqsXFyjIvqY3 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 特征
SIFT
转灰度
图像
BGR 彩色图
cvtColor 转灰度
SIFT_create()

detectAndCompute
keypoints(位置) +

descriptors(128维向量)

真实演示图(真实马头照片 + 粉色关键点圆圈):

💡 图片来源:Wikimedia Commons(CC BY-SA 4.0)------Chestnut French Trotter horse head

SIFT 检测到的关键点

关键点被标记在眼睛、耳朵、口鼻、辔头环等特征明显的区域(黄圈+红心)。


第 2 课 · 特征匹配(BFMatcher)

概念

  • 利用 SIFT 得到的描述符 ,把两张图像里相似的特征配对
  • 例:裁剪的人脸(图1)与完整场景(图2)之间的特征匹配
  • 暴力匹配(Brute-Force Matcher,BFMatcher) :比较两图描述符距离,距离越短匹配越好

函数签名

python 复制代码
bf = cv2.BFMatcher()
matches = bf.match(descriptor1, descriptor2)      # 按距离返回匹配
matches = sorted(matches, key=lambda x: x.distance)  # 距离升序=最佳优先

image_match = cv2.drawMatches(img1, keypoints1, img2, keypoints2,
                              matches[:50], None)
  • bf.match(desc1, desc2):匹配两组描述符
  • sorted(matches, key=lambda x: x.distance):按距离排序,取前 N 个最佳
  • cv2.drawMatches(img1, kp1, img2, kp2, matches, None):把匹配连线画在两图间

完整代码(feature_matching.py)

python 复制代码
import cv2

image_path1 = "../data/horse_face.jpg"
image_path2 = "../data/horse.jpg"
image1 = cv2.imread(image_path1)
image2 = cv2.imread(image_path2)

image_gray1 = cv2.cvtColor(image1, cv2.COLOR_BGR2GRAY)
image_gray2 = cv2.cvtColor(image2, cv2.COLOR_BGR2GRAY)

sift = cv2.SIFT_create()
keypoints1, descriptor1 = sift.detectAndCompute(image_gray1, None)
keypoints2, descriptor2 = sift.detectAndCompute(image_gray2, None)

bf = cv2.BFMatcher()
matches = bf.match(descriptor1, descriptor2)
matches = sorted(matches, key=lambda x: x.distance)   # 最佳优先

image_match = cv2.drawMatches(image1, keypoints1,
                              image2, keypoints2, matches[:50], None)

cv2.imshow("image match", image_match)
cv2.waitKey(0)
cv2.destroyAllWindows()

流程示意

#mermaid-svg-r50u5rCK6MNevMeb{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-r50u5rCK6MNevMeb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-r50u5rCK6MNevMeb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-r50u5rCK6MNevMeb .error-icon{fill:#552222;}#mermaid-svg-r50u5rCK6MNevMeb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-r50u5rCK6MNevMeb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-r50u5rCK6MNevMeb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-r50u5rCK6MNevMeb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-r50u5rCK6MNevMeb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-r50u5rCK6MNevMeb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-r50u5rCK6MNevMeb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-r50u5rCK6MNevMeb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-r50u5rCK6MNevMeb .marker.cross{stroke:#333333;}#mermaid-svg-r50u5rCK6MNevMeb svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-r50u5rCK6MNevMeb p{margin:0;}#mermaid-svg-r50u5rCK6MNevMeb .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-r50u5rCK6MNevMeb .cluster-label text{fill:#333;}#mermaid-svg-r50u5rCK6MNevMeb .cluster-label span{color:#333;}#mermaid-svg-r50u5rCK6MNevMeb .cluster-label span p{background-color:transparent;}#mermaid-svg-r50u5rCK6MNevMeb .label text,#mermaid-svg-r50u5rCK6MNevMeb span{fill:#333;color:#333;}#mermaid-svg-r50u5rCK6MNevMeb .node rect,#mermaid-svg-r50u5rCK6MNevMeb .node circle,#mermaid-svg-r50u5rCK6MNevMeb .node ellipse,#mermaid-svg-r50u5rCK6MNevMeb .node polygon,#mermaid-svg-r50u5rCK6MNevMeb .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-r50u5rCK6MNevMeb .rough-node .label text,#mermaid-svg-r50u5rCK6MNevMeb .node .label text,#mermaid-svg-r50u5rCK6MNevMeb .image-shape .label,#mermaid-svg-r50u5rCK6MNevMeb .icon-shape .label{text-anchor:middle;}#mermaid-svg-r50u5rCK6MNevMeb .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-r50u5rCK6MNevMeb .rough-node .label,#mermaid-svg-r50u5rCK6MNevMeb .node .label,#mermaid-svg-r50u5rCK6MNevMeb .image-shape .label,#mermaid-svg-r50u5rCK6MNevMeb .icon-shape .label{text-align:center;}#mermaid-svg-r50u5rCK6MNevMeb .node.clickable{cursor:pointer;}#mermaid-svg-r50u5rCK6MNevMeb .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-r50u5rCK6MNevMeb .arrowheadPath{fill:#333333;}#mermaid-svg-r50u5rCK6MNevMeb .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-r50u5rCK6MNevMeb .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-r50u5rCK6MNevMeb .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-r50u5rCK6MNevMeb .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-r50u5rCK6MNevMeb .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-r50u5rCK6MNevMeb .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-r50u5rCK6MNevMeb .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-r50u5rCK6MNevMeb .cluster text{fill:#333;}#mermaid-svg-r50u5rCK6MNevMeb .cluster span{color:#333;}#mermaid-svg-r50u5rCK6MNevMeb div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-r50u5rCK6MNevMeb .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-r50u5rCK6MNevMeb rect.text{fill:none;stroke-width:0;}#mermaid-svg-r50u5rCK6MNevMeb .icon-shape,#mermaid-svg-r50u5rCK6MNevMeb .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-r50u5rCK6MNevMeb .icon-shape p,#mermaid-svg-r50u5rCK6MNevMeb .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-r50u5rCK6MNevMeb .icon-shape .label rect,#mermaid-svg-r50u5rCK6MNevMeb .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-r50u5rCK6MNevMeb .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-r50u5rCK6MNevMeb .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-r50u5rCK6MNevMeb :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} drawMatches
BFMatcher
SIFT 各自检测
两张图
图1 (裁剪)

horse_face
图2 (整体)

horse
kp1 + desc1
kp2 + desc2
bf.match(desc1, desc2)

按距离排序,取前50
两图间连线相同特征

真实演示图(左=完整马头,右=马脸裁剪放大,绿色连线=匹配特征):

💡 图片来源:Wikimedia Commons(CC BY-SA 4.0)

特征匹配结果(局部裁剪 ↔ 整体)

左侧绿色框圈出马脸区域,右侧为该区域放大图;每条绿线把框内特征点(眼睛、口鼻、辔头环)连到放大图对应位置,证明局部特征能与整体匹配。


本章小结(速查表)

操作 函数 关键点
建 SIFT cv2.SIFT_create() 创建 SIFT 对象
检测特征 sift.detectAndCompute(img, mask) 返回(keypoints, descriptors),desc 128维
画关键点 cv2.drawKeypoints(img, kp, None, color) 图上标记关键点
建匹配器 cv2.BFMatcher() 暴力匹配
匹配 bf.match(desc1, desc2) 按距离返回匹配
排序 sorted(matches, key=lambda x: x.distance) 距离升序=最佳优先
画匹配 cv2.drawMatches(img1, kp1, img2, kp2, matches, None) 两图连线

核心易错点

  1. SIFT 输入需灰度图 (BGR 先 cvtColor
  2. detectAndCompute 返回 两个值(keypoints, descriptors)
  3. descriptors.shape(N, 128)------每个特征 128 维
  4. 匹配排序key=lambda x: x.distance,距离越短越好
  5. drawMatches 取前 N 个(如 matches[:50])画最佳匹配
相关推荐
java1234_小锋42 分钟前
YOLO26 计算机视觉 - YOLO26 简介 & Hello World项目搭建
人工智能·yolo·计算机视觉·机器视觉·yolo26
Mr数据杨1 小时前
商店商品销量预测助力库存优化
人工智能·数据分析·kaggle竞赛
东方佑1 小时前
202 个参数干掉 59 万个参数:ConvLinear 的设计、融合与实战
人工智能
唯创知音1 小时前
电动车仪表语音芯片方案 速度、电量、故障三语播报用WT588F02-8S-C
人工智能·语音识别·电动车仪表语音芯片方案
前沿在线1 小时前
Vbot ATOM发布,人形机器人进入产品化时刻
人工智能·ai·大模型
武子康1 小时前
Claude 已经给文本加水印了吗?真正缺的不是声明,而是逐模型状态
人工智能·llm·agent
人工智能培训1 小时前
通用人工智能AGI发展的潜在风险与管控思路
人工智能·agi
王志来137944730081 小时前
工控服务器机箱选型决策要素解析:匀天以“快全准省”构建价值坐标
运维·服务器·python·devops
liukuang1101 小时前
商汤2026上半年盈利6.2亿元,生成式AI业务占比近80%
大数据·人工智能