Python OpenCV HoughLinesP 检测线失败

Python OpenCV HoughLinesP Fails to Detect Lines

我正在使用 OpenCV HoughlinesP 查找水平线和垂直线。它大部分时间都找不到任何线路。即使它找到一条线,它甚至不接近实际图像。

import cv2
import numpy as np

img = cv2.imread('image_with_edges.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)


flag,b = cv2.threshold(gray,0,255,cv2.THRESH_OTSU)

element = cv2.getStructuringElement(cv2.MORPH_CROSS,(1,1))
cv2.erode(b,element)

edges = cv2.Canny(b,10,100,apertureSize = 3)

lines = cv2.HoughLinesP(edges,1,np.pi/2,275, minLineLength = 100, maxLineGap = 200)[0].tolist()

for x1,y1,x2,y2 in lines:
   for index, (x3,y3,x4,y4) in enumerate(lines):

    if y1==y2 and y3==y4: # Horizontal Lines
        diff = abs(y1-y3)
    elif x1==x2 and x3==x4: # Vertical Lines
        diff = abs(x1-x3)
    else:
        diff = 0

    if diff < 10 and diff is not 0:
        del lines[index]

    gridsize = (len(lines) - 2) / 2

   cv2.line(img,(x1,y1),(x2,y2),(0,0,255),2)
   cv2.imwrite('houghlines3.jpg',img)

输入图片:

输出图像:(见红线):

@ljetibo 试试这个: c_6.jpg

这里有很多错误,所以我将从头开始。

好的,打开图像后要做的第一件事就是阈值处理。我强烈建议您再看看 tresholding and the exact meaning of the treshold methods 上的 OpenCV 手册。

手册中提到

cv2.threshold(src, thresh, maxval, type[, dst]) → retval, dst

the special value THRESH_OTSU may be combined with one of the above values. In this case, the function determines the optimal threshold value using the Otsu’s algorithm and uses it instead of the specified thresh .

我知道这有点令人困惑,因为您实际上并没有 THRESH_OTSU 与任何其他方法(THRESH_BINARY 等...)结合使用,不幸的是,手册可能就是这样。这个方法实际上做的是它假设有一个 "foreground" 和一个 "background" 遵循双峰直方图,然后应用我相信的 THRESH_BINARY。

想象一下,就像您在中午拍摄大教堂或高层建筑的照片一样。在阳光明媚的日子里,天空会非常明亮和蔚蓝,而 cathedral/building 会更暗一些。这意味着属于天空的像素组都将具有高亮度值,即位于直方图的右侧,属于教堂的像素将较暗,即位于直方图的中间和左侧直方图。

Otsu 使用它来尝试猜测正确的 "cutoff" 点,称为阈值。对于您的图像 Otsu 的 alg。假设地图侧面的所有白色是背景,地图本身是前景。因此,阈值处理后的图像如下所示:

在这一点之后,不难猜出哪里出了问题。但是让我们继续,我相信你想要实现的是这样的:

flag,b = cv2.threshold(gray,160,255,cv2.THRESH_BINARY)

那你继续,侵蚀图像。我不确定您为什么要这样做,是要 "bold" 线条,还是要消除噪音。无论如何,您从未将侵蚀的结果分配给某物。 Numpy 数组,这是图像的表示方式,是可变的,但它不是语法的工作方式:

cv2.erode(src, kernel, [optionalOptions] ) → dst

所以你必须写:

b = cv2.erode(b,element)

好的,现在介绍元素以及侵蚀的工作原理。侵蚀将内核拖到图像上。内核是一个简单的矩阵,其中包含 1 和 0。该矩阵的元素之一,通常是中心元素,称为锚点。锚点是将在操作结束时被替换的元素。当您创建

cv2.getStructuringElement(cv2.MORPH_CROSS, (1, 1))

您创建的实际上是一个 1x1 矩阵(1 列,1 行)。这使得侵蚀完全无用。

侵蚀的作用是首先从原始图像中检索所有像素亮度值,其中与图像片段重叠的内核元素具有“1”。然后它找到检索到的像素的最小值并用该值替换锚点。

在您的情况下,这意味着您将 [1] 矩阵拖到图像上,比较源图像像素亮度是否大于、等于或小于自身,然后将其替换为自身。

如果您打算删除 "noise",那么最好在图像上使用矩形内核。这么想,"noise"就是那个跟周围环境"doesn't fit in"的东西。因此,如果您将中心像素与其周围的像素进行比较,并且发现它不合适,则很可能是噪声。

此外,我说过它用内核检索到的最小值替换锚点。在数值上,最小值为 0,这恰好是黑色在图像中的表示方式。这意味着在您的图像以白色为主的情况下,侵蚀会 "bloat up" 黑色像素。侵蚀会将 255 值的白色像素替换为 0 值的黑色像素(如果它们在内核的范围内)。在任何情况下,它都不应该是 (1,1) 的形状。

>>> cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
array([[0, 1, 0],
       [1, 1, 1],
       [0, 1, 0]], dtype=uint8)

如果我们用 3x3 矩形核腐蚀第二张图像,我们会得到下面的图像。

好的,现在我们已经解决了这个问题,接下来要做的是使用 Canny 边缘检测找到边缘。你从中得到的图像是:

好的,现在我们寻找 EXACTLY 垂直线和 EXACTLY 水平线 ONLY。当然,除了图像左侧的子午线之外,没有这样的线(这就是所谓的吗?),正确完成后得到的最终图像将是这样的:

既然你从来没有描述过你的确切想法,我最好的猜测是你想要纬线和经线,你在比例尺较小的地图上会更幸运,因为这些不是直线,它们是曲线。此外,完成概率霍夫是否有特定原因? "regular" 霍夫还不够吗?

抱歉太长了post,希望对您有所帮助。


此处的文本是作为 11 月 24 日 OP 的澄清请求添加的。因为无法将答案放入字符有限的评论中。

我建议 OP 问一个更具体的新问题 curves 因为你正在处理曲线 op,而不是水平和垂直 lines.

检测曲线的方法有多种,但其中 none 很简单。按照从最简单到最难实现的顺序:

  1. 使用RANSAC算法。开发一个描述多头性质的公式。和纬度。线路取决于所讨论的地图。 IE。当你靠近赤道时,纬度曲线在地图上几乎是一条完美的直线,赤道是一条完美的直线,但当你处于高纬度(靠近两极)时,它会非常弯曲,类似于圆弧段). SciPy 已经有 RANSAC implemented as a class all you have to do is find and the programatically define the model you want to try to fit to the curves. Of course there's the ever-usefull 4dummies text here。这是最简单的,因为你所要做的就是数学。
  2. 有点难做的是创建一个矩形网格,然后尝试使用 cv findHomography 将网格扭曲到图像上的位置。对于您可以对网格进行的各种几何变换,您可以查看 OpenCv manual。这是一种 hack-ish 方法,可能比 1 更糟糕。因为它取决于这样一个事实,即您可以重新创建一个包含足够细节和对象的网格,以便 cv 可以识别您正在尝试的图像上的结构把它扭曲到。这需要您进行与 1. 类似的数学运算,并且只需编写一些代码即可从几个不同的函数中组成最终解决方案。
  3. 真正做到。有一些数学上简洁的方法可以将曲线描述为曲线上的切线列表。您可以尝试将一堆较短的 HoughLines 适合您的图像或图像片段,然后尝试对所有找到的线进行分组,并通过假设它们与曲线相切来确定它们是否真的遵循所需形状的曲线或者是他们随机的。关于此事,请参阅 this paper。在所有方法中,这种方法是最难的,因为它需要相当多的独立编码和一些关于该方法的数学知识。

