OpenCV检测45度直线

4

我有一张图片:

original image where opencv houghP transform can't find the -45 degree line

在这张图片中,OpenCV的霍夫变换无法检测到大的-45度线。

minLineLength = 150 
maxLineGap = 5 
line_thr = 150 
linesP = cv.HoughLinesP(dst, 1, np.pi / 180, line_thr, None, minLineLength, maxLineGap) 

唯一找到的线条如下所示: lines found 我尝试使用不同的阈值来处理图片,但是仍然无法在这里找到线条。
如果我手动裁剪图片,像这样: cropped version where line is found 那么我就可以清楚地看到OpenCV霍夫变换找到了正确的线条: line found 我想在未裁剪的版本中找到相同的线条。有没有关于非裁剪版本如何找到它的建议?
同时,也可能存在没有线条或者线条长度不足X轴长度的情况。例如: enter image description here enter image description here

如果你知道线条是在45度,为什么要使用霍夫变换呢?你可以保持简单,只寻找45度角的线条。 - Cris Luengo
Cris Luengo,你有什么想法可以实现这个功能吗?如果能提供一些代码就更好了。我正在寻找线条长度大于某个单位且在其中可能存在一些噪点(黑点)的情况。 - pilogo
2
你可以使用滤波器并定义一个核心,平滑掉不是45度方向的任何东西。这可以使用Kirsch算子或Sobel算子来完成。很可能有很多方法可以实现这一点。请参考https://dev59.com/6l7Va4cB1Zd3GeqPJWak。 - Sane_or_not
4个回答

1

我使用终端上的ImageMagick完成了这个操作,但你也可以用OpenCV完全相同的技术来实现。

步骤1

取出图像并将其旋转45度,在需要的地方引入黑色像素作为背景:

convert 45.jpg -background black -rotate 45 result.png

enter image description here

步骤2

现在,在之前的命令基础上,将每个像素设置为其周围1像素宽、250像素高的框中位数:

convert 45.jpg -background black -rotate 45 -statistic median 1x250 result.png

enter image description here

步骤三

现在,基于之前的命令,再次将其旋转回45度:

convert 45.jpg -background black -rotate 45 -statistic median 1x250 -rotate -45 result.png

enter image description here


因此,总体而言,整个处理过程如下:

convert input.jpg -background black -rotate 45 -statistic median 1x250 -rotate -45 result.png

显然,然后将其裁剪回原始大小,并与原始图像并排附加以进行检查:

convert 45.jpg -background black -rotate 45 -statistic median 5x250 -rotate -45 +repage -gravity center -crop 184x866+0+0 result.png
convert 45.jpg result.png +append result.png 

enter image description here


你也可以使用平均值统计加阈值而不是中位数,因为它比排序查找中位数更快,但它往往会导致模糊:

convert 45.jpg -background black -rotate 45 -statistic mean 1x250 result.png

enter image description here

你新添加的图片将被处理成以下结果:

enter image description here


1
我使用比之前回答更简单的算法,但这次是用Python和OpenCV实现的。
基本上,它不是取垂直像素列的平均值,而是将列中的像素相加并选择最亮的列。如果我显示填充、旋转的图像,并在下面用另一幅图像表示列之和,你应该能看到它的工作原理:

enter image description here

#!/usr/bin/env python3

import cv2
import numpy as np

# Load image as greyscale
im = cv2.imread('45.jpg',cv2.IMREAD_GRAYSCALE)

# Pad with border so it isn't cropped when rotated
bw=300
bordered = cv2.copyMakeBorder(im, top=bw, bottom=bw, left=bw, right=bw, borderType= cv2.BORDER_CONSTANT)

# Rotate -45 degrees
w, h = bordered.shape
M = cv2.getRotationMatrix2D((h/2,w/2),-45,1)
paddedrotated = cv2.warpAffine(bordered,M,(h,w))
# DEBUG cv2.imwrite('1.tif',paddedrotated)

# Sum the elements of each column and find column with most white pixels
colsum = np.sum(paddedrotated,axis=0,dtype=np.float)
col = np.argmax(colsum)
# DEBUG cv2.imwrite('2.tif',colsum)

