簡單記錄LSD算法的實現過程,當做備忘錄用,如有問題歡迎指出和討論
LSD的基本實現流程是計算出圖像的梯度和場方向,然后對梯度進行排序,然后從大到小進行區域增長,之后對增長得到的區域求最小外接矩形,如果矩形不滿足要求,則修改參數重新生長或者修改矩形的大小和位置,若仍舊不滿足,則放棄該區域
筆者從數據結構層面優化了原算法的時間復雜度和空間復雜度
高斯降采樣:
分x方向和y方向進行采樣,方法相同,計算高斯核的公式為正態分布(即高斯分布)公式,因原公式對中心店偏移了0.5並向下取整,所以觀察結果可發現,在0.3的采樣比例下(其他參數需要重新計算或者用通用公式),高斯核的重心偏差為0,-1/3,1/3,因此只需計算一次高斯核,減少計算量,此高斯核對兩個方向是通用的

采樣的時候每隔10/3(+0.5后取整)個像素一次采樣,然后用(此地圖的高斯核為1x17)高斯核對這個像素及其左右8個像素進行卷積然后得到該點的新數值
梯度與Level-Line場方向:
求梯度的公式一般使用這三個公式

筆者使用的是LSD的公式

將1x2窗口變成了2x2窗口
人為設定角度閾值(degreeThreshold),記為degThre,一般設為22.5°,用弧度制表示,angleThreshold(簡記為angThre)是對應的角度制,通過下面公式計算出梯度閾值(gradientThreshold),記為gradThre
![]()
如果該點梯度小於閾值,則加入usedMap,這步主要將邊角和某些凹凸區域給去掉了,然后用gx和gy計算出每點的梯度方向,即level-line場方向,每個點的值記為degree(簡記為deg)
![]()
區域生長RegionGrower:
對所有剩下的像素進行快排(原論文使用的是分成了1024級梯度然后進行偽排序),然后從梯度最大的像素開始增長,增長的方式是在usedMap為空的像素中增長,方法是比較當前像素與周圍8個像素的deg差,如果小於閾值degThre則加入區域Region,然后將最后比較並加入的像素作為下次比較的基准像素(動畫中為淺藍色),通過動畫和代碼可知,新增長點有滯后性,所以在區域兩個端點切換的時候,基准像素在端點的另一側,所以能夠保證兩端的角度差較小,若最后生長得到的直線弧度較大,后面有算法進行修正,如果得到的區域所含像素過少,則舍去該區域
最小外接矩形RectangleConvert:
遍歷所有像素,找到四個方向上最邊界上的像素,然后得到他們的外接矩形,返回值為矩陣兩個短邊的中點坐標(x1,y1)和(x2,y2),短邊邊長width(簡記為wid),重心坐標(centerX,centerY)(簡記為(cenX,cenY)),主方向角弧度degree(簡記為deg),角弧度余弦值degreeX和角弧度正弦值degreeY(簡記為dx和dy),像素點和角弧度相符的概率probability(簡記為p),像素點場方向與主方向角弧度之差的閾值prec
其中p是直接使用的閾值占180°的比例,即1/8,prec直接使用的22.5°對應的弧度制閾值
精煉Rifiner:
這步主要是解決前面所說的弧度過大問題,因為可能在增長的時候一側增長完,則基准像素一直在一端,則無法控制弧度,最終導致弧度過大,使用下式計算出密度density(簡記為den),若大於閾值則進行精煉
![]()
首先計算出新的degThre

然后利用新得到的degThre重新進行區域生長和建立最小外接矩形,若得到的區域仍未滿足密度閾值,則減小區域半徑(往往都要減小半徑)
減小半徑的算法是找出矩形四個頂點中離生長點最遠的距離作為半徑,每次以一定的比例減小半徑,然后對半徑內的像素點再次生成最小外接矩形並計算密度,直到密度小於密度閾值
改善矩形RectangleImprover:
先利用rec結構體里的數據求出矩形四個頂點,然后根據之間的大小關系調整順序,使得四個頂點的順序和四個邊的斜率順序如下圖

然后設定1到3的橫坐標為yLow和yHigh的橫坐標,分辨率為長度1的像素,分為兩段結合K3K4計算出yLow的縱坐標(向下取整,圖中為向上取整),即K3K4所在邊的縱坐標,下邊同理計算出K1K2所在邊的縱坐標(向上取整,圖中為向下取整),記為yHigh,然后yLow與yHigh所包圍的所有像素為矩形的全部像素,數量記為allPixelNumber(簡記為allPixNum),其中計算每個像素的場方向與矩陣主方向角度差,若小於閾值degThre則判定為alignedPixelNumber(簡記為aliPixNum),意為方向相同的向量
首先需要計算虛警數Number of False Alarms(簡記為NFA),如何推出此公式可以閱讀文末的鏈接
![]()
第一項為當前大小(m*n)圖像中直線(矩形框)的數量,在m*n的圖像中直線的起點和終點分別有m*n種選擇,所以一共有(m*n)*(m*n)種起點和終點搭配,線段的寬度為[0,m*n ^0.5],因此在m*n大小的圖像中有(m*n)^2.5 種不同直線,兩邊同時求對數可得,yLim和xLim是圖像的長寬

二項式的計算為,其中組合數C(n,k)使用廣義階乘轉化為伽馬函數

帶入B(n,k,p)然后求首項,並對兩側求對數可得二項分布值,伽馬函數使用了Windschitl方法和Lanczos方法進行近似計算,此處不詳細說明

若首項很小,則忽略尾項,直接將B帶入原式可得NFA,反之,則繼續計算尾項,其中利用前后項的關系可以簡化計算

可推出B(n,k,p)后項與前項的比值為
![]()
即對前面求出的首項乘以上式然后累加可得二項式,然后將B帶入原式可得NFA
論文中給出的建議值是虛警數小於1,此處取負對數,則大於0,然后開始改善精度,分為四步,分別是減小角度容忍度(p),僅減少寬度(wid),減小寬度(wid)同時改變重心位置(x1 y1 x2 y2),然后再次減小角度容忍度(p)
減小角度容忍度(p):每次減小一半為p的一半,然后根據新的閾值計算NFA,若比原NFA大則保存新的矩陣和p
僅減小寬度(wid):根據前面算出的p減小矩陣寬度,步進為0.5,若新NFA大則保存新的矩陣和wid
減小寬度(wid)同時改變重心位置(x1 y1 x2 y2):保持兩個長邊中一個邊不動,縮進另一個邊,同時減小寬度,若新NFA大則保存新的矩陣和wid

如圖所示意,1和2表示rec結構里面的x1 y1和x2 y2,如果計算得到的NFA值已經大於0,則直接返回,若最后返回NFA值小於0,則判定為虛警(False Alarm),原矩陣所含有的像素被標記后釋放,可重新用於生長,但是不能成為生長起始點
循環以上算法直到所有種子像素都完成生長則可得到所有生長區域
筆者的代碼托管於Github,同時錄制了算法的動畫
https://github.com/Pyrokine/LineSegmentDetector17
https://www.bilibili.com/video/av43174965/
感謝以下Geeks和論文
基於LSD的直線提取算法 https://blog.csdn.net/tianwaifeimao/article/details/17678669 LSD: a Line Segment Detector http://www.ipol.im/pub/art/2012/gjmr-lsd/
線特征---LSD算法(二)
https://www.cnblogs.com/Jessica-jie/p/7512152.html
