使用 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
假设我有一个作为 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