# Fill with black except for the line we have located which we make white
paddedrotated[:,:] = 0
paddedrotated[:,col] = 255

# Rotate back to straight
w, h = paddedrotated.shape
M = cv2.getRotationMatrix2D((h/2,w/2),45,1)
straight = cv2.warpAffine(paddedrotated,M,(h,w))

# Remove padding and save to disk
straight = straight[bw:-bw,bw:-bw]
cv2.imwrite('result.png',straight)

请注意,您实际上不需要将图像旋转回正常状态并将其裁剪回原始大小。您实际上可以在第一行停止,该行说:
col = np.argmax(colsum)

使用一些基本三角函数来计算在原始图像中意味着什么。

这是输出结果:

enter image description here


关键词: 线检测、检测线、旋转、填充、边框、投影、图像、图像处理、Python、OpenCV、仿射变换、Hough变换


马克,你的方法很棒,但更通用的解决方案会更有帮助,因为我的图片可能没有任何线条(只有一些白点),或者线条不完全沿着x轴。在原问题中添加了几张更多的图片。 - pilogo
我的另一个答案对于新图像运行良好。这个答案也适用于带有部分线条的新图像。如果您不想在没有线条的图像中找到任何内容,则需要考虑最小长度阈值,并将其应用于代码中的col = np.argmax(colsum)处。 - Mark Setchell

1
问题很明显,你正在搜索的那条线并不是一条线,实际上看起来更像一系列连接在一起的圆圈和方框。因此,我建议你执行以下操作:
使用“查找轮廓”功能找到图像中的所有轮廓。
img = cv.imread('image.jpg')
img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(img_gray, 127, 255, 0)
img2, contours, hierarchy = cv.findContours(thresh, CHAIN_APPROX_SIMPLE ,cv.RETR_EXTERNAL)

这将返回许多轮廓,因此请使用循环仅保存足够长的轮廓。由于图像大小为814x1041像素,我假设如果轮廓至少为图像宽度的10%,即接近100,则该轮廓足够长(您显然必须优化此值)。

long_contours = []
for contour in contours[i]:
    perimeter = cv2.arcLength(contour,True)
    if (perimeter > 0.1 * 1018)  # 10% of the image width
        long_contours.append(contour)

现在在那些可能是线的长轮廓周围绘制一个旋转的边界矩形。如果长轮廓的宽度远大于其高度,或其长宽比很大(如8,并且您还需要优化此值),则将其视为一条线。
for long_contour in long_contours:
    rect = cv2.minAreaRect(long_contour)
    aspec_ratio =  rect.width / rect.height

    if aspec_ratio > 8 : 
       box = cv2.boxPoints(rect)
       box = np.int0(box)
       cv2.drawContours(img,[box],0,(255,255,255),cv.FILLED)

最终你应该得到类似这样的结果。请注意,这里的代码仅供参考。

Rotated rectangle over a suspected line


这是一个很好的观察。我希望你没有使用轮廓线。那对我来说毫无意义。你可以使用连通组件分析更简单、更高效地完成这个任务。轮廓线被过度使用了。只有在需要对象轮廓时,才应该提取轮廓线。 - Cris Luengo
谢谢你的提示。我同意使用连通组件可能更容易和更有效。使用轮廓没有具体的优势,除了我在许多项目中重复使用它们。我将来会改变这一点。 - Sameh Yassin

1

您的原始代码非常好。唯一的问题是您的图像包含太多信息,这会混淆累加器分数。如果您将线阈值增加到255,一切都会解决。

minLineLength = 150 
maxLineGap = 5
line_thr = 255
linesP = cv2.HoughLinesP(dst, 1, np.pi / 180.0, line_thr, None, minLineLength, maxLineGap) 

使用该值会得到以下结果。由于大的白色像素尺寸,这里检测到了3条线路。
[  1  41 286 326]
[  0  42 208 250]
[  1  42 286 327]

enter image description here

由于与上述相同的原因,在同一区域检测到了5条线。使用形态学运算或距离变换来减小像素大小应该可以解决这个问题。

[110 392 121 598]
[112 393 119 544]
[141 567 147 416]
[ 29 263  29 112]
[  0  93 179 272]

enter image description here

这里没有找到任何行。 在此输入图片描述


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