OpenCV 特征点检测与匹配

news2025/2/8 9:44:48

一 OpenCV特征场景

①图像搜索,如以图搜图;
②拼图游戏;
③图像拼接,将两长有关联得图拼接到一起;

1 拼图方法

寻找特征
特征是唯一的
可追踪的
能比较的

二 角点

在特征中最重要的是角点
灰度剃度的最大值对应的像素
两条线的角点
极值点(一阶导数最大值,但二阶导数为0)

三 Harris角点

哈里斯角点检测
在这里插入图片描述
Harris点
① 光滑地区,无论向哪里移动,衡量系数不变;
②边缘地址,垂直边缘移动时,衡量系统变换剧烈;
③在交点处,往哪个方向移动,衡量系统都变化剧烈;

1 Harris角点检测API

cornerHarris(img,dst,blockSize,ksize,k)
blockSize:检测窗口大小
ksize:Sobel的卷积核
k:权重系数,经验值,一般取0.02~0.04之间。
import cv2
import numpy as np

blockSize=2

ksize=1

k=0.04

img=cv2.imread('chess.jpg')

#灰度化
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

#Harris角点检测
dst=cv2.cornerHarris(gray,blockSize,ksize,k)

#Harris角点的展示
img[dst>0.04*dst.max()]=[0,0,255]
cv2.imshow('img',img)
cv2.waitKey(0)


四 Shi_Tomasi交点检测

Shi-Tomasi是Harris角点检测的改进;
Harris角点检测的稳定性和K有关,而k是个经验值,不好设定最佳值。

1 Shi-Tomasi角点检测API

goodFeaturesToTrack(img,maxCorners,...)
maxCorners:角点的最大数,值为0表示无限制
qualityLevel:小于1.0的正数,一般在0.01~0.1之间
minDistance:角之间最小欧式距离,忽略小于此距离的点。
mask:感兴趣的区域
blockSize:检测窗口
useHarrisDetector:是否使用Harris算法
k:默认是0.04
import cv2
import numpy as np

maxCorners=100
ql=0.01


img=cv2.imread('./chess.jpg')
cv2.imshow('img',img)
#灰度化
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

# Shi-Tomasi
corners=cv2.goodFeaturesToTrack(gray,maxCorners,ql,10)
print(corners)
corners=np.int32(corners)
# Shi-Tomasi绘制角点
for i in corners:
    x,y=i.ravel()
    cv2.circle(img,(x,y),3,(255,0,0),-1)

cv2.imshow('img',img)
cv2.waitKey(0)

五 SIFT(Scale-Invariant Feature Transform)

SIFT出现的原因:
harris 角点具有旋转不变的特性;
但缩放后,原来的角点有可能就不是角点了;

图放大
在这里插入图片描述

使用SIFT的步骤

①创建SIFT对象
②进行检测,kp=sift.detect(img,…)
③绘制关键点,drawKeypoints(gray,kp,img)

from email.mime import image

import cv2
import numpy as np

img=cv2.imread('./chess.jpg')

gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

sift=cv2.xfeatures2d.SIFT_create()

kp=sift.detect(gray,None)

cv2.drawKeypoints(gray,kp,img)

cv2.imshow('img',img)
cv2.waitKey(0)

在这里插入图片描述

六 SIFT计算描述子

关键点和描述子
关键点:位置,大小和方向
关键点描述子:记录了关键点周围对其有贡献的像素点的一组向量值,其不受仿射变换、光照变换等影响。

计算描述子

kp,des=sift.compute(img,kp)
其作用是进行特征匹配

同时计算关键点和描述

kp,des=sift.detectAndCompute(img,...)
mask:指明对img中哪个区域进行计算
import cv2
import numpy as np

img=cv2.imread('./chess.jpg')

gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

sift=cv2.xfeatures2d.SIFT_create()

kp,des=sift.detectAndCompute(gray,None)
print(des)
cv2.drawKeypoints(gray,kp,img)

