轉載:https://blog.csdn.net/Murdock_C/article/details/93748001 openpifpaf源碼解析
轉載:https://zhuanlan.zhihu.com/p/93896207 深度解析pifpaf
這是CVPR2019上面的一篇基於Bottom-Up方法的姿態估計文章,標題是:PifPaf: Composite Fields for Human Pose Estimation。Paper鏈接為:
開源代碼在:
摘要
我們提出了一種新的自下而上的多人2D人體姿勢估計方法,該方法特別適合於城市機動性,例如無人駕駛汽車和送貨機器人。 新方法PifPaf使用“部件強度場”(Part Intensity Field,PIF)來定位身體部位,並使用“部件關聯場”(Part Association Field,PAF)將身體部位彼此關聯以形成完整的人體姿勢。 由於(i)我們新的復合場PAF編碼細粒度信息,以及(ii)選擇拉普拉斯損失進行回歸,並結合了不確定性,因此我們的方法在低分辨率和擁擠,混亂和遮擋的場景中優於以前的方法。 我們的體系結構基於全卷積,單發,box-free設計。 我們的方法在標准COCO關鍵點任務上與現有的state-of-the-art 自下而上方法效果相同,並在運輸領域的改進的COCO關鍵點任務上產生了state-of-the-art結果。
1、介紹
在流行的數據收集運動的推動下,在“野外”估計人的姿勢方面已經取得了巨大進展[1,27]。 但是,當涉及到諸如自動駕駛汽車或社交機器人等“運輸領域”時,我們仍然遠遠不能達到可接受的准確性水平。 雖然姿勢估計不是最終目標,但它是有效的低維度且可解釋的人類表示形式,可以盡早地為自主導航系統檢測關鍵動作(例如,檢測打算過馬路的行人)。 因此,可以檢測到人體姿勢越遠,自動駕駛系統越安全。 這直接與提高感知人體姿勢所需的最低分辨率的限制有關。
在這項工作中,我們解決了給定單個輸入圖像的多人2D人體姿勢估計問題。 我們專門解決了如圖1所示的自主導航設置中出現的挑戰:(i)對人的分辨率有限的寬視角,即30-90像素的高度,以及(ii)行人彼此遮擋的高密度人群 。 當然,我們的目標是提高召回率和准確性。

