图像特征
角点检测
所谓角点是 当沿着x和y进行移动 灰度值极大变化
和边界不同 边界是沿着x或者y变化
角点是图像中某些属性较为突出的像素
例如 像素最大或最小 线段端点
孤立的边缘点
常见的角点 有
灰度梯度最大值对应的点
两直线 或者曲线的交点
...
关键点绘制:
关键点指的是角点或者特征点
outImg = cv.drawKeypoints(img,keypoint,outimg)
keypoints 里面存放的是keypoint类型的对象
keypoint类:
特征的被放在特殊的类中
里面有特征点的坐标 角度
在绘制关键点的时候 有时需要进行数据类型的转换
cv.Keypoint(x,y,_size)
_size为关键点邻域
Harris角点
Harris角点是最经典的角点之一
从像素值变化的角度来对角点进行定义
要检测Harris角点:
先创建一个以某像素为中心的矩形滑动窗口
通过线性叠加 滑动窗口覆盖图像的像素值得到一个
滑动窗口内的所有像素值的衡量系数
当窗口内像素值整体变大 衡量系数变大
当窗口内像素值整体变小 衡量系数变小
当窗口以某像素为中心 向任意方向移动 衡量系数都下降 则为角点
Harris角点可以计算出一个R
R较大 表示为角点、
R<0 表示在直线
|R| 较小 表示在平面
dst = cv.cornerHarris()
img
blockSize 邻域大小通常为2
ksize 索贝尔算子大小
k [0.02--0.04] 计算R的权重系数
该函数计算图像中每个图像的R 并且通过矩阵返回
通过对R的比较可以确定是否为角点
R有正由负 需要归一化 到指定区域再通过阈值来判断
阈值往往根据经验的出
点击查看代码
import cv2 as cv
import numpy as np
if __name__ == '__main__':
img = cv.imread('img_7.png')
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
#得到R
har_m = cv.cornerHarris(gray, 2, 3, 0.04, borderType=cv.BORDER_DEFAULT)
#归一化
har_m = cv.normalize(har_m, None, alpha=0, beta=255, norm_type=cv.NORM_MINMAX)
print(har_m)
#换个类型
har_m = har_m.astype('uint8')
# print(np.argwhere(har_m > 100))
kps = []
# print(np.argwhere(har_m > 0))
# 拿到大于阈值的点转换为keypoint类型
for i in np.argwhere(har_m > 125):
# print(i)
kps.append(cv.KeyPoint(i[1], i[0], 1))
# 画出来
res = cv.drawKeypoints(img, kps, None)
cv.imshow('R', har_m)
cv.imshow('res', res)
cv.waitKey(0)
cv.destroyAllWindows()
特征点检测算法
特征点 与角点 在宏观定义上相同 但特征点具有能唯一描述像素特征的描述子 例如 左侧像素大于右侧像素...
特征点通常由 关键点 和 描述子组成 例如SIFT特征点 ORB特征点 都需要先计算关键点坐标 然后计算描述子
让计算机可以在远近,清晰模糊都可以认出
尺度空间:
高斯滤波 清糊
多分辨率图像金字塔 远近
因为特征点种类繁多 但几乎每一类特征点都涉及了 关键点 和 描述子的计算
所以Opencv提供了一个抽象基类 Feature2D 包含了 detect() compute() detectAndCompute()
描述子:
用来描述关键点的一串数字 通过描述子
可以区分两个关键点
cv.Feature2D.compute(img,keypoint)
img 关键点的图像
keypoint 关键点 通过关键点得到描述子
keypoints,des = detectAndCompute(img)
直接得到关键点和描述子 不用分步计算
SIFT特征点有专利了 不能用 所以直接接受ORB特征点
SIFI特征点是最著名的特征点 好多特征点都是SIFT特征点的衍生物
它在光照 噪声 缩放 旋转的稳定性
取得SIFI特征点 需要构建一个多尺度的高斯金字塔
每层具有同样的大小 但是每层又有多个不同的高斯模糊图像
这样模拟了实际生活中的近大远小 进清远模糊
对于同一层的相邻图像进行相减 构成高斯差分金字塔
关键点是其内部的极值组成
对于关键点进行筛选后 剩余的就是SIFT的特征点
特征点的方向根据像素的梯度确定
为了实现旋转不变性 计算描述子时 将图像坐标轴旋转到与特征点方向一致
SURF 因为SIFT太慢了 所以 SURF通过方框滤波的方法 代替了差分..等来实现更快的特征点检测
ORB由FAST角点和BRIEF描述子组成 更加快速 是SURF的10倍 是SIFT的100倍
ORB特征点:(特征点的计算方式不同 都继承了抽象基类)
ret = cv.ORB_create()
points = ORB.detect(img,None) 返还的是列表
outimg = cv.drawKeypoints(img,points,outimg)
特征点匹配
顾名思义 利用特征点来进行匹配 在两个图片中 寻找同一个物体的的
相同特征点 每个特征点都是特殊的 所以是寻找相似的特征点
特征点匹配可以快速的确定一个物体在新的图像中的位置
同样的提供了特征点匹配的抽象基类 DescriptorMatcher 里面由不同的匹配方式
而不同的匹配方法 都继承了这个类
1. 一对一对于的描述子匹配
matchs = cv.DescriptorMatcher.match(qpoints,tpoints)
qpoint 查询描述子集合
tpoint 训练描述子集合
matchs 符合的结果
2. 一对多匹配指定数目 一个描述子对应了多个可能与之匹配的描述子 为了后续的处理
matchs = cv.DescriptorMatcher.match(qpoints,tpoints,k)
3. 指定条件 通过距离阈值来匹配 距离是两个描述子的欧氏距离 汉明距离等
matchs = cv.DescriptorMatcher.match(qpoints,tpoints,maxDistance)
matchs 需要float32类型的描述子
所以的matchs存的是[DMatch类]
汉明距离是以理查德•卫斯里•汉明的名字命名的。在信息论中,两个等长字符串之间的汉明距离是两个字符串对应位置的不同字符的个数。换句话说,它就是将一个字符串变换成另外一个字符串所需要替换的字符个数。例如:
1011101 与 1001001 之间的汉明距离是 2。
2143896 与 2233796 之间的汉明距离是 3。
"toned" 与 "roses" 之间的汉明距离是 3。
DMatch类
匹配结果是DMatch类
有在训练集和匹配集的特征点的索引 和 汉明距离
显示匹配结果
outimg = cv.drawMatches(img1,keypoint1,img2,keypoint2,)
暴力匹配为每个描述子一定寻找一个最佳描述子 即使只在一张图片内存在 也会强行进行匹配
FLANN 匹配 可以实现特征点的快速匹配
即使使用了描述子的距离 及汉明距离来作为第一次筛选 但是还是会有部分的误匹配
为了提高匹配的精度 我们使用RANSAC 算法
匹配点优化
retval,mask = cv.findHomogarphy(spoints,dstpoints,method)
retval: 返还的是单应矩阵
mask: 则是返还的一个掩码 通过判断是否为1 来确定是否匹配
spoints: 原图像特征点的坐标 [-1, 1,2] -1行数 1,2 一行二列的矩阵 (x,y)
dstpoints: 目标图像特征点的坐标
method : cv.RANSAC
步骤总结:
点击查看代码
import cv2 as cv
import numpy as np
if __name__ == '__main__':
# 拿到特征点
img1 = cv.imread('AI.png')
img2 = cv.imread('img_4.png')
gray1 = cv.cvtColor(img1, cv.COLOR_BGR2GRAY)
gray2 = cv.cvtColor(img2, cv.COLOR_BGR2GRAY)
orb = cv.ORB_create()
# points1 = orb.detect(gray1, None) # 返还的是一个列表
# points2 = orb.detect(gray2, None)
# points1_des = orb.compute(gray1, points1)
# points2_des = orb.compute(gray2, points2)
#拿到特征点和描述子
p1, pdes1 = orb.detectAndCompute(gray1, None, None)
p2, pdes2 = orb.detectAndCompute(gray2, None, None)
# print(pdes1)
#创建一个用于特征点匹配的对象 内部有匹配方法
flann_match = cv.FlannBasedMatcher()
# 转换类型 描述子需要float32位类型
pdes1 = pdes1.astype('float32')
pdes2 = pdes2.astype('float32')
# 得到匹配结果 匹配结果位DMatch 类的对象
# 内部有 匹配的特征点在 图一和图二中的特征点索引
matchs = flann_match.match(pdes1, pdes2)
# 拿到匹配的特征点的在图1和图2的位置 并转换位ndarray的数据类型
src_kps = np.float32([p1[i.queryIdx].pt for i in matchs]).reshape(-1, 1, 2)
dst_kps = np.float32([p2[i.trainIdx].pt for i in matchs]).reshape(-1, 1, 2)
# 拿到掩码 该函数根据位置坐标进行筛选 并且返回一个掩码结果 来对匹配的进行筛选
M, mask = cv.findHomography(src_kps, dst_kps, cv.RANSAC)
print(M, mask)
good_pt = []
for i in range(len(mask)):
# 掩码不为1的
if mask[i] == 1:
good_pt.append(matchs[i])
# 画出来筛选后的点
res = cv.drawMatches(gray1, p1, gray2, p2, good_pt, None)
cv.imshow('res', res)
# print(matchs)
cv.waitKey(0)
cv.destroyAllWindows()