cv2.imshow('img',img)
cv2.waitKey(0)

在这里插入图片描述

七 SURF特征检测

SURF的优点
SIFT最大的问题是速度慢,因此才有SURF

使用SURF的步骤

surf=cv2.xfeatures2d.SUFR_create()
kp,des=surf.detectAndCompute(img,mask)
import cv2
import numpy as np

img=cv2.imread('./chess.jpg')

gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

surf=cv2.xfeatures2d.SURF_create()

kp,des=surf.detectAndCompute(gray,None)

print(des[0])

cv2.drawKeypoints(gray,kp,img)

cv2.imshow('img',img)
cv2.waitKey(0)

八 ORB(Oriented FAST and Rotated BRIEF)特征检测

1 ORB 优势

①ORB可以做到实时检测
②ORB=Oriented FAST+Rotated BRIEF

2 FAST

可以做到特征点的实时检测

3 BRIEF

BRIEF是对已检测到的特征点进行描述,它加快了特征描述符建立的速度。同时也极大的降低了特征匹配的时间。

3 ORB使用步骤

orb=cv2.ORB_create()
kp,des=orb.detectAndCompute(img,mask)
import cv2
import numpy as np

img=cv2.imread('./chess.jpg')

gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

orb=cv2.ORB_create()

kp,des=orb.detectAndCompute(gray,None)

cv2.drawKeypoints(gray,kp,img)

cv2.imshow('img',img)
cv2.waitKey(0)


在这里插入图片描述

九 暴力特征匹配

1 特征匹配方法

①BF(Brute-Force),暴力特征匹配方法;
②FLANN最快邻近区特征匹配方法;
它使用第一组中的每个特征的描述子与第二组中的所有特征描述子进行匹配计算它们之间的差距,然后将最接近一个匹配返回。

2 OpenCV特征匹配步骤

创建匹配器,BFMatcher(normType,crossCheck)
进行特征匹配,bf.match(des1,des2)
绘制匹配点,cv2.drawMatches(img1,kp1,img2,k2,...)
BFMatcher
normType:NORM_L1,NORM_L2,HAMMING1....
crossCheck:是否进行交叉匹配,默认为false
Match方法
参数为SIFT,SURF,OBR等计算的描述子
对两幅图的描述子进行计算
drawMatches
搜索img,kp
匹配图img,kp
match()方法返回的匹配结果

import cv2
import numpy as np

img=cv2.imread('./opencv_search.png')
img1=cv2.imread('./opencv_orig.png')



gray=cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray1=cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)

sift=cv2.xfeatures2d.SIFT_create()

kp1,des1=sift.detectAndCompute(gray,None)
kp2,des2=sift.detectAndCompute(gray1,None)

bf=cv2.BFMatcher(cv2.NORM_L1)
match=bf.match(des1,des2)

img3=cv2.drawMatches(img,kp1,img1,kp2,match,None)

cv2.imshow('img3',img3)
cv2.waitKey(0)

在这里插入图片描述

十 FLANN特征匹配

1 FLANN优缺点

在进行批量特征匹配时,FLANN速度更快;
由于它使用的是邻近近似值,所以精度较差;

2 使用FLANN特征匹配的步骤

①创建FLANN匹配器,FlannBasedMatcher(...)
②进行特征匹配,flann.match/knnMatch(...)
③绘制匹配点,cv2.drawMathes/drawMatchesKnn(...)
FlannBasedMathcer
index_params字典:匹配算法KDTREE、LSH
search_params字典:指定KETREE算法中遍历树的次数
KDTREE
index_params=dict(algorithmFLANN_INDEX_KETREE,trees=5)
search_params
search_params=dict(checks=50)
knnMatch方法
参数为SIFT、SURF、ORB等计算的描述子;
k,表示取欧式距离最近的前k个关键点;
返回的是匹配的结果DMatch对象;
DMatch的内容
distance,描述子之间的距离,值越低越好;
queryIdx,第一个图像的描述子索引值;
trainIdx,第二个图的描述子索引值;
imgIdx,第二个图的索引值;

