特征提取是計算機視覺和圖像處理中的一個概念。它指的是使用計算機提取圖像信息,決定圖像中的每一個點是否屬於該圖像的一個特征。特征提取的結果是把圖像上的點分為不同的子集,這些子集往往屬於孤立的點、連續的曲線或者連續的區域。
那麼,什麼樣的點才能稱之為特征呢?
至今為止特征並沒有絕對精確的定義。特征的精確定義往往由問題或者應用類型決定。特征是一個數字圖像中“有趣”(ROI)的部分,它是許多計算機圖像分析算法的起點。一個算法是否成功往往由它使用和定義的特征決定。因此特征提取最重要的一個特性是“可重復性”:同一場景的不同圖像所提取的特征應該是相同的。
常用的圖像特征有顏色特征、紋理特征、形狀特征和空間關系特征。
邊緣是組成兩個圖像區域之間邊界(或邊緣)的像素。
一般一個邊緣的形狀可以是任意的,還可能包括交叉點。在實踐中邊緣一般被定義為圖像中擁有大的梯度的點組成的子集。一些常用的算法還會把梯度高的點聯系起來構成一個更完善的邊緣的描寫。這些算法也可能對邊緣提出一些限制。局部地看邊緣是一維結構。
角是圖像中點似的特征,在局部它有二維結構。
早期的算法首先進行邊緣檢測,然後分析邊緣的走向來尋找邊緣突然轉向即角。後來發展的算法不再需要邊緣檢測這個步驟,而是可以直接在圖像梯度中尋找高度曲率。後來發現這樣有時可以在圖像中本來沒有角的地方發現具有同角一樣的特征的區域。
與角不同的是區域描寫一個圖像中的一個區域性的結構,但是區域也可能僅由一個像素組成,因此許多區域檢測也可以用來檢測角。一個區域檢測器檢測圖像中一個對於角檢測器來說太平滑的區域,區域監測器可以被想象為把一張圖像縮小,然後在縮小的圖像上進行角檢測。
長條形的物體被稱之為脊。在實踐中脊可以被看作是代表對稱軸的一維曲線,此外局部針對於每個脊像素有一個脊寬度。從灰梯度圖像中提取脊要比提取邊緣、角和區域困難。
在空中攝影中往往使用脊檢測來分辨道路,在醫學圖像中它被用來分辨血管。
通過對影像內容、特征、結構、關系、紋理及灰度等的對應關系,相似性和一致性的分析,尋求相似影像目標的方法。
圖像匹配是指通過一定的匹配算法在兩幅或多幅圖像之間識別同名點,如二維圖像匹配中通過比較目標區和搜索區中相同大小的窗口的相關系數,取搜索區中相關系數最大所對應的窗口中心點作為同名點。其實質是在基元相似性的條件下,運用匹配准則的最佳搜索問題。
從不同的距離,不同的方向、角度,不同的光照條件下觀察一個物體時,物體的大小,形狀,敏感都會有所不同。但我們依然可以判斷它是同一物體。
理想的特征描述子應該具備這些性質。即,在大小、方向、明暗不同的圖像中,同一特征點應具有足夠相似的描述子,稱之為描述子的可復現性。
接下來我們將分別通過Harris角點檢測算法和SIFT特征檢測算法來直觀感受一下計算機是如何實現圖像特征提取和圖像匹配的。
是一種基於圖像灰度的方法通過計算點的曲率和梯度來檢測角點。
如果在各個方向上移動窗口,窗口中的灰度值都會發生較大變化,那麼認定在窗口遇到了角點;如果在一個方向發生變化,另一個方向不變,就可能是一條直線;如果各個方向移動,窗口內灰度值都沒有發生變化,不存在角點。
假設圖像像素點(x,y)的灰度為 I(x,y),以像素點為中心的窗口沿 x 和 y 方向分別移動 u 和 v 的灰度強度變化的表達式為:
其中 E(u,v)是灰度變化,w(x,y) 是窗口函數,一般是高斯函數,所以可以把w(x,y)看做是高斯濾波器(注:高斯濾波就是對整幅圖像進行加權平均的過程)。I(x,y)是圖像灰度, I(x+u,y+v)是平移後的圖像灰度。
受到泰勒公式的啟發,在這裡我們可以將 I(x+u,y+v)函數在(x,y)處泰勒展開,為了提高抗干擾的能力並且簡化運算,我們取到了一階導數部分,後面的無窮小量可以忽略,整理得到表達式如下:
將[ Ixu+Iyv ]展開後整理可以用矩陣表達為:
最後我們可以近似得到E(x,y)的表達式,將其化為二次型後得到:
其中M是一個2X2的矩陣,稱為像素點的自相關矩陣,可以由圖像的導數求得。M=窗口函數*偏導矩陣,表達式為:
# 局部圖像描述子
# Harris角點檢測
from pylab import *
from PIL import Image
from numpy import *
from scipy.ndimage import filters
def compute_harris_response(im, sigma=3):
"""在一幅灰度圖像中,對每個像素計算Harris角點檢測器響應函數"""
# 計算導數
imx = zeros(im.shape)
filters.gaussian_filter(im, (sigma, sigma), (0, 1), imx)
imy = zeros(im.shape)
filters.gaussian_filter(im, (sigma, sigma), (1, 0), imy)
# 計算Harris矩陣的分量
Wxx = filters.gaussian_filter(imx * imx, sigma)
Wxy = filters.gaussian_filter(imx * imy, sigma)
Wyy = filters.gaussian_filter(imy * imy, sigma)
# 計算特征值和跡
Wdet = Wxx * Wyy - Wxy ** 2
Wtr = Wxx + Wyy
return Wdet / Wtr
def get_harris_points(harrisim, min_dist=10, threshold=0.1):
"""從一幅Harris響應圖像中返回角點。min_dist為分割角點和圖像邊界的最小像素數目"""
# 尋找高於阈值的候選角點
corner_threshold = harrisim.max() * threshold
harrisim_t = (harrisim > corner_threshold) * 1
# 得到候選點的坐標
coords = array(harrisim_t.nonzero()).T
# 以及它們的Harris響應值
candidate_values = [harrisim[c[0], c[1]] for c in coords]
# 對候選點按照Harris響應值進行排序
index = argsort(candidate_values)
# 將可行點的位置保存到數組中
allowed_locations = zeros(harrisim.shape)
allowed_locations[min_dist:-min_dist, min_dist:-min_dist] = 1
# 按照min_distance原則,選擇最佳Harris點
filtered_coords = []
for i in index:
if allowed_locations[coords[i, 0], coords[i, 1]] == 1:
filtered_coords.append(coords[i])
allowed_locations[(coords[i, 0] - min_dist):(coords[i, 0] + min_dist),
(coords[i, 1] - min_dist):(coords[i, 1] + min_dist)] = 0
return filtered_coords
def plot_harris_points(image,filtered_coords):
"""繪制圖像中檢測到的角點"""
figure()
gray()
imshow(image)
plot([p[1] for p in filtered_coords],[p[0] for p in filtered_coords],'*')
axis('off')
show()
# 調用展示
im = array(Image.open('memorial_hall1.jpg').convert('L'))
harrisim = compute_harris_response(im)
filtered_coords = get_harris_points(harrisim,6, 0.05)
plot_harris_points(im, filtered_coords)
注意:示例代碼運行有bug,RuntimeWarning: invalid value encountered in divide
return Wdet/Wtr
運行截圖
from PIL import Image
from numpy import *
import harris
from pylab import *
wid=5
im1=array(Image.open('memorial_hall1.jpg').convert('L'))
im2=array(Image.open('memorial_hall2.jpg').convert('L'))
harrisim=harris.compute_harris_response(im1,5)
filtered_coords1=harris.get_harris_points(harrisim,wid+1,0.2)
d1=harris.get_descriptors(im1,filtered_coords1,wid)
harrisim=harris.compute_harris_response(im2,5)
filtered_coords2=harris.get_harris_points(harrisim,wid+1,0.2)
d2=harris.get_descriptors(im2,filtered_coords2,wid)
print ('starting matching')
運行截圖
尺度不變特征轉換即SIFT (Scale-invariant feature transform)是一種計算機視覺的算法。它用來偵測與描述影像中的局部性特征,它在空間尺度中尋找極值點,並提取出其位置、尺度、旋轉不變量。
SIFT算法的實質是在不同的尺度空間上查找關鍵點(特征點),並計算出關鍵點的方向。SIFT所查找到的關鍵點是一些十分突出,不會因光照,仿射變換和噪音等因素而變化的點,如角點、邊緣點、暗區的亮點及亮區的暗點等。
在不同的尺度空間上查找關鍵點,並計算出關鍵點的方向。
L(x, y, σ) ,定義為原始圖像 I(x, y)與一個可變尺度的2維高斯函數G(x, y, σ) 卷積運算。
表示卷積運算*,(x,y)代表圖像的像素位置。?是尺度空間因子,值越小表示圖像被平滑的越少,相應的尺度也就越小。
大尺度對應於圖像的概貌特征,小尺度對應於圖像的細節特征。
from PIL import Image
from pylab import *
import sys
from PCV.localdescriptors import sift
if len(sys.argv) >= 3:
im1f, im2f = sys.argv[1], sys.argv[2]
else:
im1f = 'mansion1.jpg'
im2f = 'mansion2.jpg'
im1 = array(Image.open(im1f))
im2 = array(Image.open(im2f))
sift.process_image(im1f, 'out_sift_1.txt')
l1, d1 = sift.read_features_from_file('out_sift_1.txt')
figure()
gray()
subplot(121)
sift.plot_features(im1, l1, circle=False)
sift.process_image(im2f, 'out_sift_2.txt')
l2, d2 = sift.read_features_from_file('out_sift_2.txt')
subplot(122)
sift.plot_features(im2, l2, circle=False)
# matches = sift.match(d1, d2)
matches = sift.match_twosided(d1, d2)
print('{} matches'.format(len(matches.nonzero()[0])))
figure()
gray()
sift.plot_matches(im1, im2, l1, l2, matches, show_below=True)
show()
運行截圖
模板匹配:模板匹配是一種最原始、最基本的模式識別方法,研究某一特定對象物的圖案位於圖像的什麼地方,進而識別對象物,這就是一個匹配問題。它是圖像處理中最基本、最常用的匹配方法。模板匹配具有自身的局限性,主要表現在它只能進行平行移動,若原圖像中的匹配目標發生旋轉或大小變化,該算法無效。
特征匹配:所謂特征匹配(FBM),就是指將從影像中提取的特征作為共轭實體,而將所提特征屬性或描述參數(實際上是特征的特征,也可以認為是影像的特征)作為匹配實體,通過計算匹配實體之間的相似性測度以實現共轭實體配准的影像匹配方法。在匹配目標發生旋轉或大小變化時,該算法依舊有效。
#版本python 3.7.1
pip install numpy==1.15.3
pip install matplotlib==3.0.1
pip install opencv-python==3.4.2.16
pip install opencv-contrib-python==3.4.2.16
目標圖片:target.jpg
模板圖片:template.jpg
#opencv模板匹配----單目標匹配
import cv2
#讀取目標圖片
target = cv2.imread("target.jpg")
#讀取模板圖片
template = cv2.imread("template.jpg")
#獲得模板圖片的高寬尺寸
theight, twidth = template.shape[:2]
#執行模板匹配,采用的匹配方式cv2.TM_SQDIFF_NORMED
result = cv2.matchTemplate(target,template,cv2.TM_SQDIFF_NORMED)
#歸一化處理
cv2.normalize( result, result, 0, 1, cv2.NORM_MINMAX, -1 )
#尋找矩陣(一維數組當做向量,用Mat定義)中的最大值和最小值的匹配結果及其位置
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
#匹配值轉換為字符串
#對於cv2.TM_SQDIFF及cv2.TM_SQDIFF_NORMED方法min_val越趨近與0匹配度越好,匹配位置取min_loc
#對於其他方法max_val越趨近於1匹配度越好,匹配位置取max_loc
strmin_val = str(min_val)
#繪制矩形邊框,將匹配區域標注出來
#min_loc:矩形定點
#(min_loc[0]+twidth,min_loc[1]+theight):矩形的寬高
#(0,0,225):矩形的邊框顏色;2:矩形邊框寬度
cv2.rectangle(target,min_loc,(min_loc[0]+twidth,min_loc[1]+theight),(0,0,225),2)
#顯示結果,並將匹配值顯示在標題欄上
cv2.imshow("MatchResult----MatchingValue="+strmin_val,target)
cv2.waitKey()
cv2.destroyAllWindows()
運行結果: 代碼沒跑通,需要調試!
可以看到顯示的min_val的值為0.0,說明完全匹配。
目標圖片:target.jpg
#opencv模板匹配----多目標匹配
import cv2
import numpy
#讀取目標圖片
target = cv2.imread("target.jpg")
#讀取模板圖片
template = cv2.imread("template.jpg")
#獲得模板圖片的高寬尺寸
theight, twidth = template.shape[:2]
#執行模板匹配,采用的匹配方式cv2.TM_SQDIFF_NORMED
result = cv2.matchTemplate(target,template,cv2.TM_SQDIFF_NORMED)
#歸一化處理
#cv2.normalize( result, result, 0, 1, cv2.NORM_MINMAX, -1 )
#尋找矩陣(一維數組當做向量,用Mat定義)中的最大值和最小值的匹配結果及其位置
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
#繪制矩形邊框,將匹配區域標注出來
#min_loc:矩形定點
#(min_loc[0]+twidth,min_loc[1]+theight):矩形的寬高
#(0,0,225):矩形的邊框顏色;2:矩形邊框寬度
cv2.rectangle(target,min_loc,(min_loc[0]+twidth,min_loc[1]+theight),(0,0,225),2)
#匹配值轉換為字符串
#對於cv2.TM_SQDIFF及cv2.TM_SQDIFF_NORMED方法min_val越趨近與0匹配度越好,匹配位置取min_loc
#對於其他方法max_val越趨近於1匹配度越好,匹配位置取max_loc
strmin_val = str(min_val)
#初始化位置參數
temp_loc = min_loc
other_loc = min_loc
numOfloc = 1
#第一次篩選----規定匹配阈值,將滿足阈值的從result中提取出來
#對於cv2.TM_SQDIFF及cv2.TM_SQDIFF_NORMED方法設置匹配阈值為0.01
threshold = 0.01
loc = numpy.where(result<threshold)
#遍歷提取出來的位置
for other_loc in zip(*loc[::-1]):
#第二次篩選----將位置偏移小於5個像素的結果捨去
if (temp_loc[0]+5<other_loc[0])or(temp_loc[1]+5<other_loc[1]):
numOfloc = numOfloc + 1
temp_loc = other_loc
cv2.rectangle(target,other_loc,(other_loc[0]+twidth,other_loc[1]+theight),(0,0,225),2)
str_numOfloc = str(numOfloc)
#顯示結果,並將匹配值顯示在標題欄上
strText = "MatchResult----MatchingValue="+strmin_val+"----NumberOfPosition="+str_numOfloc
cv2.imshow(strText,target)
cv2.waitKey()
cv2.destroyAllWindows()
運行結果:未測試
目標圖片:target.jpg
模板圖片:template_adjst.jpg
#opencv----特征匹配----BFMatching
import cv2
from matplotlib import pyplot as plt
#讀取需要特征匹配的兩張照片,格式為灰度圖。
template=cv2.imread("template_adjust.jpg",0)
target=cv2.imread("target.jpg",0)
orb=cv2.ORB_create()#建立orb特征檢測器
kp1,des1=orb.detectAndCompute(template,None)#計算template中的特征點和描述符
kp2,des2=orb.detectAndCompute(target,None) #計算target中的
bf = cv2.BFMatcher(cv2.NORM_HAMMING,crossCheck=True) #建立匹配關系
mathces=bf.match(des1,des2) #匹配描述符
mathces=sorted(mathces,key=lambda x:x.distance) #據距離來排序
result= cv2.drawMatches(template,kp1,target,kp2,mathces[:40],None,flags=2) #畫出匹配關系
plt.imshow(result),plt.show() #matplotlib描繪出來
#
''' 基於FLANN的匹配器(FLANN based Matcher) 1.FLANN代表近似最近鄰居的快速庫。它代表一組經過優化的算法,用於大數據集中的快速最近鄰搜索以及高維特征。 2.對於大型數據集,它的工作速度比BFMatcher快。 3.需要傳遞兩個字典來指定要使用的算法及其相關參數等 對於SIFT或SURF等算法,可以用以下方法: index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5) 對於ORB,可以使用以下參數: index_params= dict(algorithm = FLANN_INDEX_LSH, table_number = 6, # 12 這個參數是searchParam,指定了索引中的樹應該遞歸遍歷的次數。值越高精度越高 key_size = 12, # 20 multi_probe_level = 1) #2 '''
import cv2 as cv
from matplotlib import pyplot as plt
queryImage=cv.imread("template_adjust.jpg",0)
trainingImage=cv.imread("target.jpg",0)#讀取要匹配的灰度照片
sift=cv.xfeatures2d.SIFT_create()#創建sift檢測器
kp1, des1 = sift.detectAndCompute(queryImage,None)
kp2, des2 = sift.detectAndCompute(trainingImage,None)
#設置Flannde參數
FLANN_INDEX_KDTREE=0
indexParams=dict(algorithm=FLANN_INDEX_KDTREE,trees=5)
searchParams= dict(checks=50)
flann=cv.FlannBasedMatcher(indexParams,searchParams)
matches=flann.knnMatch(des1,des2,k=2)
#設置好初始匹配值
matchesMask=[[0,0] for i in range (len(matches))]
for i, (m,n) in enumerate(matches):
if m.distance< 0.5*n.distance: #捨棄小於0.5的匹配結果
matchesMask[i]=[1,0]
drawParams=dict(matchColor=(0,0,255),singlePointColor=(255,0,0),matchesMask=matchesMask,flags=0) #給特征點和匹配的線定義顏色
resultimage=cv.drawMatchesKnn(queryImage,kp1,trainingImage,kp2,matches,None,**drawParams) #畫出匹配的結果
plt.imshow(resultimage,),plt.show()
#基於FLANN的匹配器(FLANN based Matcher)定位圖片
import numpy as np
import cv2
from matplotlib import pyplot as plt
MIN_MATCH_COUNT = 10 #設置最低特征點匹配數量為10
template = cv2.imread('template_adjust.jpg',0) # queryImage
target = cv2.imread('target.jpg',0) # trainImage
# Initiate SIFT detector創建sift檢測器
sift = cv2.xfeatures2d.SIFT_create()
# find the keypoints and descriptors with SIFT
kp1, des1 = sift.detectAndCompute(template,None)
kp2, des2 = sift.detectAndCompute(target,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)
# store all the good matches as per Lowe's ratio test.
good = []
#捨棄大於0.7的匹配
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)
#計算變換矩陣和MASK
M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)
matchesMask = mask.ravel().tolist()
h,w = template.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)
cv2.polylines(target,[np.int32(dst)],True,0,2, cv2.LINE_AA)
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,
flags=2)
result = cv2.drawMatches(template,kp1,target,kp2,good,None,**draw_params)
plt.imshow(result, 'gray')
plt.show()
運行結果: