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)

输入图像: 输入图像

输出图像:(见红线): 在此处输入图像描述

@ljetibo 试试这个: c_6.jpg

4

1 回答 1

20

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

好的,打开图像后你要做的第一件事就是阈值化。我强烈建议您再看一下关于阈值的 OpenCV 手册以及阈值方法的确切含义。

手册中提到

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

特殊值 THRESH_OTSU 可以与上述值之一组合。在这种情况下,该函数使用 Otsu 算法确定最佳阈值,并使用它代替指定的 thresh 。

我知道这有点令人困惑,因为您实际上并没有THRESH_OTSU 与任何其他方法(THRESH_BINARY 等)结合起来,不幸的是,该手册可能是这样的。这个方法实际上做的是它假设有一个“前景”和一个“背景”遵循一个双模态直方图,然后应用我相信的 THRESH_BINARY。

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

Otsu 使用它来尝试猜测正确的“截止”点,称为 thresh。为了您的形象大津的算法。假设地图一侧的所有白色都是背景,而地图本身就是前景。因此,阈值处理后的图像如下所示:

OP 阈值处理后的图像

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

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

具有手动猜测阈值的图像。

然后你继续,并尝试侵蚀图像。我不确定您为什么要这样做,是您打算“加粗”线条,还是您打算消除噪音。无论如何,您从未将侵蚀的结果分配给某物。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]矩阵拖动到图像上,比较源图像像素亮度是否大于、等于或小于自身,然后将其替换为自身。

如果您的意图是消除“噪音”,那么在图像上使用矩形内核可能会更好。这样想,“噪音”就是与周围环境“格格不入”的东西。因此,如果您将中心像素与周围环境进行比较,发现它不适合,则很可能是噪声。

此外,我说过它用内核检索到的最小值替换了锚点。数值上,最小值为 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 矩形内核腐蚀第二个图像,我们会得到下面的图像。

被侵蚀的脱粒图像。

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

精明的边缘

好的,现在我们只寻找完全垂直和完全水平的线。当然,除了图像左侧的子午线之外没有这样的线条(这就是它的名字吗?),你做对后得到的最终图像是这样的:

在此处输入图像描述

现在,由于您从未描述过您的确切想法,而我最好的猜测是您想要平行线和经线,因此您在比例较小的地图上会更幸运,因为它们一开始不是直线,而是曲线。此外,是否有特定的理由来完成概率霍夫?“常规”霍夫还不够吗?

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


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

我建议 OP 提出一个更具体于曲线检测的新问题,因为您正在处理曲线 op,而不是水平线和垂直线

有几种检测曲线的方法,但没有一个是容易的。按照从最简单到最难的顺序:

  1. 使用 RANSAC 算法。制定一个描述多头性质的公式。和纬度。线路取决于相关地图。即,当您靠近赤道时,纬度曲线在地图上几乎是一条完美的直线,而赤道是完美的直线,但当您处于高纬度时(靠近极)。SciPy 已经将RANSAC实现为一个类,您所要做的就是找到并以编程方式定义您想要尝试拟合曲线的模型。当然这里有永远有用的 4dummies文本。这是最简单的,因为您所要做的就是数学。
  2. 更难做的是创建一个矩形网格,然后尝试使用 cv findHomography 将网格扭曲到图像上的适当位置。对于您可以对网格进行的各种几何变换,您可以查看OpenCv 手册。这是一种 hack-ish 方法,可能比 1 效果更差。因为这取决于您可以重新创建一个网格,其中包含足够的细节和对象,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条件绘制的。将原始图像与带有线条的图像进行比较时,请密切注意。相似之处是不可思议的(嘿,明白了吗?)但因为它们不是线条,所以很多它看起来像垃圾。(尤其是检测到的最高纬度线,它看起来太“倾斜”,但实际上这些线在其最厚点与纬度线完美切线,正如霍夫算法所要求的那样)。承认使用直线检测算法检测曲线存在局限性

最好的检测线。

于 2015-11-21T02:54:56.653 回答