drawMatchesKnn
搜索img,kp
匹配图img,kp
match()方法返回的匹配结果

十一 实战FLANN特征匹配

import cv2
import numpy as np

img1=cv2.imread('opencv_search.png')
img2=cv2.imread('opencv_orig.png')

gray1=cv2.cvtColor(img1,cv2.COLOR_BGR2GRAY)
gray2=cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY)

#创建SIFT特征
sift=cv2.xfeatures2d.SIFT_create()

#计算描述子与特征
kp1,des1=sift.detectAndCompute(img1,None)
kp2,des2=sift.detectAndCompute(img2,None)

# 创建匹配器
index_params=dict(algorithm=1,trees=5)
search_params=dict(checks=50)
flann=cv2.FlannBasedMatcher(index_params,search_params)

#对描述子进行匹配计算
matchs=flann.knnMatch(des1,des2,k=2)

good=[]
for i,(m,n)in enumerate(matchs):
    if m.distance<0.7*n.distance:
        good.append(m)

ret=cv2.drawMatchesKnn(img1,kp1,img2,kp2,[good],None)

cv2.imshow('result',ret)
cv2.waitKey(0)

在这里插入图片描述

十二 图像查找

1 图像查找

特征匹配+单应性矩阵

什么是单应性矩阵
在这里插入图片描述

import cv2
import numpy as np

img1=cv2.imread('opencv_search.png')
img2=cv2.imread('opencv_orig.png')

gray1=cv2.cvtColor(img1,cv2.COLOR_BGR2GRAY)
gray2=cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY)

#创建SIFT特征
sift=cv2.xfeatures2d.SIFT_create()

#计算描述子与特征
kp1,des1=sift.detectAndCompute(img1,None)
kp2,des2=sift.detectAndCompute(img2,None)

# 创建匹配器
index_params=dict(algorithm=1,trees=5)
search_params=dict(checks=50)
flann=cv2.FlannBasedMatcher(index_params,search_params)

#对描述子进行匹配计算
matchs=flann.knnMatch(des1,des2,k=2)


good=[]

for i,(m,n) in enumerate(matchs):
     if m.distance<0.7*n.distance:
         good.append(m)


if len(good)>=4:
    #print(len(good.trainIdx))
    srcPts = np.float32([kp1[m.queryIdx].pt for m in good]).reshape(-1, 1, 2)
    dstPts = np.float32([kp2[m.trainIdx].pt for m in good]).reshape(-1, 1, 2)

    H, _ = cv2.findHomography(srcPts, dstPts, cv2.RANSAC, 5.0)

    h, w = img1.shape[:2]

    pts = np.float32([[0, 0], [0, h - 1], [w - 1, h - 1], [w - 1, 0]]).reshape(-1, 1, 2)
    dst = cv2.perspectiveTransform(pts, H)

    cv2.polylines(img2, [np.int32(dst)], True, (0, 0, 255))
else:
    print('the number of god is less than 4')
    exit()

ret=cv2.drawMatchesKnn(img1,kp1,img2,kp2,[good],None)

cv2.imshow('resutl',ret)

cv2.waitKey(0)


十三 图像拼接

1 图像合并的步骤

读文件并重置尺寸;
根据特征点和计算描述子,得到单应性矩阵;
图像变换;
图像拼接并输出图像;

import cv2
import numpy as np

#第一步 读取文件,将图片设置成一样大小640x640
#第二步 找特征点,描述子,计算单应性矩阵
#第三步 根据单应性矩阵对图像进行变换,然后平移
#第四步 拼接并输出最终结果

#读取两张图片
img1=cv2.imread('map1.png')
img2=cv2.imread('map2.png')

#将两张同样图片设置成同样大小
img1=cv2.resize(img1,(640,480))
img2=cv2.resize(img2,(640,480))

