2020年10月12日
FastLineDetectors opencv-contrib python
cv::ximgproc::FastLineDetectors是opencv-contrib中用于检测直线的模块,该方法能在较短时间内获得精度较高的直线检测结果,且不需要调节参数。本文介绍该功能的使用方法并剖析其输出结果。
本文范例运行环境
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 2 |
pip uninstall opencv-python pip install opencv-contrib-python |
在opencv4.1.0之前的版本中,有一个LineSegmentDetector模块,功能强大,检测直线非常便捷,但该函数因为版权问题已在4.1.X版本上已移除,故此处不讨论此方案。LineSegmentDetector相关的问题可以参考:
FastLineDetectors应用示例
1 2 3 4 5 6 7 8 9 10 11 12 |
import cv2 import numpy as np img = cv2.imread('sudo.png') gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #Create default Fast Line Detector (FSD) fld = cv2.ximgproc.createFastLineDetector() #Detect lines in the image lines = fld.detect(gray) #Draw detected lines in the image drawn_img = fld.drawSegments(gray,lines) cv2.imshow("FLD", drawn_img) cv2.waitKey(0) |
如以上代码所示,第1~2行导入python包。
第3行读入测试图片,本文中测试图片与代码文件在同一路径,故只有图片名称。更改测试图像时,更改该行代码中的图像名称即可。
第4行将测试图片转换为灰度图。
第6行创建FastLineDetectors检测实例,这里使用的是默认参数,其参数顺序及意义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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检测结果的各种属性:
1 2 3 4 5 |
print("数据类型",type(lines)) #打印数组数据类型 print("数组元素数据类型:",lines.dtype) #打印数组元素数据类型 print("数组元素总数:",lines.size) #打印数组尺寸,即数组元素总数 print("数组形状:",lines.shape) #打印数组形状 print("数组的维度数目",lines.ndim) #打印数组的维度数目 |
运行后,可以看到FastLineDetectors的检测结果的各种输出为:
1 2 3 4 5 |
数据类型 <class 'numpy.ndarray'> 数组元素数据类型: float32 数组元素总数: 724 数组形状: (181, 1, 4) 数组的维度数目 3 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
void FastLineDetectorImpl::drawSegments(InputOutputArray _image, InputArray lines, bool draw_arrow) { CV_INSTRUMENT_REGION(); CV_Assert(!_image.empty() && (_image.channels() == 1 || _image.channels() == 3)); Mat gray; if (_image.channels() == 1) { gray = _image.getMatRef(); } else if (_image.channels() == 3) { cvtColor(_image, gray, COLOR_BGR2GRAY); } // Create a 3 channel image in order to draw colored lines std::vector<Mat> planes; planes.push_back(gray); planes.push_back(gray); planes.push_back(gray); merge(planes, _image); double gap = 10.0; double arrow_angle = 30.0; Mat _lines; _lines = lines.getMat(); int N = _lines.checkVector(4); // Draw segments for(int i = 0; i < N; ++i) { const Vec4f& v = _lines.at<Vec4f>(i); Point2f b(v[0], v[1]); Point2f e(v[2], v[3]); line(_image.getMatRef(), b, e, Scalar(0, 0, 255), 1); if(draw_arrow) { SEGMENT seg; seg.x1 = b.x; seg.y1 = b.y; seg.x2 = e.x; seg.y2 = e.y; getAngle(seg); double ang = (double)seg.angle; Point2i p1; p1.x = cvRound(seg.x2 - gap*cos(arrow_angle * CV_PI / 180.0 + ang)); p1.y = cvRound(seg.y2 - gap*sin(arrow_angle * CV_PI / 180.0 + ang)); pointInboardTest(_image.getMatRef(), p1); line(_image.getMatRef(), Point(cvRound(seg.x2), cvRound(seg.y2)), p1, Scalar(0,0,255), 1); } } } |
上述代码中:
第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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
import cv2 import numpy as np from scipy.spatial import distance as dist img = cv2.imread('sDQLM.png') gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #Create default Fast Line Detector (FSD) fld = cv2.ximgproc.createFastLineDetector() #Detect lines in the image lines = fld.detect(gray) dMax = 0 bx_Max = 0 by_Max = 0 ex_Max = 0 ey_Max = 0 for L in lines: bx,by,ex,ey = L[0] # compute the Euclidean distance between the two points, D = dist.euclidean((bx, by), (ex, ey)) if D > dMax: dMax = D bx_Max = bx by_Max = by ex_Max = ex ey_Max = ey lineMax = np.array([[[bx_Max, by_Max, ex_Max,ey_Max]]]) #Draw detected lines in the image drawn_img = fld.drawSegments(gray,lineMax,True) cv2.circle(drawn_img, (bx_Max, by_Max), 1, (255,0,0), 2)#line begin cv2.circle(drawn_img, (ex_Max, ey_Max), 1, (0,255,0), 2)#line end cv2.imshow("FLD", drawn_img) cv2.waitKey(0) |
上述代码中25行用于计算直线的长度,便于我们找出检测结果中最长的直线。
第34行用最长的直线的两个端点重新构建一个np.array。
第36行绘制最长的直线,并且第三个参数设置为TRUE,即在直线其中一端绘制箭头。
第37~38行,在图中绘制两个端点。
运行结果如下:
FastLineDetectors可以方便的检测图中的直线,并且可以简单的取用直线两个端点的坐标,接下来就可以用它实现更多有趣的功能。
本文到此结束,感谢阅读,欢迎关注。