Python OpenCV HoughLinesP无法检测线 [英] Python OpenCV HoughLinesP Fails to Detect Lines

查看:635
本文介绍了Python OpenCV HoughLinesP无法检测线的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am using OpenCV HoughlinesP to find horizontal and vertical lines. It is not finding any lines most of the time. Even when it finds a lines it is not even close to actual image.

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)

输入图像:

输出图像:(请参见红线):

Output Image: (see the Red Line):

@ljetibo尝试使用以下方法: c_6.jpg

@ljetibo Try this with: c_6.jpg

推荐答案

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

There's quite a bit wrong here so I'll just start from the beginning.

好吧,打开图像后要做的第一件事就是打扰.我强烈建议您在 tresholding 以及阈值方法的确切含义.

Ok, first thing you do after opening an image is tresholding. I recommend strongly that you have another look at the OpenCV manual on tresholding and the exact meaning of the treshold methods.

手册中提到

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

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

特殊值THRESH_OTSU可以与上述之一结合使用 价值观.在这种情况下,该功能将确定最佳阈值 使用大津的算法的值,并使用它而不是指定的值 脱粒.

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_BINARY等...)强行组合 combine THRESH_OTSU,不幸的是,该手册可能像这样.此方法实际执行的操作是假定在遵循双峰直方图之后存在前景"和背景",然后应用我相信的THRESH_BINARY.

I know it's a bit confusing because you don't actully combine THRESH_OTSU with any of the other methods (THRESH_BINARY etc...), unfortunately that manual can be like that. What this method actually does is it assumes that there's a "foreground" and a "background" that follow a bi-modal histogram and then applies the THRESH_BINARY I believe.

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

Imagine this as if you're taking an image of a cathedral or a high building mid day. On a sunny day the sky will be very bright and blue, and the cathedral/building will be quite a bit darker. This means the group of pixels belonging to the sky will all have high brightness values, that is will be on the right side of the histogram, and the pixels belonging to the church will be darker, that is to the middle and left side of the histogram.

Otsu使用此方法尝试猜测正确的临界"点,即脱粒".对于您的图像,大津的业务.假设地图侧面的所有白色都是背景,而地图本身就是前景.因此,阈值化后的图像如下所示:

Otsu uses this to try and guess the right "cutoff" point, called thresh. For your image Otsu's alg. supposes that all that white on the side of the map is the background, and the map itself the foreground. Therefore your image after thresholding looks like this:

在此之后,不难猜测出了什么问题.但是,让我们继续,我相信您想要实现的目标是这样的:

After this point it's not hard to guess what goes wrong. But let's go on, What you're trying to achieve is, I believe, something like this:

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

然后继续,尝试侵蚀图像.我不确定为什么要这样做,是要加粗"线条,还是要消除噪音.无论如何,您从未将腐蚀的结果分配给某些东西. numpy数组(表示图像的方式)是可变的,但语法不是这样的:

Then you go on, and try to erode the image. I'm not sure why you're doing this, was your intention to "bold" the lines, or was your intention to remove noise. In any case you never assigned the result of erosion to something. Numpy arrays, which is the way images are represented, are mutable but it's not the way the syntax works:

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

所以你必须写:

b = cv2.erode(b,element)

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

Ok, now for the element and how the erosion works. Erosion drags a kernel over an image. Kernel is a simple matrix with 1's and 0's in it. One of the elements of that matrix, usually centre one, is called an anchor. An anchor is the element that will be replaced at the end of the operation. When you created

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

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

what you created is actually a 1x1 matrix (1 column, 1 row). This makes erosion completely useless.

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

What erosion does, is firstly retrieves all the values of pixel brightness from the original image where the kernel element, overlapping the image segment, has a "1". Then it finds a minimal value of retrieved pixels and replaces the anchor with that value.

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

What this means, in your case, is that you drag [1] matrix over the image, compare if the source image pixel brightness is larger, equal or smaller than itself and then you replace it with itself.

