README.md

    OpenCV中的快速直线检测

    cv::ximgproc::FastLineDetectors是opencv-contrib中用于检测直线的模块,该方法能在较短时间内获得精度较高的直线检测结果,且不需要调节参数。该函数是LineSegmentDetector因版权问题从OpenCV中移除后最易用的直线检测小能手,没有之一。本文介绍该功能的使用方法其输出结果剖析。

    本文范例运行环境

    • python3.6
    • opencv-contrib-python 4.4.0.44

    FastLineDetectors运行必要条件

    FastLineDetectors属于opencv-contrib中的模块,需要安装opencv-contrib-python。在python的opencv相关的安装包中,opencv-python 包含主要模块,opencv-contrib-python 包含主要模块以及一些扩展模块。但这两个模块并不兼容,如果已经安装过opencv-python,需要先卸载,再安装opencv-contrib-python。

    1 pip uninstall opencv-python 
    2 pip install opencv-contrib-python

    在OpenCV 4.1.0之前的版本中,有一个LineSegmentDetector模块,功能强大,检测直线非常便捷,但该函数因为版权问题已在4.1.X版本上已移除,故此处不讨论此方案。LineSegmentDetector相关的问题可以参考:

    FastLineDetectors应用示例

     1 import cv2 
     2 import numpy as np 
     3 img = cv2.imread('sudo.png') 
     4 gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) 
     5 #Create default Fast Line Detector (FSD) 
     6 fld = cv2.ximgproc.createFastLineDetector() 
     7 #Detect lines in the image 
     8 lines = fld.detect(gray) 
     9 #Draw detected lines in the image 
    10 drawn_img = fld.drawSegments(gray,lines) 
    11 cv2.imshow("FLD", drawn_img) 
    12 cv2.waitKey(0)

    如以上代码所示:

    第1~2行导入python包。

    第3行读入测试图片,本文中测试图片与代码文件在同一路径,故只有图片名称。更改测试图像时,更改该行代码中的图像名称即可。

    第4行将测试图片转换为灰度图。

    第6行创建FastLineDetectors检测实例,这里使用的是默认参数,其参数顺序及意义如下:

    retval = cv.ximgproc.createFastLineDetector( [, _length_threshold[, _distance_threshold[, _canny_th1[, _canny_th2[, _canny_aperture_size[, _do_merge]]]]]] ) 
    Parameters 
    _length_threshold 10 - Segment shorter than this will be discarded 
    _distance_threshold 1.41421356 - A point placed from a hypothesis line segment farther than this will be regarded as an outlier 
    _canny_th1 50 - First threshold for hysteresis procedure in Canny() 
    _canny_th2 50 - Second threshold for hysteresis procedure in Canny() 
    _canny_aperture_size 3 - Aperturesize for the sobel operator in Canny() 
    _do_merge false - If true, incremental merging of segments will be perfomred

    第8行使用FastLineDetectors检测测试图像中的直线。

    第10行根据检测结果绘制直线。

    第11~12行用于显示检测结果。

    如下动图所示,我选取了两张图,均使用默认参数来检测,图中的直线基本被检出,说明该函数的通用性还是比较强的。

    以其中停车场的图片为例,图中有许多小短线,此时可以更改createFastLineDetector的默认参数,设置直线的长度来对检测直线进行过滤。

    FastLineDetectors检测结果详解

    通常我们检测直线并不只是为了将其绘制出来而已,我们可能需要知道该直线与其他直线的关系。这时我们就需要能取用检测得到直线两个端点的坐标。

    接下来开始解析FastLineDetectors的检测结果,学习如何取用检测直线的端点坐标。

    在"FastLineDetectors应用示例"小节中,在完成直线检测代码之后,添加如下代码,先观察一下FastLineDetectors检测结果的各种属性:

    print("数据类型",type(lines)) #打印数组数据类型
    print("数组元素数据类型:",lines.dtype) #打印数组元素数据类型 
    print("数组元素总数:",lines.size) #打印数组尺寸,即数组元素总数 
    print("数组形状:",lines.shape) #打印数组形状 
    print("数组的维度数目",lines.ndim) #打印数组的维度数目

    运行后,可以看到FastLineDetectors的检测结果的各种输入为:

    数据类型 <class 'numpy.ndarray'> 
    数组元素数据类型: float32 
    数组元素总数: 724 
    数组形状: (181, 1, 4) 
    数组的维度数目 3

    然后我们到github查看一下drawSegments的实现函数,如下所示:

     1 void FastLineDetectorImpl::drawSegments(InputOutputArray _image, InputArray lines, bool draw_arrow)
     2 {
     3   CV_INSTRUMENT_REGION();
     4
     5   CV_Assert(!_image.empty() && (_image.channels() == 1 || _image.channels() == 3));
     6 
     7    Mat gray;
     8    if (_image.channels() == 1)
     9    {
    10        gray = _image.getMatRef();
    11   }
    12    else if (_image.channels() == 3)
    13    {
    14       cvtColor(_image, gray, COLOR_BGR2GRAY);
    15    }
    16 
    17   // Create a 3 channel image in order to draw colored lines
    18    std::vector<Mat> planes;
    19    planes.push_back(gray);
    20    planes.push_back(gray);
    21    planes.push_back(gray);
    22 
    23    merge(planes, _image);
    24 
    25    double gap = 10.0;
    26    double arrow_angle = 30.0;
    27 
    28    Mat _lines;
    29    _lines = lines.getMat();
    30    int N = _lines.checkVector(4);
    31    // Draw segments
    32    for(int i = 0; i < N; ++i)
    33    {
    34        const Vec4f& v = _lines.at<Vec4f>(i);
    35        Point2f b(v[0], v[1]);
    36        Point2f e(v[2], v[3]);
    37        line(_image.getMatRef(), b, e, Scalar(0, 0, 255), 1);
    38        if(draw_arrow)
    39        {
    40            SEGMENT seg;
    41            seg.x1 = b.x;
    42            seg.y1 = b.y;
    43            seg.x2 = e.x;
    44            seg.y2 = e.y;
    45            getAngle(seg);
    46            double ang = (double)seg.angle;
    47            Point2i p1;
    48            p1.x = cvRound(seg.x2 - gap*cos(arrow_angle * CV_PI / 180.0 + ang));
    49            p1.y = cvRound(seg.y2 - gap*sin(arrow_angle * CV_PI / 180.0 + ang));
    50            pointInboardTest(_image.getMatRef(), p1);
    51            line(_image.getMatRef(), Point(cvRound(seg.x2), cvRound(seg.y2)), p1, Scalar(0,0,255), 1);
    52        }
    53    }
    54}

    上述代码中:

    第28~37行用于绘制检测到的直线,其中lines[i][0]~lines[i][3]中存储的四个元素分别为第i条直线端点1的X,Y坐标和端点2的X,Y坐标。

    第38行中的变量[draw_arrow],是函数drawSegments的参数之一,默认值为false,用于设定是否在检测直线的其中一个端点处绘制箭头。

    根据以上信息,我们推测在python的FastLineDetectors检测结果中,lines[i][0][0]~lines[i][0][3]分别对应第 i 条直线端点1的X,Y坐标和端点2的X,Y坐标。

    为了证明上述推测,我们选取下图作为测试图片,修改一下相关代码,实现如下功能:找出检测结果中最长的一根直线,绘制该直线及其两个端点,并且在该直线一个端点处绘制箭头。

     1 import cv2
     2 import numpy as np
     3 from scipy.spatial import distance as dist
     4 
     5 img = cv2.imread('sDQLM.png')
     6 gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
     7 
     8 #Create default Fast Line Detector (FSD)
     9 fld = cv2.ximgproc.createFastLineDetector()
    10 
    11 #Detect lines in the image
    12 lines = fld.detect(gray)
    13 
    14 dMax = 0
    15 bx_Max = 0
    16 by_Max = 0
    17 ex_Max = 0
    18 ey_Max = 0
    19 
    20 for L in lines:
    21 
    22    bx,by,ex,ey = L[0]
    23   
    24    # compute the Euclidean distance between the two points,
    25    D = dist.euclidean((bx, by), (ex, ey))
    26    
    27    if D > dMax:
    28        dMax = D
    29        bx_Max = bx
    30        by_Max = by
    31        ex_Max = ex
    32        ey_Max = ey
    33        
    34 lineMax = np.array([[[bx_Max, by_Max, ex_Max,ey_Max]]])
    35 #Draw detected lines in the image
    36 drawn_img = fld.drawSegments(gray,lineMax,True)
    37 cv2.circle(drawn_img, (bx_Max, by_Max), 1, (255,0,0), 2)#line begin
    38 cv2.circle(drawn_img, (ex_Max, ey_Max), 1, (0,255,0), 2)#line end
    39 
    40 cv2.imshow("FLD", drawn_img)
    41 cv2.waitKey(0)

    上述代码中:

    第25行用于计算直线的长度,便于我们找出检测结果中最长的直线。

    第34行用最长的直线的两个端点重新构建一个np.array。

    第36行绘制最长的直线,并且第三个参数设置为TRUE,即在直线其中一端绘制箭头。

    第37~38行,在图中绘制两个端点。

    运行结果如下:

    FastLineDetectors可以方便的检测图中的直线,并且可以简单的取用直线两个端点的坐标,接下来就可以用它实现更多有趣的功能。

    本文到此结束,感谢阅读。

    原文链接:https://livezingy.com/fastlinedetectors-opencv-contrib/

    项目简介

    opencv_technology

    发行版本

    当前项目没有发行版本

    贡献者 2

    M MaoXianxin @MaoXianxin
    C CODE CHINA @CODE CHINA

    开发语言