使用OpenCV-python实现以图搜图,首先加载必要的库
import numpy as np
import glob
import csv
import cv2
使用BRISK方法描述图片的特征
建立封面描述,计算特征点和特征点周围的信息
class CoverDescriptor:
def describe(self, image):
# 使用BRISK方法对图片进行特征提取,同类型的还有SIFT,SURF等等
descriptor = cv2.BRISK_create()
# 第一步是detect找到特征点,特征点信息包括关键点的位置,大小,旋转角等等
# 第二步是计算特征点周围的特征描述子,None表示没有遮罩
(kps, descs) = descriptor.detectAndCompute(image, None)
# 这里的keypoint只需要坐标位置就可以了,丢弃其他性质
kps = np.float32([kp.pt for kp in kps])
# 返回ROI点和对应周围的环境描述子
return (kps, descs)
使用KNN和RANSAC方法对特征进行匹配
建立匹配描述
class CoverMatcher:
# 2个必须输入分别为:待查找照片的描述对象,所有封面所在的文件夹位置
# ratio是计算单应矩阵最邻近举例的比例
# BRISK算法产生的是二值特征向量binary feature vectors,不能用欧拉距离衡量,需要使用Hamming方法
# 当然对应的SIFT产生的是实值特征向量real-valued feature vectors,可以用欧拉距离度量
def __init__(self, descriptor, coverPaths, ratio = 0.7, minMatches = 40,
useHamming = True):
self.descriptor = descriptor
self.coverPaths = coverPaths
self.ratio = ratio
self.minMatches = minMatches
self.distanceMethod = "BruteForce"
if useHamming:
self.distanceMethod += "-Hamming"
def search(self, queryKps, queryDescs):
results = {}
for coverPath in self.coverPaths:
cover = cv2.imread(coverPath)
gray = cv2.cvtColor(cover, cv2.COLOR_BGR2GRAY)
(kps, descs) = self.descriptor.describe(gray)
# query是待查图片的特征信息,后面的是当前数据库里每一张图片的信息
score = self.match(queryKps, queryDescs, kps, descs)
# 把分数存入字典,字典的键固定为50个,值为对应的匹配分
results[coverPath] = score
if len(results) > 0:
results = sorted([(v, k) for (k, v) in results.items() if v > 0],
reverse = True)
return results
# 输入为两张图的keypoints和feature vectors
def match(self, kpsA, featuresA, kpsB, featuresB):
# 新建一个描述匹配的对象,距离的度量方法是BruteForce-Hamming
matcher = cv2.DescriptorMatcher_create(self.distanceMethod)
# 对两幅图片的特征描述做knn近邻匹配
# 2表示对每个特征向量寻找和他最近的2个邻居
# 返回的并不是实际映射的关键点mapping keypoints
rawMatches = matcher.knnMatch(featuresB, featuresA, 2)
matches = []
for m in rawMatches:
# 首先确保是2个匹配matches
# 然后确保第1个match的距离<0.7*第2个match的距离,从而减少计算量
if len(m) == 2 and m[0].distance < m[1].distance * self.ratio:
matches.append((m[0].trainIdx, m[0].queryIdx))
# 确保有足够多的匹配点
if len(matches) > self.minMatches:
# 保存匹配点的(x,y)坐标
ptsA = np.float32([kpsA[i] for (i, _) in matches])
ptsB = np.float32([kpsB[j] for (_, j) in matches])
# RANSAC表示 Random Sample Consensus随机样本一致性,是一种迭代方法
# 这里也可以用cv2.LMEDS方法 Least-Median robust
# 4.0表示ptsA和ptsB之间的误差
# 返回值为变换矩阵和一系列布尔量的列表,1表示匹配,0表示不匹配
(_, status) = cv2.findHomography(ptsA, ptsB, cv2.RANSAC, 4.0)
# status是一个list,里面都是[1,0,1,0,0,1],使用sum()求和,使用size求长度
# 返回的是匹配的比率
return float(status.sum()) / status.size
# 如果没有返回匹配比率,说明没有匹配到,函数报错结束
return -1.0
在数据库中实现以图搜图
# 封面图片的路径
coverPath = "covers"
# 待查询照片的路径
queryPath = "queries/query01.png"
queryPath = "queries/query02.png"
queryPath = "queries/query03.png"
queryPath = "queries/query04.png"
# queryPath = "queries/query05.png"
# 书本数据库,里面保存了照片-作者-书名
bookDatabasePath = "books.csv"
# 新建一个保存封面的字典
bookDatabase = {}
# 把csv里面的数据放入字典,字典的键就是照片名,值就是作者,书名信息
for book in csv.reader(open(bookDatabasePath)):
# book 读出来是“书名,作者,其他等信息”
bookDatabase[book[0]] = book[1:]
# 初始化封面描述和封面匹配对象
# 封面描述需要指定描述方法,例如SIFT
cd = CoverDescriptor()
# 封面匹配需要指定距离度量方法,例如Hamming
cm = CoverMatcher(cd, glob.glob(coverPath+"/*.png"),ratio=0.7,minMatches=40)
# 加载查询图像
queryImage = cv2.imread(queryPath)
# 降噪,转换成灰度图
gray = cv2.cvtColor(queryImage, cv2.COLOR_BGR2GRAY)
# 提取关键点和描述子
(queryKps, queryDescs) = cd.describe(gray)
# 注意这个cm是CoverMatcher,search是CoverMatcher里面的一个方法
# 在已有的数据库中进行匹配,返回匹配分数和匹配对象的路径,注意这里的路径表达里面有个转义字符
# 举例,某一个返回值是[(0.9871794871794872, 'covers\\cover016.png')]
results = cm.search(queryKps, queryDescs)
# 显示出待查询的封面
cv2.imshow("query",queryImage)
# 如果在数据库里没有找到书的封面
if len(results)==0:
print("cannot find a match for that cover in databse!")
cv2.waitKey(0)
else:
# results的格式是[(0.9871794871794872, 'covers\\cover016.png')]
for i,(score,coverPath) in enumerate(results):
# rfind和find不同,rfind找出最右边的字符
# 在字典中查询coverPath对应的值,首先需要提取'covers\\cover016.png'中的'cover016.png',注意这里需要使用转义反斜杠
# 也可以这样使用coverPath.split('||')[-1]
(author,title) = bookDatabase[coverPath[coverPath.rfind("\\")+1:]]
# (author,title) = bookDatabase[coverPath.split("\\")[-1]]
# 打印输出结果,百分比保留2位小数点
print("loop{} precision is {:.2f}%: author is {} - title is {}".format(i+1,score*100,author,title))
# 这个coverPath是对应数据库文件里的图片
result = cv2.imread(coverPath)
cv2.imshow("results",result)
cv2.waitKey(0)
cv2.destroyAllWindows()
运行结果首先显示精确度和对应的作者和书名
loop1 precision is 68.42%: author is Michael Crichton - title is State of Fear
然后显示被查找的图片,和数据库里面对应的照片