如果您打算去除噪音",那么最好在图像上使用矩形内核.这样想,噪音"就是不适合"周围环境的东西.因此,如果将中心像素与周围环境进行比较,发现它不合适,则很可能是噪声.

If your intention was to remove "noise", then it's probably better to use a rectangular kernel over the image. Think of it this way, "noise" is that thing that "doesn't fit in" with the surroundings. So if you compare your centre pixel with it's surroundings and you find it doesn't fit, it's most likely noise.

此外,我已经说过,它将锚替换为内核检索到的最小值.在数值上,最小值是0,这恰好是图像中黑色的表示方式.这意味着在您的图像主要为白色的情况下,腐蚀会膨胀"黑色像素.如果在内核可及范围内,侵蚀会将255个值的白色像素替换为0个值的黑色像素.无论如何,它永远都不应该是形状(1,1).

Additionally, I've said it replaces the anchor with the minimal value retrieved by the kernel. Numerically, minimal value is 0, which is coincidentally how black is represented in the image. This means that in your case of a predominantly white image, erosion would "bloat up" the black pixels. Erosion would replace the 255 valued white pixels with 0 valued black pixels if they're in the reach of the kernel. In any case it shouldn't be of a shape (1,1), ever.

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

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

If we erode the second image with a 3x3 rectangular kernel we get the image bellow.

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

Ok, now we got that out of the way, next thing you do is you find edges using Canny edge detection. The image you get from that is:

好吧,现在我们寻找完全垂直线和完全水平线.当然,除了图像左侧的子午线以外,没有其他线条(就是所谓的吗?),正确处理后得到的最终图像将是:

Ok, now we look for EXACTLY vertical and EXACTLY horizontal lines ONLY. Of course there are no such lines apart from the meridian on the left of the image (is that what it's called?) and the end image you get after you did it right would be this:

现在,由于您从未描述过确切的想法,而我最好的猜测是您想要平行线和子午线,因此在比例较小的地图上会有更多的运气,因为这些不是以直线开头的,而是曲线.此外,是否有特定原因可以完成概率霍夫测验? 常规"霍夫还不够吗?

Now since you never described your exact idea, and my best guess is that you want the parallels and meridians, you'll have more luck on maps with lesser scale because those aren't lines to begin with, they are curves. Additionally, is there a specific reason to get a Probability Hough done? The "regular" Hough doesn't suffice?

很抱歉,篇幅太长,希望对您有所帮助.

Sorry for the too-long post, hope it helps a bit.

此处的文字是根据11月24日的操作说明的要求而添加的.因为没有办法将答案放入字符有限的评论中.

Text here was added as a request for clarification from the OP Nov. 24th. because there's no way to fit the answer into a char limited comment.

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

I'd suggest OP asks a new question more specific to the detection of curves because you are dealing with curves op, not horizontal and vertical lines.

有几种检测曲线的方法,但都不容易.按照从最容易实现到最困难的顺序:

There are several ways to detect curves but none of them are easy. In the order of simplest-to-implement to hardest:

  1. 使用RANSAC算法.制定一个描述多头性质的公式.和拉特.线取决于所讨论的地图. IE.当您靠近赤道时,纬度曲线几乎是地图上的一条完美的直线,赤道是一条完美的直线,但是当您处于高纬度时(极点附近),它将非常弯曲,类似于圆弧段). SciPy已经将 RANSAC 实现为一个类,您所要做的就是找到并以编程方式定义要尝试拟合曲线的模型.当然,在此处一个>.这是最简单的,因为您要做的就是数学.
  2. 要做的困难一点是创建一个矩形网格,然后尝试使用cv findHomography将网格扭曲到图像上的适当位置.对于各种几何变换,您可以对网格进行操作,可以查看 OpenCv手册.这是一种骇客"的方法,可能比1更糟糕.因为它取决于您可以重新创建一个网格的事实,该网格上有足够的细节和对象,从而cv可以识别您尝试的图像上的结构扭曲它.这需要您执行与1.类似的数学运算,并且只需执行少量编码即可从几种不同的功能中构成最终解决方案.
  3. 要真正做到这一点.在数学上有很整洁的方式将曲线描述为曲线上的切线列表.您可以尝试将一堆较短的HoughLines拟合到您的图像或图像段中,然后尝试将所有找到的线分组,并通过假定它们与曲线相切来确定它们是否确实遵循所需形状的曲线或他们随机.有关此问题,请参见本文.在所有方法中,这是最难的一种,因为它需要大量的单独编码和一些数学方法.
  1. Use RANSAC algorithm. Develop a formula describing the nature of the long. and lat. lines depending on the map in question. I.e. latitude curves will almost be a perfect straight lines on the map when you're near the equator, with the equator being the perfectly straight line, but will be very curved, resembling circle segments, when you're at high latitudes (near the poles). SciPy already has 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. This is the easiest because all you have to do is the math.
  2. A bit harder to do would be to create a rectangular grid and then try to use cv findHomography to warp the grid into place on the image. For various geometric transformations you can do to the grid you can check out OpenCv manual. This is sort of a hack-ish approach and might work worse than 1. because it depends on the fact that you can re-create a grid with enough details and objects on it that cv can identify the structures on the image you're trying to warp it to. This one requires you to do similar math to 1. and just a bit of coding to compose the end solution out of several different functions.
  3. To actually do it. There are mathematically neat ways of describing curves as a list of tangent lines on the curve. You can try to fit a bunch of shorter HoughLines to your image or image segment and then try to group all found lines and determine, by assuming that they're tangents to a curve, if they really follow a curve of the desired shape or are they random. See this paper on this matter. Out of all approaches this one is the hardest because it requires a quite a bit of solo-coding and some math about the method.

