LSD快速直线算法用于局部提取直线,时间复杂度低。LSD算法通过对图像局部分析,得出直线的像素点集,在通过假设参数进行验证求解,将像素点集合与误差控制集合合并,进而自适应控制误检的数量。检测图像中的直线最基本的思想就是检测图像中梯度变化较大的像素点集,LSD正是充分利用了梯度信息和行列线来进行直线检测的。
(1)行列线及支撑线。行列线是图像的灰度从黑到白或从白到黑剧烈变化的分割线,即梯度形成区域。首先计算梯度图像中每个像素点行列线角度以生产行列线区域,对于单位向量域而言,该区域被分为若干子区域,所有向量与基准点行列线相切,连接区域包含同样或相似的容忍误差为
的角度。通过这样的运算可以确保它并不需要同样强度的梯度行列线,最后得到的效果是,即使有锯齿线、混叠效应,同样可以使局部水平线的方向大致是平行的。
每个支撑线区域都是以候选的直线分割方式对应支撑线区域最小外接矩形,同时满足相应的几何目标与其一一对应,几何形状矩形的主方向作为支撑线的主轴方向,矩形区域覆盖整个区域
(2)对齐点。在每个支撑线区域形成的矩形区域内,若该像素点形成的行列线方向角度与矩形主轴方向在容忍误差
范围内保存一致性,则矩形区域内的像素个数n及对齐点个数k都需要计算出来,然后通过相应准则来验证该矩形分割是否可作为正确的直线分割。
LSD算法并不重复计算单个行列线,为了避免出现这样的情况,行列线将会被基于其梯度大小进行排序,梯度变化方向最大的为最重要的行列线,该行列支撑线的获取将会根据其强梯度降序排列来得到。该算法的优点是可以根据几个参数的设置来提升不同应用场景下的检测性能,线性时间内就可以完成,但算法本身也存在局部算法的缺点。
(1)图像缩放。将输入图像缩小到原图像的80%,减弱或消除图像中的锯齿效应,利用高斯下采样的方式对输入图像进行操作。图像首先与高斯核卷积来平滑消除锯齿效应,然后进行降采样操作,防止其他干扰噪声。
(2)计算梯度。图像的梯度计算是通过2x2模板完成的,较小模板也使得计算相对较快,同时也保证了邻域方向分布的先对独立性。对于灰度图像f(i,j),图像的梯度由下式可得:
图像梯度的幅度G及行列线的角度
可由下式得到:
(3)梯度排序。梯度值剧烈变化的像素区域一般是图像中较强边缘存在的区域,LSD算法中区域像素点的处理将会直接影响后续的梯度检测,中间像素一般具有最高的梯度幅度,需要将像素点梯度值进行从大到小的排序,中间像素一般具有最高的梯度幅值,需要将像素点梯度幅值进行从大到小的排序,进而可以完成直线检测。LSD中实现的排序算法是伪排序算法,可在线性时间内完成,等间距均匀设置种子点,像素按照对应幅值归类到种子区域,然后利用像素映射关系对梯度幅值进行排序。
(4)梯度阈值。梯度幅度较小的像素点区域对应图像中的平坦区域,较小梯度值往往出现在平滑区域,不在关注的范围内,但是它们的存在往往会严重影响直线角度的计算。对平坦区域像素值计算其梯度时,很有可能出现更多的误差,因此在LSD计算中,对于梯度幅值小于
的像素点将限制参与支撑线区域的计算。
(5)区域生成。在排序像素列表中选取状态为未使用的像素点作为种子点,搜索角度满足状态为未使用的点8邻域形成的区域生成支撑线区域。邻域内的角度误差范围
,像素将依次被添加到该区域内进行验证更新,种子点的行列线角度为
,区域的角度更新为
,其中
为行列线区域角度。根据实验得出
设置为
则为最优参数。
(6)矩形估计。支撑线区域的外接矩形对应于直线分割,在分割步骤实施之前,应先计算出矩形区域的中心,根据像素的梯度幅值,矩形区域的主轴方向将会被设置为矩形的最小特征向量值对应的角度。
最新opencv模块提供了LSD算法,其中相关API在imgproc文件,cv::LineSegmentDetector类中:
cv::LineSegmentDetector::compareSegment()绘图两组线(蓝色和红色)
cv::LineSegmentDetector::detect():给定图像的线检测
cv::LineSegmentDetector::drawSegment():给定图像的线分割绘图
参考代码如下:
#include <opencv2/core/core.hpp>
#include <opencv2/core/utility.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/imgcodes.hpp>
#include <iostream>
#include <string>
using namespace std;
using namespace cv;
int main()
{
cv::Mat srcImage = cv::imread();
Canny(srcImage,srcImage,50,200,3);
#if 1
Ptr<LineSegmentDetector> lsd = createLineSegmentDetector(LSD_REFINE_STD);
#else
Ptr<LineSegmentDetector> lsd = createLineSegmentDetector(LSD_REFINE_NONE);
#endif
double start = double(getTickCount());
vector<Vec4f> vecLines;
double times = (double(getTickCount()) - start) * 1000 / getTickFrequency();
std::cout<<"times:" <<times<<"ms"<<std::endl;
cv::Mat reslineMat(srcImage);
lsd->drawSegments(reslineMat,vecLines);
cv::imshow("reslineMat",reslineMat);
cv::waitKey();
return 0;
}