Python OpenCV HoughLinesP无法检测到直线

3

我正在使用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)

输入图像: input image

输出图像:(看红线): enter image description here

@ljetibo 试试这个: c_6.jpg

1个回答

20

这里有些问题,所以我会从头开始讲起。

好的,打开图像后第一件事就是二值化。我强烈建议您再次查看OpenCV手册中的二值化二值化方法的确切含义。

手册提到:

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

特殊值THRESH_OTSU可以与上述任何一种值组合。在这种情况下,函数使用Otsu算法确定最优阈值并将其用于指定的thresh。

我知道这有点令人困惑,因为实际上您并没有将THRESH_OTSU与其他方法(THRESH_BINARY等)“组合”,不幸的是手册可能会出现这种情况。这种方法实际上假设存在遵循双峰直方图的“前景”和“背景”,然后应用THRESH_BINARY。

将其想象为在正午拍摄大教堂或高层建筑的图像。在晴朗的一天,天空会非常明亮和蓝色,而教堂/建筑物会比较暗淡。这意味着属于天空的像素组将具有高亮度值,即在直方图的右侧,而属于教堂的像素将更暗淡,即在直方图的中间和左侧。

Otsu使用此方法尝试猜测正确的“截止点”,称为thresh。对于您的图像,Otsu的算法假设地图侧面的所有白色都是背景,地图本身是前景。因此,在阈值处理后,您的图像看起来像这样:

Image after OP's thresholding

在此之后,不难猜出发生了什么问题。但是,让我们继续讲下去,我相信您想要实现的是这样的效果:

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

手动猜测阈值的图像。

然后你继续进行,并尝试侵蚀图像。我不确定你为什么这样做,是你想要“加粗”线条,还是你想消除噪声。无论如何,你从未将侵蚀的结果分配给任何变量。 Numpy数组表示图像的方式可变,但语法并非如此使用:

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

所以你需要编写:

b = cv2.erode(b,element)

好的,现在让我们来谈谈元素以及腐蚀算法的工作原理。腐蚀算法会将一个矩阵(即 Kernel)拖过一张图像。Kernel 是一个简单的矩阵,其中包含了 1 和 0。通常情况下,Kernel 的其中一个元素被称为锚点,通常是中心元素。锚点是在操作结束时将被替换的元素。当您创建好这个 Kernel 后,

cv2.getStructuringElement(cv2.MORPH_CROSS, (1, 1))
你所创建的实际上是一个1x1矩阵(1列,1行)。这使得侵蚀操作完全无用。
侵蚀操作首先从原始图像中检索覆盖图像段的内核元素具有“1”的像素亮度值。然后它找到检索到的像素的最小值,并用该值替换锚点。
在你的情况下,这意味着你将[1]矩阵拖动到图像上,比较源图像像素亮度与其自身大小是否相等或更小,然后用其自身替换它。
如果你的目的是去除“噪声”,那么最好使用一个矩形内核来处理图像。可以这样想,“噪声”就是那些“不适合”周围环境的东西。因此,如果你将中心像素与周围像素进行比较,并发现它不适合,则很可能是噪声。
此外,我已经说了它会用内核检索到的最小值替换锚点。数字上,最小值是0,这恰好是图像中黑色表示的值。这意味着在你主要是白色的图像中,侵蚀操作会“膨胀”黑色像素。如果255值的白色像素在内核范围内,侵蚀操作会用0值的黑色像素替换它们。无论如何,它永远不应该是形状为(1,1)的矩阵。
>>> cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
array([[0, 1, 0],
       [1, 1, 1],
       [0, 1, 0]], dtype=uint8)