inputs=np.hstack((img1,img2))
cv2.imshow('input img',inputs)
cv2.waitKey(0)
import cv2
import numpy as np

def stitch_image(img1,img2,H):
#获得每张图片的四个角点
#对图片进行变换(单应性矩阵使图进行旋转,平移)
#创建一张大图,将两张图拼接到一起
#将结果输出

#获得原始图的高/宽
       h1,w1=img1.shape[:2]
       h2,w2=img2.shape[:2]

       img1_dims=np.float32([[0,0],[0,h1],[w1,h1],[w1,0]]).reshape(-1,1,2)
       img2_dims=np.float32([[0,0],[0,h2],[w2,h2],[w2,0]]).reshape(-1,1,2)

       img1_transform=cv2.perspectiveTransform(img1_dims,H)

       result_dims=np.concatenate((img2_dims,img1_transform),axis=0)


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

#平移的距离
       transform_dist=[-x_min,-y_min]

       transform_array=np.array([[1,0,transform_dist[0]],
                                 [0,1,transform_dist[1]],
                                 [0,0,1]])

       result_img=cv2.warpPerspective(img1,transform_array.dot(H),(x_max-x_min,y_max-y_min))

       result_img[transform_dist[1]:transform_dist[1]+h2,
       transform_dist[0]:transform_dist[0]+w2]=img2

       return result_img

#第一步 读取文件,将图片设置成一样大小640x640
#第二步 找特征点,描述子,计算单应性矩阵
#第三步 根据单应性矩阵对图像进行变换,然后平移
#第四步 拼接并输出最终结果

def get_homo(img1,img2):
#创建特征转换对象
#通过特征转换对象获得特征点和描述子
#创建特征匹配器
#进行特征匹配
#过滤特征,找出有效的特征匹配点
     sift=cv2.xfeatures2d.SIFT_create()

     k1,d1=sift.detectAndCompute(img1,None)
     k2,d2=sift.detectAndCompute(img2,None)

#创建特征匹配
     bf=cv2.BFMatcher()
     matches=bf.knnMatch(d1,d2,k=2)
#过滤特征,找出有效的特征匹配点
     verify_ratio=0.8
     verify_matches=[]
     for m1,m2 in matches:
         if m1.distance<0.8*m2.distance:
             verify_matches.append(m1)

     min_matches=8
     if len(verify_matches)>min_matches:
         img1_pts=[]
         img2_pts=[]

         for m in verify_matches:
             img1_pts.append(k1[m.queryIdx].pt)
             img2_pts.append(k2[m.trainIdx].pt)

         img1_pts=np.float32(img1_pts).reshape(-1,1,2)
         img2_pts=np.float32(img2_pts).reshape(-1,1,2)
         H,mask=cv2.findHomography(img1_pts,img2_pts,cv2.RANSAC,5.0)
         return H
     else:
          print("err:Not enough matches!")
          exit()
#第一步 读取文件,将图片设置成一样大小640x480
#第二步 找特征点,描述子,计算单应性矩阵
#第三步 根据单应性矩阵对图像进行变换,然后平移
#第四步 拼接并输出最终结果

#读取两张图片
img1=cv2.imread('map1.png')
img2=cv2.imread('map2.png')

#将两张同样图片设置成同样大小
img1=cv2.resize(img1,(640,480))
img2=cv2.resize(img2,(640,480))

inputs=np.hstack((img1,img2))

#获得单应性矩阵
H=get_homo(img1,img2)

#进行图像拼接
result_image=stitch_image(img1,img2,H)

cv2.imshow('input img',inputs)
cv2.waitKey(0)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1850798.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

少儿编程是骗局吗?少儿编程到底在学什么

少儿编程是一种有益的教育方式&#xff0c;它可以帮助孩子们培养逻辑思维、创造力和解决问题的能力。 少儿编程&#xff1a;开启未来的密码 在当今数字化的时代&#xff0c;编程已经成为一项重要的技能。而少儿编程&#xff0c;作为培养孩子们计算思维和创造力的新兴教育领域&…