【圖1、我們要估算在擁擠場景中運行自主導航系統的交通領域中的人類2D姿勢。 人體占據圖像的一小部分,並且可能部分相互遮擋。 我們用彩色線段顯示PifPaf方法的輸出。】
盡管在深度學習時代之前就已經對姿勢估計進行了研究,但重要的基石是OpenPose [3]的工作,其次是Mask R-CNN [18]。 前者是一種自下而上的方法(在沒有人檢測器的情況下檢測關節),后者是一種自上而下的方法(首先使用人檢測器並在檢測到的邊界框中輸出關節)。 盡管這些方法在足夠高分辨率的圖像上表現出驚人的效果,但它們在有限的分辨率范圍內以及人與人之間相互遮擋的人群中表現不佳。
在本文中,我們建議將姿勢估計中的場概念[3]從標量場和矢量場擴展到復合場。我們介紹了一種具有兩個頭部網絡的新神經網絡架構。對於每個身體部位或關節,一個頭部網絡可預測該關節的置信度分數,精確位置和大小,我們稱其為“部件強度場”(PIF),與[34]中的融合部分置信度圖相似。另一個頭部網絡預測零件之間的關聯,稱為部件關聯場(PAF),它是一種新的復合結構。我們的編碼方案具有在低分辨率激活圖上存儲細粒度信息的能力。精確地回歸關節位置至關重要,我們使用基於拉普拉斯的L1損失[23]而不是vanilla L1損失[18]。我們的實驗表明,我們在低分辨率圖像上的性能優於自下而上和已建立的自上而下的方法,而在高分辨率下卻表現出色。該軟件是開源的,可以在網絡上獲取。
2、相關工作
在過去的幾年中,state-of-the-art姿勢估計方法基於卷積神經網絡[18,3,31,34]。 它們優於基於圖形結構[12、8、9]和可變形零件模型[11]的傳統方法。 深度學習海嘯始於DeepPose [39],DeepPose使用級聯的卷積網絡進行全身姿勢估計。 然后,某些工作代替預測絕對的人體關節位置,而是通過預測每次迭代的錯誤反饋(即校正)來精煉姿態估計[4、17],或使用人體姿態修正網絡來利用輸入和輸出空間之間的依賴性[13]。 。現在正在爭相提出替代性神經網絡架構:從卷積姿勢機[42],堆疊式沙漏網絡[32、28]到遞歸網絡[2]和投票方案,例如[26]。
所有這些用於人體姿勢估計的方法都可以分為自下而上和自上而下的方法。前者首先估計每個人體關節,然后將它們分組以形成唯一的姿勢。后者首先運行人體檢測器,並在檢測到的邊界框中估計人體關節。
Top-down 方法 自上而下方法的示例是PoseNet [35],RMPE [10],CFN [20],Mask R-CNN [18、15],以及最近的CPN [6]和MSRA [44]。 這些方法得益於人體檢測器的進步以及為人們提供的大量標記邊界框。 利用這些數據的能力將人體檢測器的需求變成了優勢。
值得注意的是,Mask R-CNN將關鍵點檢測視為實例細分任務。 在訓練期間,對於每個獨立關鍵點,目標都將轉換為包含單個前景像素的二進制蒙版。 通常,自上而下的方法是有效的,但是當人的邊界框重疊時會遇到困難。
Bottom-up 方法 自下而上的方法包括Pishchulin的DeepCut [37]以及Insafutdinov的DeeperCut [21]的開創性工作。 他們使用整數線性程序解決部件關聯問題,這會導致單個圖像處理時間的增加。 后來的工作加快了預測時間[5],並擴大了跟蹤動物行為的應用[30]。 通過將貪婪解碼器與其他工具如Part Affinity Fields[3], Associative Embedding [31]和PersonLab [34]結合使用, 其他方法可以大大減少預測時間。 最近,MultiPoseNet [24]開發了一種多任務學習架構,該架構結合了對人的檢測,分割和姿勢估計。
在圖像平面的2D姿勢估計之上還建立了其他中間表示,包括3D姿勢估計[29],視頻中的人體姿勢估計[36]和密集姿勢估計[16],這些都可以從改進的2D姿勢估計中獲利。
3、方法
我們方法的目標是估計擁擠圖像中的人體姿勢。 我們解決與低分辨率和部分被遮擋的行人相關的挑戰。 當行人被邊界框碰撞的其他行人遮擋時,自頂向下方法特別困難。 以前的自下而上的方法沒有邊界框,但仍包含用於本地化的粗略特征圖。 我們的方法不受關節空間定位的任何基於網格的約束,並且能夠估計多個相互遮擋的姿勢。
圖2展示了我們的整體模型。 它是一個具有兩個頭部網絡的共享ResNet[19]基礎網絡:一個頭部網絡可預測置信度,精確位置和關節大小,我們將其稱為部件強度場(PIF),另一個頭部網絡可預測部件之間的關聯 ,稱為部件關聯場(PAF)。 我們將我們的方法稱為PifPaf。
在詳細描述每個頭網絡之前,我們先定義我們的場符號。