可能有更简单的方法,我以前从未真正处理过曲线检测。也许有一些技巧可以更轻松地做到这一点,我不知道。如果您提出一个新问题,一个尚未作为答案关闭的问题,您可能会有更多人注意到它。请确保就您感兴趣的确切主题提出完整而完整的问题。人们通常不会花太多时间写这么广泛的主题。

为了向您展示仅使用 Hough 变换可以做什么,请查看以下内容:

import cv2
import numpy as np

def draw_lines(hough, image, nlines):
   n_x, n_y=image.shape
   #convert to color image so that you can see the lines
   draw_im = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)

   for (rho, theta) in hough[0][:nlines]:
      try:
         x0 = np.cos(theta)*rho
         y0 = np.sin(theta)*rho
         pt1 = ( int(x0 + (n_x+n_y)*(-np.sin(theta))),
                 int(y0 + (n_x+n_y)*np.cos(theta)) )
         pt2 = ( int(x0 - (n_x+n_y)*(-np.sin(theta))),
                 int(y0 - (n_x+n_y)*np.cos(theta)) )
         alph = np.arctan( (pt2[1]-pt1[1])/( pt2[0]-pt1[0]) )
         alphdeg = alph*180/np.pi
         #OpenCv uses weird angle system, see: http://docs.opencv.org/3.0-beta/doc/py_tutorials/py_imgproc/py_houghlines/py_houghlines.html
         if abs( np.cos( alph - 180 )) > 0.8: #0.995:
            cv2.line(draw_im, pt1, pt2, (255,0,0), 2)
         if rho>0 and abs( np.cos( alphdeg - 90)) > 0.7:
            cv2.line(draw_im, pt1, pt2, (0,0,255), 2)    
      except:
         pass
   cv2.imwrite("/home/dino/Desktop/3HoughLines.png", draw_im,
             [cv2.IMWRITE_PNG_COMPRESSION, 12])   

img = cv2.imread('a.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

flag,b = cv2.threshold(gray,160,255,cv2.THRESH_BINARY)
cv2.imwrite("1tresh.jpg", b)

element = np.ones((3,3))
b = cv2.erode(b,element)
cv2.imwrite("2erodedtresh.jpg", b)

edges = cv2.Canny(b,10,100,apertureSize = 3)
cv2.imwrite("3Canny.jpg", edges)

hough = cv2.HoughLines(edges, 1, np.pi/180, 200)   
draw_lines(hough, b, 100)

从下图中可以看出,直线只是经度。纬度不是那么直,因此对于每个纬度,您都有几条检测到的线,这些线的行为类似于线上的切线。蓝色线条由 if abs( np.cos( alph - 180 )) > 0.8: 条件绘制,而红色线条由 rho>0 and abs( np.cos( alphdeg - 90)) > 0.7 条件绘制。将原始图像与画有线条的图像进行比较时要特别注意。这种相似是不可思议的(嘿,明白了吗?)但是因为它们不是线条,所以很多看起来只是垃圾。 (尤其是检测到的最高纬度线,它看起来太 "angled" 但实际上这些线在其最粗点处与纬度线完美相切,正如霍夫算法所要求的那样)。承认使用直线检测算法检测曲线存在局限性