SuperPoint:深度学习特征点+描述子
【原文鏈接】:https://www.vincentqin.tech/posts/superpoint/
本文出自近幾年備受矚目的創(chuàng)業(yè)公司MagicLeap,發(fā)表在CVPR 2018,一作Daniel DeTone,[paper],[slides],[code]。
這篇文章設(shè)計(jì)了一種自監(jiān)督網(wǎng)絡(luò)框架,能夠同時(shí)提取特征點(diǎn)的位置以及描述子。相比于patch-based方法,本文提出的算法能夠在原始圖像提取到像素級精度的特征點(diǎn)的位置及其描述子。
本文提出了一種單應(yīng)性適應(yīng)(Homographic Adaptation)的策略以增強(qiáng)特征點(diǎn)的復(fù)檢率以及跨域的實(shí)用性(這里跨域指的是synthetic-to-real的能力,網(wǎng)絡(luò)模型在虛擬數(shù)據(jù)集上訓(xùn)練完成,同樣也可以在真實(shí)場景下表現(xiàn)優(yōu)異的能力)。
介紹
諸多應(yīng)用(諸如SLAM/SfM/相機(jī)標(biāo)定/立體匹配)的首要一步就是特征點(diǎn)提取,這里的特征點(diǎn)指的是能夠在不同光照&不同視角下都能夠穩(wěn)定且可重復(fù)檢測的2D圖像點(diǎn)位置。
基于CNN的算法幾乎在以圖像作為輸入的所有領(lǐng)域表現(xiàn)出相比于人類特征工程更加優(yōu)秀的表達(dá)能力。目前已經(jīng)有一些工作做類似的任務(wù),例如人體位姿估計(jì),目標(biāo)檢測以及室內(nèi)布局估計(jì)等。這些算法以通常以大量的人工標(biāo)注作為GT,這些精心設(shè)計(jì)的網(wǎng)絡(luò)用來訓(xùn)練以得到人體上的角點(diǎn),例如嘴唇的邊緣點(diǎn)亦或人體的關(guān)節(jié)點(diǎn),但是這里的問題是這里的點(diǎn)實(shí)際是ill-defined(我的理解是,這些點(diǎn)有可能是特征點(diǎn),但僅僅是一個(gè)大概的位置,是特征點(diǎn)的子集,并沒有真正的把特征點(diǎn)的概念定義清楚)。
本文采用了非人工監(jiān)督的方法提取真實(shí)場景的特征點(diǎn)。本文設(shè)計(jì)了一個(gè)由特征點(diǎn)檢測器監(jiān)督的具有偽真值數(shù)據(jù)集,而非是大量的人工標(biāo)記。為了得到偽真值,本文首先在大量的虛擬數(shù)據(jù)集上訓(xùn)練了一個(gè)全卷積網(wǎng)絡(luò)(FCNN),這些虛擬數(shù)據(jù)集由一些基本圖形組成,例如有線段、三角形、矩形和立方體等,這些基本圖形具有沒有爭議的特征點(diǎn)位置,文中稱這些特征點(diǎn)為MagicPoint,這個(gè)pre-trained的檢測器就是MagicPoint檢測器。這些MagicPoint在虛擬場景的中檢測特征點(diǎn)的性能明顯優(yōu)于傳統(tǒng)方式,但是在真實(shí)的復(fù)雜場景中表現(xiàn)不佳,此時(shí)作者提出了一種多尺度多變換的方法Homographic Adaptation。對于輸入圖像而言,Homographic Adaptation通過對圖像進(jìn)行多次不同的尺度/角度變換來幫助網(wǎng)絡(luò)能夠在不同視角不同尺度觀測到特征點(diǎn)。
綜上:SuperPoint = MagicPoint+Homographic Adaptation
算法優(yōu)劣對比
- 基于圖像塊的算法導(dǎo)致特征點(diǎn)位置精度不夠準(zhǔn)確;
- 特征點(diǎn)與描述子分開進(jìn)行訓(xùn)練導(dǎo)致運(yùn)算資源的浪費(fèi),網(wǎng)絡(luò)不夠精簡,實(shí)時(shí)性不足;或者僅僅訓(xùn)練特征點(diǎn)或者描述子的一種,不能用同一個(gè)網(wǎng)絡(luò)進(jìn)行聯(lián)合訓(xùn)練;
網(wǎng)絡(luò)結(jié)構(gòu)
上圖可見特征點(diǎn)檢測器以及描述子網(wǎng)絡(luò)共享一個(gè)單一的前向encoder,只是在decoder時(shí)采用了不同的結(jié)構(gòu),根據(jù)任務(wù)的不同學(xué)習(xí)不同的網(wǎng)絡(luò)參數(shù)。這也是本框架與其他網(wǎng)絡(luò)的不同之處:其他網(wǎng)絡(luò)采用的是先訓(xùn)練好特征點(diǎn)檢測網(wǎng)絡(luò),然后再去進(jìn)行對特征點(diǎn)描述網(wǎng)絡(luò)進(jìn)行訓(xùn)練。
網(wǎng)絡(luò)共分成以下4個(gè)主要部分,在此進(jìn)行詳述:
1. Shared Encoder 共享的編碼網(wǎng)絡(luò)
從上圖可以看到,整體而言,本質(zhì)上有兩個(gè)網(wǎng)絡(luò),只是前半部分共享了一部分而已。本文利用了VGG-style的encoder以用于降低圖像尺寸,encoder包括卷積層,max-pooling層,以及非線性激活層。通過3個(gè)max-pooling層將圖像的尺寸變成Hc=H/8H_c = H/8Hc?=H/8和Hc=H/8H_c = H/8Hc?=H/8,經(jīng)過encoder之后,圖像由I∈RH×WI \in \mathcal{R}^{H \times W}I∈RH×W變?yōu)閺埩?span id="ze8trgl8bvbq" class="katex--inline">B∈RHc×Wc×F\mathcal{B} \in \mathbb{R}^{H_c \times W_c \times F}B∈RHc?×Wc?×F
2. Interest Point Decoder
這里介紹的是特征點(diǎn)的解碼端。每個(gè)像素的經(jīng)過該解碼器的輸出是該像素是特征點(diǎn)的概率(probability of “point-ness”)。
通常而言,我們可以通過反卷積得到上采樣的圖像,但是這種操作會導(dǎo)致計(jì)算量的驟增以及會引入一種“checkerboard artifacts”。因此本文設(shè)計(jì)了一種帶有“特定解碼器”(這種解碼器沒有參數(shù))的特征點(diǎn)檢測頭以減小模型計(jì)算量(子像素卷積)。
例如:輸入張量的維度是RHc×Wc×65\mathbb{R}^{H_c \times W_c \times 65}RHc?×Wc?×65,輸出維度RH×W\mathbb{R}^{H \times W}RH×W,即圖像的尺寸。這里的65表示原圖8×88 \times 88×8的局部區(qū)域,加上一個(gè)非特征點(diǎn)dustbin。通過在channel維度上做softmax,非特征點(diǎn)dustbin會被刪除,同時(shí)會做一步圖像的reshape:RHc×Wc×64?RH×W\mathbb{R}^{H_c \times W_c \times 64} \Rightarrow \mathbb{R}^{H \times W}RHc?×Wc?×64?RH×W 。(這就是**子像素卷積**的意思,俗稱像素洗牌)
拋出特征點(diǎn)解碼端部分代碼:
# Compute the dense keypoint scores cPa = self.relu(self.convPa(x)) scores = self.convPb(cPa) # DIM: N x 65 x H/8 x W/8 scores = torch.nn.functional.softmax(scores, 1)[:, :-1] # DIM: N x 64 x H/8 x W/8 b, _, h, w = scores.shape scores = scores.permute(0, 2, 3, 1).reshape(b, h, w, 8, 8) # DIM: N x H/8 x W/8 x 8 x 8 scores = scores.permute(0, 1, 3, 2, 4).reshape(b, h*8, w*8) # DIM: N x H x W這個(gè)過程看似比較繁瑣,但是這其實(shí)就是一個(gè)由depth to space的過程,以N = 1為例,上述過程如下圖所示:
上圖中所示的3個(gè)藍(lán)色小塊的就是對應(yīng)的一個(gè)cell經(jīng)過depth to space后得到的,易知其尺寸是8×88 \times 88×8。
注意 :這里解釋一下為何此作者設(shè)置選擇增加一個(gè)dustbin通道,以及為何先進(jìn)行softmax再進(jìn)行slice操作,先進(jìn)行slice再進(jìn)行softmax是否可行?(scores = torch.nn.functional.softmax(scores, 1)[:, :-1])
之所以要設(shè)置65個(gè)通道,這是因?yàn)樗惴ㄒ獞?yīng)對不存在特征點(diǎn)的情況。注意到之后的一步中使用了softmax,也就是說沿著通道維度把各個(gè)數(shù)值通過運(yùn)算后加和為1。如果沒有Dustbin通道,這里就會產(chǎn)生一個(gè)問題:若該cell處沒有特征點(diǎn),此時(shí)經(jīng)過softmax后,每個(gè)通道上的響應(yīng)就會出現(xiàn)受到噪聲干擾造成異常隨機(jī),在隨后的特征點(diǎn)選擇一步中會將非特征點(diǎn)判定為特征,這個(gè)過程由下圖左圖所示。在添加Dustbin之后,在沒有特征的情況下,只有在Dustbin通道的響應(yīng)值很大,在后續(xù)的特征點(diǎn)判斷階段,此時(shí)該圖像塊的響應(yīng)都很小,會成功判定為無特征點(diǎn),這個(gè)過程由下圖右圖所示。
上述過程中得到的scores就是圖像上特征點(diǎn)的概率(或者叫做特征響應(yīng),后文中響應(yīng)值即表示概率值),概率越大,該點(diǎn)越有可能是特征點(diǎn)。之后作者進(jìn)行了一步nms,即非極大值抑制(simple_nms的實(shí)現(xiàn)見文末),隨后選擇響應(yīng)值較大的位置作為特征點(diǎn)。
scores = simple_nms(scores, self.config['nms_radius']) keypoints = [ torch.nonzero(s > self.config['keypoint_threshold']) for s in scores]nms的效果如下,左圖是未使用nms時(shí)score的樣子,響應(yīng)值極大的位置周圍也聚集著響應(yīng)較大的點(diǎn),如果不進(jìn)行nms,特征點(diǎn)將會很集中;右圖是進(jìn)行nms操作后的score,響應(yīng)值極大的位置周圍的響應(yīng)為0。
nms前后對應(yīng)的特征點(diǎn)的位置如下所示,可見nms對于避免特征點(diǎn)位置過于集中起到了比較大的作用。
熟悉SuperPoint的同學(xué)應(yīng)該注意到了,Daniel在CVPR 2018公開的實(shí)現(xiàn)中nms在特征點(diǎn)提取之后,而Sarlin于CVPR 2020年公開SuperGlue的同時(shí)對SuperPoint進(jìn)行了重構(gòu),后者在score上進(jìn)行nms,這兩種實(shí)現(xiàn)上存在一些差異。
下面給出的是Daniel在CVPR 2018開源的SuperPoint推理代碼節(jié)選。
nodust = nodust.transpose(1, 2, 0) heatmap = np.reshape(nodust, [Hc, Wc, self.cell, self.cell]) heatmap = np.transpose(heatmap, [0, 2, 1, 3]) heatmap = np.reshape(heatmap, [Hc*self.cell, Wc*self.cell]) xs, ys = np.where(heatmap >= self.conf_thresh) # Confidence threshold. if len(xs) == 0:return np.zeros((3, 0)), None, None pts = np.zeros((3, len(xs))) # Populate point data sized 3xN. pts[0, :] = ys pts[1, :] = xs pts[2, :] = heatmap[xs, ys] pts, _ = self.nms_fast(pts, H, W, dist_thresh=self.nms_dist) # Apply NMS.但Sarlin為何要這么做呢?本人在Github上提交了一個(gè)#issue112咨詢了Sarlin,如下是他的回復(fù),總結(jié)起來就重構(gòu)后的代碼優(yōu)勢有兩點(diǎn):1. 更加快速,能夠在GPU上運(yùn)行,常數(shù)級時(shí)間復(fù)雜度;2. 支持多圖像輸入。
3. Descriptor Decoder
首先利用類似于UCN的網(wǎng)絡(luò)得到一個(gè)半稠密的描述子(此處參考文獻(xiàn)UCN),這樣可以減少算法訓(xùn)練內(nèi)存開銷同時(shí)減少算法運(yùn)行時(shí)間。之后通過雙三次多項(xiàng)式插值得到其余描述,然后通過L2-normalizes歸一化描述子得到統(tǒng)一的長度描述。特征維度由D∈RHc×Wc×D\mathcal{D} \in \mathbb{R}^{H_c \times W_c \times D}D∈RHc?×Wc?×D變?yōu)?span id="ze8trgl8bvbq" class="katex--inline">RH×W×D\mathbb{R}^{H\times W \times D}RH×W×D 。
由特征點(diǎn)得到其描述子的過程文中沒有細(xì)講,看了一下源代碼就明白了。其實(shí)該過程主要用了一個(gè)函數(shù)即grid_sample,畫了一個(gè)草圖作為解釋。
- 圖像尺寸歸一化:首先對圖像的尺寸進(jìn)行歸一化,(-1,-1)表示原來圖像的(0,0)位置,(1,1)表示原來圖像的(H-1,W-1)位置,這樣一來,特征點(diǎn)的位置也被歸一化到了相應(yīng)的位置。
- 構(gòu)建grid:將歸一化后的特征點(diǎn)羅列起來,構(gòu)成一個(gè)尺度為1*1*K*2的張量,其中K表示特征數(shù)量,2分別表示xy坐標(biāo)。
- 特征點(diǎn)位置反歸一化:根據(jù)輸入張量的H與W對grid(1,1,0,:)(表示第一個(gè)特征點(diǎn),其余特征點(diǎn)類似)進(jìn)行反歸一化,其實(shí)就是按照比例進(jìn)行縮放+平移,得到反歸一化特征點(diǎn)在張量某個(gè)slice(通道)上的位置;但是這個(gè)位置可能并非為整像素,此時(shí)要對其進(jìn)行雙線性插值補(bǔ)齊,然后其余slice按照同樣的方式進(jìn)行雙線性插值。注:代碼中實(shí)際的就是雙線性插值,并非文中講的雙三次插值;
- 輸出維度:1*C*1*K。
描述子解碼部分代碼如下:
# Compute the dense descriptors cDa = self.relu(self.convDa(x)) descriptors = self.convDb(cDa) # DIM: N x 256 x H/8 x W/8 descriptors = torch.nn.functional.normalize(descriptors, p=2, dim=1) #按通道進(jìn)行歸一化# Extract descriptors # 根據(jù)特征點(diǎn)位置插值得到描述子, DIM: N x 256 x Mdescriptors = [sample_descriptors(k[None], d[None], 8)[0]for k, d in zip(keypoints, descriptors)]4. 誤差構(gòu)建
L(X,X′,D,D′;Y,Y′,S)=Lp(X,Y)+Lp(X′,Y′)+λLd(D,D′,S)\begin{array}{l}{\mathcal{L}\left(\mathcal{X}, \mathcal{X}^{\prime}, \mathcal{D}, \mathcal{D}^{\prime} ; Y, Y^{\prime}, S\right)=} \\ {\qquad \mathcal{L}_{p}(\mathcal{X}, Y)+\mathcal{L}_{p}\left(\mathcal{X}^{\prime}, Y^{\prime}\right)+\lambda \mathcal{L}_ze8trgl8bvbq\left(\mathcal{D}, \mathcal{D}^{\prime}, S\right)}\end{array} L(X,X′,D,D′;Y,Y′,S)=Lp?(X,Y)+Lp?(X′,Y′)+λLd?(D,D′,S)?
可見損失函數(shù)由兩項(xiàng)組成,其中一項(xiàng)為特征點(diǎn)檢測lossLp\mathcal{L}_{p}Lp? ,另外一項(xiàng)是描述子的lossLd\mathcal{L}_ze8trgl8bvbqLd?。
對于檢測項(xiàng)loss,此時(shí)采用了交叉熵?fù)p失函數(shù):
Lp(X,Y)=1HcWc∑h=1w=1Hc,Wclp(xhw;yhw)\mathcal{L}_{p}(\mathcal{X}, Y)=\frac{1}{H_{c} W_{c}} \sum_{h=1 \atop w=1}^{H_{c}, W_{c}} l_{p}\left(\mathbf{x}_{h w} ; y_{h w}\right) Lp?(X,Y)=Hc?Wc?1?w=1h=1?∑Hc?,Wc??lp?(xhw?;yhw?)
其中:
lp(xhw;y)=?log?(exp?(xhwy)∑k=165exp?(xhwk))l_{p}\left(\mathbf{x}_{h w} ; y\right)=-\log \left(\frac{\exp \left(\mathbf{x}_{h w y}\right)}{\sum_{k=1}^{65} \exp \left(\mathbf{x}_{h w k}\right)}\right) lp?(xhw?;y)=?log(∑k=165?exp(xhwk?)exp(xhwy?)?)
此時(shí)類似于一個(gè)多分類任務(wù),log?\loglog 運(yùn)算內(nèi)部就是cell中元素為特征點(diǎn)的概率(即softmax之后的值),即樣本xhw\mathbf{x}_{hw}xhw?屬于特征的概率。這是一個(gè)2D location classifier,每個(gè)8x8的范圍內(nèi)只能有一個(gè)特征點(diǎn),即圖像中最多有$H \times W / 64 $個(gè)SuperPoint特征點(diǎn)。
描述子的損失函數(shù):
Ld(D,D′,S)=1(HcWc)2∑h=1w=1Hc,Wc∑h′=1w′=1Hc,Wcld(dhw,dh′w′′;shwh′w′)\mathcal{L}_ze8trgl8bvbq\left(\mathcal{D}, \mathcal{D}^{\prime}, S\right)=\frac{1}{\left(H_{c} W_{c}\right)^{2}} \sum_{h=1 \atop w=1}^{H_{c}, W_{c}} \sum_{h^{\prime}=1 \atop w^{\prime}=1}^{H_{c}, W_{c}} l_ze8trgl8bvbq\left(\mathbfze8trgl8bvbq_{h w}, \mathbfze8trgl8bvbq_{h^{\prime} w^{\prime}}^{\prime} ; s_{h w h^{\prime} w^{\prime}}\right) Ld?(D,D′,S)=(Hc?Wc?)21?w=1h=1?∑Hc?,Wc??w′=1h′=1?∑Hc?,Wc??ld?(dhw?,dh′w′′?;shwh′w′?)
其中ldl_ze8trgl8bvbqld?為Hinge-loss(合頁損失函數(shù),用于SVM,如支持向量的軟間隔,可以保證最后解的稀疏性);
ld(d,d′;s)=λd?s?max?(0,mp?dTd′)+(1?s)?max?(0,dTd′?mn)l_ze8trgl8bvbq\left(\mathbfze8trgl8bvbq, \mathbfze8trgl8bvbq^{\prime} ; s\right)=\lambda_ze8trgl8bvbq * s * \max \left(0, m_{p}-\mathbfze8trgl8bvbq^{T} \mathbfze8trgl8bvbq^{\prime}\right)+(1-s) * \max \left(0, \mathbfze8trgl8bvbq^{T} \mathbfze8trgl8bvbq^{\prime}-m_{n}\right) ld?(d,d′;s)=λd??s?max(0,mp??dTd′)+(1?s)?max(0,dTd′?mn?)
同時(shí)指示函數(shù)為shwh′w′s_{h w h^{\prime} w^{\prime}}shwh′w′?,SSS表示所有正確匹配對集合:
shwh′w′={1,if?∥Hphw^?ph′w′∥≤80,otherwise?s_{h w h^{\prime} w^{\prime}}=\left\{\begin{array}{ll}{1,} & {\text { if }\left\|\widehat{\mathcal{H} \mathbf{p}_{h w}}-\mathbf{p}_{h^{\prime} w^{\prime}}\right\| \leq 8} \\ {0,} & {\text { otherwise }}\end{array}\right. shwh′w′?={1,0,??if?∥∥∥?Hphw???ph′w′?∥∥∥?≤8?otherwise??
上式中的p\mathbf{p}p是cell的中心點(diǎn)坐標(biāo),Hp\mathcal{H} \mathbf{p}Hp與p′\mathbf{p}^{\prime}p′的距離小于8個(gè)pixel的認(rèn)為是正確的匹配,這其實(shí)對應(yīng)于cell上的的1個(gè)pixel。
讓我們仔細(xì)看一下這個(gè)損失函數(shù),這其實(shí)是一個(gè)Double margin Siamese loss。當(dāng)正例描述子余弦相似度dTd′\mathbfze8trgl8bvbq^T\mathbfze8trgl8bvbq^{\prime}dTd′大于mpm_pmp?時(shí),此時(shí)不需要懲罰;但如果該相似度較小時(shí),此時(shí)就要懲罰了;負(fù)樣本時(shí)我們的目標(biāo)是讓dTd′\mathbfze8trgl8bvbq^T\mathbfze8trgl8bvbq^{\prime}dTd′變小,但網(wǎng)絡(luò)性能不佳時(shí)可能這個(gè)值很大(大于上式中的mnm_nmn?),此時(shí)要懲罰這種現(xiàn)象,網(wǎng)絡(luò)權(quán)重經(jīng)過調(diào)整后使得該loss降低,對應(yīng)的描述子相似度降低;
讓我們再看一下這個(gè)所謂的Double margin Siamese loss,上圖示中的連線表示distdistdist函數(shù)。想象一下,我們希望正例𝑑𝑖𝑠𝑡(𝑑,𝑑′)𝑑𝑖𝑠𝑡(𝑑,𝑑^{\prime})dist(d,d′)越小越好,如果𝑑𝑖𝑠𝑡(𝑑,𝑑′)>𝑚𝑝1𝑑𝑖𝑠𝑡(𝑑,𝑑^{\prime})>𝑚_{𝑝1}dist(d,d′)>mp1?,網(wǎng)絡(luò)要懲罰這種現(xiàn)象,會使得𝑑𝑖𝑠𝑡(𝑑,𝑑′)<𝑚𝑝1𝑑𝑖𝑠𝑡(𝑑,𝑑^{\prime})<𝑚_{𝑝1}dist(d,d′)<mp1?.相應(yīng)的的我們希望負(fù)例𝑑𝑖𝑠𝑡(𝑑,𝑑′)𝑑𝑖𝑠𝑡(𝑑,𝑑^{\prime})dist(d,d′)越大越好,如果𝑑𝑖𝑠𝑡(𝑑,𝑑′)<𝑚𝑛1𝑑𝑖𝑠𝑡(𝑑,𝑑^{\prime})<𝑚_{𝑛1}dist(d,d′)<mn1?,網(wǎng)絡(luò)要懲罰這種現(xiàn)象,最終會使得𝑑𝑖𝑠𝑡(𝑑,𝑑′)>𝑚𝑛1𝑑𝑖𝑠𝑡(𝑑,𝑑^{\prime})>𝑚_{𝑛1}dist(d,d′)>mn1?。
網(wǎng)絡(luò)訓(xùn)練
本文一共設(shè)計(jì)了兩個(gè)網(wǎng)絡(luò),一個(gè)是BaseDetector,用于檢測角點(diǎn)(注意,此處提取的并不是最終輸出的特征點(diǎn),可以理解為候選的特征點(diǎn)),另一個(gè)是SuperPoint網(wǎng)絡(luò),輸出特征點(diǎn)和描述子。
網(wǎng)絡(luò)的訓(xùn)練共分為三個(gè)步驟:
這里需要注意的是,聯(lián)合訓(xùn)練使用的單應(yīng)變換相較于Homographic Adaptation中設(shè)置的單應(yīng)變換更加嚴(yán)格,即沒有特別離譜的in-plane的旋轉(zhuǎn)。作者在論文中提到,這是由于在HPatches數(shù)據(jù)集中沒有這樣的數(shù)據(jù)才進(jìn)行這種設(shè)置,原話是“we avoid sampling extreme in-plane rotations as they are rarely seen in HPatches”,這也是為什么SuperPoint無法有效地應(yīng)對in-plane rotations的原因。
預(yù)訓(xùn)練Magic Point
此處參考作者之前發(fā)表的一篇論文**[Toward Geometric Deep SLAM]**,其實(shí)就是MagicPoint,它僅僅保留了SuperPoint的主干網(wǎng)絡(luò)以及特征點(diǎn)解碼端,即SuperPoint的檢測端就是MagicPoint。
Homographic Adaptation
算法在虛擬數(shù)據(jù)集上表現(xiàn)極其優(yōu)秀,但是在真實(shí)場景下表示沒有達(dá)到預(yù)期,此時(shí)本文進(jìn)行了Homographic Adaptation。
作者使用的數(shù)據(jù)集是MS-COCO,為了使網(wǎng)絡(luò)的泛化能力更強(qiáng),本文不僅使用原始了原始圖片,而且對每張圖片進(jìn)行隨機(jī)的旋轉(zhuǎn)和縮放形成新的圖片,新的圖片也被用來進(jìn)行識別。這一步其實(shí)就類似于訓(xùn)練里常用的數(shù)據(jù)增強(qiáng)。經(jīng)過一系列的單應(yīng)變換之后特征點(diǎn)的復(fù)檢率以及普適性得以增強(qiáng)。值得注意的是,在實(shí)際訓(xùn)練時(shí),這里采用了迭代使用單應(yīng)變換的方式,例如使用優(yōu)化后的特征點(diǎn)檢測器重新進(jìn)行單應(yīng)變換進(jìn)行訓(xùn)練,然后又可以得到更新后的檢測器,如此迭代優(yōu)化,這就是所謂的self-supervisd。
最后的關(guān)鍵點(diǎn)檢測器,即F^(I;fθ)\hat{F}\left(I ; f_{\theta}\right)F^(I;fθ?),可以表示為再所有隨機(jī)單應(yīng)變換/反變換的聚合:
F^(I;fθ)=1Nh∑i=1NhHi?1fθ(Hi(I))\hat{F}\left(I ; f_{\theta}\right)=\frac{1}{N_{h}} \sum_{i=1}^{N_{h}} \mathcal{H}_{i}^{-1} f_{\theta}\left(\mathcal{H}_{i}(I)\right) F^(I;fθ?)=Nh?1?i=1∑Nh??Hi?1?fθ?(Hi?(I))
構(gòu)建殘差,迭代優(yōu)化描述子以及檢測器
利用上面網(wǎng)絡(luò)得到的關(guān)鍵點(diǎn)位置以及描述子表示構(gòu)建殘差,利用ADAM進(jìn)行優(yōu)化。
實(shí)驗(yàn)結(jié)果
總結(jié)
未來工作:
作者最后提到,他相信該網(wǎng)絡(luò)能夠解決SLAM或者SfM領(lǐng)域的數(shù)據(jù)關(guān)聯(lián)*,并且*learning-based前端可以使得諸如機(jī)器人或者AR等應(yīng)用獲得更加魯棒。
代碼
以下給出的是Sarlin在SuperGlue代碼中重構(gòu)的SuperPoint前向推理代碼,與Daniel于2018年的原始版本有些差異。不過Sarlin的版本與原版結(jié)果幾乎一致,另外增加多batch的支持,執(zhí)行效率更高。
# %BANNER_BEGIN% # --------------------------------------------------------------------- # %COPYRIGHT_BEGIN% # # Magic Leap, Inc. ("COMPANY") CONFIDENTIAL # # Unpublished Copyright (c) 2020 # Magic Leap, Inc., All Rights Reserved. # # NOTICE: All information contained herein is, and remains the property # of COMPANY. The intellectual and technical concepts contained herein # are proprietary to COMPANY and may be covered by U.S. and Foreign # Patents, patents in process, and are protected by trade secret or # copyright law. Dissemination of this information or reproduction of # this material is strictly forbidden unless prior written permission is # obtained from COMPANY. Access to the source code contained herein is # hereby forbidden to anyone except current COMPANY employees, managers # or contractors who have executed Confidentiality and Non-disclosure # agreements explicitly covering such access. # # The copyright notice above does not evidence any actual or intended # publication or disclosure of this source code, which includes # information that is confidential and/or proprietary, and is a trade # secret, of COMPANY. ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, # PUBLIC PERFORMANCE, OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS # SOURCE CODE WITHOUT THE EXPRESS WRITTEN CONSENT OF COMPANY IS # STRICTLY PROHIBITED, AND IN VIOLATION OF APPLICABLE LAWS AND # INTERNATIONAL TREATIES. THE RECEIPT OR POSSESSION OF THIS SOURCE # CODE AND/OR RELATED INFORMATION DOES NOT CONVEY OR IMPLY ANY RIGHTS # TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, OR TO MANUFACTURE, # USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. # # %COPYRIGHT_END% # ---------------------------------------------------------------------- # %AUTHORS_BEGIN% # # Originating Authors: Paul-Edouard Sarlin # # %AUTHORS_END% # --------------------------------------------------------------------*/ # %BANNER_END%from pathlib import Path import torch from torch import nndef simple_nms(scores, nms_radius: int):""" Fast Non-maximum suppression to remove nearby points """assert(nms_radius >= 0)def max_pool(x):return torch.nn.functional.max_pool2d(x, kernel_size=nms_radius*2+1, stride=1, padding=nms_radius)zeros = torch.zeros_like(scores)max_mask = scores == max_pool(scores)for _ in range(2):supp_mask = max_pool(max_mask.float()) > 0supp_scores = torch.where(supp_mask, zeros, scores)new_max_mask = supp_scores == max_pool(supp_scores)max_mask = max_mask | (new_max_mask & (~supp_mask))return torch.where(max_mask, scores, zeros)def remove_borders(keypoints, scores, border: int, height: int, width: int):""" Removes keypoints too close to the border """mask_h = (keypoints[:, 0] >= border) & (keypoints[:, 0] < (height - border))mask_w = (keypoints[:, 1] >= border) & (keypoints[:, 1] < (width - border))mask = mask_h & mask_wreturn keypoints[mask], scores[mask]def top_k_keypoints(keypoints, scores, k: int):if k >= len(keypoints):return keypoints, scoresscores, indices = torch.topk(scores, k, dim=0)return keypoints[indices], scoresdef sample_descriptors(keypoints, descriptors, s: int = 8):""" Interpolate descriptors at keypoint locations """b, c, h, w = descriptors.shapekeypoints = keypoints - s / 2 + 0.5keypoints /= torch.tensor([(w*s - s/2 - 0.5), (h*s - s/2 - 0.5)],).to(keypoints)[None]keypoints = keypoints*2 - 1 # normalize to (-1, 1)args = {'align_corners': True} if torch.__version__ >= '1.3' else {}descriptors = torch.nn.functional.grid_sample(descriptors, keypoints.view(b, 1, -1, 2), mode='bilinear', **args)descriptors = torch.nn.functional.normalize(descriptors.reshape(b, c, -1), p=2, dim=1)return descriptorsclass SuperPoint(nn.Module):"""SuperPoint Convolutional Detector and DescriptorSuperPoint: Self-Supervised Interest Point Detection andDescription. Daniel DeTone, Tomasz Malisiewicz, and AndrewRabinovich. In CVPRW, 2019. https://arxiv.org/abs/1712.07629"""default_config = {'descriptor_dim': 256,'nms_radius': 4,'keypoint_threshold': 0.005,'max_keypoints': -1,'remove_borders': 4,}def __init__(self, config):super().__init__()self.config = {**self.default_config, **config}self.relu = nn.ReLU(inplace=True)self.pool = nn.MaxPool2d(kernel_size=2, stride=2)c1, c2, c3, c4, c5 = 64, 64, 128, 128, 256self.conv1a = nn.Conv2d(1, c1, kernel_size=3, stride=1, padding=1)self.conv1b = nn.Conv2d(c1, c1, kernel_size=3, stride=1, padding=1)self.conv2a = nn.Conv2d(c1, c2, kernel_size=3, stride=1, padding=1)self.conv2b = nn.Conv2d(c2, c2, kernel_size=3, stride=1, padding=1)self.conv3a = nn.Conv2d(c2, c3, kernel_size=3, stride=1, padding=1)self.conv3b = nn.Conv2d(c3, c3, kernel_size=3, stride=1, padding=1)self.conv4a = nn.Conv2d(c3, c4, kernel_size=3, stride=1, padding=1)self.conv4b = nn.Conv2d(c4, c4, kernel_size=3, stride=1, padding=1)self.convPa = nn.Conv2d(c4, c5, kernel_size=3, stride=1, padding=1)self.convPb = nn.Conv2d(c5, 65, kernel_size=1, stride=1, padding=0)self.convDa = nn.Conv2d(c4, c5, kernel_size=3, stride=1, padding=1)self.convDb = nn.Conv2d(c5, self.config['descriptor_dim'],kernel_size=1, stride=1, padding=0)path = Path(__file__).parent / 'weights/superpoint_v1.pth'self.load_state_dict(torch.load(str(path)))mk = self.config['max_keypoints']if mk == 0 or mk < -1:raise ValueError('\"max_keypoints\" must be positive or \"-1\"')print('Loaded SuperPoint model')def forward(self, data):""" Compute keypoints, scores, descriptors for image """# Shared Encoderx = self.relu(self.conv1a(data['image']))x = self.relu(self.conv1b(x))x = self.pool(x)x = self.relu(self.conv2a(x))x = self.relu(self.conv2b(x))x = self.pool(x)x = self.relu(self.conv3a(x))x = self.relu(self.conv3b(x))x = self.pool(x)x = self.relu(self.conv4a(x))x = self.relu(self.conv4b(x))# Compute the dense keypoint scorescPa = self.relu(self.convPa(x))scores = self.convPb(cPa)scores = torch.nn.functional.softmax(scores, 1)[:, :-1]b, _, h, w = scores.shapescores = scores.permute(0, 2, 3, 1).reshape(b, h, w, 8, 8)scores = scores.permute(0, 1, 3, 2, 4).reshape(b, h*8, w*8)scores = simple_nms(scores, self.config['nms_radius'])# Extract keypointskeypoints = [torch.nonzero(s > self.config['keypoint_threshold'])for s in scores]scores = [s[tuple(k.t())] for s, k in zip(scores, keypoints)]# Discard keypoints near the image borderskeypoints, scores = list(zip(*[remove_borders(k, s, self.config['remove_borders'], h*8, w*8)for k, s in zip(keypoints, scores)]))# Keep the k keypoints with highest scoreif self.config['max_keypoints'] >= 0:keypoints, scores = list(zip(*[top_k_keypoints(k, s, self.config['max_keypoints'])for k, s in zip(keypoints, scores)]))# Convert (h, w) to (x, y)keypoints = [torch.flip(k, [1]).float() for k in keypoints]# Compute the dense descriptorscDa = self.relu(self.convDa(x))descriptors = self.convDb(cDa)descriptors = torch.nn.functional.normalize(descriptors, p=2, dim=1)# Extract descriptorsdescriptors = [sample_descriptors(k[None], d[None], 8)[0]for k, d in zip(keypoints, descriptors)]return {'keypoints': keypoints,'scores': scores,'descriptors': descriptors,}歡迎大家關(guān)注我的公眾號,最新文章第一時(shí)間推送。
總結(jié)
以上是生活随笔為你收集整理的SuperPoint:深度学习特征点+描述子的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JAVA算法:哈希查找
- 下一篇: sqoop报错以及解决:Access d