特征检测的基本概念
特征检测指的是使用计算机提取图像信息,决定每个图像的点是否属于一个图像特征。
-
我们发现:
- 平坦部分很难找到它在原图中的位置
- 边缘相比平坦要好找一些, 但是也不能一下确定
- 角点可以一下就找到其在原图中的位置
图像特征就是值有意义的图像区域, 具有独特性, 易于识别性, 比较角点, 斑点以及高密度区.
在图像特征中最重要的就是角点. 哪些是角点呢?
- 灰度梯度的最大值对应的像素
- 两条线的交点
- 极值点(一阶导数最大, 二阶导数为0)
Harris角点检测
检测窗口在图像上移动, 上图对应着三种情况:
- 在平坦区域, 无论向哪个方向移动, 衡量系统变换不大.
- 边缘区域, 垂直边缘移动是, 衡量系统变换剧烈.
- 在角点处, 往哪个方向移动, 衡量系统都变化剧烈.
- cornerHarris(src, blockSize, ksize, k[, dst[, borderType]])
- blockSize: 检测窗口大小
- ksize: sobel的卷积核
- k: 权重系数, 经验值, 一般取0.02~0.04之间.一般默认0.04
import cv2
import numpy as np
img = cv2.imread('./chess.png')
# 变成灰度图片
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 角点检测
# blockSize没有要求必须是奇数
# 返回角点响应, 每一个像素都可以计算出一个角点响应.
dst = cv2.cornerHarris(gray, blockSize=2, ksize=3, k=0.04)
print(dst)
# 显示角点
# 设定阈值:显示大于 0.01 * dst.max() 的像素点
img[dst > (0.01 * dst.max())] = [0, 0, 255]
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.waitKey(1)
[[-4.12074951e-06 -4.12074951e-06 1.70350657e-04 ... -1.50460633e-03 3.06900591e-04 1.07793661e-04] [-4.12074951e-06 -4.12074951e-06 1.70350657e-04 ... -1.50460633e-03 3.06900591e-04 1.07793661e-04] [ 1.70350540e-04 1.70350540e-04 5.27939573e-03 ... -1.63096981e-03 7.48526957e-03 1.70810893e-03] ... [-1.50460668e-03 -1.50460668e-03 -1.63097167e-03 ... 6.62978389e-04 6.67610439e-04 -9.67213651e-04] [ 1.95719767e-04 1.95719767e-04 7.43987132e-03 ... 6.67612301e-04 1.01685515e-02 2.30534747e-03] [ 6.05240202e-05 6.05240202e-05 1.55772560e-03 ... -9.67212720e-04 2.30534747e-03 5.62405097e-04]] -1
Shi-Tomasi角点检测
-
Shi-Tomasi是Harris角点检测的改进.
-
Harris角点检测计算的稳定性和K有关, 而K是一个经验值, 不太好设定最佳的K值.
-
goodFeaturesToTrack(image, maxCorners, qualityLevel, minDistance[, corners[, mask[, blockSize[, useHarrisDetector[, k]]]]])
- maxCorners: 角点的最大数, 值为0表示无限制
- qualityLevel: 角点质量, 小于1.0的整数, 一般在0.01-0.1之间.
- minDistance: 角之间最小欧式距离, 忽略小于此距离的点.
- mask: 感兴趣的区域.
- blockSize: 检测窗口大小
- useHarrisDetector: 是否使用Harris算法.
- k: 默认是0.04
import cv2
import numpy as np
img = cv2.imread('chess.png')
# 灰度化
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# shi-tomasi角点检测,返回角点的坐标
corners = cv2.goodFeaturesToTrack(gray, maxCorners=1000, qualityLevel=0.1, minDistance=10)
print(corners)
# 将角点的坐标变为 整数
corners = np.int0(corners)
# 画出角点
for i in corners:
# i相当于corners中的每一行数据
# ravel()把二维变一维了.即角点的坐标点
x,y = i.ravel()
cv2.circle(img, (x, y), 3, (0, 0, 255), -1)
cv2.imshow('Shi-Tomasi', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.waitKey(1)
[[[187. 97.]] .... [[253. 295.]] [[476. 418.]]] -1
sift算法
使用SIFT的步骤
- 创建SIFT对象 sift = cv2.SIFT_create()
- 进行检测, kp = sift. detect(img, …)
- 绘制关键点, drawKeypoints(gray, kp, img)
关键点和描述子
关键点: 位置, 大小和方向.
关键点描述子: 记录了关键点周围对其有共享的像素点的一组向量值, 其不受仿射变换, 光照变换等影响.描述子的作用就是进行特征匹配, 在后面进行特征匹配的时候会用上.
import cv2
import numpy as np
img = cv2.imread('chess.png')
# 灰度化
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 创建sift对象
# sift算法已经专利过期了.
sift = cv2.SIFT_create()
# 进行检测,kp是一个列表, 里面存放的是封装的KeyPoint对象
kp = sift.detect(gray)
# 计算描述子
kp, des = sift.compute(gray, kp)
# 还可以 一步到位计算
# kp, des = sift.detectAndCompute(gray, None)
print(kp)
# 绘制关键点
cv2.drawKeypoints(gray, kp, img)
cv2.imshow('SIFT', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.waitKey(1)
-1
ORB算法
ORB最大的优势就是可以做到实时检测
ORB的劣势是检测准确性略有下降.
import cv2
import numpy as np
img = cv2.imread('./chess.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 创建ORB对象
orb = cv2.ORB_create()
# 进行检测
kp = orb.detect(gray)
# 计算描述子
kp, des = orb.compute(img, kp)
# ORB算法的描述子只有32维向量
print(des.shape)
# 也可以一步到位得到关键点和描述子
# kp, des = orb.detectAndCompute(img, None)
# 绘制关键点
cv2.drawKeypoints(gray, kp, img)
cv2.imshow('ORB', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.waitKey(1)
(500, 32)
-1
暴力特征匹配
我们获取到图像特征点和描述子之后, 可以将两幅图像进行特征匹配.
BF(Brute-Force) 暴力特征匹配方法, 通过枚举的方式进行特征匹配.
暴力匹配器很简单。它使用第一组(即第一幅图像)中一个特征的描述子,并使用一些距离计算将其与第二组中的所有其他特征匹配。并返回最接近的一个。
-
BFMatcher(normType, crossCheck)
- normType计算距离的方式.
- NORM_L1, L1距离, 即绝对值, SIFT和SURF使用.
- NORM_L2, L2距离, 默认值. 即平方. SIFT和SURF使用
- HAMMING 汉明距离. ORB使用
- crossCheck: 是否进行交叉匹配, 默认False.
- normType计算距离的方式.
-
使用match函数进行特征点匹配, 返回的对象为DMatch对象. 该对象具有以下属性:
- DMatch.distance - 描述符之间的距离。 越低,它就越好。
- DMatch.trainIdx – 训练描述符中描述符的索引
- DMatch.queryIdx - 查询描述符中描述符的索引
- DMatch.imgIdx – 训练图像的索引
-
drawMatches 绘制匹配的特征点
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 = cv2.SIFT_create()
# 得到关键点和描述子
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)
# 进行暴力匹配
# 创建匹配对象bf
bf = cv2.BFMatcher(cv2.NORM_L1)
# 进行匹配
match = bf.match(des1, des2)
# 除了match可以进行匹配, 还有knnMatch
# 一般k=2
match = bf.knnMatch(des1, des2, k=2)
print(type(match))
print(len(match))
print(match)
print(match[0][0].distance) # 距离,越低,说明特征越接近
print(match[0][0].queryIdx) # 前一张图片 特征点的索引
print(match[0][0].trainIdx) # 后一张图片 特征点的索引
# 绘制knnmatch匹配的结果
good = [] # 存储符合要求的match对象
for m, n in match:
# 设定阈值, 距离小于对方距离的0.7倍, 我们认为是好的匹配点.
if m.distance < 0.7 * n.distance:
good.append(m)
result = cv2.drawMatchesKnn(img1, kp1, img2, kp2, [good], None)
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.waitKey(1)
<class ‘tuple’>
18
2523.0
0
229-1
FLANN特征匹配
FLANN是快速最近邻搜索包(Fast_Library_for_Approximate_Nearest_Neighbors)的简称。在面对大数据集是它的效果要好于BFMatcher。
特征匹配记录下目标图像与待匹配图像的特征点(KeyPoint),并根据特征点集合(即特征描述子)构造特征量(descriptor),对这个特征量进行比较、筛选,最终得到一个匹配点的映射集合。我们也可以根据这个集合的大小来衡量两幅图片的匹配程度。
- FlannBasedMatcher(index_params, search_params)
- index_params字典: 匹配算法KDTREE, LSH, SIFT和SURF使用KDTREE算法, OBR使用LSH算法.
- 设置示例:
- index_params=dict(algorithm=cv2.FLANN_INDEX_KDTREE, tree=5)
- index_params=dict(algorithm =cv2.FLANN_INDEX_LSH, table_number = 6, key_size = 12, multi_probe_level = 1)
- 设置示例:
- search_params字典: 指定KDTREE算法中遍历树的次数.经验值, 如KDTREE设为5, 那么搜索次数设为50.
- search_params = dict(checks=50)
- index_params字典: 匹配算法KDTREE, LSH, SIFT和SURF使用KDTREE算法, OBR使用LSH算法.
- Flann中除了普通的match方法, 还有knnMatch方法.
- 多了个参数–k, 表示取欧式距离最近的前k个关键点.
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 = cv2.SIFT_create()
# 计算描述子
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)
# 创建FLANN特征匹配对象
index_params = dict(algorithm=1, tree=5)
# 根据经验, kdtree设置5个tree, 那么checks一般设置为50
search_params = dict(checks=50)
flann = cv2.FlannBasedMatcher(index_params, search_params)
# 对描述子进行匹配计算
# 返回的是第一张图和第二张图的匹配点.
matchs = flann.knnMatch(des1, des2, k=2)
print(matchs)
good = [] # 存储过滤后的 match对象
for i, (m, n) in enumerate(matchs):
# 设定阈值, 距离小于对方的距离的0.7倍我们认为是好的匹配点.
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)
cv2.destroyAllWindows()
cv2.waitKey(1)
-1