圖2、模型結構
【輸入是具有三個顏色通道的大小為(H,W)的圖像,用“ x3”表示。 基於神經網絡的編碼器產生具有17×5和19×7通道的PIF和PAF場。 用“ // 2”表示步幅為2的操作。 解碼器是一個程序,可將PIF和PAF場轉換為姿勢估計,每個姿勢包含17個關節。 每個關節均由x和y坐標以及置信度分數表示。】
3.1、場符號
場是用於推斷圖像頂部結構的有用工具。 復合場的概念直接激勵了我們提出的部件關聯場。
我們將使用i, j在空間上枚舉神經網絡的輸出位置,並使用x, y表示實值坐標。 場在域
上用
表示,並且可以具有標量,向量或復合數作為共域(場的值)。 例如,標量場
和向量場
的復合可以表示為{s,
,
},等效於用向量場“覆蓋”置信度圖。
3.2、Part Intensity Fields
部件強度場(PIF)可檢測並精確定位人體部位。 在[35]中引入了置信度圖與回歸點融合以進行關鍵點檢測。 在這里,我們用復合場的語言來概括該技術,並添加一個標度σ作為新的成分來形成我們的PIF場。
PIF具有復合結構。 它們由一個用於表示置信度的標量分量,一個指向特定類型的最接近人體部位的矢量分量和另一個用於關節大小的標量分量組成。 更正式地說,在每個輸出位置(i,j),PIF都會預測置信度c,具有spread b(詳見3.4節)和比例σ的一個向量(x,y),可以寫成
。
PIF的置信度圖非常粗糙。圖3a顯示了示例圖像的左肩置信度圖。為了改善此置信度圖的定位,我們將其與圖3b中所示的PIF的矢量部分融合為高分辨率的置信度圖。我們創建了一個高分辨率的零件置信度圖f(x, y),該圖由未標准化的高斯核N的卷積組成,寬度為pσ,其寬度由零件強度場中經其置信度pc加權的回歸目標得出:

該方程式強調了定位的無網格性質。 關節的空間范圍σ被視為該場的一部分。 圖3c中顯示了一個示例。 生成的高度局部化的關節圖用於為姿勢生成提供種子,並對新提出的關節的位置進行評分。

圖3、可視化左肩的PIF組件
【這是17個復合PIF之一。置信度圖如(a)所示,矢量域如(b)所示。(c)中顯示了融合的置信度,向量和比例分量。】
3.3、Part Association Fields
在人群相互遮擋的擁擠場景中,將關節組合成多個姿勢是具有挑戰性的。在這種情況下,尤其是兩步過程(自上而下的方法)難以解決:首先,他們檢測到人的邊界框,然后嘗試為每個邊界框找到一種關節類型。自下而上的方法沒有邊界框,因此不會遇到沖突的邊界框問題。
我們提出了自下而上的零件關聯字段(PAF),以將關節位置連接到姿勢中。 PAF方案的圖示如圖4所示。在每個輸出位置,PAF都會預測一個置信度,該關聯所連接的兩個部分的兩個向量和兩個寬度b(詳細信息請參見第3.4節)用於回歸的空間精度。 PAF用

表示。 左肩和左臀部之間的關聯的可視化效果如圖5所示。

圖4、feature map網格上PersonLab的mid-range offsets(a)與PAF(b)之間差異的說明。
【藍色圓圈表示關節,置信度以綠色標記。 Mid-range offsets(a)的起點在feature map單元格的中心。 部件關聯場(b)具有其原點的浮點精度。】

圖5、PAF組件的可視化,該組件將左肩與左臀部關節關聯
【這是19個PAF之一。 特征圖的每個位置都是兩個向量的起點,它們指向要關聯的肩膀和臀部。 在(a)中顯示了關聯ac的置信度,在(b)中顯示了ac> 0.5的向量分量。】
這兩個端點都使用回歸進行局部化,因為回歸不會出現在基於網格的方法中,因此不會受到離散化的影響。 這有助於精確地解決附近人的關節位置,並將其解析為不同的標簽。
COCO數據集中有19個針對人體姿態的連接,每個連接都連接兩種類型的關節。 例如,從右膝到右腳踝有關聯。 在特定特征圖位置構造PAF組件的算法包括兩個步驟。 首先,找到確定矢量分量之一的兩種類型中最接近的一種。 第二,GT姿勢決定了另一個表示關聯的向量分量。 第二關節不一定是最接近的關節,並且可以很遠。
在訓練期間,該場的組成部分必須指向應關聯的部分。 與矢量域的x分量始終必須與y分量指向相同的目標類似,PAF場的分量必須指向相同的部件關聯。
3.4、自適應回歸損失
人體姿勢估計算法傾向於與人體姿勢在圖像中可能具有的比例尺的多樣性作斗爭。 雖然大尺度的人體的關節的定位錯誤可能很小,但對於小尺度的人體來說,相同的絕對錯誤可能是主要錯誤。 我們使用L1型損失來訓練回歸輸出。 我們通過使用SmoothL1 [14]或Laplace損失[23]向回歸損失中注入比例依賴來提高網絡的定位能力。
SmoothL1損耗允許圍繞原點調整半徑
,在原點處產生較柔和的漸變。對於一個人體實例,邊界框面積
和
的關鍵點大小 ,
可以被設置成與
比例,我們在表3中進行了研究。
拉普拉斯損失是另一種L1損失,它通過預測的spread b衰減:

