Python OpenCV HoughLinesP 无法检测线
Posted
技术标签:
【中文标题】Python OpenCV HoughLinesP 无法检测线【英文标题】:Python OpenCV HoughLinesP Fails to Detect Lines 【发布时间】:2015-11-21 00:15:26 【问题描述】:我正在使用 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
【问题讨论】:
【参考方案1】:这里有点错误,所以我从头开始。
好的,打开图像后您要做的第一件事就是阈值化。我强烈建议您再看看tresholding 上的OpenCV 手册以及treshold methods 的确切含义。
手册中提到
cv2.threshold(src, thresh, maxval, type[, dst]) → retval, dst
特殊值 THRESH_OTSU 可以与上述之一组合 价值观。在这种情况下,函数确定最佳阈值 使用 Otsu 算法的值并使用它而不是指定的 脱粒。
我知道这有点令人困惑,因为您实际上并没有将 THRESH_OTSU 与任何其他方法(THRESH_BINARY 等...)结合,不幸的是,该手册可能是这样的。这个方法实际上做的是它假设有一个“前景”和一个“背景”遵循一个双模态直方图,然后应用我相信的 THRESH_BINARY。
想象一下,如果您在中午拍摄大教堂或高层建筑的照片。在阳光明媚的日子里,天空会非常明亮和蔚蓝,而大教堂/建筑会有点暗。这意味着属于天空的像素组都会有高亮度值,即在直方图的右侧,而属于教堂的像素会更暗,即在直方图的中间和左侧。直方图。
Otsu 使用它来尝试猜测正确的“截止”点,称为 thresh。为了您的形象大津的算法。假设地图一侧的所有白色都是背景,而地图本身就是前景。因此,阈值处理后的图像如下所示:
在这一点之后,不难猜测出了什么问题。但是让我们继续,我相信你想要实现的是这样的:
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 边缘检测找到边缘。你从中得到的图像是:
好的,现在我们寻找EXACTLY垂直线和EXACTLY水平线ONLY。当然除了图片左边的子午线之外没有这样的线条(这就是它的名字吗?),你做对后得到的最终图像是这样的:
现在,由于您从未描述过您的确切想法,而我最好的猜测是您想要平行线和经线,因此您在比例较小的地图上会更幸运,因为它们一开始不是直线,而是曲线。此外,是否有特定的理由来完成概率霍夫? “常规”霍夫不够用?
抱歉,帖子太长了,希望对你有所帮助。
此处的文字是作为 OP 11 月 24 日的澄清请求而添加的。因为没有办法将答案放入字符有限的评论中。
我建议 OP 针对 curves 的检测提出一个更具体的新问题,因为您处理的是曲线 op,而不是水平和垂直 lines。
检测曲线的方法有多种,但都不是简单的方法。按照从最简单到最难的顺序:
-
使用 RANSAC 算法。制定一个描述多头性质的公式。和纬度。线路取决于相关地图。 IE。当您靠近赤道时,纬度曲线在地图上几乎是一条完美的直线,赤道是完美的直线,但当您处于高纬度(靠近两极)时,纬度曲线会非常弯曲,类似于圆段)。 SciPy 已经将RANSAC 实现为一个类,您所要做的就是找到并以编程方式定义您想要尝试拟合曲线的模型。当然还有永远有用的 4dummies 文本here。这是最简单的,因为您所要做的就是数学。
更难做的是创建一个矩形网格,然后尝试使用 cv findHomography 将网格扭曲到图像上的适当位置。对于您可以对网格进行的各种几何变换,您可以查看OpenCv manual。这是一种 hack-ish 方法,可能比 1 效果更差。因为这取决于您可以重新创建一个网格,其中包含足够的细节和对象,cv 可以识别您正在尝试的图像上的结构将其扭曲到。这需要您执行与 1 类似的数学运算,并且只需进行一些编码即可将最终解决方案组合成几个不同的函数。
真正做到这一点。将曲线描述为曲线上的切线列表有很多数学上简洁的方法。您可以尝试将一堆较短的 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
条件绘制。将原始图像与带有线条的图像进行比较时,请密切注意。相似之处是不可思议的(嘿,明白了吗?)但因为它们不是线条,所以很多它看起来像垃圾。 (尤其是检测到的最高纬度线,它看起来太“倾斜”,但实际上这些线在其最粗点上与纬度线完美切线,正如霍夫算法所要求的那样)。承认使用直线检测算法检测曲线存在局限性
【讨论】:
我需要同时捕捉水平纬线和垂直经线。我遇到的问题是我得到很多误报,特别是在山附近。我用长度来忽略其中的大部分,但仍然得到很多误报。有什么建议吗? @user914425;检查水平线下方帖子底部添加的文本。 cmets没有地方可以做。 我只得到一条蓝线(在子午线)而不是红线。我有很多更难解决的问题。我有一张有很多山脉和地形的地图。 (参见上面的 c_6.jpg)您的脚本没有检测到任何内容。完成所有这些之后,检测我想要的经纬度交叉点的最佳方法是什么? 如果您以“一般”形式(Ax+By=C
和 Dx+Ey=F
)而不是“斜截距”形式重写方程,那么您所要做的就是求解系统的行列式线性方程组,最容易通过克莱默规则完成,以获得截点。我不知道什么不适合你,sn-p 对我来说工作正常。我一直告诉你你不能完全这样做,你不能把这个 sn-p 应用到你的问题上。有些限制是您不想承认的。以上是关于Python OpenCV HoughLinesP 无法检测线的主要内容,如果未能解决你的问题,请参考以下文章
OpenCV-累计概率霍夫变换cv::HoughLinesP
HoughlinesP 参数“threshold”和“minLineLength”