6

我已尽力了解如何使用 OpenCV 进行线检测。但是,我找不到我正在寻找的示例。我想用它在简单的二维点云中找到线。作为测试,我想使用以下几点:

在此处输入图像描述

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()

我发现了很多关于 Hough Tranform 工作原理的初始示例。但是,当涉及到代码示例时,我只能发现使用了图像。

有没有办法使用 OpenCV 霍​​夫变换来检测一组点中的线,或者你能推荐任何其他方法或库吗?

- - 编辑 - -

在阅读了一些很棒的答案之后,我觉得我可以描述我打算更好地使用它的目的。我有一个高分辨率 2D 激光雷达,需要从数据中提取墙壁。典型扫描可能如下所示: 在此处输入图像描述

“正确的输出”看起来像这样: 在此处输入图像描述

在我做了更多研究之后,我怀疑霍夫变换在这种情况下不是最适合使用的。关于我应该寻找什么的任何提示?

(如果有人感兴趣,可以使用 LiDAR 和墙壁提取来生成地图和导航机器人。)

谢谢,雅各布

4

4 回答 4

2

这是一种方法

  • 将图像转换为灰度
  • 获取二值图像的阈值
  • 执行形态学操作以连接轮廓和平滑图像
  • 查找行

转换为灰度后,我们对图像进行阈值处理,得到二值图像

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)

最后我们使用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)

替代使用cv2.HoughLinesP(),另一种方法是使用 查找轮廓并过滤cv2.contourArea()。最大的轮廓将是我们的线。


完整代码

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()
于 2019-08-27T01:49:45.507 回答
2

一种方法是按照这些跳过边缘检测部分的幻灯片自己实施霍夫变换。

或者,您可以从点列表中创建图像,例如

#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

按照这个问题的答案

编辑:不要提供给 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()

结果

于 2019-08-19T14:26:28.043 回答
1

您很可能无法使用霍夫变换来检测一组点中的线。霍夫变换适用于图像。更好的是,边缘标记为1和背景的二值化图像保持为0. 所以,忘记霍夫变换。

对于您的特定情况,我建议使用某种 RANSAC 算法,它会按照某些规则查找特定点,而忽略其他所有内容。但是,在您的情况下,您有很多(=太多)噪音。如果您可以将噪声点保持在 50% 以下,RANSAC 就可以解决问题。您可以在此处阅读详细信息:OpenCV - Ransac 拟合线

或者这是具有最通用解释的 Wiki:https ://en.wikipedia.org/wiki/RANSAC

于 2019-08-19T15:59:58.053 回答
0

在花了一些(很多)时间解决这个问题后,我最终找到了一个我满意的解决方案。

我的解决方案是在读取扫描数据时(作为旋转扫描仪)并迭代查看数据的较小部分,然后运行自定义 ransac 算法以将一条线拟合到当前段。

然后,如果该段满足可能线的标准,则该段被扩展并再次检查。然后以不同的方式对所有小数据段重复此操作。简而言之,我使用了一个自定义的、从头开始编写的、迭代的ransac line fit。

如果我们举一个与我最初给出的类似的例子: 在此处输入图像描述

算法现在生成以下结果: 在此处输入图像描述

与环境的实际(墙壁)地图相比,我们可以看到以下比较:

在此处输入图像描述

我会说这已经足够好了。另一个重要的注意事项是,该算法可以针对数据的多次扫描运行,而环境在所有扫描之间自然会略有变化(需要相当长的时间来执行):

在此处输入图像描述

可以看出,有一些额外的墙不是地图的一部分,这是可以预料的,还有一些错误的发现可以过滤掉,因为它们是明显的异常值。

现在是代码......最终的解决方案是一个 300 行长的 python 脚本,通过.pyx文件转换并使用它进行编译Cython,以提高时间复杂度。如果需要代码或更重要的是伪代码(因为我的实现是根据我的特定需要调整的),我可以提供它,因为有人会喜欢使用/阅读它:)

于 2020-07-02T12:06:56.017 回答