在图像中插值连接虚线

4
我有如下所示的图像。这是一个二进制掩码

enter image description here

我使用以下代码创建了此图像。基本上,我仅获取了那些白色像素的x_idxy_idx,并且我知道实际图像的大小,因此我首先创建了一个空数组,并利用x_idxy_idx的帮助填充了这些线。
image = np.empty((x_shape, y_shape))

def line_to_img(linedf, image):
    
    x_idx = linedf.x
    y_idx = linedf.y

    for i,j in zip(x_idx, y_idx):
        image[i, j] = 1
            
    return image

你可以看到,除了左侧和右侧各有一条线外,所有像素都是相同的。

如您所见,右边的线段不连续,我想通过某些插值方法使其连续。

我尝试了两种不同的方法来实现这一点,但目前还没有成功。

第一种方法使用 skimage

new_image = skimage.morphology.remove_small_holes(old_image, 40, connectivity=2, in_place=False)

输出:enter image description here

输出解释:没有进行任何插值的相同图像

第二种方法使用 cv2

kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(3,3))
new_image = cv2.morphologyEx(old_image,cv2.MORPH_OPEN,kernel)

输出:在这里输入图片描述

输出解释:线条由于某些原因被移除了

请帮我完成这个任务,以及如何插值图像中的线来获取连续的线条

编辑(用例):基本上,我得到了白色像素的x_idxy_idx,并且我知道实际图像的大小,所以我首先创建一个空数组,并借助x_idxy_idx来填充那些线条。我不能控制数据,就是这样,现在我想连接右侧的线条。基本上,我必须创建分割标签,在线条上面是一个标签,在线条下面是一个标签,左边是没问题的,我可以根据那条线将图像分成两个标签,而中间部分仍然是类1,即上部分,虽然我确定右侧是一条单行线,只是数据退化了,所以我希望这种插值出现在图片中。


1
也许你在之前的处理步骤中已经有了你寻找的那一行,而且在提取它时可能能够更好地保留它,而不是尝试重新创建它?只是想知道。 - Mark Setchell
1
我几乎看不到这些图片中的任何内容。请不要发布缩略图,而是发布源分辨率图像(未经缩放)。 - Christoph Rackwitz
@MarkSetchell,不,我无法控制数据,这就是它的样子,现在我想要在右侧加入该行。基本上,我必须创建分割标签,在该标签上方为一个标签,在该标签下方为另一个标签,左侧没有问题,我可以根据该行将图像分成两个标签,而中间部分将保留为类别1,即上部分,同时我确定右侧线是一条单独的线,只是我得到的数据已经退化了,所以我希望进行插值处理。 - Quamer Nasim
@ChristophRackwitz 实际上这就是实际数据,整个图像除了那些白色像素外都是黑色像素。请查看问题的编辑部分,我已经添加了更多信息。 - Quamer Nasim
1
“某些原因导致该行被删除”——原因是您应用了一个开放符号,这正是开放符号的作用。您可能打算应用一个闭合符号,但它也无法关闭该行中的空隙。 - Cris Luengo
2个回答

6

如果您只是想连接数据中的区域间隙,那么 Bresenham 算法(许多人知道它是一种常见的线绘制算法)在这种情况下应该表现良好。

https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm

伪代码:

  1. 从二进制掩模获取所有 (x, y) 坐标对
  2. 按 X 从左到右排序(这假设您将拥有一个水平线,如果不是,您可以选择按不同的方式对各个分割掩模进行排序)
  3. 遍历每个坐标对,并使用 Bresenham 连接它们。

实现:

import numpy as np
import cv2
from matplotlib import pyplot as plt


def _bresenham(x0: int, y0: int, x1: int, y1: int):
    dx = x1 - x0
    dy = y1 - y0

    xsign = 1 if dx > 0 else -1
    ysign = 1 if dy > 0 else -1

    dx = abs(dx)
    dy = abs(dy)

    if dx > dy:
        xx, xy, yx, yy = xsign, 0, 0, ysign
    else:
        dx, dy = dy, dx
        xx, xy, yx, yy = 0, ysign, xsign, 0

    D = 2 * dy - dx
    y = 0

    for x in range(dx + 1):
        yield x0 + x * xx + y * yx, y0 + x * xy + y * yy
        if D >= 0:
            y += 1
            D -= 2 * dx
        D += 2 * dy

# Read in image and convert to binary mask
img = cv2.imread("C:\\Test\\so1.png", 0)
ret, thresh = cv2.threshold(img, 1, 255, cv2.THRESH_BINARY)

# Get xy coordinate list of points
pairs = []
points = np.nonzero(thresh)
points_row = points[0]
points_col = points[1]

for row, col in zip(points_row, points_col):
    pairs.append((col, row))

# Sort coordinates by X
coords_sorted = sorted(pairs, key=lambda x: x[0])

# Apply bresenham algorithm
result_coords = []
for n in range(len(coords_sorted) - 1):
    for p in _bresenham(coords_sorted[n][0], coords_sorted[n][1], coords_sorted[n + 1][0], coords_sorted[n + 1][1]):
        result_coords.append(p)

# Update the binary mask with the connected lines
for x, y in result_coords:
    thresh[y][x] = 255

plt.imshow(thresh, 'gray', vmin=0, vmax=255)
plt.show()

