OpenCV 可以检测图像的主要特征,然后提取这些特征,使其成为图像描述符,这些图像特征可作为图像搜索的数据库;此外可以利用关键点将图像拼接 stitch 起来,组成一个更大的图像。如将各照片组成一个360度的全景照片。
本章节将介绍使用 OpenCV 来检测图像特例,并利用这些特征进行图像匹配和搜索。本章节选取一些图像,检测它们的主要特征,并通过单应性(homography)来检测这些图像是否存在于另一个图像中。
1 特征检测算法
特征检测和提取算法有很多,OpenCV 中常用的有如下几种:
- Harris - 检测角点
- SIFT - 检测斑点(blob)
- SURF - 检测半点
- FAST - 检测角点
- BRIEF - 检测斑点
ORB - 该算法代表带方向的FAST算法与具有旋转不变性的 BRIEF 算法
通过以下方法进行特征匹配:
- 暴力(Brute-Force)匹配法
- 基于 FLANN 的匹配法
可以通过单应性进行空间验证
1.1 特征定义
简而言之,特征就是图像有意义的区域。该区域具有独特性或易于识别性。
- 角点及高密度区域是很好的特征,大量重复的模式或低密度区域则是不好的特征;如图像中的蓝色天空就不是很好的特征。
- 边缘可将图像分为两个区域,因此也可看作好的特征。
- 斑点(与周围有很大差别的图像区域)也是有意义的特征。
所以大多数特征检测算法都会涉及图像的角点、边和斑点的识别,也有一些涉及脊向(ridge)的概念,可认为脊向是细长物体的对称轴(如识别图像中的一条路)。
1.2 检测角点特征
采用 cornerHarris 检测角点特征的分析对象是国际象棋,主要是因:
方格图案适合多种类型的特征检测
国际象棋很受欢迎
cornerHarris 是一个非常方便且实用的OpenCV 函数,该函数可以检测图像的角点,图像中的角可作为此响应图的局部最大值被找到。与 cornerMinEigenVal 和 cornerEigenValsAndVecs 函数功能类似。
cornerHarris(src, blockSize, ksize, k[, dst[, borderType]]) -> dst
参数:
src - 单通道8bit图像,即灰度图
blockSize - int 滑块窗口尺寸 / 邻域大小(请参阅#cornerEigenValsAndVecs的详细信息)。
ksize - int Sobel算子的滤波器参数 / Sobel 算子的中孔。
k - double Harris 检测中的*参数(free parameter)经验值 0.04~0.06
borderType - 差值类型
返回值:
dst - 用于存储Harris检测器响应的图像。 它的类型为CV_32FC1,大小与src相同。
具体代码如下:
import cv2 import numpy as np img = cv2.imread("./board_a.png") gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) gray = np.float32(gray) # 检测角点特征 dst = cv2.cornerHarris(gray, 2, 23, 0.04)# 将检测到的角点标记为红色 img[dst > 0.01 * dst.max()] = [0, 0, 255] while (True): cv2.imshow('corners', img) if cv2.waitKey(50) & 0xff == ord("q"): break cv2.destroyAllWindows()
备注:
1、cv2.cornerHarris() 函数的图片格式为灰度图。
2、cv2.cornerHarris() 函数最重要的是第三个参数,该参数定义了角点检测的敏感度,该值必须是介于3 ~ 31 之间的奇数;若为3,意味当检测到方块的边界时,棋盘中黑色方块的所有对角线都会被认为是角点;若为23,意味只有每个方块的角点才会被检测为角点。
3、cv2.cornerHarris() 函数中的第三个参数限定了Sobel 算子的中孔 aperture。Sobel 算子通过对图像行、列的变化检测来检测边缘,Sobel 算子是通过核 Kernel 来完成检测的。
4、
img[dst > 0.01 * dst.max()] = [0, 0, 255]
代码会将检测到的角点标记为红色,
5、注意第 4 项中角点标记大小与 cv2.cornerHarris() 中的第二个参数有关,参数值越小标记角点的记号越小。
原图片下载【点击】
2 使用 DoG 和 SIFT 提取特征及描述
上节cv2.cornerHarris() 函数可很好地检测角点,这与角点本身的特性有关;但是该函数会因为图像的大小而产生不同的识别结果,较小的图会丢失更多的角点信息。
解决该特征损失现象需要一种与图像比例无关的角点来作为检测方法——尺度不变特征变换(Scale-Invariant Feature Transform , SIFT),该函数会对不同的图像尺度(尺度不变特征变换)输出相同的结果。
注意:上述概念中只是进行 “ 特征变换 ”,此意味 SIFT 会通过一个特征向量来描述关键点周围区域情况,而不检测关键点(关键点可由 Difference of Gaussians DoG检测)。
DoG 是对同一图像使用不同高斯滤波器所得到的结果(cv2.GaussianBlur() 函数相关的低通滤波器和模糊操作),其与使用DoG 技术进行边缘检测的原理是一致的。采用DoG 操作的最终结果会得到感兴趣的区域(关键点)。
import cv2 img = cv2.imread('./city.jpg') gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 创建sift对象,计算灰度图。 sift = cv2.xfeatures2d.SIFT_create()# detectAndCompute函数进行检测和计算# 函数返回关键点信息(关键点)和描述符 keypoints, descriptor = sift.detectAndCompute(gray, None) # 在图像上绘制关键点 img = cv2.drawKeypoints( image=img, outImage=img, keypoints=keypoints, # 该标志意味对图像上的每一个关键点都绘制了圆圈和方向。 flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS, color=(51, 163, 236)) # 用 inshow 函数显示这幅图 cv2.imshow('sift_keypoints', img) while (True): if cv2.waitKey(100) & 0xff == ord("q"): break cv2.destroyAllWindows()
处理后的结果
原图【下载】
备注:
1、python 中大多数的处理算法都需要图像为灰度格式
2、SIFT 对象会使用 DoG 检测关键点,并对每个关键点周围的区域计算特征向量。
3、关键点剖析(从OpenCV 提供的文档会发现关键点有如下属性定义):
- pt(点)属性表示图像中关键点的x坐标和y坐标
- size 属性表示特征的直径
- angle 属性表示特征的方向
- response 属性表示关键点强度;某些特征会通过SIFT来分类,因为他得到的特征比较其他特征更好,通过查看 response 属性可以评估特征强度
- octave 属性表示特征所在金字塔的层级。如算法在每次迭代(octave)时,作为参数的图像尺寸和相邻图像都会发生变化,因此octave属性表示的是检测到的关键点所在的层级。
- ID 对象表示关键点的 ID
3 使用快速 Hessian 算法和 SURF 来提取和检测特征
SIFT算法是 David Lowe 于1999年发表的,距现在只有16年时间,SURF 特征检测算法由 Herbert Bay 于2006 年发表,该算法比 SIFT 快好几倍,它吸收了 SIFT 算法的思想。
注意:SIFT 和 SURF 都受专利保护,因此,都被归到 OpenCV 的 Xfeatures2d 模块中。
SURF 采用快速 Hessian 算法检测关键点,并提取特征,这和 SIFT 很像,SIFT 采用 DoG 检测关键点后提取关键点周围的特征。
此外,尽管 SURF 和 SIFT 这两个特征检测算法所提供的 API 不相同,但通过简单修改前的脚本就可以动态选择特征检测算法,不必重写整个程序。
其代码如下:
import cv2 img = cv2.imread('./city.jpg') gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 创建sift对象,计算灰度图。 sift = cv2.xfeatures2d.SURF_create(8000) keypoints, descriptor = sift.detectAndCompute(gray, None) img = cv2.drawKeypoints( image=img, outImage=img, keypoints=keypoints, # flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS, # 与 4 等价。其值就是4. flags=4, color=(0, 0, 255)) cv2.imshow('sift_keypoints', img) while (True): if cv2.waitKey(100) & 0xff == ord("q"): break cv2.destroyAllWindows()
运行结果:
这是由 SURF 算法处理的。所采用的 Hessian 阈值为 8000;实际上,阈值越高,能识别的特征就越少,因此采用试探法来得到最优检测。
当将阈值提升一倍时,
sift = cv2.xfeatures2d.SURF_create(16000)
则图像为
4 基于ORB的特征检测和特征匹配
ORB 将基于 FAST 关键点检测的技术和基于 BRIEF 描述符的技术相结合,因此首先学习 FAST 和 BRIEF ,然后再讨论 Brute-Force 匹配(其中的一种特征匹配算法)并展示一个特征匹配的例子。
4.1 FAST
FAST(features from Accelerated Segment Test)算法会对输入图像中的每一个像素进行计算。
在某一像素周围绘制一个圆,比较区域圆上的点与该像素的差值。
如上图,首先标记测试像素点,分别为1、5、9、13;当四分之三的测试像素值均为 brighter ( darker ) 时,另外四分之一的测试像素值必须为 darker ( brighter ),那么这个圆心就被称之为角点;如全都为 brighter 或 darker 、两个为 brighter 或 darker 时,则该圆心不是角点。
4.2 BRIEF
BRIEF(Binary Robust Independent Elementary Features ) 是一个描述符,而不是一种算法。
SIFT(SIFT 是 DoG 和SIFT的组合)和 SURF(SURF 是快速 Hessian 和 SURF 的组合)分析图像时,核心函数为 detectAndCompute 函数,该函数通过检测和计算返回两个结果;检测结果是一组关键点,计算结果是描述符。
关键点描述符是图像的一种表示,因此可比较两个图像的关键点描述符;并找到它们的共同之处,所以描述符可作为特征匹配的一种方法(gateway)
BRIEF 是目前最快的描述符,其理论相当复杂,但 BRIEF 采用了一些列的优化措施,使其成为不错的特征匹配方法。
4.3 暴力匹配
暴力(Brute-Force)匹配方法是一种描述符匹配方法,该方法会比较两个描述符,并产生匹配结果的列表。
之所以称它为暴力匹配,只要是因为该算法基本不涉及优化。
第一个描述符的所有特征都用来和第二和描述符的特征进行比较,每次比较都会给出一个距离值,其中比较结果中最好的那个被认为是一个匹配。
暴力往往与穷举所有可能的组合(穷举可能字符进行密码破解)有关。OpenCV 专门提供了 BFMatcher 对象来实现暴力匹配。
5 ORB 特征匹配
在 ORB 的论文中,作者得到如下结果:
- 向 FAST 增加一个快速、准确的方向分量(component)
- 能高效计算带方向的 BRIEF 特征
- 基于带方向的 BRIEF 特征的方差分析和相关性分析
- 在旋转不变性条件下学习一种不想管的 BRIEF 特征,这会在最邻近的应用中得到较好的性能。
ORB 旨在优化和加快操作速度,同时包括非常重要的一步:
以旋转感知(rotation-aware)的方式使用 BRIEF ,这样即使在训练图像和查询图像之间旋转差别很大的情况下也能够提高匹配效果。
具体实例代码如下:
import cv2 from matplotlib import pyplot as plt # 首先加载两幅图(查询图像和训练图像) img1 = cv2.imread('./cat.jpg') img2 = cv2.imread('./cat_rabbit.jpg') # 创建ORB特征检测器和描述符 orb = cv2.ORB_create() kp1, des1 = orb.detectAndCompute(img1, None) kp2, des2 = orb.detectAndCompute(img2, None) # 对查询图像和训练图像都要检测,然后计算关键点和描述符 # BFMatcher 创建匹配器对象, bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)# match 实现暴力匹配 matches = bf.match(des1, des2) matches = sorted(matches, key=lambda x: x.distance) # 现已经获取所有需要的信息,将其图标来绘制这些匹配 img3 = cv2.drawMatches(img1, kp1, img2, kp2, matches[:40], img2, flags=2) plt.imshow(img3) plt.show()
运行:
备注:
匹配非常简单,遍历描述符,确定描述符是否已经匹配,然后计算匹配质量(距离)并排序,这样就可以在一定置信度下显示前 n 个匹配,以此得到那两幅图是匹配的。
6 K - 最邻近匹配
有很多可以用来检测匹配的算法,从而可以绘制这些匹配。k - 最邻近(KNN)是其中一种匹配检测算法;在所有的机器学习的算法中,KNN 可能是最简单的。
在脚本中使用 KNN ,只需要上节 1.5 ORB 特征匹配进行修改即可
- KNN 代替暴力匹配
- 对应匹配函数替换 drawMatches -> drawMatchesKnn
具体代码如下:
import cv2 from matplotlib import pyplot as plt # 首先加载两幅图(查询图像和训练图像) img1 = cv2.imread() img2 = cv2.imread() # 创建ORB特征检测器和描述符 orb = cv2.ORB_create() kp1, des1 = orb.detectAndCompute(img1, None) kp2, des2 = orb.detectAndCompute(img2, None) # 对查询图像和训练图像都要检测,然后计算关键点和描述符 bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True) # knn 匹配 matches = bf.knnMatch(des1, des2, k=) # 现已经获取所有需要的信息,将其图标来绘制这些匹配 img3 = cv2.drawMatchesKnn(img1, kp1, img2, kp2, matches, img2, flags=) plt.imshow(img3) plt.show()
运行
最后的结果与ORB 运算结果类似。
match 函数与 knnMatch 函数的区别:
- match 函数返回最佳匹配
- KNN 函数返回 k 个匹配,开发人员可以用knnMatch 进一步处理这些匹配项
备注:由于KNN返回的是 k 个匹配,不言而喻,也有可能是 1 个匹配,所以在本图例中如果将 k=2
matches = bf.knnMatch(des1, des2, k=2)
则会报错:
cv2.error: OpenCV(4.0.1-dev) D:\Software\opencv-master\opencv-master\modules\core\src\batch_distance.cpp:303:error: (-215:Assertion failed) K == 1 && update == 0 && mask.empty() in function 'cv::batchDistance'
7 FLANN 匹配
FLANN(Fast Library for Approximate Nearest Neighbors)称之为 近似最近邻居的快速库。
FLANN是用于在高维空间中执行快速近似最近邻搜索的库。它包含一系列我们发现最适合最近邻搜索的算法,以及一个根据数据集自动选择最佳算法和最佳参数的系统。FLANN是用C ++编写的,包含以下语言的绑定:C,MATLAB,Python和Ruby。
FLANN is a library for performing fast approximate nearest neighbor searches in high dimensional spaces. It contains a collection of algorithms we found to work best for nearest neighbor search and a system for automatically choosing the best algorithm and optimum parameters depending on the dataset. FLANN is written in C++ and contains bindings for the following languages: C, MATLAB, Python, and Ruby.
FLANN 官网:https://www.cs.ubc.ca/research/flann/ 或 https://github.com/mariusmuja/flann
像 ORB 一样,FLANN 比 SIFT 或 SURF 有更为宽松的许可协议,可以在项目中*使用。
实际上,FLANN 具有一种内部机制,该机制可以根据数据本身选择最合适的算法来处理数据集,经验证,FLANN 比其它的最近邻搜索软件快 10 倍。
具体代码:
import cv2 from matplotlib import pyplot as plt # 首先加载两幅图(查询图像和训练图像) queryImage = cv2.imread('./python2.png', 0) trainingImage = cv2.imread('./python3.png', 0) # 创建 SIFT 和 detect / compute sift = cv2.xfeatures2d.SIFT_create() kp1, des1 = sift.detectAndCompute(queryImage, None) kp2, des2 = sift.detectAndCompute(trainingImage, None) # FLANN 匹配参数 FLANN_INDEX_KDTREE = 0 indexParams = dict(algorithm=FLANN_INDEX_KDTREE, trees=5) searchParams = dict(checks=50) #or pass empty dictionary flann = cv2.FlannBasedMatcher(indexParams, searchParams) matches = flann.knnMatch(des1, des2, k=2) # prepare an empty mask to draw good matches matchesMask = [[0, 0] for i in range(len(matches))] # David G. Lowe's ratio test,populate the mask for i, (m, n) in enumerate(matches): if m.distance < 0.7*n.distance: matchesMask[i] = [1, 0] drawParams = dict( matchColor=(255, 0, 0), singlePointColor=(255.0, 0), matchesMask=matchesMask, flags=0 ) resultImage = cv2.drawMatchesKnn(queryImage, kp1, trainingImage, kp2, matches, None, **drawParams) plt.imshow(resultImage) plt.show()
运行:
备注:
1、原 python2 中有 range() 和 xrange() 两个函数,在 python3 中将 range() 替代 xrange() 函数,原 python2 中的 range() 函数取消。
2、FLANN 匹配器有两个参数:
- indexParams
- searchParams
这两个参数在python中以字典形式传参(在 c++ 中以结构体形式传参);为匹配计算,FLANN 内部会决定如何处理索引和搜索对象。
3、在这种情况下,可以选择LinearIndex、KTreeIndex、KMeansIndex、CompositeIndex 和 AutotuneIndex 。上述示例中选择的是KTreeIndex。
4、KTreeIndex 配置
- 简单——配置索引时只需要指定待处理核密度树的数量(最理想的数量在1~16之间)。
- 灵活——Kd-trees 可被并行处理。
5、searchParams 字典只包含一个字段(名为 checks),用来指定索引树要被遍历的次数,该值越高,计算越准确,但计算匹配所需时间就越长。
6、实际上,匹配效果很大程度上取决于输入。5 Kd-tree 和 50 checks 总能取得具有合理精度结果,而且在很短时间内就能完成。
7、在创建 FLANN 匹配器以及匹配数组之后,可根据 Lowe 在其论文( Distinctive Image Features from Scale-Invariant Keypoints 【点击下载】)中所描述的测试来对匹配进行过滤。
8、在这篇论文的 Application to object recognition 这一章中(P20),Lowe 指出:并非所有的匹配都是 “ 好 ” 的,在任意阈值下过滤匹配几乎不能得到好的匹配结果,其原因如下:
可以通过取最近邻居的距离与次近邻的距离的比率来确定匹配正确的概率。
The probability that a match is correct can be determined by taking the ratio of distance from the closest neighbor to the distance of the second closest.
在上述代码示例中,丢弃任何距离大于 0.7 的值,可避免几乎 90% 的错误匹配。但得到的 “ 好 ” 匹配也会很少。
8 FLANN 的单应性匹配
单应性 - 单应性是几何中的一个概念;在计算机视觉领域,空间中同一平面的任意两幅图通过单应性关联在一起。
比如,一个物体可以通过旋转相机镜头获取两张不同的照片,照片A和照片B的内容不一定完全一样,只需要部分对应即可;同时我们设定一个系数(该系数是二维矩阵M),那么照片A乘以系数M就是照片B,这种现象就可以称为单应性。
也可以这样理解:单应性是一个条件,一幅图通过该条件出现投影畸变(perspective distortion)时,另一幅图能够与之匹配。
具体代码:
import cv2 import numpy as np from matplotlib import pyplot as plt MIN_MATCH_COUNT = 10 # 首先加载两幅图(查询图像和训练图像) # image_query = cv2.imread('./cones_left.jpg', 0) # image_train = cv2.imread('./cones_right.jpg', 0) image_query = cv2.imread('./cat.jpg', 0) image_train = cv2.imread('./cat_rabbit.jpg', 0) # 创建 SIFT 和 detect / compute sift = cv2.xfeatures2d.SIFT_create() kp1, des1 = sift.detectAndCompute(image_query, None) kp2, des2 = sift.detectAndCompute(image_train, None) # FLANN 匹配参数 FLANN_INDEX_KDTREE = 0 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) # print(help(flann.knnMatch)) # print(matches) # store all the good matches as per Lowe's ratio test good = [] for m, n in matches: # print('m', m) # print('n', n) if m.distance < 0.7*n.distance: good.append(m) min_len = min(len(good), len(kp1), len(kp2)) # print("good", len(good)) # print("kp1", len(kp1)) # print("kp2", len(kp2)) # print("min_len", min_len) # len(good)= 470,len(kp1)=1287,len(kp2)=1270 # 由good的赋值过程来讲,其不连续append进值 # good 个数仅470,但最大值可达到1282 # len(kp1)、len(kp2)、 if len(good) > MIN_MATCH_COUNT: # 在原始图像和训练图像中发现关键点 # 有些图像会报错:IndexError: list index out of range # 有些图像不会报错, # 为了使得该代码具有普适性,增加异常处理 try: src_pts = np.float32([kp1[i.queryIdx].pt for i in good]).reshape(-1, 1, 2) dst_pts = np.float32([kp2[i.queryIdx].pt for i in good]).reshape(-1, 1, 2) # 单应性,创建的matchesMask 将最后用来绘制匹配图 # 从而matchesMask可以只绘制单应性图像中关键点的匹配线 M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0) matchesMask = mask.ravel().tolist() # 对第二张图像计算相对原始目标的投影畸变 h, w = image_query.shape pts = np.float32([[0, 0], [0, h-1], [w-1, h-1], [w-1, 0]]).reshape(-1, 1, 2) dst = cv2.perspectiveTransform(pts, M) img2 = cv2.polylines(image_train, [np.int32(dst)], True, 255, 3, cv2.LINE_AA) except IndexError as IE: print(IE) else: print("Not enough matches are found -%d/%d" % (len(good), MIN_MATCH_COUNT)) matchesMask = None draw_params = dict( matchColor=(0, 255, 0), singlePointColor=None, matchesMask=matchesMask,# draw only inliers flags=2) img3 = cv2.drawMatches(image_query, kp1, img2, kp2, good, None, **draw_params) plt.imshow(img3, 'gray'), plt.show()
运行
9 基于纹身取证的应用程序示例
采用现实生活中的一个例子来对本章的知识进行总结。
现有犯罪嫌疑人在罪案现场遗留下的纹身原始照片/ 图案,但是尚不知犯罪嫌疑人的其他信息;已知已经建立纹身数据库,现需要通过纹身照片比对照出犯罪嫌疑人信息。
将上述语言分析为计算机语言,可分为两部分:
- 第一步,将图像描述符保存到文件中
- 第二步,以该照片作为查询图像在数据库中检索匹配图像
9.1 将数据库图像描述符保存到文件中
当两幅图像进行匹配和单应性分析时,不用每次都重建描述符。
编写应用程序,将数据库图像保存到文件夹中,并创建响应的描述符文件,可供后面被搜索时使用。
创建描述符并保存到文件中的方法在上文中反复使用,本章节主要实现的过程是:
1、加载图像
2、创建特征检测器
3、检测并计算
由于这里面涉及到路径问题,所有需要将文件(夹)位置进行必要的说明。
按照书中的代码理解,是将所有的文件都放在一个文件夹中了,我为了便于分类,建立了纹身数据库和罪犯纹身数据两个子文件夹。
卷 Softwrae 的文件夹 PATH 列表 卷序列号为 1C63-1E89 D:. │ generate_descriptors.py # 纹身数据库处理py文件 │ scan_for_matches.py # 查询罪犯py文件 │ ├─database # 纹身数据库 │ 1.jpg │ 10.jpg │ 11.jpg │ 12.jpg │ 2.jpg │ 3.jpg │ 4.jpg │ 5.jpg │ 6.jpg │ 7.jpg │ 8.jpg │ 9.jpg │ └─query # 罪犯作案现场遗留纹身图案 tattoo_seed.jpg
具体代码为 generate_descriptors.py :
# generate_descriptors.py import cv2 import numpy as np import os # 设定图像格式 IMG_FORMAT = ".jpg" # 创建描述符 def create_descriptors(folder): ''' 加载图像、创建特征检测器、检测并计算 该函数没有返回值,但将文件及描述符保存到文件中。 对数据库中的所有图像进行预处理。 实际上也对罪犯纹身数据也进行了处理 所以在sacn_for_matches.py文件中不需要对两者重复处理 ''' # 创建空列表存储图像的路径 files = [] # 初始化SIFT对象 / 创建 SIFT 对象,用于图像的检测和计算 sift = cv2.xfeatures2d.SIFT_create() # 筛选目标图像并保存 # 遍历给定图像文件夹路径,将符合格式的图像添加到files列表中 for dirpath, dirnames, filenames in os.walk(folder): # 判断 filenames 文件格式 for filename in filenames: if filename.endswith(IMG_FORMAT): img_path = os.path.join(dirpath, filename) # 向files列表后面添加序列 files.append(img_path) # 从files列表中逐个读取目标图像 for img_path in files: # 以灰度(IMREAD_GRAYSCALE = 0)方式读取 img = cv2.imread(img_path, 0) # 对读入图像进行检测和计算并返回关键点信息和描述符 keypoints, descriptors = sift.detectAndCompute(img, None) # 对图像文件保存成numpy专用的二进制格式npy img_descriptor = img_path.replace("jpg", "npy") # 将descriptors保存到img_descriptor文件中 np.save(img_descriptor, descriptors) # 获取文档的绝对路径 dir_top = os.getcwd() # 函数调用 create_descriptors(dir_top)
运行完毕后,打开文件夹后会发现 “ .npy " 已经都存在了!
文件树列表如下:
卷 Softwrae 的文件夹 PATH 列表 卷序列号为 1C63-1E89 D:. │ dirtree.txt │ generate_descriptors.py │ scan_for_matches.py │ ├─database │ 1.jpg │ 1.npy │ 10.jpg │ 10.npy │ 11.jpg │ 11.npy │ 12.jpg │ 12.npy │ 2.jpg │ 2.npy │ 3.jpg │ 3.npy │ 4.jpg │ 4.npy │ 5.jpg │ 5.npy │ 6.jpg │ 6.npy │ 7.jpg │ 7.npy │ 8.jpg │ 8.npy │ 9.jpg │ 9.npy │ └─query tattoo_seed.jpg tattoo_seed.npy
9.2 扫描匹配
将描述符保存到文件后,接下来需要对所有描述符进行单应性处理,由此找到可能与查询图像匹配的图像。
实现步骤如下:
1、加载纹身数据库的描述符
2、加载罪犯纹身的描述符
3、FLANN 单应性匹配
4、输出操作
具体代码如下:
import numpy as np import cv2 import os # 加载纹身数据库,已在generate_descriptors文件中处理 # 创建files_database变量存放纹身数据库npy文件 files_database = [] # 创建files_query变量存放罪犯纹身npy文件 files_query = [] # 获取数据库路径 path_database = os.getcwd() for dirpath, dirnames, filenames in os.walk(path_database): for filename in filenames: # 若为罪犯纹身储存列表 if filename == "tattoo_seed.npy": files_query.append(os.path.join(dirpath, filename)) # 若不为罪犯的,且后缀为.npy文件则存放到纹身数据库中 elif filename.endswith(".npy"): files_database.append(os.path.join(dirpath, filename)) else: continue # FLANN 的单应性匹配create FLANN matcher FLANN_INDEX_KDTREE = 0 index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5) search_params = dict(checks=50) flann = cv2.FlannBasedMatcher(index_params, search_params) # 阈值,当匹配值大于该数时,则判定为犯罪 MIN_MATCH_COUNT = 10 # 初始化犯罪嫌疑人名单 potential_culprits = [] # 提示性语言 print(">> 启动图片扫描...") # 首先获取罪犯纹身描述符, # 罪犯只有一个纹身图案,直接load即可 # 如果是多个图案,可以做一个嵌套循环 descriptors_query = np.load(files_query[0]) # 获取数据库中的描述符 for file_database in files_database: # 获取数据库中纹身所属者 # 默认图片名称记为人名 # basename结果1.npy、2.npy basename = os.path.basename(file_database)[:-4] # 打印空行,只是为了结果好看 print() # 提示性语言 print("---- analyzing %s for matches ----" % basename) # 开始匹配,返回匹配结果matches matches = flann.knnMatch(descriptors_query, np.load(file_database), k=2) # 罪证 certificate = [] for m, n in matches: # 关于knnMatch返回值需要深入了解 if m.distance < 0.7*n.distance: certificate.append(m) if len(certificate) > MIN_MATCH_COUNT: potential_culprits.append(basename) print("%s is a match! (%d)" % (basename, len(certificate))) else: print("%s is not a match" % basename) # 最后打印出所有匹配上的犯罪嫌疑人 for potential_culprit in potential_culprits: print("=================================") print("potential suspect is %s" % potential_culprit.upper())
运行结果:
(为了调试方便,将图片的数字名称改为了字母名称,此处不在对上文进行修改)
>> 启动图片扫描... ---- analyzing angela for matches ---- angela is not a match ---- analyzing bill for matches ---- bill is not a match ---- analyzing chris for matches ---- chris is not a match ---- analyzing Daniel for matches ---- Daniel is not a match ---- analyzing darren for matches ---- darren is a match! (16) ---- analyzing james for matches ---- james is not a match ---- analyzing Johnney for matches ---- Johnney is not a match ---- analyzing kevin for matches ---- kevin is not a match ---- analyzing linda for matches ---- linda is not a match ---- analyzing michale for matches ---- michale is not a match ---- analyzing peter for matches ---- peter is not a match ---- analyzing Ryan for matches ---- Ryan is not a match ================================= potential suspect is DARREN
其实,调试时候可以字母+数字。因为字母出现输错不易看出,数字能够看出,比如调试过程中,本来犯罪嫌疑人是 3 ,结果输出了 9 ,经过辨析发现是一个变量没有改过来。数字一下子就看出来了,字母名称出现的错误可能就难以发现。
参考1:
Common Interfaces of Descriptor Matchers 官网
cv::flann::IndexParams Struct Reference官网
cv::FlannBasedMatcher Class Reference官网
特征匹配:我们将在OpenCV中使用Brute-Force匹配器和FLANN Matcher官网
OpenCV中feature2D学习——BFMatcher和FlannBasedMatcher
参考2:
list.append(object) 向列表中添加一个对象object
list.extend(sequence) 把一个序列seq的内容添加到列表中
os.replace(old,new) : 将文件重命名
numpy.save() 函数将数组保存到以 .npy 为扩展名的文件中。
numpy.save 官方文档
下载
本节所用图片【点击下载】
OpenCV 学习笔记 06 图像检索以及基于图像描述符的搜索的更多相关文章
-
OpenCV 学习笔记 06 SIFT使用中出现版权问题error: (-213:The function/feature is not implemented)
1 错误原因 1.1 报错全部信息: cv2.error: OpenCV(4.0.1) D:\Build\OpenCV\opencv_contrib-4.0.1\modules\xfeatures2d ...
-
OpenCV 学习笔记 07 目标检测与识别
目标检测与识别是计算机视觉中最常见的挑战之一.属于高级主题. 本章节将扩展目标检测的概念,首先探讨人脸识别技术,然后将该技术应用到显示生活中的各种目标检测. 1 目标检测与识别技术 为了与OpenCV ...
-
OpenCV 学习笔记03 boundingRect、minAreaRect、minEnclosingCircle、boxPoints、int0、circle、rectangle函数的用法
函数中的代码是部分代码,详细代码在最后 1 cv2.boundingRect 作用:矩形边框(boundingRect),用于计算图像一系列点的外部矩形边界. cv2.boundingRect(arr ...
-
paper 93:OpenCV学习笔记大集锦
整理了我所了解的有关OpenCV的学习笔记.原理分析.使用例程等相关的博文.排序不分先后,随机整理的.如果有好的资源,也欢迎介绍和分享. 1:OpenCV学习笔记 作者:CSDN数量:55篇博文网址: ...
-
(转) OpenCV学习笔记大集锦 与 图像视觉博客资源2之MIT斯坦福CMU
首页 视界智尚 算法技术 每日技术 来打我呀 注册 OpenCV学习笔记大集锦 整理了我所了解的有关OpenCV的学习笔记.原理分析.使用例程等相关的博文.排序不分先后,随机整理的 ...
-
OpenCV学习笔记(27)KAZE 算法原理与源码分析(一)非线性扩散滤波
http://blog.csdn.net/chenyusiyuan/article/details/8710462 OpenCV学习笔记(27)KAZE 算法原理与源码分析(一)非线性扩散滤波 201 ...
-
机器学习实战(Machine Learning in Action)学习笔记————06.k-均值聚类算法(kMeans)学习笔记
机器学习实战(Machine Learning in Action)学习笔记————06.k-均值聚类算法(kMeans)学习笔记 关键字:k-均值.kMeans.聚类.非监督学习作者:米仓山下时间: ...
-
OpenCV 学习笔记03 findContours函数
opencv-python 4.0.1 1 函数释义 词义:发现轮廓! 从二进制图像中查找轮廓(Finds contours in a binary image):轮廓是形状分析和物体检测和识别的 ...
-
opencv 学习笔记集锦
整理了我所了解的有关OpenCV的学习笔记.原理分析.使用例程等相关的博文.排序不分先后,随机整理的.如果有好的资源,也欢迎介绍和分享. 1:OpenCV学习笔记 作者:CSDN数量:55篇博文网址: ...
随机推荐
-
[CareerCup] 5.7 Find Missing Integer 查找丢失的数
5.7 An array A contains all the integers from 0 to n, except for one number which is missing. In thi ...
-
数据库 定义 bit 类型 (true=1,false=0)
当Sql Server数据库定义 数据 为 bit 类型时, 编写代码时 要用 true or false 赋值. 例如: OffTheShelf 定义类型为 bit 后台赋值时 OffTheSh ...
-
Spring MVC + Spriing + MyBatis整合,写给新人
开发环境: 开发工具:MyEclipse 8.6 数据库:MySQL 操作系统:WIN8.1 Jar包: Spirng和SpringMVC版本:3.2.9 MyBatis版本:3.2.8 其他关联Ja ...
-
Java 第六章
第六章 for语法:for(表达式①;表达式②;表达式③){ //④循环操作}表达式含义:表达式1:赋值语句, 它用来给循环变量赋初值 例如:int i = 1;表达式2:循环条件,一个关系表达式, ...
-
mac本webstrom破解
之前忙着加班一直没搞,有时间解决一下 首先编辑hosts文件 https://jingyan.baidu.com/article/f3ad7d0f55154309c3345bdd.html Mac系统 ...
-
Spring-Boot自动装载servlet
Spring-Boot自动装载servlet 本人spring-boot相关博客均自己手动编写,但技术均从简书 恒宇少年 处学习,该大佬一直是我的偶像,鉴于能充分理解,所以已做笔记的方式留下这些文档, ...
-
史上最全的 Python 3 类型转换指南
int 支持转换为 int 类型的,仅有 float.str.bytes,其他类型均不支持. float -> int 会去掉小数点及后面的数值,仅保留整数部分. int(-12.94) # - ...
-
JAVA中String类常用方法 I
String类常用方法有: int length() -– 返回当前字符串的长度 int indexOf(int ch) -– 查找ch字符在该字符串中第一次出现的位置 int indexOf(Str ...
-
linux内存不足,swap交换分区创建
为什么需要swap 根 据Redhat公司的建议,Linux系统swap分区最适合的大小是物理内存的1-2倍.不过Linux上有些软件对swap分区得需求较大,例如要顺 利执行Oracle数据库软件, ...
-
Qt_阴影效果
一.控件阴影效果 为子部件添加阴影比较简单,使用如下方式: QGraphicsDropShadowEffect *shadow_effect = new QGraphicsDropShadowEffe ...