SIFT之二:獲取精確特徵點位置
最近微博上有人發起投票那篇論文是自己最受益匪淺的論文,不少人說是lowe的這篇介紹SIFT的論文。確實,在影象特徵識別領域,SIFT的出現是具有重大意義的,SIFT特徵以其穩定的存在,較高的區分度推進了諸多領域的發展,比如識別和配準。上一篇文章,解析了SIFT特徵提取的第一步高斯金字塔的構建,並詳細分析了高斯金字塔以及差分高斯金字塔如何完成一個連續的尺度空間的構建。構建高斯金字塔不是目的,目的是如何利用高斯金字塔找到極值點。
lowe在論文中闡述了為什麼使用差分高斯金字塔:
1)差分高斯影象可以直接由高斯影象相減獲得,簡單高效
2) 差分高斯函式是尺度規範化的高斯拉普拉斯函式的近似,而高斯拉普拉斯函式的極大值和極小值點是一種非常穩定的特徵點(與梯度特徵、Hessian特徵和Harris角點相比)
有了這些基礎,我們就可以放開手腳從差分高斯金字塔中找點了。
特徵點的確定主要包括兩個過程:確定潛在特徵點,精確確定特徵點的位置和去除不穩定特徵點。
確定潛在特徵點
上文已經闡述,高斯拉普拉斯函式的極大值和極小值點是一種非常穩定的特徵點,因此我們從差分高斯金字塔中尋找這些潛在特徵點。差分高斯金字塔是一個三維空間(平面圖像二維,尺度一維),因此我們在三維空間中在尋找極大值點和極小值點。具體方法是比較當前特徵點的灰度值和其他26個點的灰度值的大小,這26個點包括:當前尺度下該點的8鄰域以及前一尺度和後一尺度下與該點最近的9個點(9*2+8=26),如下圖所示:
OpenCV該部分原始碼:
- void SIFT::findScaleSpaceExtrema( const vector<Mat>& gauss_pyr, const vector<Mat>& dog_pyr,
- vector<KeyPoint>& keypoints ) const
- {
- ......
- for( int o = 0; o < nOctaves; o++ )//每一個八度
-
for( int i = 1; i <= nOctaveLayers; i++ )
- {
- ......
- for( int r = SIFT_IMG_BORDER; r < rows-SIFT_IMG_BORDER; r++)//影象二維空間.行
- {
- ......
- for( int c = SIFT_IMG_BORDER; c < cols-SIFT_IMG_BORDER; c++)//影象二維空間.列
- {
- .......
- // 當前點與26個點比較,比較兩次,分別確定是否是極大值,是否是極小值
- if( std::abs(val) > threshold &&
- ((val > 0 && val >= currptr[c-1] && val >= currptr[c+1] &&
- val >= currptr[c-step-1] && val >= currptr[c-step] && val >= currptr[c-step+1] &&
- val >= currptr[c+step-1] && val >= currptr[c+step] && val >= currptr[c+step+1] &&
- val >= nextptr[c] && val >= nextptr[c-1] && val >= nextptr[c+1] &&
- val >= nextptr[c-step-1] && val >= nextptr[c-step] && val >= nextptr[c-step+1] &&
- val >= nextptr[c+step-1] && val >= nextptr[c+step] && val >= nextptr[c+step+1] &&
- val >= prevptr[c] && val >= prevptr[c-1] && val >= prevptr[c+1] &&
- val >= prevptr[c-step-1] && val >= prevptr[c-step] && val >= prevptr[c-step+1] &&
- val >= prevptr[c+step-1] && val >= prevptr[c+step] && val >= prevptr[c+step+1]) ||
- (val < 0 && val <= currptr[c-1] && val <= currptr[c+1] &&
- val <= currptr[c-step-1] && val <= currptr[c-step] && val <= currptr[c-step+1] &&
- val <= currptr[c+step-1] && val <= currptr[c+step] && val <= currptr[c+step+1] &&
- val <= nextptr[c] && val <= nextptr[c-1] && val <= nextptr[c+1] &&
- val <= nextptr[c-step-1] && val <= nextptr[c-step] && val <= nextptr[c-step+1] &&
- val <= nextptr[c+step-1] && val <= nextptr[c+step] && val <= nextptr[c+step+1] &&
- val <= prevptr[c] && val <= prevptr[c-1] && val <= prevptr[c+1] &&
- val <= prevptr[c-step-1] && val <= prevptr[c-step] && val <= prevptr[c-step+1] &&
- val <= prevptr[c+step-1] && val <= prevptr[c+step] && val <= prevptr[c+step+1])))
- {
- ......
- }
- }
- }
- }
- }
尺度空間中的極值點已經確定出來了,下面有兩個問題需要解決:
(1)這些點是最終我們確定的SIFT特徵點集的超集,該超集裡包含許多“間諜”-----不穩定的特徵點,因此必須去掉這些不穩定的特徵點。這些不穩定的特徵點主要包含兩類:低對比度的點(對噪聲敏感)和邊緣點。
(2)這一步驟中極值點的座標還是離散的整數值,如何精確確定特徵點的位置。
由於在計算上(2)問題的解決可以捎帶解決(1)中低對比度點的問題,因此我們先討論問題(2)。本部分的OpenCV原始碼位於sift.cpp檔案的adjustLocalExtrema函式中,本文最後會貼出此部分原始碼,下面首先分析如何解決以上兩個問題。
精確確定特徵點的位置:
由於影象是一個離散的空間,特徵點的位置的座標都是整數,但是極值點的座標並不一定就是整數,如下圖所示。
因此,如何從離散空間中估計出極值點的精確位置是重要的。為了精確確定極值點座標,Brown和Lowe使用了三元二次函式,通過迭代確定極值點的位置,具有良好的效果。
主要是根據泰勒公式,泰勒公式作用:用值已知的點A估計點A附近的某點B的值。
求上式極值,對其求導,導數等於0,得到
去除不穩定特徵點
去除對比度低的點
以上求出了極值點的精確的位置,將求出的 x 帶入原式,得:
我們就利用這個函式去除對比度低的點,lowe文中,當D(x)<=0.03時,去除這個特徵點。
去除邊緣點
差分高斯金字塔中的極值點會有許多邊緣點,邊緣點對一些噪聲不穩定,因此需要去除這些邊緣相應點。
差分高斯金字塔中會有一些不是很好的極值點,這些點的特徵是:在跨越邊緣的方向有較大的主曲率,在與邊緣相切的方向主曲率較小。在本步驟中,需要去除這些不好的邊緣相應。主曲率可以通過2階Hessian方陣獲得:
D函式中某點的主曲率和該點的H矩陣的特徵值是成比例的,因此我們可以通過H矩陣的特徵值來確定某點在差分高斯金字塔中的主曲率。
設矩陣H的特徵值分別為α(較大)和β(較小),有如下公式:
通過以上兩式,α和β就可以計算出來了,但是,不急!
如上文所述,那些不好的邊緣點:跨越邊緣的方向有較大的主曲率,與邊緣相切的方向主曲率較小。因此,我們通過α/β的比率函式並確定閾值來體現表徵那些不好的邊緣點,α/β越大,說明這個點就越糟糕,就越應該被刪掉,但是這樣就要真真切切計算α和β的值,前面讓大家不急了,是的,先不用著急計算,設定r=α/β(即 α=rβ),使用如下公式:
以上函式是關於r的增函式(已經假設α是特徵值中較大的一個),r 越大,以上函式值就越大,反之,以上函式值越大,r 就是越大的,因此我們可以通過已知的Tr(H)和Det(H)“曲線地”去判斷 r的大小!所以在本步驟中,去除不好的邊緣點的閾值是:
lowe論文中設定r=10。
到這裡,在差分高斯金字塔中提取的特徵點就完成了提純的步驟。
下面是OpenCV原始碼中特徵點精確位置的確定過程以及特徵點提純過程,主要實現函式為sift.cpp中adjustLocalExtrema函式:
- // Interpolates a scale-space extremum's location and scale to subpixel
- // accuracy to form an image feature. Rejects features with low contrast.
- // Based on Section 4 of Lowe's paper.
- staticbool adjustLocalExtrema( const vector<Mat>& dog_pyr, KeyPoint& kpt, int octv,
- int& layer, int& r, int& c, int nOctaveLayers,
- float contrastThreshold, float edgeThreshold, float sigma )
- {
- constfloat img_scale = 1.f/(255*SIFT_FIXPT_SCALE);
- constfloat deriv_scale = img_scale*0.5f;
- constfloat second_deriv_scale = img_scale;
- constfloat cross_deriv_scale = img_scale*0.25f;
- float xi=0, xr=0, xc=0, contr=0;
- int i = 0;
- // 如上文所述,迭代計算特徵點的精確位置
- for( ; i < SIFT_MAX_INTERP_STEPS; i++ )
- {
- int idx = octv*(nOctaveLayers+2) + layer;
- const Mat& img = dog_pyr[idx];
- const Mat& prev = dog_pyr[idx-1];
- const Mat& next = dog_pyr[idx+1];
- Vec3f dD((img.at<sift_wt>(r, c+1) - img.at<sift_wt>(r, c-1))*deriv_scale,
- (img.at<sift_wt>(r+1, c) - img.at<sift_wt>(r-1, c))*deriv_scale,
- (next.at<sift_wt>(r, c) - prev.at<sift_wt>(r, c))*deriv_scale);
- float v2 = (float)img.at<sift_wt>(r, c)*2;
- float dxx = (img.at<sift_wt>(r, c+1) + img.at<sift_wt>(r, c-1) - v2)*second_deriv_scale;
- float dyy = (img.at<sift_wt>(r+1, c) + img.at<sift_wt>(r-1, c) - v2)*second_deriv_scale;
- float dss = (next.at<sift_wt>(r, c) + prev.at<sift_wt>(r, c) - v2)*second_deriv_scale;
- float dxy = (img.at<sift_wt>(r+1, c+1) - img.at<sift_wt>(r+1, c-1) -
- img.at<sift_wt>(r-1, c+1) + img.at<sift_wt>(r-1, c-1))*cross_deriv_scale;
- float dxs = (next.at<sift_wt>(r, c+1) - next.at<sift_wt>(r, c-1) -
- prev.at<sift_wt>(r, c+1) + prev.at<sift_wt>(r, c-1))*cross_deriv_scale;
- float dys = (next.at<sift_wt>(r+1, c) - next.at<sift_wt>(r-1, c) -
- prev.at<sift_wt>(r+1, c) + prev.at<sift_wt>(r-1, c))*cross_deriv_scale;
- Matx33f H(dxx, dxy, dxs,
- dxy, dyy, dys,
- dxs, dys, dss);//通過當前畫素點以及周圍畫素點差值出H矩陣
- Vec3f X = H.solve(dD, DECOMP_LU);
- xi = -X[2];
- xr = -X[1];
- xc = -X[0];
- //有任何一個維度的偏移超過0.5,會更新當前畫素點
- //如果每一個維度的偏移都沒有超過0.5,當前畫素的位置加上偏移就是最終的精確點
- if( std::abs(xi) < 0.5f && std::abs(xr) < 0.5f && std::abs(xc) < 0.5f )
- break;
- if( std::abs(xi) > (float)(INT_MAX/3) ||
- std::abs(xr) > (float)(INT_MAX/3) ||
- std::abs(xc) > (float)(INT_MAX/3) )
- returnfalse;
- c += cvRound(xc);
- r += cvRound(xr);
- layer += cvRound(xi);
- if( layer < 1 || layer > nOctaveLayers ||
- c < SIFT_IMG_BORDER || c >= img.cols - SIFT_IMG_BORDER ||
- r < SIFT_IMG_BORDER || r >= img.rows - SIFT_IMG_BORDER )
- returnfalse;
- }
- //迭代結束
- // ensure convergence of interpolation
- if( i >= SIFT_MAX_INTERP_STEPS )
- returnfalse;
- {
- int idx = octv*(nOctaveLayers+2) + layer;
- const Mat& img = dog_pyr[idx];
- const Mat& prev = dog_pyr[idx-1];
- const Mat& next = dog_pyr[idx+1];
- Matx31f dD((img.at<sift_wt>(r, c+1) - img.at<sift_wt>(r, c-1))*deriv_scale,
- (img.at<sift_wt>(r+1, c) - img.at<sift_wt>(r-1, c))*deriv_scale,
- (next.at<sift_wt>(r, c) - prev.at<sift_wt>(r, c))*deriv_scale);
- float t = dD.dot(Matx31f(xc, xr, xi));
- contr = img.at<sift_wt>(r, c)*img_scale + t * 0.5f;
- if( std::abs( contr ) * nOctaveLayers < contrastThreshold )//去除低對比度的點
- returnfalse;
- // principal curvatures are computed using the trace and det of Hessian
- float v2 = img.at<sift_wt>(r, c)*2.f;
- float dxx = (img.at<sift_wt>(r, c+1) + img.at<sift_wt>(r, c-1) - v2)*second_deriv_scale;
- float dyy = (img.at<sift_wt>(r+1, c) + img.at<sift_wt>(r-1, c) - v2)*second_deriv_scale;
- float dxy = (img.at<sift_wt>(r+1, c+1) - img.at<sift_wt>(r+1, c-1) -
- img.at<sift_wt>(r-1, c+1) + img.at<sift_wt>(r-1, c-1)) * cross_deriv_scale;
- float tr = dxx + dyy;
- float det = dxx * dyy - dxy * dxy;
- if( det <= 0 || tr*tr*edgeThreshold >= (edgeThreshold + 1)*(edgeThreshold + 1)*det )//去除邊緣噪聲點
-
相關推薦
SIFT之二:獲取精確特徵點位置
最近微博上有人發起投票那篇論文是自己最受益匪淺的論文,不少人說是lowe的這篇介紹SIFT的論文。確實,在影象特徵識別領域,SIFT的出現是具有重大意義的,SIFT特徵以其穩定的存在,較高的區分度推進了諸多領域的發展,比如識別和配準。上一篇文章,解析了SIFT特徵提取的第
SpringBoot 入門之二:獲取Properties中的值,通過類配置來替代原SpringXML的配值和注入方式
•application.properties •application.yml person.last-name=\u674E\u56DB person.age=12 person.birth=2017/12/15 person.boss=false person.maps.k
Unity&Android之二:獲取手機電量資訊、網路狀況
Unity&Android之二:獲取手機電量資訊、網路狀況 遊戲中經常會在UI顯示電量以及網路狀況 手機電量包括: 1、當前正在充電還是放電 2、當前電量值 網路包括: 1、如果是WIFI,WIFI訊號強度 2、如果是流量,訊號強度等資料
Halcon學習之二:攝像頭獲取圖像和相關參數
digg tail tours mage eight sta vision name pict 1、close_all_framegrabbers ( : : : ) 關閉所有圖像采集設備。 2、close_framegrabber ( : : AcqHand
面向物件的特徵之二:繼承
繼承:子類繼承分類的非私有化的成員。具體的理解:就是當一個類成員與另一個類的成員屬性和方法一樣,我們就會用繼承來。 繼承的好處就是:提高了程式碼的複用性。 繼承的關鍵字:extends class 子類 extends class 父類{ }
「數據治理那點事」系列之二:手握數據「戶口本」,數據治理肯定穩!
物理 系列 數據對比 概念 決策者 等等 ges mode 架構 這篇文章主要從數據治理的基礎和核心之一:元數據 入手,從以下幾個角度展開具體講解: 元數據概念元數據的分布和采集元數據的一些實際應用場景. 1.元數據到底是個啥? 如果我說:元數據(Meta Data),就是
Qt總結之二:遍歷資料夾和檔案目錄,並過濾和獲取檔案資訊、字尾名、字首名(二)
前言 需要在特定目錄或磁碟下查詢特定檔案 一、篩選目錄 (一)單一目錄下遍歷,篩選特定檔案 QDir dir("./SaveFiles"); QFileInfoList list = dir.entryInfoList(); (二)裝置所有磁碟中遍歷 QF
C#基礎拾遺系列之二:使用ILSpy探索C#7.0新增功能點
第一部分: C#是一種通用的,型別安全的,面向物件的程式語言。有如下特點: (1)面向物件:c# 是面向物件的範例的一個豐富實現, 它包括封裝、繼承和多型性。C#面向物件的行為包括: 統一的型別系統 類與介面 屬性、方法、事件 (2)型別安全:C#還允許通過dynamic關鍵字動態
如何在OD載入程式遇到入口點之前執行程式碼之二:靜態裝載DLL
編譯器:VS2010 需要的知識:DLL的編寫和使用 1.dllmain.cpp // dllmain.cpp : Defines the entry point for the DLL application. #include "stdafx.h" BOOL API
事件處理之二:點選事件監聽器的五種寫法
首選方法二! 方法一:寫一個內部類,在類中實現點選事件 1、在父類中呼叫點選事件 bt_dail.setOnClickListener(new MyButtonListener()); 2、建立內部類 private class MyButtonListener
影象演算法之二:特徵提取算法系列之Harris
Harris運算元介紹: 該運算元是C.Harris和M.J.Stephens在1988年提出的一種點特徵提取運算元。這種運算元受訊號處理中自相關函式的啟發,可以給出影象中某一畫素點的自相關矩陣肘,其特徵值是自相關函式的一階曲率,如果X,Y兩個方向上的曲率值都
openwrt之二:載入掛載點
啟動上篇博文中的虛擬機器中的openwrt,使用secureCRT軟體ssh登入到192.168.11.254(參見上篇博文中該虛擬機器橋接網絡卡的ip),然後 opkg update opkg install luci-i18n-chinese 使用http訪問頁面上的系
4.AngularJS四大特征之二: 雙向數據綁定
sco font int out grep 模型 多行文本 pan oot AngularJS四大特征之二: 雙向數據綁定 (1)方向一:把Model數據綁定到View上——此後不論何時只要Model發生了改變,則View中的呈現會立即隨之改變!實現方法: {{ }}、
linux學習之二:日常的基礎命令收集
幫助文檔 gedit 查看 日期 取整 style 位置 某月 linux 1、 ls 2、pwd 顯示當前目錄所在位置 3、date 日期時間 4、cal 日歷 默認顯示當前該月 cal 2012 :查看2012年的日歷 cal 月 年 : 查看某年某月
【只怕沒有幾個人能說清楚】系列之二:Unity中的特殊文件夾
物體 avi ebp time 編輯模式 tro hive 預覽 打包 參考:http://www.manew.com/thread-99292-1-1.html 1. 隱藏文件夾 以.開頭的文件夾會被忽略。在這種文件夾中的資源不會被導入,腳本不會被編譯。也不會出現
雙態運維分享之二: 服務型CMDB的消費場景
新增 iso20000 那種 .cn 關聯 通知 變更 不同 維護 近年來,CMDB在IT運維管理中的價值逐步得到認可,使用CMDB的期望值也日益增長。然而,CMDB實施和維護的高成本卻一直是建設者們的痛點。那麽今天,我們來探討一下如何通過消費來持續驅動CMDB的逐步完善。
OneNET麒麟座應用開發之五:獲取加速度傳感器ADXL345數據
命令 多個 data lag 基本 采集 .cn 端口 成了 由於數據采集站基本都安裝在野外或者樓頂,安裝位置以及震動對檢測數據的準確性有一定影響。所以想要有一個位置狀態數據,正好發現麒麟作上有ADXL345,這樣一個數字輸出的加速度傳感器。如圖中紅框所示: 1、ADXL
UVM序列篇之二:sequence和item(上)
技術 一點 目標 idt 需要 開始 掛載 ron 前行 無論是自駕item,穿過sequencer交通站,通往終點driver,還是坐上sequence的大巴,一路沿途觀光,最終跟隨導遊停靠到風景點driver,在介紹如何駕駛item和sequence,遵守什麽交規,最終
Horizon7.1部署之二:Horizon Composer服務器安裝
vmware horizon composerHorizon Composer是個可選服務,如果計劃部署鏈接克隆桌面池(可以節省90%磁盤利用率),則需要安裝。我在windows2016上部署的Sql Server2016,ip是X.X.X.2,並在建立一個名為Horizon Composer的數據庫,防火墻
【2017-07-01】Linux應用開發工程師面試問題記錄之二:關於結構體的大小及內存對齊問題
偶數 而且 strong span net 但是 開發 f11 flag Tencent後臺服務器開發有一道題是計算一個結構體的sizeof的大小: struct strData { int m_Int; char m_Char; short m_Short; char