如果我们使用3x3的矩形核对第二幅图像进行腐蚀操作,得到的图像如下所示。 Eroded threshed image. 现在,我们需要用Canny边缘检测找到边缘。得到的图像如下所示: Canny edges 现在,我们只需寻找完全垂直和完全水平的线而已。当然,除了图像左侧的子午线之外,没有这样的线条。如果您正确执行了上述步骤,将获得以下最终图像: enter image description here 由于您从未描述您的确切想法,我猜测您要的是平行线和子午线。对于比例更小的地图,您会更有运气,因为那些本来就不是线,而是曲线。此外,是否有特定原因要进行概率霍夫变换?常规霍夫变换不足吗?
抱歉这篇文章太长了,希望能对您有所帮助。
为了澄清问题,下面的文本是于11月24日添加的请求。
我建议您提出一个更具体的问题,以便检测曲线,因为您正在处理曲线,而不是水平和垂直的线条
检测曲线有几种方法,但没有一种方法是容易的。按照从最简单到最难的顺序列出如下:
  1. 使用RANSAC算法。开发一个公式,描述经度和纬度线的性质取决于所涉及的地图。即,当你接近赤道时,纬度曲线几乎是一条完美的直线,在地图上表现为一条完全的直线,但当你在高纬度(靠近极点)时,它们将非常弯曲,类似于圆弧段。SciPy已经实现了RANSAC,你只需要找到并以程序方式定义你想要尝试拟合曲线的模型即可。当然还有对于初学者的指南文本在这里。这是最容易的,因为所有你需要做的就是数学计算。
  2. 稍微难一些的方法是创建一个矩形网格,然后尝试使用cv findHomography将网格变形并放置在图像中。你可以查看OpenCv手册,了解可以应用于网格的各种几何变换。这是一种类似于“hack-ish”的方法,可能比第一种方法效果更差,因为它依赖于您可以重新创建具有足够细节和对象的网格,以使cv能够识别您要将其变形到的图像上的结构。这个方法需要你做类似于1.的数学运算,并编写一些代码,将多个不同的函数组合成最终的解决方案。
  3. 实际上完成曲线检测还存在一些数学上很好的方法,例如将曲线描述为曲线上的切线列表。你可以尝试拟合一堆较短的HoughLines到你的图像或图像段,并尝试将所有发现的线条分组,并通过假设它们是曲线的切线来确定它们是否真正遵循所需形状的曲线,还是随机的。请参见这篇论文了解详细信息。在所有方法中,这是最困难的,因为它需要相当多的单独编码和一些关于该方法的数学知识。

可能有更简单的方法,我以前从未处理过曲线检测。也许有更容易的技巧,我不知道。如果你提出一个新问题,一个还没有被视为答案的问题,你可能会得到更多人的关注。确保就您感兴趣的确切主题提出一个完整而完整的问题。人们通常不会花费太多时间在如此广泛的主题上写作。

看看以下内容,了解仅使用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条件绘制的。在比较原始图像和绘制线条的图像时,请注意细节。它们非常相似,但由于它们不是直线,很多内容看起来像垃圾(特别是最高检测到的纬度线,似乎太“倾斜”了,但实际上这些线在其最厚点处与纬度线呈完美的切线,正如hough算法所要求的)。请承认使用线检测算法检测曲线存在局限性。

最佳检测到的线条


我需要捕捉水平纬线和垂直经线。我遇到的问题是在山区附近会收到很多误报。我使用长度来排除大部分,但仍然有很多误报。有什么建议吗? - user914425
@user914425; 请检查下面水平线下方添加的文本。在评论中没有地方可以这样做。 - ljetibo
我只得到了一个蓝线(在子午线处),没有红线。我有更困难的问题要解决。我有一张地图,上面有很多山和地形。(请参见上面的c_6.jpg)你的脚本没有检测到任何山。在完成所有这些之后,最好的方法是什么来检测经纬度交叉点,这就是我想要的? - user914425
1
如果您将方程重写为“一般”形式(Ax+By=CDx+Ey=F),而不是“斜率-截距”形式,那么您所要做的就是解线性方程组的行列式,最简单的方法是使用Cramer法则来获得截距点。我不知道您遇到了什么问题,这段代码对我来说运行得很好。我一直在告诉您不能完全像这样做,也不能将此片段应用于您的问题。您不想承认的是有限制的。 - ljetibo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接