完整代码和附录
import numpy as np
import glob
import csv
import cv2
class CoverDescriptor:
def describe(self, image):
descriptor = cv2.BRISK_create()
(kps, descs) = descriptor.detectAndCompute(image, None)
kps = np.float32([kp.pt for kp in kps])
return (kps, descs)
class CoverMatcher:
def __init__(self, descriptor, coverPaths, ratio = 0.7, minMatches = 40,
useHamming = True):
self.descriptor = descriptor
self.coverPaths = coverPaths
self.ratio = ratio
self.minMatches = minMatches
self.distanceMethod = "BruteForce"
if useHamming:
self.distanceMethod += "-Hamming"
def search(self, queryKps, queryDescs):
# initialize the dictionary of results
results = {}
# loop over the book cover images
for coverPath in self.coverPaths:
cover = cv2.imread(coverPath)
gray = cv2.cvtColor(cover, cv2.COLOR_BGR2GRAY)
(kps, descs) = self.descriptor.describe(gray)
# query是待查图片的特征信息,后面的是当前数据库里每一张图片的信息
score = self.match(queryKps, queryDescs, kps, descs)
# 把分数存入字典,字典的键固定为50个,值为对应的匹配分
results[coverPath] = score
# if matches were found, sort them
if len(results) > 0:
results = sorted([(v, k) for (k, v) in results.items() if v > 0],
reverse = True)
return results
def match(self, kpsA, featuresA, kpsB, featuresB):
# 新建一个描述匹配的对象,方法是BruteForce-Hamming
matcher = cv2.DescriptorMatcher_create(self.distanceMethod)
# 对两幅图片的特征描述做knn近邻匹配
rawMatches = matcher.knnMatch(featuresB, featuresA, 2)
matches = []
for m in rawMatches:
if len(m) == 2 and m[0].distance < m[1].distance * self.ratio:
matches.append((m[0].trainIdx, m[0].queryIdx))
# check to see if there are enough matches to process
if len(matches) > self.minMatches:
# construct the two sets of points
ptsA = np.float32([kpsA[i] for (i, _) in matches])
ptsB = np.float32([kpsB[j] for (_, j) in matches])
# compute the homography between the two sets of points
# and compute the ratio of matched points
(_, status) = cv2.findHomography(ptsA, ptsB, cv2.RANSAC, 4.0)
# return the ratio of the number of matched keypoints
# to the total number of keypoints
return float(status.sum()) / status.size
# no matches were found
return -1.0
# 封面图片的路径
coverPath = "covers"
# 待查询照片的路径
queryPath = "queries/query01.png"
queryPath = "queries/query02.png"
queryPath = "queries/query03.png"
queryPath = "queries/query04.png"
# queryPath = "queries/query05.png"
# 书本数据库,里面保存了照片-作者-书名
bookDatabasePath = "books.csv"
# 新建一个保存封面的字典
bookDatabase = {}
# 把csv里面的数据放入字典,字典的键就是照片名,值就是作者,书名信息
for book in csv.reader(open(bookDatabasePath)):
# book 读出来是“书名,作者,其他等信息”
bookDatabase[book[0]] = book[1:]
# 初始化封面描述和封面匹配对象
# 封面描述需要指定描述方法,例如SIFT
cd = CoverDescriptor()
# 封面匹配需要指定距离度量方法,例如Hamming
cm = CoverMatcher(cd, glob.glob(coverPath+"/*.png"),ratio=0.7,minMatches=40)
# 加载查询图像
queryImage = cv2.imread(queryPath)
# 降噪,转换成灰度图
gray = cv2.cvtColor(queryImage, cv2.COLOR_BGR2GRAY)
# 提取关键点和描述子
(queryKps, queryDescs) = cd.describe(gray)
# 注意这个cm是CoverMatcher,search是CoverMatcher里面的一个方法
# 在已有的数据库中进行匹配,返回匹配分数和匹配对象的路径,注意这里的路径表达里面有个转义字符
# 举例,某一个返回值是[(0.9871794871794872, 'covers\\cover016.png')]
results = cm.search(queryKps, queryDescs)
# 显示出待查询的封面
cv2.imshow("query",queryImage)
# 如果在数据库里没有找到书的封面
if len(results)==0:
print("cannot find a match for that cover in databse!")
cv2.waitKey(0)
else:
# results的格式是[(0.9871794871794872, 'covers\\cover016.png')]
for i,(score,coverPath) in enumerate(results):
# rfind和find不同,rfind找出最右边的字符
# 在字典中查询coverPath对应的值,首先需要提取'covers\\cover016.png'中的'cover016.png',注意这里需要使用转义反斜杠
# 也可以这样使用coverPath.split('||')[-1]
(author,title) = bookDatabase[coverPath[coverPath.rfind("\\")+1:]]
# (author,title) = bookDatabase[coverPath.split("\\")[-1]]
# 打印输出结果,百分比保留2位小数点
print("loop{} precision is {:.2f}%: author is {} - title is {}".format(i+1,score*100,author,title))
# 这个coverPath是对应数据库文件里的图片
result = cv2.imread(coverPath)
cv2.imshow("results",result)
cv2.waitKey(0)
cv2.destroyAllWindows()
本文介绍如何利用OpenCV-Python通过BRISK方法描述图片特征,并结合KNN和RANSAC进行匹配,实现在数据库中的以图搜图功能。最终展示精确度、匹配的作者和书名,以及搜索图片与数据库照片的对比。
1303





