简体   繁体   English

使用 OpenCV 霍​​夫变换在 2D 点云中进行线检测

[英]Using OpenCV Hough Tranform for line detection in 2D point cloud

I have tried my best to find out how to use OpenCV for line detection.我已尽力找出如何使用 OpenCV 进行线路检测。 However, I cannot find the examples that I'm looking for.但是,我找不到我正在寻找的示例。 I want to use it to find lines in simple 2-d point clouds.我想用它来查找简单的二维点云中的线条。 As a test I want to use the following points:作为测试,我想使用以下几点:

在此处输入图片说明

import random
import numpy as np
import matplotlib.pyplot as plt

a = np.random.randint(1,101,400)  # Random points.
b = np.random.randint(1,101,400)  # Random points.

for i in range(0, 90, 2):  # A line to detect
    a = np.append(a, [i+5])
    b = np.append(b, [0.5*i+30])

plt.plot(a, b, '.')
plt.show()

I have found a lot of initial examples of how the Hough Tranform works.我找到了很多关于霍夫变换如何工作的初始示例。 However, when it comes to code examples, I can only find that images have been used.但是,在代码示例中,我只能发现使用了图像。

Is there a way to use the OpenCV Hough Transform to detect the line in a set of points, or can you recommend any other methods or libraries?有没有办法使用 OpenCV 霍​​夫变换来检测一组点中的线,或者您能推荐任何其他方法或库吗?

---- Edit ---- - - 编辑 - -

After reading some great ansewers I feel like I scould discribe what i intent to use it for a little bit better.在阅读了一些很棒的答案后,我觉得我可以描述一下我打算更好地使用它的内容。 I have a high resolution 2D LiDAR and need to extract walls from the data.我有一个高分辨率的 2D LiDAR,需要从数据中提取墙壁。 A typicle scan can look like this:典型的扫描可能如下所示: 在此处输入图片说明

Where the "correct output" would look something like this: “正确的输出”看起来像这样: 在此处输入图片说明

After I have done some more research I suspect that the Hough transform is less than optimal to use in this case.在我做了更多的研究之后,我怀疑在这种情况下使用霍夫变换不是最佳的。 Any tips on what i should look for?关于我应该寻找什么的任何提示?

(If anyone is interested, the LiDAR and wall extraction is used to generate a map and navigate a robot.) (如果有人感兴趣,可以使用 LiDAR 和墙壁提取来生成地图并导航机器人。)

Thanks, Jakob谢谢,雅各布

One way would be to implement Hough Transformation yourself following these slides skipping the Edge Detection part.一种方法是按照这些幻灯片跳过边缘检测部分自己实现霍夫变换。

Alternatively you could create an image from your list of points such as或者,您可以从您的点列表中创建一个图像,例如

#create an image from list of points
x_shape = int(np.max(a) - np.min(a))
y_shape = int(np.max(b) - np.min(b))

im = np.zeros((x_shape+1, y_shape+1))

indices = np.stack([a-1,b-1], axis =1).astype(int)
im[indices[:,0], indices[:,1]] = 1

plt.imshow(im)

#feed to opencv as usual

following the answer to this question按照这个问题的答案

EDIT: Do not feed to OpenCV but use instead skimage such as described here in the documentation :编辑:不要提供给 OpenCV,而是使用 skimage,如文档中所述:

import numpy as np

from skimage.transform import (hough_line, hough_line_peaks,
                               probabilistic_hough_line)
from skimage.feature import canny
from skimage import data

import matplotlib.pyplot as plt
from matplotlib import cm


# Constructing test image
#image = np.zeros((100, 100))
#idx = np.arange(25, 75)
#image[idx[::-1], idx] = 255
#image[idx, idx] = 255

image = im

# Classic straight-line Hough transform
h, theta, d = hough_line(image)

# Generating figure 1
fig, axes = plt.subplots(1, 3, figsize=(15, 6))
ax = axes.ravel()