泰国普吉岛与曼谷7天自由行路线与踩坑经历

本文介绍泰国6日自由行&#xff08;普吉岛3日、曼谷3日&#xff09;的每日详细行程、游览心得、避坑经历等。 2024年06月初&#xff0c;我们一行5人前往泰国普吉岛与曼谷等2地&#xff0c;进行了一共为期7天的旅行&#xff1b;其中真正花在游玩上的时间大概是5至6天。在这里就介…

深入讲解C++基础知识(一)

目录 一、基本内置类型1. 类型的作用2. 分类3. 整型3.1 内存描述及查询3.2 布尔类型 —— bool3.3 字符类型 —— char3.4 其他整型 4. 有符号类型和无符号类型5. 浮点型6. 如何选择类型7. 类型转换7.1 自动类型转换7.2 强制类型转换7.3 类型转换总结 8. 类型溢出8.1 注意事项 …

【Mac】Pixelmator Pro for Mac(媲美PS的修图软件)软件介绍

软件介绍 Pixelmator Pro是一款功能强大的图像编辑软件&#xff0c;专为macOS平台设计。它结合了丰富的图像编辑功能和直观的用户界面&#xff0c;适合专业摄影师、设计师以及图像编辑爱好者。以下是Pixelmator Pro的一些主要特点和功能介绍&#xff1a; 功能特色 非破坏性编…

罗克韦尔Rockwell EDI 项目案例

项目挑战 企业A有两个工厂需要接受来自Rockwell的订单&#xff0c;出于成本考虑&#xff0c;两个工厂需要使用同一套EDI系统实现对接&#xff0c;因此在EDI系统中要区分发给不同工厂的订单数据。除此之外&#xff0c;此项目中涉及到了EDI系统与SAP系统的集成&#xff0c;SAP系统…

ArmSoM-Sige7/5/1 和树莓派5规格比较

引言 在当今快速发展的嵌入式系统领域&#xff0c;选择一款性能强大、功能丰富的开发板对于项目的成功至关重要。本文将介绍并比较 Sige7、Sige5、Raspberry Pi 5 和 Sige1 这四款开发板的关键规格和特性&#xff0c;帮助开发者和爱好者选择最适合其需求的平台。 ArmSoM-Sige…

【鸿蒙踩坑记录】解决:list组件滑动至左边或右边,回弹效果过大问题

一、问题描述 开发过程中使用List组件&#xff0c;当内容超过一屏时可出现滚动效果&#xff0c;此时按住内容迅速滑动至左边&#xff0c;或者滑动到右边&#xff0c;回弹效果过大 期望&#xff1a;滑动时&#xff0c;不要有那么大的回弹效果 二、目前效果 三、解决方法 3.1…

怎么把pdf文件转cad图纸?方法分享!

怎么把pdf文件转cad图纸&#xff1f;在数字化时代&#xff0c;PDF和CAD作为两种常见的文件格式&#xff0c;各自在各自的领域发挥着重要作用。然而&#xff0c;当需要在两者之间进行转换时&#xff0c;许多人可能会感到困惑和无从下手。今天&#xff0c;我将为大家推荐三款强大…

Java-拼接字符串数组(String.join()方法)

问题引入 刷算法题lc2288的时候遇见的一个小细节&#xff0c;记录一下&#xff0c;有兴趣的朋友可以做一下&#xff0c;练习一下哈哈~ 此题需要使用大家都比较熟悉的split方法将句子按照空格拆分为字符串数组。 然后再在数组中对每一个字符串操作&#xff0c;操作完成后要求…

无需安装就能一键部署Stable Diffusion 3?

一键部署使用SD3&#xff1f;让你的创作更加便捷&#xff01; 前言 厚德云上架SD3! 距离Stable Diffusion 3的上线已经有一阵时间了。从上线至今SD3也是一直好评不断&#xff0c;各项性能的提升也让它荣获“最强开源新模型”的称号。成为了AI绘画设计师们新的香馍馍。 可对于SD…