输出屏蔽:

输出图像


2
@QuamerNasim 如果以45度角画一条线应该是没问题的。重要的是在某种意义上的“绘图顺序”。如果您想避免填补中心的大间隙,可以检查点之间的欧几里得距离,并仅连接小于某个阈值的线条。 - Abstract
2
@QuamerNasim 如果不进行特殊处理,基于Bresenham算法的默认设置无法使两个水平并行的线条按照X值排序而不出现混乱。你需要巧妙地对坐标进行分组和排序。例如,可以根据相邻点之间的欧几里得距离而非简单地按X排序。迭代地按距离排序可能会解决所有用例,但我不确定有多少边缘情况。 - Abstract
1
我已经用我的实现运行了你的输入图像,看起来一切正常: https://pasteboard.co/KaTewEM.png 从你的输出图像来看,似乎你的值没有按正确顺序排序。在这种情况下,我甚至怀疑你是按Y而不是X排序的。 - Abstract
1
由于您跳过了阈值处理步骤(因为您已经有了索引),我猜这可能是导致您的解决方案出错的原因:pairs.append((col, row))请注意,我将元组存储为(x,y)。 您可能已经将元组反转为(行,列),例如(y,x)。 如果是这种情况,您可以随时修改此行 sorted(pairs,key = lambda x:x [0])sorted(pairs,key = lambda x:x [1]) - Abstract
1
@QuamerNasim 只需将两个额外的点附加到排序后的对列表中。获取第一个对(x,y),并将(0,y)附加到列表的前面,然后获取最后一个对,并将(image_width - 1, y)附加到列表的末尾。这将推断出图像的边缘。 - Abstract
显示剩余10条评论

1
这里提供了一种使用轮廓的极点的简单方法。
优势?
这种方法有一个小优势。对于每个获得的轮廓,都有4个极点;它们是轮廓的最上面、最下面、最右边和最左边的点。我们只需要迭代每个轮廓的这4个点。与使用Bresenham算法迭代图像中的每个非零点的方法不同。
流程:
- 获取二进制图像 - 执行形态学操作以连接附近的点 - 查找轮廓 - 迭代找到的每个轮廓的极点,并在它们之间画一条线。
代码:
img = cv2.imread('image_path', 0)
img1 = cv2.imread(f, 1)

# binary image
th = cv2.threshold(img, 150, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1]

# morphological operations
k1 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
dilate = cv2.morphologyEx(th, cv2.MORPH_DILATE, k1)
k2 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
erode = cv2.morphologyEx(dilate, cv2.MORPH_ERODE, k2)

enter image description here

# find contours
cnts1 = cv2.findContours(erode, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
#cnts = cnts[0] if len(cnts1) == 2 else cnts[1]
cnts = cnts1[0]

# For each contour, find the closest distance between their extreme points and join them
for i in range(len(cnts)):
    min_dist = max(img.shape[0], img.shape[1])
    cl = []
    
    ci = cnts[i]
    ci_left = tuple(ci[ci[:, :, 0].argmin()][0])
    ci_right = tuple(ci[ci[:, :, 0].argmax()][0])
    ci_top = tuple(ci[ci[:, :, 1].argmin()][0])
    ci_bottom = tuple(ci[ci[:, :, 1].argmax()][0])
    ci_list = [ci_bottom, ci_left, ci_right, ci_top]
    
    for j in range(i + 1, len(cnts)):
        cj = cnts[j]
        cj_left = tuple(cj[cj[:, :, 0].argmin()][0])
        cj_right = tuple(cj[cj[:, :, 0].argmax()][0])
        cj_top = tuple(cj[cj[:, :, 1].argmin()][0])
        cj_bottom = tuple(cj[cj[:, :, 1].argmax()][0])
        cj_list = [cj_bottom, cj_left, cj_right, cj_top]
        
        for pt1 in ci_list:
            for pt2 in cj_list:
                dist = int(np.linalg.norm(np.array(pt1) - np.array(pt2)))     #dist = sqrt( (x2 - x1)**2 + (y2 - y1)**2 )
                if dist < min_dist:
                    min_dist = dist             
                    cl = []
                    cl.append([pt1, pt2, min_dist])
    if len(cl) > 0:
        cv2.line(erode, cl[0][0], cl[0][1], 255, thickness = 2)

最终结果:

enter image description here

我谈到了极值点,但它们在哪里?以下代码片段显示了它们的位置:
# visualize extreme points for each contour
for c in cnts:
    left = tuple(c[c[:, :, 0].argmin()][0])
    right = tuple(c[c[:, :, 0].argmax()][0])
    top = tuple(c[c[:, :, 1].argmin()][0])
    bottom = tuple(c[c[:, :, 1].argmax()][0])
    
    # Draw dots onto image
    #cv2.drawContours(img1, [c], -1, (36, 255, 12), 2)
    cv2.circle(img1, left, 2, (0, 50, 255), -1)
    cv2.circle(img1, right, 2, (0, 255, 255), -1)
    cv2.circle(img1, top, 2, (255, 50, 0), -1)
    cv2.circle(img1, bottom, 2, (255, 255, 0), -1)

enter image description here

上面显示的极值是从图像“腐蚀”轮廓中得到的。

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