可能有更简单的方法,我以前从未真正进行过曲线检测.我不知道,也许有一些技巧可以使它变得更容易.如果您提出一个新问题,但尚未结束作为一个答案,您可能会有更多的人注意到它.一定要问一个关于您感兴趣的确切主题的完整问题.人们通常不会花太多时间在如此广泛的主题上写作.

There could be easier ways, I've never actually had to deal with curve detection before. Maybe there are tricks to do it easier, I don't know. If you ask a new question, one that hasn't been closed as an answer already you might have more people notice it. Do make sure to ask a full and complete question on the exact topic you're interested in. People won't usually spend so much time writing on such a broad topic.

要向您展示使用Hough变换可以做什么,请查看下面的内容:

To show you what you can do with just Hough transform check out bellow:

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条件绘制.将原始图像与在其上绘制线条的图像进行比较时,请格外注意.相似之处是不可思议的(嘿,明白吗?),但是由于它们之间没有线条,所以很多相似之处只是看起来像垃圾. (尤其是检测到的最高纬度线似乎过于倾斜",但实际上,正如霍夫算法所要求的那样,这些线在最粗点与纬线完美切线).认识到使用线检测算法检测曲线存在局限性

As you can see from the image bellow, straight lines are only longitudes. Latitudes are not as straight therefore for each latitude you have several detected lines that behave like tangents on the line. Blue drawn lines are drawn by the if abs( np.cos( alph - 180 )) > 0.8: while the red drawn lines are drawn by rho>0 and abs( np.cos( alphdeg - 90)) > 0.7 condition. Pay close attention when comparing the original image with the image with lines drawn on it. The resemblance is uncanny (heh, get it?) but because they're not lines a lot of it only looks like junk. (especially that highest detected latitude line that seems like it's too "angled" but in reality those lines make a perfect tangent to the latitude line on its thickest point, just as hough algorithm demands it). Acknowledge that there are limitations to detecting curves with a line detection algorithm

这篇关于Python OpenCV HoughLinesP无法检测线的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