JS小游戏-像素鸟#源码#Javascript

1、游戏图片 2、HTML部分 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title>&…

操作系统:高效、稳定的承上启下

标题&#xff1a;操作系统&#xff1a;高效、稳定的承上启下 水墨不写bug &#xff08;图片来源于网络&#xff09; 目录 一、初识操作系统 第一个操作系统&#xff1a;Uinx Uinx的商业化 Linux&#xff1a;横空出世 二、如何在Windows上使用Linux 正文开始&#xff1a;…

2023年JCR影响因子正式发布,点击查看能源与燃料领域期刊变化【持续更新02】

2024年6月20日&#xff0c;科睿唯安发布了2024年度《期刊引证报告》(Journal Citation Reports&#xff0c;JCR)&#xff0c;报告覆盖全面的高质量期刊资源&#xff0c;提供了丰富的数据、指标和分析。今年JCR的最大变化为&#xff1a;把属于不同数据库&#xff0c;但属于同一学…

移动端的HSR技术

overdraw问题&#xff1a; overdraw顾名思义就是过度绘制&#xff0c;就是在渲染过程中**绘制一帧FBO&#xff08;或者RenderTarget&#xff09;**超过一次相同像素的现象!这个是CG的问题&#xff01;特别在是用来大量的透明混合的情况下会产生的&#xff0c;当然客户端andrio…

【代码随想录】【算法训练营】【第45天】 [198]打家劫舍 [213]打家劫舍II [337]打家劫舍III

前言 思路及算法思维&#xff0c;指路 代码随想录。 题目来自 LeetCode。 day 45&#xff0c;周五&#xff0c;坚持不了一点~ 题目详情 [198] 打家劫舍 题目描述 198 打家劫舍 解题思路 前提&#xff1a; 思路&#xff1a; 重点&#xff1a; 代码实现 C语言 虚拟头…

1027. 方格取数

Powered by:NEFU AB-IN Link 文章目录 1027. 方格取数题意思路代码 1027. 方格取数 题意 某人从图中的左上角 A 出发&#xff0c;可以向下行走&#xff0c;也可以向右行走&#xff0c;直到到达右下角的 B 点。 在走过的路上&#xff0c;他可以取走方格中的数&#xff08;取…

MySQL数据库中的索引知识

MySQL数据库中索引的作用是用来加快数据的查询速度。 索引 index&#xff08;表的层面&#xff09; 在数据库中使用select来查询数据的时候会一条一条得去查询符合要求的数据&#xff0c;而索引就相当于在这张表中依据某一个字段的数值给这张表的数据创建了一个目录。目录帮…

NSIS 入门教程 (一)

介绍 大多数应用程序都附带一个安装程序&#xff0c;它将所需的文件复制到正确的文件夹中&#xff0c;创建注册表项&#xff0c;并提供卸载例程以&#xff08;希望&#xff09;从计算机中彻底删除应用程序. 有多种解决方案可以为自主开发的应用程序配备安装程序。除了Install …

服务器安装JDK,Maven等常用环境

生产环境部署服务器需要安装一些常用工具&#xff0c;下面我就把常用的jdk&#xff0c;maven&#xff0c;node&#xff0c;git的安装方法和步骤演示 一、安装JDK环境 执行如下命令&#xff0c;安装JDK,所有命令都是 复制&#xff0c;粘贴&#xff0c;回车 yum install -y jav…

Apple - Secure Coding Guide

本文翻译整理自&#xff1a;Secure Coding Guide https://developer.apple.com/library/archive/documentation/Security/Conceptual/SecureCodingGuide/Introduction.html#//apple_ref/doc/uid/TP40002477-SW1 文章目录 一、安全编码指南简介1、概览黑客和攻击者没有平台是免疫…