使用 Numpy 将分割的地面实况有效地转换为轮廓图像

Converting a Segemented Ground Truth to a Contour Image efficiently with Numpy

假设我有一个作为 Numpy 数组的分割图像,其中图像中的每个条目都是从 1, ... C, C+1 开始的数字,其中 C 是分割数 classes, class C+1 是一些背景 class。我想找到一种有效的方法将其转换为轮廓图像(二值图像,其中轮廓像素的值为 1,其余像素的值为 0),以便任何在其 8 邻域中有邻居的像素(或 4-neighborhood) 将是一个轮廓像素。

低效的方法是这样的:

def isValidLocation(i, j, image_height, image_width):
    if i<0:
        return False
    if i>image_height-1:
        return False
    if j<0:
        return False
    if j>image_width-1:
        return False
    return True

def get8Neighbourhood(i, j, image_height, image_width):
    nbd = []
    for height_offset in [-1, 0, 1]:
        for width_offset in [-1, 0, 1]:
            if isValidLocation(i+height_offset, j+width_offset, image_height, image_width):
                nbd.append((i+height_offset, j+width_offset))
    return nbd


def getContourImage(seg_image):
    seg_image_height = seg_image.shape[0]
    seg_image_width = seg_image.shape[1]
    contour_image = np.zeros([seg_image_height, seg_image_width], dtype=np.uint8)
    for i in range(seg_image_height):
        for j in range(seg_image_width):
            nbd = get8Neighbourhood(i, j, seg_image_height, seg_image_width)
            for (m,n) in nbd:
                if seg_image[m][n] != seg_image[i][j]:
                    contour_image[i][j] = 1
                    break
    return contour_image

我正在寻找一种更有效的“矢量化”方法来实现这一点,因为我需要能够在 运行 时间在深度学习环境中一次对 8 个图像的批次进行计算.任何见解表示赞赏。下面的视觉示例。第一张图像是覆盖在地面真值分割蒙版上的原始图像(诚然不是最好的分割......),第二张是我的代码的输出,看起来不错,但速度太慢了。使用英特尔 9900K cpu.

每张图像需要我大约 10 秒

来自 SUN RGBD 数据集的图像信用。

这可能有效,但它可能有一些限制,如果不对实际数据进行测试,我无法确定这些限制,因此我将依赖您的反馈。

import numpy as np
from scipy import ndimage
import matplotlib.pyplot as plt

# some sample data with few rectangular segments spread out
seg = np.ones((100, 100), dtype=np.int8)
seg[3:10, 3:10] = 20
seg[24:50, 40:70] = 30
seg[55:80, 62:79] = 40
seg[40:70, 10:20] = 50

plt.imshow(seg)
plt.show()

现在要找到轮廓,我们将使用内核对图像进行卷积,当在图像的同一段内进行卷积时,该内核应该给出 0 值,并且 <0>0 值当对具有多个片段的图像区域进行卷积时。

# kernel for convolving
k = np.array([[1, -1, -1],
              [1, 0, -1],
              [1, 1, -1]])
convolved = ndimage.convolve(seg, k)

# contour pixels
non_zeros = np.argwhere(convolved != 0)
plt.scatter(non_zeros[:, 1], non_zeros[:, 0], c='r', marker='.')
plt.show()

正如您在此示例数据中看到的那样,内核有一个小的限制并且错过了识别由于数据的对称性而导致的 两个 轮廓像素(我认为这将是罕见的实际分割输出中的情况)

为了更好地理解,这是核卷积无法识别轮廓的情况(发生在矩形的左上角和右下角),即错过一个像素

[ 1,  1,  1]
[ 1,  1,  1]
[ 1, 20, 20]

基于@sai 的想法,我想出了这个片段,它产生了相同的结果,比我的原始代码快得多。运行时间为 0.039 秒,与原来的接近 8-10 秒相比,我认为这是一个相当大的加速!

filters = []
for i in [0, 1, 2]:
    for j in [0, 1, 2]:
        filter = np.zeros([3,3], dtype=np.int)
        if i ==1 and j==1:
            pass
        else:
            filter[i][j] = -1
            filter[1][1] = 1
            filters.append(filter)


def getCountourImage2(seg_image):
    convolved_images = []
    for filter in filters:
        convoled_image = ndimage.correlate(seg_image, filter, mode='reflect')
        convolved_images.append(convoled_image)
    convoled_images = np.add.reduce(convolved_images)
    seg_image = np.where(convoled_images != 0, 255, 0)
    return seg_image