通过图像中的插值连接虚线

joining dotted line by interpolation in an image

我有这张图片,如下所示。 这是一个二进制掩码

我使用以下代码创建了这张图片。基本上我只得到了那些白色像素的 x_idxy_idx,而且我知道实际的图像大小,所以我首先创建一个空数组并在 x_idx 的帮助下填充这些行, y_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)

输出:

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

第二种方法使用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 的帮助下填充这些行 我无法控制数据,就是这样,现在我想加入该行尺寸合适。基本上,我必须创建分割标签,其中线上方是一个标签,线下方是一个标签,左侧很好,我可以根据该线将图像分成两个标签,而中间部分将保留对于 class 1 即上半部分,虽然我确定右侧是单行,但我得到的数据已经降级,所以我希望这个插值进入画面

由于您只是想连接数据中的区域间隙,因此 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()

输出掩码:

这是一个使用等高线极值点的简单方法。

优势?

这种方法有一点优势。对于获得的每个轮廓,有 4 个极值点;这些是轮廓的最顶部、最底部、最右侧和最左侧的点。我们 为每个轮廓迭代这 4 个点。与使用 Bresenham 算法的方法不同,该算法遍历图像中的每个 non-zero 点。

流量:

  • 获取二值图像
  • 执行形态学操作以连接附近的点
  • 寻找轮廓
  • 遍历为每个轮廓找到的极值点,并在最接近的点之间画一条线。

代码:

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)

# 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)

最终结果:

我谈到了极值点,但它们位于何处?以下代码段显示:

# 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)

上面显示的极值点是从图像erode.

的轮廓中获得的