使用 python 在图像中查找触摸 labels/objects/masks 的身份

Finding identity of touching labels/objects/masks in images using python

很遗憾,我找不到与此主题相关的任何内容,所以这里是:

我有一个图像作为一个 numpy 数组,其中包含不同细胞核的掩码作为整数,如下所示:

https://i.stack.imgur.com/nn8hG.png

各个蒙版具有不同的值,背景为 0。现在对于该图像中的每个蒙版,我想获得其他触摸蒙版(如果有的话)的标识。到目前为止,我所拥有的是获取每个掩码值的像素位置(通过 argwhere 函数)并检查周围 8 个像素中的任何像素是否不为 0 或其自身值的代码。

for i in range(1, np.max(mask_image+1)):
     coordinates = np.argwhere(mask_image==i)
     touching_masks = []
     for pixel in coordinates:
    
         if mask_image[pixel[0] + 1, pixel[1]] != 0 and mask_image[pixel[0] + 1, pixel[1]] != i:
         touching_masks.append(mask_image[pixel[0] + 1, pixel[1]]) #bottom neighbour
    
         elif mask_image[pixel[0] -1, pixel[1]] != 0 and mask_image[pixel[0] -1, pixel[1]] != i:
         touching_masks.append(mask_image[pixel[0] -1, pixel[1]]) #top neighbour
    
         elif mask_image[pixel[0], pixel[1]-1] != 0 and mask_image[pixel[0], pixel[1]-1] != i:
         touching_masks.append(mask_image[pixel[0], pixel[1]-1]) #left neighbour
    
         elif mask_image[pixel[0], pixel[1] + 1] != 0 and mask_image[pixel[0], pixel[1] + 1] != i:
         touching_masks.append(mask_image[pixel[0], pixel[1] + 1]) #right neighbour
        
         elif mask_image[pixel[0] + 1, pixel[1] + 1] != 0 and mask_image[pixel[0] + 1, pixel[1] + 1] != i:
         touching_masks.append(mask_image[pixel[0] + 1, pixel[1] + 1]) #bottom-right neighbour
    
         elif mask_image[pixel[0] - 1, pixel[1] - 1] != 0 and mask_image[pixel[0] - 1, pixel[1] - 1] != i:
         touching_masks.append(mask_image[pixel[0] - 1, pixel[1] - 1]) #top-left neighbour
    
         elif mask_image[pixel[0] + 1, pixel[1] - 1] != 0 and mask_image[pixel[0] + 1, pixel[1] - 1] != i:
         touching_masks.append(mask_image[pixel[0] + 1, pixel[1] - 1]) #bottom-left neighbour
        
         elif mask_image[pixel[0] - 1, pixel[1] + 1] != 0 and mask_image[pixel[0] - 1, pixel[1] + 1] != i:
         touching_masks.append(mask_image[pixel[0] - 1, pixel[1] + 1]) #top-right neighbour

由于我每张图像有大约 500 个蒙版和大约 200 张图像的时间序列,所以这非常慢,我想改进它。我对 regionprops、skimage.segmentation 和 scipy 做了一些尝试,但没有找到合适的函数。

我想知道是否

  1. 已经有一个预先存在的函数可以做到这一点(我盲目地忽略了)
  2. 可以只保留 argwhere 函数的位置,即掩码的边界像素,从而减少检查周围 8 个像素的输入像素数。条件是这些边界像素始终保留其原始值作为标识符的形式。

非常感谢任何类型的建议!

关于我为什么要这样做的更多背景信息:

我目前正在采集多个细胞在不同时间段内的延时摄影。有时,在细胞分裂后,两个子细胞核粘附在一起,可能会错误分割为一个细胞核或正确地分割为两个细胞核。这种情况很少发生,但我想过滤掉在一个或两个掩码之间交替的此类细胞的时间轨迹。我也计算了这样的单元格的面积,但是过滤mask面积的不合理变化会遇到两个问题:

  1. 进入(或离开)图像的单元格也可以显示这种大小变化并且
  2. 显微镜的聚焦不当也会导致面罩变小(当再次获得正确聚焦时会导致面罩变大)。不幸的是,在游戏中时光倒流期间,我们的显微镜也会不时发生这种情况。 我的想法是在整个游戏中时光倒流中获得触摸面具的身份,以便在过滤掉此类细胞时考虑更多标准。

您可以通过例如找到掩码的共享边界使用 skimage.segmentation 中的 find_boundaries() 函数。遗憾的是,这也将包括背景边框,但我们可以通过对所有前景像素进行异或运算来过滤掉它。

a = find_boundaries(mask_image)
b = find_boundaries(mask_image != 0)
touching_masks = np.logical_xor(a, b)

在我的电脑上,一张 1000x1000 的图像和 500 个蒙版大约需要 0.05 秒。

如果你想要掩码的值,你可以只取

mask_values = mask_image.copy()
mask_values[~touching_masks] = 0

并使用您的代码找到相邻值。

skimage.graph.pixel_graph 函数会告诉您图像中的哪些像素与其他像素相连。您可以使用这张图来回答您的问题——我想很快。

(请注意,您分享的图像不是分割蒙版,而是RGBA灰度图像,其值为[0, 255],因此我无法在下面的分析中使用它。)

第 1 步:我们构建像素图。为此,我们只想保留两个标签不同的边缘,因此我们传入一个边缘函数,当值不同时 returns 1.0,否则为 0.0。

import numpy as np
from skimage.graph import pixel_graph


def different_labels(center_value, neighbor_value, *args):
    return (center_value != neighbor_value).astype(float)


label_mask = ... # load your image here
g, nodes = pixel_graph(
        label_mask,
        mask=label_mask.astype(bool),
        connectivity=2,  # count diagonals in 2D
        )

现在,您需要知道 g 是 scipy.sparse.csr_matrix 格式,并且行索引对应于图像中的所有非零像素。要返回实际图像位置,您需要 nodes 数组,其中包含从矩阵索引到像素索引的映射。

矩阵还包含我们不关心的像素的所有零项,因此使用 scipy.sparse.csr_matrix.eliminate_zeros() method.

删除它们

为了得到我们的不同像素对,我们将矩阵转换为COO matrix,然后我们抓取相应的图像坐标并抓取值:

g.eliminate_zeros()

coo = g.tocoo()
center_coords = nodes[coo.row]
neighbor_coords = nodes[coo.col]

center_values = label_mask.ravel()[center_coords]
neighbor_values = label_mask.ravel()[neighbor_coords]

现在我们有一个包含 (i, j) 对接触的原子核的列表。 (它们 somewhat-arbitrarily 排列成 center/neighbor。此外,这些对同时显示为 (i, j) 和 (j, i)。)你可以用这些数组做你想做的事,例如将它们保存到文本文件中:

touching_masks = np.column_stack((center_values, neighbor_values))
np.savetxt('touching_masks.txt', touching_masks, delimiter=',')

或者制作一个字典,将每个核映射到相邻核的列表:

from collections import defaultdict
pairs = defaultdict(list)

for i, j in zip(center_values, neighbor_values):
    pairs[i].append(j)

一般来说,最好尽量避免对像素进行迭代,而改用 NumPy 向量化运算。 source code for the pixel_graph function 可能会为如何思考此类问题提供进一步的启发!