找到一个已知大小的旋转包围盒,包围一个有噪声边的矩形

2024-04-26 23:39:04 发布

您现在位置:Python中文网/ 问答频道 /正文

我试图在一个不太完美的二值化矩形图像周围找到一个旋转的边界框。缺陷总是不同的:有时是空心的,有时里面有东西,有时其中一个边少了一块,有时边上的某个地方多了一块,它们总是以随机的数量稍微旋转,但预期边界框的大小和形状总是几乎相同的绝对值像素。你知道吗

以下是我作为输入的一些示例(调整大小以更好地适应帖子):

理想情况下,我希望在白色矩形的外侧找到一个边界框(尽管我主要对边缘感兴趣),如下所示:

(通过反转其中一个空心构件,得到最大的连接构件,得到一个强制大小的旋转体)

到目前为止,我已经尝试了只得到一个rotatedrect,然后强制一个形状,这几乎适用于所有情况,除了有一个额外的块沿着一个边缘。我试着让连接的组件隔离其中的一部分,并在它们周围设置边界框,只要它们是空心的,这种方法就适用于任何情况。我试过放大和腐蚀图像,得到轮廓和霍夫线,试图只找到四个角点,但我也没有运气。我也在网上找过任何有用的东西,但都没有用。你知道吗

任何帮助或想法都将不胜感激。你知道吗


Tags: 图像示例数量地方情况像素帖子边缘
1条回答
网友
1楼 · 发布于 2024-04-26 23:39:04

我的解决方案包括两部分:

  1. 通过查找最大的连接组件来查找白色大矩形的(垂直)边界框,填充其中的所有孔,查找外部垂直和水平线(Hough),通过取最小/最大x/y坐标来获取边界框。你知道吗
  2. 将给定大小的(填充的)矩形与步骤1中边界框的中心在不同角度进行匹配,打印出最佳匹配结果。你知道吗

下面是一个演示这种方法的简单程序。开头的参数(文件名、已知矩形的大小、角度搜索范围)通常从命令行传入。你知道吗

    import cv2
    import numpy as np

    # arguments
    file = '1.png'
    w0, h0 = 425, 630  # size of known rectangle
    ang_range = 1      # posible range (+/-) of angle in degrees

    # read image
    img = cv2.imread(file, cv2.IMREAD_GRAYSCALE)
    h, w = img.shape

    # find biggest connceted components
    nb_components, output, stats, _ = cv2.connectedComponentsWithStats(img, connectivity=4)
    sizes = stats[:, -1]
    max_label, max_size = 1, sizes[1]
    for i in range(2, nb_components):
        if sizes[i] > max_size:
            max_label = i
            max_size = sizes[i]
    img2 = np.zeros(img.shape, np.uint8)
    img2[output == max_label] = 128

    # fill holes
    contours, _ = cv2.findContours(img2, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
    for contour in contours:
        cv2.drawContours(img2, [contour], 0, 128, -1)

    # find lines
    edges = cv2.Canny(img2, 50, 150, apertureSize = 3)
    lines = cv2.HoughLinesP(edges, 1, np.pi/180, 40)

    # find bounding lines
    xmax = ymax = 0
    xmin, ymin = w-1, h-1
    for i in range(lines.shape[0]):
        x1 = lines[i][0][0]
        y1 = lines[i][0][1]
        x2 = lines[i][0][2]
        y2 = lines[i][0][3]
        cv2.line(img2, (x1,y1), (x2,y2), 255, 2, cv2.LINE_AA)
        if abs(x1-x2) < abs(y1-y2):
            # vertical line
            xmin = min(xmin,x1,x2)
            xmax = max(xmax,x1,x2)
        else:
            # horizcontal line
            ymin = min(ymin,y1,y2)
            ymax = max(ymax,y1,y2)
    cv2.rectangle(img2, (xmin,ymin), (xmax,ymax), 255, 1, cv2.LINE_AA)
    cv2.imwrite(file.replace('.png', '_intermediate.png'), img2)

    # rectangle of known size centered at bounding box
    xc = (xmax + xmin) / 2
    yc = (ymax + ymin) / 2
    box = np.zeros(img.shape, np.uint8)
    box[int(yc-h0/2):int(yc+h0/2), int(xc-w0/2):int(xc+w0/2)] = 255

    # find best match of this rectangle at different angles
    smax = angmax = 0
    for ang in np.linspace(-ang_range, ang_range, 20):
       rm = cv2.getRotationMatrix2D((xc,yc), ang, 1)
       rotbox = cv2.warpAffine(box, rm, (w,h))
       s = cv2.countNonZero(cv2.bitwise_and(rotbox, img))
       if s > smax:
           smax = s
           angmax = ang

    # output and visualize result
    def draw_rotated_rect(img, size, center, angle, color, thickness):
        rm = cv2.getRotationMatrix2D(center, angle, 1)
        p0 = np.dot(rm,(xc-w0/2, yc-h0/2,1))
        p1 = np.dot(rm,(xc-w0/2, yc+h0/2,1))
        p2 = np.dot(rm,(xc+w0/2, yc+h0/2,1))
        p3 = np.dot(rm,(xc+w0/2, yc-h0/2,1))
        pnts = np.int32(np.vstack([p0,p1,p2,p3]) + 0.5).reshape(-1,4,2)
        cv2.polylines(img, pnts, True, color, thickness, cv2.LINE_AA)
        print(f'{file}: edges {pnts[0].tolist()}, angle = {angle:.2f}°')

    res = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
    draw_rotated_rect(res, (w0,h0), (xc,yc), angmax, (0,255,0), 2)
    cv2.imwrite(file.replace('.png', '_result.png'), res)

显示其工作原理的中间结果(灰色=填充的最大连接组件,粗白线=霍夫线,细白色矩形=垂直边界框):
(要查看全尺寸图片,请单击它们,然后删除文件扩展名前的最后一个m

intermediate 1intermediate 2intermediate 3intermediate 4

结果可视化(绿色=已知大小的旋转矩形):

final 1final 2final 3final 4

结果(最终应钳制为[0,图像大小),-1是由于浮点旋转):

1.png: edges [[17, -1], [17, 629], [442, 629], [442, -1]], angle = 0.00°
2.png: edges [[7, 18], [9, 648], [434, 646], [432, 16]], angle = 0.26°
3.png: edges [[38, 25], [36, 655], [461, 657], [463, 27]], angle = -0.26°
4.png: edges [[36, 14], [28, 644], [453, 650], [461, 20]], angle = -0.79°

如图3所示,匹配并不完美。这可能是因为示例图像缩小到了不同的大小,当然我不知道已知矩形的大小,所以我只是为演示假设了一个合适的值。
如果真实数据也出现这种情况,您可能不仅要改变角度以找到最佳匹配,还要将匹配框上下左右移动几个像素。有关更多详细信息,请参见Dawson-Howe: A Practical Introduction to Computer Vision with OpenCV的第8.1节。你知道吗

相关问题 更多 >