ax[0].imshow(image, cmap=cm.gray)
ax[0].set_title('Input image')
ax[0].set_axis_off()

ax[1].imshow(np.log(1 + h),
             extent=[np.rad2deg(theta[-1]), np.rad2deg(theta[0]), d[-1], d[0]],
             cmap=cm.gray, aspect=1/1.5)
ax[1].set_title('Hough transform')
ax[1].set_xlabel('Angles (degrees)')
ax[1].set_ylabel('Distance (pixels)')
ax[1].axis('image')

ax[2].imshow(image, cmap=cm.gray)
for _, angle, dist in zip(*hough_line_peaks(h, theta, d)):
    y0 = (dist - 0 * np.cos(angle)) / np.sin(angle)
    y1 = (dist - image.shape[1] * np.cos(angle)) / np.sin(angle)
    ax[2].plot((0, image.shape[1]), (y0, y1), '-r')
ax[2].set_xlim((0, image.shape[1]))
ax[2].set_ylim((image.shape[0], 0))
ax[2].set_axis_off()
ax[2].set_title('Detected lines')

plt.tight_layout()
plt.show()

# Line finding using the Probabilistic Hough Transform
image = data.camera()
edges = canny(image, 2, 1, 25)
lines = probabilistic_hough_line(edges, threshold=10, line_length=5,
                                 line_gap=3)

# Generating figure 2
fig, axes = plt.subplots(1, 3, figsize=(15, 5), sharex=True, sharey=True)
ax = axes.ravel()

ax[0].imshow(image, cmap=cm.gray)
ax[0].set_title('Input image')

ax[1].imshow(edges, cmap=cm.gray)
ax[1].set_title('Canny edges')

ax[2].imshow(edges * 0)
for line in lines:
    p0, p1 = line
    ax[2].plot((p0[0], p1[0]), (p0[1], p1[1]))
ax[2].set_xlim((0, image.shape[1]))
ax[2].set_ylim((image.shape[0], 0))
ax[2].set_title('Probabilistic Hough')

for a in ax:
    a.set_axis_off()

plt.tight_layout()
plt.show()

结果

Here's an approach这是一个方法

  • Convert image to grayscale将图像转换为灰度
  • Threshold to obtain binary image获取二值图像的阈值
  • Perform morphological operations to connect contours and smooth image执行形态学操作以连接轮廓和平滑图像
  • Find lines查找行

After converting to grayscale, we threshold image to obtain a binary image转换为灰度后,我们对图像进行阈值处理以获得二值图像

import cv2
import numpy as np

image = cv2.imread('1.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255,cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]

Next we perform morphological operations to connect contours接下来我们进行形态学操作来连接轮廓

kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (9,9))
close = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=3)

Finally we find the line using cv2.HoughLinesP()最后我们使用cv2.HoughLinesP()找到该行

minLineLength = 550
maxLineGap = 70
lines = cv2.HoughLinesP(close,1,np.pi/180,100,minLineLength,maxLineGap)
for line in lines:
    for x1,y1,x2,y2 in line:
        cv2.line(image,(x1,y1),(x2,y2),(36,255,12),3)

Instead of using cv2.HoughLinesP() , an alternative method is to find contours and filter using cv2.contourArea() .代替使用cv2.HoughLinesP() ,另一种方法是使用cv2.contourArea()查找轮廓和过滤器。 The largest contour will be our line.最大的轮廓将是我们的线。


Full code完整代码

import cv2
import numpy as np

image = cv2.imread('1.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255,cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]

kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (9,9))
close = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=3)

minLineLength = 550
maxLineGap = 70
lines = cv2.HoughLinesP(close,1,np.pi/180,100,minLineLength,maxLineGap)
for line in lines:
    for x1,y1,x2,y2 in line:
        cv2.line(image,(x1,y1),(x2,y2),(36,255,12),3)