它獨立於
和
的任何估計,我們將其用於所有矢量分量。
3.5、Greedy Decoding
解碼是將神經網絡的輸出特征圖轉換為17組坐標的過程,這些坐標可進行人體姿態估計。我們的過程類似於[34]中使用的快速貪婪解碼。
在等式1中定義的高分辨率置信度映射
中,具有最高值的PIF向量為新的姿勢播種。從種子開始,借助PAF字段添加與其他關節的連接。 該算法既快速又貪心。 一旦建立了新關節的連接,該決定即為最終決定。
多個PAF關聯可以在當前關節和下一個關節之間形成連接。給定起始關節的位置
,可以用以下公式計算PAF關聯a的分數s

其中考慮到了此連接ac的置信度,到第一向量位置的距離(使用兩尾Laplace分布概率校准)和第二個向量的目標位置f2的高分辨率部分置信度。為了確認新關節的建議位置,我們進行反向匹配。重復該過程,直到獲得完整姿勢為止。我們在關鍵點級別上應用了非最大抑制,如[34]。抑制半徑是動態的,並且基於PIF字段的預測比例分量。無論是在訓練還是測試期間,我們都不對任何場進行refine。
代碼解析過程:
openpifpaf 的decode過程:
- 網絡的輸出:
pif, 原始的輸出共有4個, 分別為:
joint_intensity_fields, shape 為 [17, output_h, output_w]. 其實就是輸出的每個位置上的confidence map, 17表示channel數, 在pose檢測里面表示總共有多少個關鍵點需要檢測.
joint_offset_fields, shape 為[17, 2, output_h, output_w]. 為對應位置上的離其最近的關節點位置的偏移量. 這個是學習得到的, 2表示是兩個方向(x, y)的偏移量. 所以關節點的真正位置需要把該位置的(x, y)和其兩個方向的(x_offset, y_offset)相加起來得到.
joint_b, shape為[17, output_h, output_w]. 論文里提到的spread b,是自適應並且經過網絡學習得到的, 用來參與loss計算, 在decode的時候並沒有用到.
joint_scale_fields. shape為[17, output_h, output_w]. 自適應的scale值, 用來表明該關鍵點的scale大小.不確定是否有用在loss計算里. decode的時候則是作為類似gaussian的sigma值參與decode過程.
paf, 原始的輸出共有5個, 按照順序為: (首先說明下, 論文提出的paf和之前OpenPose及PersonLab提出的連接方式都不一樣. 該論文提出的paf連接為, 每個位置預測出哪兩個點需要連接在一起, 因此不是單純的兩個關節點之間的直接連接, 而是經過了另外一個位置進行第三方連接)
joint_intensity_fields, shape為[19, output_h, output_w]. 19表明共有多少個連接需要學習, 對應的是每個輸出位置上的paf的confidence值
joint1_fields, shape為[19, 2, output_h, output_w]. 這個位置表明的兩個可以連接在一起的點中的第一個點的信息, 其實就是偏移值, (x_offset, y_offset).
joint2_fields, shape為[19, 2, output_h, output_w]. 同上, 表示的是一條線段上的第二個點的偏移值.
joint1_fields_logb, shape為[19, output_h, output_w]. 論文里提到的spread b,是joint1的, 用來參與loss計算和decode. 根據decode的過程來看, 網絡輸出的這個值是經過log計算后的, 所以叫做logb,在decode的時候需要先exp還原.
joint2_fields_logb, shape為[19, output_h, output_w]. 同上, 只不過變成是第二個點的b了.
- decode過程:
normalize_pif. 就是把網絡的pif 4個輸出整合在一起, 首先是對joint_intensity_fields和joint_scale_fields進行擴維, 把shape從[17, output_h, output_w]變成[17, 1, output_h, output_w]. 接着是根據joint_offset_fields對[output_h, output_w]這么大的矩陣上, 對應的位置(x, y) + offset. 舉例來說, 原來的joint_offset_fields, 其中[1, :, 4, 5] 這個位置表示的offset值為(1, 2), 那么意思就是在(4, 5)這個位置坐標(4, 5)上, 需要加上偏移(1, 2)才是真正的這個位置所表示的關鍵點坐標值, 也即是(5, 7), 因此最后[1, :, 4, 5] == [5, 7]. 最后把更新后的這三個矩陣concatenate一起, 變成[17, 4, output_h, output_w]的pif信息, 4表示每個位置上都有四個值, 分別是[confidence, x, y, scale]. 這個時候的[x, y]就是真正的這個點表示的關鍵點的坐標值.
normalize_paf. 同pif一樣, 也是把網絡的paf 5個輸出整合在一起. 除了對joint1_fields和joint2_fields進行同樣的offset相加外(和pif的操作一樣), 還對兩個logb進行了exp操作, 然后對joint_intensity_fields和兩個b進行擴維成[19, 1, output_h, output_w]. 最后對更新后的joint_intensity_fields, joint1_fields, joint1_logb進行concatenate一起, 得到[19, 4, output_h, output_w]大小的矩陣, 這個存儲的全是有關joint1的, 同理, 對joint2做同樣的操作, 同樣得到[19, 4, output_h, output_w]大小的矩陣. 4表示[confidence, x, y, b]. 需要注意的是joint1和joint2共享同樣的confidence值. 最后, 對這兩個矩陣進行stack一起,得到最終的[19, 2, 4, output_h, output_w]輸出.
_target_intensities: 根據上面得到的新的pif信息, 利用文章提出的公式1, 得到在高維空間(也就是網絡的輸入分辨率下)的pif_hr, pifhr_scales信息. hr表示的就是high resolution的意思. 方法就是首先找到pif里所有confidence > v_th的值, 先把(x, y, s) 都乘上網絡的scale值, 然后針對這些位置, 以這些位置為中心, 位置對應的scale值為gaussian的sigma, 范圍為當前位置對應的confidence/16(不是很理解為什么是這個值), 接着對這個范圍內的值做高斯變換, 和pif里原來的位置信息相加, 使得本來confidence就很高的位置值更高, confidence值低的位置值更低. (具體圖示可以參考文章圖3). 源碼里的scalar_square_add_gaussian函數就是這個作用, 這樣就得到了pif_hr. cumulative_average函數的作用好像是對這個區域的scale求個平均? 沒有很理解這個函數的作用, 但結果是得到對應的scale值.
_score_paf_target: 這個函數的作用就是根據上面得到的paf信息, 得出哪些點是連在一起的. 因為paf的shape是[19, 2, 4, output_h, output_w], 因此對於每個連接, 其對應的paf field shape均為[2, 4, output_h, output_w]. 首先, 對於單個線段的信息, 假設為第一條線段的信息, 為fourds, shape = [2, 4, output_h, output_w]. 首先, 找到fourds里每個位置上的confidence值最小的那個(源碼是scores = np.min(fourds[:, 0], axis=0), 但我感覺因為對於同一個feature map上的位置而言, joint1 和 joint2的confidence是一致的, 因此其實就是把feature map上對應位置的score值去出來). 然后, 找到滿足scores > score_th條件的位置, 把這些位置取出來, 組成個新的fourds. 同理, scores也取出來, 組成個新的scores. 這時fourds的維度就是[2, 4, n], scores的維度是[n, ], n為滿足前面score條件的點的個數. 然后, 找到第一條線段對應的pif的channel位置, 例如第一條線段在coco是[15, 13], 那么就把pif_hr[15]當作是這個線段的joint1所在的featuremap, pif_hr[13]就是這個線段的joint2所在的featuremap. 因為此時fourds的shape為[2, 4, n], 那么fourds[0]就是所有滿足剛才那個條件的joint1集合, fourds[1]就是joint2集合. 地一個函數scalar_values的意思, 就是找到在pif_hr[15]上, 位置為(fourds[0, 1] * self.stride, fourds[0, 2]*self.sride)的confidence值,(注意這個confidence是指在pif_hr上的confidence, 不知道為啥程序里變量名起做pifhr_b.) 此時得到的pifhr_b就是joint1對應的在pifhr上的confidence值, 接着執行代碼scores_b = scores * (pifhr_floor + (1.0 - pifhr_floor) * pifhr_b), 我的理解是這行代碼的作用就是根據confidence對scores進行更新, pifhr_floor是0.1, 如果本身的pifhr_b值很大, 那么其對應的在paf的score就還是很大, 如果小, 相應的也縮小了其對應的paf的score值. 更新完后得到scores_b, 再根據這個值進一步過濾找到符合條件的joint1, joint2點, 最后把符合條件的點集合, 得到scored_backward里的一個元素. 因為paf總共有19個channel, 所以scored_backward總共有19個元素, 每個元素都是[7, m]大小的矩陣, 存儲的信息分別是[score_b, joint2_x, joint2_y, joint2_b, joint1_x, joint1_y, joint1_b]. m是scores_b中符合條件的點的個數. 因為元素是先joint2元素信息再是joint1信息, 所以叫做scored_backward. 下面還會有在**_pifhr[j2i] (_pifhr[13])**搜索符合條件的joint2的信息, 仿照上面的步驟, 同樣得到一個[7, n]的矩陣, 信息為[score_b, joint1_x, joint1_y, joint1_b, joint2_x, joint2_y, joint2_b], 因為這個是先jioint1的信息再是joint2的信息, 因此其組成的列表又叫做scored_forward, 其實就是看是以joint1為出發點還是終止點表示的這個線段信息.
- 總結下先, 3執行完之后, 得到了一個在高分辨率下的pifhr信息和pif_scales信息, shape均為[17, hr_h, hr_w]. 4執行完后, 得到了兩個均含有19個元素的列表, 分別叫做scored_forward 和 scored_backward. 列表里的每個元素, 都是滿足一系列條件的點的信息, 例如對於scored_forward[0]來說, shape大小為[7, m], m為點的個數, 7表示[score_b, joint1_x, joint1_y, joint1_b, joint2_x, joint2_y, joint2_b], 形象話說, 就是對於7*m的矩陣, 每一列都能找到兩個點用來表示線段0. 需要明確的一點, 這里面的兩個點, 都還是通過paf信息得到的點的位置, 並不是在pif信息上的點的位置
接着就是根據3和4得到的結果, 用來連線, 判斷哪些線段應該連起來組成一個人**.**
_pif_seeds函數: pif_seeds函數用來在self.pif上找到confidence符合閾值的點的信息, , 然后再在pifhr上找到該點對應的confidence值, 接着把(v, field_i, xx, yy, ss)組成一個seed放入seeds中, v是在pifhr上的confidence, field_i是用來表明這個點在第幾個channel上, (xx, yy, ss)都是在pif上的位置和scale信息. 最后, 對seeds按照v的值降序排列, 越靠近前面的seed, confidence值更大.(比較奇怪的是為什么confidence是在pifhr找, 但(xx, yy, ss)都是在pif上面找)
首先根據3得到的pifhr_scales生成同樣大小的矩陣occupied, 找到了pif_seeds之后, 按照排過序的seeds序號, 先從confidence值最大的seed進行尋找. 首先判斷當前seed的(x, y)是否超過了occupied的范圍, 如果超過就尋找下一個seed, 沒有就返回當前occupied[y, x]的值. (這邊同樣有個問題, occupied初始化為0, 那尋找第一個seed的時候, 無論是否超出范圍, 返回的值都為0, 也就是說按照源碼來說, 第一個seed是沒有用的?我嘗試了把第一個seed當作正常運行放進去, 發現結果沒有太大差別, 就是最后的anno score值變大了一些. 不過python格式的程序速度慢了將近一倍.) 然后會執行函數scalar_square_add_single, 這個函數的作用就是讓occupied矩陣在(x*stride, y*stride) 為中心, 范圍為max(4, s*stride)的范圍內加1. 這樣就相當於更新了以(x, y)為中心的occupied值. 接着, 根據seed的(x, y, v) 和 當前所處的channel編號f, 構建Annotation類. ann.data是一個[17, 3]的矩陣, 存儲的就是一個完整的人體pose應該有的關節點個數. ann.skeleton_m1就是下標從0開始的[19, 3]的skeleton連接編號. 初始化ann的時候, 會根據f的值把傳入的(x, y, v) 放進去ann.data[f]上. 接着, 就會以當前ann為已有的skeleton信息, 以前面得到的_paf_forward, _paf_backward連接信息進行_grow操作.
_grow函數: 在進行從_paf_forward, _paf_backward抽取信息前, 會先對當前的ann執行ann.frontier_iter()函數. ann.frontier_iter()函數的意思就是找到當前ann.data里, 應該和ann.data里已有值的點相連的點, 但是卻沒有在ann.data里面的點. 然后, 根據這個點是在已有點的后面(即已有點是joint1, 未找到點是joint2)還是在前面, 判斷該點是處於forward狀態還是backward狀態. 接着, 對這些所有點進行按照已有值的點的confidence值降序排列. (函數最終會得到一個列表的集合, 每個列表的元素都是[confidence, connection_i, True/False, j1i, j2i], confidence是已有點的confidence, connection_i是這個點應在的線段連接編號, True表明已有點是joint1, 放進去的這個點是joint2, j1i 和 j2i就是這個connection連接的兩個點的channel編號). 最后, 從這個列表frontier里取出第一個值, 表明是當前已有的ann.data里, confidence值最高的那個點應該連接的點的信息. 取出的值就是上面所講的列表的元素值信息, 如果是True(表明為forward), 則xyv = ann.data[j1i], directed_paf_field = paf_forward[i], directed_paf_field_reverse = paf_backward[i], 否則的話, xyv = ann.data[j2i], directed_paf_field = paf_forward[i], directed_paf_field_reverse = paf_backward[i]. xyv是從ann.data里取出的目前confidence值最高且需要額外的一個點和它連接的點(new_xyv), directed_paf_field就是new_xyv所在的paf_field, directed_paf_field_reverse就是剛好xyv所在的paf_field. (剛好一個是paf_forward, 一個是paf_backward). 然后, 根據得到的directed_paf_field, 執行_grow_connection函數.
**_grow_connection**函數. 這個函數比較簡單, 首先是在paf_field上, 以(x,y)為中心, 找到在paf_field里的所有點符合條件的點, 拿出來構成新的paf_field, 然后, 在這個新的paf_field里面, 計算這些點和xy這個點的距離, 並根據距離更新里面點的score值, 最后選擇score值最大的那個點作為返回值. 為了保證找的這個點是正確的, 會根據找到的這個點進行reverse match, 步驟和前面的一致, 判斷通過reverse找到點是否和(x,y)滿足距離閾值條件. 如果是, 就把找打的這個點當作新的一個點放進ann.data里面. 這樣循環遍歷, 就能把ann給盡量的填充滿.
當對當前的seed點進行grow_connection之后, 會根據之前得到的pifhr_scales對ann.data的每個點加上對應的scale值, 然后根據目前已有的ann.data值, 再更新一遍每個ann.data里的點對應的occupied矩陣, 就是執行函數scalar_square_add_single函數. 最后選擇下一個seed點進行grow, 直到所有的seed點都遍歷過一次. 最后再對所有的anns執行complete_annotations操作, 就是尋找對於每個ann上額外的空置的點(應該有點和它連接的實際上沒有)是否還有可能有其它點和它連在一起, 就是再執行一邊_grow操作.
**soft_nms**函數: 對11得到的annos執行nms操作, 過濾掉一些有可能不符合要求的ann. ann.score值是其中ann.data中每個點的score值的加權平均.
