本文共 9220 字,大约阅读时间需要 30 分钟。
本章主要介绍如何检测图像特征以及如何为描述符提取特征
特征检测算法
图像特征就是有意义的图像区域,该区域具有独特性或易于识别性。因此,角点及高密度区域是很好的特征,而大量重复的模式或低密度区域则不是好的特征。边缘可以将图像分为两个区域,因此也可以看作好的特征。斑点(与周围有很大差点别的图像区域)也是有意义的特征。
大多数特征检测算法都会涉及图像的角点,边和斑点的识别,也有涉及脊向的概念,可以认为脊向的概念,可以认为脊向是细长物体的对称轴(例如:图像识别中的一条路)
检测国际象棋角点特征:
import cv2import numpy as npimport sysimg = cv2.imread('chess_board.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(int(1000 / 12)) & 0xff == ord("q"):# breakcv2.waitKey(0)cv2.destroyAllWindows()
cornerHarris函数中最重要的参数是第三个,该参数限定了sobel算子的中孔,sobel算子通过对图像行列的变化来检测边缘,sobel算子会通过核kernel来完成检测。该参数定义了角点检测的敏感度,其取值必须介于3和31之间的奇数。如果将参数设置为3,当检测到方块的边界时,棋盘中褐色方块的所有对角线都会被认为时角点。它的第二个参数被设置可以改变标记焦点的记号大小
输出:
参数=23时:
参数=3时:
通过sift得到充满角点和特征的图像:
import cv2import sysimport numpy as npimgpath = 'varese.jpg'img = cv2.imread(imgpath)gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)#创建一个sift对象sift = cv2.xfeatures2d.SIFT_create()keypoints, descriptor = sift.detectAndCompute(gray,None)"""这里的标志4穿个drawKeypoints函数,标志值4其实是下面这个cv2模块的属性值 cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINT这个代码对图像的每个关键点都绘制了圆圈和方向"""img = cv2.drawKeypoints(image=img, outImage=img, keypoints = keypoints, flags = 4, color = (51, 163, 236))cv2.imshow('sift_keypoints', img)cv2.waitKey(0)cv2.destroyAllWindows()
SIFT对象会使用DoG检测关键点,并且对每个关键点周围的区域计算特征向量,由方法名称我们就可以知道,它主要包括两个操作:检测和计算,操作的返回值是关键点信息和描述符,最后在图像上绘制关键点
SURF采用快速Hessian算法检测关键点
SURF会提取特征
import cv2import sysimport numpy as np#imgpath = sys.argv[1]imgpath = 'varese.jpg'img = cv2.imread(imgpath)#alg = sys.argv[2]alg = 'SURF'def fd(algorithm): algorithms = { "SIFT": cv2.xfeatures2d.SIFT_create(), "SURF": cv2.xfeatures2d.SURF_create(float(sys.argv[3]) if len(sys.argv) == 4 else 8000), "ORB": cv2.ORB_create() } return algorithms[algorithm]gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)fd_alg = fd(alg)keypoints, descriptor = fd_alg.detectAndCompute(gray,None)img = cv2.drawKeypoints(image=img, outImage=img, keypoints = keypoints, flags = 4, color = (51, 163, 236))cv2.imshow('keypoints', img)cv2.waitKey(0)cv2.destroyAllWindows()
这幅图像是由SURF算法处理出来的,所采用的hessian阈值为8000,阈值越高,能识别的特征就越少
输出:
基于ORB的特征检测和特征匹配:
SIFT(1999年提出)是一种新算法,SURF(2006年提出)是一种更加新的算法,ORB(2011)是刚起步 用来代替前两者的,ORB是将基于FAST关键点检测技术和基于BRIEF描述符的技术相结合
下面分别介绍一下
FAST算法:
其中匹配算法用的是暴力匹配Brute-Force
import numpy as npimport cv2from matplotlib import pyplot as plt# query and test imagesimg1 = cv2.imread('manowar_logo.png',0)img2 = cv2.imread('manowar_single.jpg',0)# create the ORB detectororb = cv2.ORB_create()kp1, des1 = orb.detectAndCompute(img1,None)kp2, des2 = orb.detectAndCompute(img2,None)# brute force matchingbf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)matches = bf.match(des1,des2)# Sort by distance.matches = sorted(matches, key = lambda x:x.distance)img3 = cv2.drawMatches(img1,kp1,img2,kp2, matches[:25], img2,flags=2)plt.imshow(img3),plt.show()
输出:
KNN匹配
与上面那个例子不同的是,这边用knn来进行匹配,
import cv2import numpy as npimport matplotlib.pyplot as pltp1 = cv2.imread('manowar_logo.png')p2 = cv2.imread('manowar_single.jpg')grayp1 = cv2.cvtColor(p1, cv2.IMREAD_GRAYSCALE)grayp2 = cv2.cvtColor(p2, cv2.IMREAD_GRAYSCALE)orb = cv2.ORB_create()keyp1 ,desp1= orb.detectAndCompute(grayp1, None)keyp2 ,desp2= orb.detectAndCompute(grayp2, None) bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True) KnnMatches = bf.knnMatch(desp1, desp2, k=1)KnnMatchImg = cv2.drawMatchesKnn(p1, keyp1, p2, keyp2, KnnMatches[:50], p2, flags=2)plt.figure(figsize=(15,8))plt.imshow(KnnMatchImg)plt.show()
输出:
FLANN匹配
FLANN具有一种内部机制,该机制可以根据数据本身选择最合适的算法来处理数据,经验证,FLANN比其他的最近邻搜索软件快10倍。
FLANN内部可以选择LinearIndex、KTreeIndex、KMeansIndex、CompositeIndex和AutotuneIndex用来配置索引,这里选择KTreeIndex配置索引(只需指定待处理密度书的数量、最理想的数量在1~16之间),并且KtreeIndex非常灵活(kd-trees)可被并行处理,searchParams字典只包含一个字段checks,用来指定索引树要被遍历的次数,该值越高,计算匹配花费的时间就越长,但也会越准确。
实际上 匹配效果很大程度取决于输入,5 kd-trees和50 checks总能取得具有合理精度的结果,而且在很短时间内就能完成
import numpy as npimport cv2from matplotlib import pyplot as pltqueryImage = cv2.imread('bathory_album.jpg',0)trainingImage = cv2.imread('bathory_vinyls.jpg',0)# create SIFT and detect/computesift = cv2.xfeatures2d.SIFT_create()kp1, des1 = sift.detectAndCompute(queryImage,None)kp2, des2 = sift.detectAndCompute(trainingImage,None)# FLANN matcher parameters#FLANN_INDEX_KDTREE = 0indexParams = dict(algorithm = 0, trees = 5)searchParams = dict(checks=50) # or pass empty dictionaryflann = cv2.FlannBasedMatcher(indexParams,searchParams)matches = flann.knnMatch(des1,des2,k=2)# prepare an empty mask to draw good matchesmatchesMask = [[0,0] for i in range(len(matches))]# David G. Lowe's ratio test, populate the maskfor i,(m,n) in enumerate(matches): if m.distance < 0.7*n.distance: matchesMask[i]=[1,0]drawParams = dict(matchColor = (0,255,0), singlePointColor = (255,0,0), matchesMask = matchesMask, flags = 0)resultImage = cv2.drawMatchesKnn(queryImage,kp1,trainingImage,kp2,matches,None,**drawParams)plt.imshow(resultImage,),plt.show()
输出:
FLANN的单应性匹配:
正确识别出右侧图像,画出了关键点的匹配线段,而且还花了一个白色边框,用来展示图像seed目标在右侧发生投影畸变的效果
import numpy as npimport cv2from matplotlib import pyplot as pltMIN_MATCH_COUNT = 10img1 = cv2.imread('bb.jpg',0)img2 = cv2.imread('color2.jpg',0)# Initiate SIFT detectorsift = cv2.xfeatures2d.SIFT_create()# find the keypoints and descriptors with SIFTkp1, des1 = sift.detectAndCompute(img1,None)kp2, des2 = sift.detectAndCompute(img2,None)FLANN_INDEX_KDTREE = 0index_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)# store all the good matches as per Lowe's ratio test.good = []for m,n in matches: if m.distance < 0.7*n.distance: good.append(m)if len(good)>MIN_MATCH_COUNT: src_pts = np.float32([ kp1[m.queryIdx].pt for m in good ]).reshape(-1,1,2) dst_pts = np.float32([ kp2[m.trainIdx].pt for m in good ]).reshape(-1,1,2) M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC,5.0) matchesMask = mask.ravel().tolist() h,w = img1.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(img2,[np.int32(dst)],True,255,3, cv2.LINE_AA)else: print ("Not enough matches are found - %d/%d" % (len(good),MIN_MATCH_COUNT)) matchesMask = Nonedraw_params = dict(matchColor = (0,255,0), # draw matches in green color singlePointColor = None, matchesMask = matchesMask, # draw only inliers flags = 2)img3 = cv2.drawMatches(img1,kp1,img2,kp2,good,None,**draw_params)plt.imshow(img3, 'gray'),plt.show()
输出:
基于纹身取证的应用程序示例:
首先得将图像描述符保存到文件中,好处是,当两幅图像进行匹配和单应性分析时,不用每次都重建描述符,应用程序每次都会扫描保存图像的文件夹,并创建相应的描述符文件,可供后面搜索时使用,
文件存放:
from os.path import joinfrom os import walkimport numpy as npimport cv2from sys import argv# create an array of filenamesfolder = './image'query = cv2.imread(join(folder, "tattoo_seed.jpg"), 0)# create files, images, descriptors globalsfiles = []images = []descriptors = []for (dirpath, dirnames, filenames) in walk(folder): files.extend(filenames) for f in files: if f.endswith("npy") and f != "tattoo_seed.npy": descriptors.append(f) print (descriptors)# create the sift detectorsift = cv2.xfeatures2d.SIFT_create()query_kp, query_ds = sift.detectAndCompute(query, None)# create FLANN matcherFLANN_INDEX_KDTREE = 0index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)search_params = dict(checks = 50)flann = cv2.FlannBasedMatcher(index_params, search_params)# minimum number of matchesMIN_MATCH_COUNT = 10potential_culprits = {}print (">> Initiating picture scan...")for d in descriptors: print ("--------- analyzing %s for matches ------------" % d) matches = flann.knnMatch(query_ds, np.load(join(folder, d)), k =2) good = [] for m,n in matches: if m.distance < 0.7*n.distance: good.append(m) if len(good) > MIN_MATCH_COUNT: print ("%s is a match! (%d)" % (d, len(good))) else: print ("%s is not a match" % d) potential_culprits[d] = len(good)max_matches = Nonepotential_suspect = Nonefor culprit, matches in potential_culprits.items(): if max_matches == None or matches > max_matches: max_matches = matches potential_suspect = culpritprint ("potential suspect is %s" % potential_suspect.replace("npy", "").upper())
输出:
['bane.npy', 'dr-hurt.npy', 'hush.npy', 'penguin.npy', 'posion-ivy.npy', 'riddler.npy', 'two-face.npy']>> Initiating picture scan...--------- analyzing bane.npy for matches ------------bane.npy is not a match--------- analyzing dr-hurt.npy for matches ------------dr-hurt.npy is a match! (298)--------- analyzing hush.npy for matches ------------hush.npy is a match! (301)--------- analyzing penguin.npy for matches ------------penguin.npy is not a match--------- analyzing posion-ivy.npy for matches ------------posion-ivy.npy is not a match--------- analyzing riddler.npy for matches ------------riddler.npy is not a match--------- analyzing two-face.npy for matches ------------two-face.npy is not a matchpotential suspect is HUSH.