cv2.imshow('thresh', thresh)
cv2.imshow('close', close)
cv2.imshow('image', image)
cv2.waitKey()

You most likely won't be able to use Hough transform to detect lines in a set of points.您很可能无法使用霍夫变换来检测一组点中的线。 Hough transform works with images.霍夫变换适用于图像。 Better yet, binarized images with edges marked as 1 and background stays as 0 .更好的是,边缘标记为1并且背景保持为0二值化图像。 So, forget about the Hough transform.所以,忘记霍夫变换。

For your particular case I'd suggest some kind of RANSAC algorithm, which looks for specific points following some rules, ignoring everything else.对于您的特定情况,我建议使用某种 RANSAC 算法,该算法会按照某些规则查找特定点,而忽略其他所有内容。 Though, in your case you have a lot (=too much) noise.但是,在您的情况下,您有很多(=太多)噪音。 If you can keep the noise points below 50%, RANSAC will do the trick.如果您可以将噪声点保持在 50% 以下,RANSAC 就能解决问题。 You may read the details here: OpenCV - Ransac fitting line您可以在此处阅读详细信息: OpenCV - Ransac 拟合线

Or here's the Wiki with the most generic explanation: https://en.wikipedia.org/wiki/RANSAC或者这里是具有最通用解释的 Wiki: https : //en.wikipedia.org/wiki/RANSAC

After spending some (alot) of time with the problem I eventually reached a solution that I was sattisfied with.在花了一些(很多)时间解决这个问题之后,我最终找到了一个令我满意的解决方案。

My solution is to go through the scan data as it is read (as a revolving scanner) and iteratively look at smalles sections of the data, then running a custom ransac algorithm to fit a line to the current segment.我的解决方案是在读取时浏览扫描数据(作为旋转扫描仪)并迭代查看数据的小部分,然后运行自定义 ransac 算法以将一条线拟合到当前段。

Then if the segment satisfies the critera for a possible line the segment is extended and checked again.然后,如果该段满足可能的线的标准,则该段被扩展并再次检查。 This is then repeated for all small segments of data in different ways.然后以不同方式对所有小数据段重复此操作。 In short, I used a custom, self written from scratch, iterative ransac line fit.简而言之,我使用了一个自定义的、从头开始编写的、迭代的ransac line fit。

If we take a similar example to what I initially gave:如果我们举一个与我最初给出的类似的例子: 在此处输入图片说明

The following result is now generated by the algorith:现在由算法生成以下结果: 在此处输入图片说明

And comparing to the actual (wall) map of the enviroment we can see the following comparison:并与环境的实际(墙壁)地图进行比较,我们可以看到以下比较:

在此处输入图片说明

Which I would say is good enough.我会说这已经足够了。 Another important note is that the algorith can be run for multiple scans of data with the enviroment naturally chaning slightly inbetween all of the scans (taking quite some time to execute):另一个重要的注意事项是,该算法可以针对多次数据扫描运行,而环境在所有扫描之间自然会略有变化(需要相当长的时间来执行):

在此处输入图片说明

As can bee seen there are some extra walls that are not a part of the map, which is to be expected and also some faulty findings that can be filtered since they are clear outliers.正如我们所看到的,有一些额外的墙不是地图的一部分,这是可以预料的,还有一些错误的发现可以过滤掉,因为它们是明显的异常值。

Now for the code.... The final solution is a 300 lines long python script converted though a .pyx file and compiled using Cython in order to improve time complexity.现在是代码......最终的解决方案是一个 300 行长的 python 脚本,通过.pyx文件转换并使用Cython编译,以提高时间复杂度。 If the code or maybe more importantly a psuedo code (since my implementation is tweeked to my specfic need) is wanted I can provide it given that someone will enjoy using/reading it :)如果需要代码或更重要的伪代码(因为我的实现是根据我的特定需求调整的),我可以提供它,因为有人会喜欢使用/阅读它:)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM