3D图像 高效连通域标记算法 connected-components-3d

对于二值图像的连通域标记算法,常见的使用方法是opencv里的connectedComponents()以及connectedComponentsWithStats(),这个实现方法很快,使用也便捷,但无法适用于3D图像。skimage中的skimage.measure.label()以及skimage.measure.regionprops()可以得到2D和3D图像的连通域和相关的统计信息,但这个实现方法较慢,对于尺寸大的3D图像(比如512*512*512尺寸的医学影像)并非最优解。

这里简单介绍一个提速的2D/3D图像连通域标记的Python/C++开源包:connected-components-3d(link)。这个包的实现结合了two-pass算法,Array-Based Union-Find并查集来存储label信息,对于3D图像的处理更快,还能够直接处理非二值图像,下载方便,使用方法简单,强烈推荐~

下载和使用

Python版本直接pip install下载,C++版本可以去github(link)上下载源码。

pip install connected-components-3d

使用:

import cc3d
import numpy as np

labels_in = np.ones((512, 512, 512), dtype=np.int32)
labels_out = cc3d.connected_components(labels_in, connectivity=26) 

详情请直接去github上并支持原作者:)

2D的情况

我们先用较为简单直观的2D图像来做示例,再扩展到3D图像。这里举例的是8邻域的连通域。主要思路是Two Pass算法,这个算法包括两次扫描,大体过程是这样的:

  • 第一次扫描:依次扫描每个像素。每遇到一个前景像素,判断是否周围像素已有label。如果已有标记过label,就用这个label;如果没有,则标记上新的label。如果有两个label相邻,记录下相邻关系。
  • 第二次扫描:遇到有label的前景像素,根据之前记录的相邻关系进行重新label。

这个算法的实现中有可以提速的点:

  • 相邻关系的记录使用Union-Find并查集,也能适用于非二值图,可以用一个array来实现。

  • 在第一遍扫描的时候,对于前景像素的判断,需要对于其周围的8个像素都遍历取值,这个过程是比较慢的。我们是从上到下、从左到右扫描的,有些点的状态是已知的,经过对于拓扑关系的总结,我们可以只用8邻域中左上角的4个点来对当前前景点做出判断。这个判断关系可以用决策树来表示,并通过优化决策树结构(判断步骤最少),可以得到对于一个前景点标记的最终判断条件。

Decision Tree

值得一提的是,对于2D的情况,可以对于多个像素合成block,以block为单位来一次性判断和赋值多个像素点来提高速度(Blocked Based Decision Tree)。
PS. 这个方法在2D图像的情况下很快,但目前还未有很好的3D图像版本的实现。

Blocked Based Decision Tree

3D的情况

我们可以想象,把之前的2D图像扩展成3D,这里以26邻域连通域举例。我们的步骤是一样的,仍旧是Two Pass,但在第一次扫描时,判断每个前景像素点的时间更久了,因为周围有26个点需要取值来进行判断。我们用之前相同的优化逻辑,同样利用拓扑和优化决策树找到最优的判断条件。如果要看详细的具体判断条件可以直接看论文或者看代码。

Rosenfeld-3D

这个包的作者还添加了一些代码实现上的加速:

  • 算法的具体逻辑代码使用C++,2D或者3D图像都转换成1D array处理。Python版本接口使用Cython来进行数据结构转换。
  • 从Two Pass改成了Four Pass。在开始之前提到的Two Pass算法之前,多加了两遍遍历来得到一些已知的统计信息,以对后续的操作进行剪枝。
    • 最开始的额外第一遍遍历:预估之后的Two Pass算法会有多少个临时的label。因为我们要用一个array来当作并查集来存储所有label的相邻关系,大概知道会有多少label可以直接申请多大空间的array,节省空间以及减少caching的时间。预估的方法是遍历每一行,记录label之间可能出现多少次转换(relabel)。
    • 额外第二遍遍历:预估前景点的位置。比如说对于每一行,记录下前景点出现的index区间,这样在之后的遍历不需要遍历整行。因为大部分图片的前景点可能是比背景点的个数少得多,这样可以节省后续的很多时间。但可以根据数据的具体情况调整。
    • Two Pass算法的两次遍历
  • 多设置一些剪枝条件,以减少不必要的时间开销。比如,如果额外第一次遍历所得到的临时label个数为0,就可以直接返回,因为没有前景点。

Reference

  1. W. Silversmith. “cc3d: Connected Components on Multilabel 3D Images”. January 2021. (link)
  2. A. Rosenfeld and J. Pfaltz. “Sequential Operations in Digital Picture Processing”. Journal of the ACM. Vol. 13, Issue 4, Oct. 1966, Pg. 471-494. doi: 10.1145/321356.321357 (link)
  3. R. E. Tarjan. “Efficiency of a good but not linear set union algorithm”. Journal of the ACM, 22:215-225, 1975. (link)
  4. K. Wu, E. Otoo, K. Suzuki. “Two Strategies to Speed up Connected Component Labeling Algorithms”. Lawrence Berkeley National Laboratory. LBNL-29102, 2005. (link)
  5. S. Selkow. “The Tree-to-Tree Editing Problem”. Information Processing Letters. Vol. 6, No. 6. June 1977. doi: 10.1016/0020-0190(77)90064-3 (link)
  6. C. Grana, D. Borghesani, R. Cucchiara. “Optimized Block-based Connected Components Labeling with Decision Trees”. IEEE Transactions on Image Processing. Vol. 19, Iss. 6. June 2010. doi: 10.1109/TIP.2010.2044963 (link)
  7. P. Sutheebanjard. “Decision Tree for 3-D Connected Components Labeling”. Proc. 2012 International Symposium on Information Technology in Medicine and Education. doi: 10.1109/ITiME.2012.6291402 (link)
  8. C. Grana, D. Borghesani, R. Cucchiara. “Fast Block Based Connected Components Labeling”. Proc. 16th IEEE Intl. Conf. on Image Processing. 2009. doi: 10.1109/ICIP.2009.5413731 (link)
  9. L. He, Y. Chao and K. Suzuki, “A Linear-Time Two-Scan Labeling Algorithm”, IEEE International Conference on Image Processing, vol. 5, pp. 241-244, 2007.
  • 6
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
二值图像连通域标记是指将二值图像中相邻的像素点组成的区域标记出来,以便于后续对这些区域进行分析处理,比如计算面积、周长等。 实现方法如下: 首先将二值图像中所有像素点初始化为未访问状态,并设置一个计数器count,用于记录连通域的数量。 然后从图像的左上角开始遍历每个像素点,对于每个未访问的像素点,将其标记为已访问,并设置一个连通域编号label,将该像素点的连通域编号保存到一个数组中。 接下来,对该像素点的上下左右四个邻居像素点进行判断,如果邻居像素点的值为1(即与当前像素点属于同一连通域),并且邻居像素点未被访问过,则将邻居像素点标记为已访问,并将其连通域编号设置为label,同时将该像素点的连通域编号保存到数组中。 遍历完整个图像后,count就是连通域的数量,数组中保存了每个像素点所属的连通域编号。 代码实现如下: ``` void labelConnectedComponent(Mat &img, Mat &labels) { int label = 0; int count = 0; int height = img.rows; int width = img.cols; labels = Mat::zeros(height, width, CV_32SC1); for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { if (img.at<uchar>(i, j) == 255 && labels.at<int>(i, j) == 0) { label++; stack<Point> s; s.push(Point(j, i)); while (!s.empty()) { Point p = s.top(); s.pop(); int x = p.x; int y = p.y; if (x >= 0 && x < width && y >= 0 && y < height && img.at<uchar>(y, x) == 255 && labels.at<int>(y, x) == 0) { labels.at<int>(y, x) = label; s.push(Point(x - 1, y)); s.push(Point(x + 1, y)); s.push(Point(x, y - 1)); s.push(Point(x, y + 1)); } } } } } count = label; cout << "Connected component count: " << count << endl; } ``` 其中,img为输入的二值图像labels为输出的连通域标记图像,其每个像素点的值为该点所属的连通域编号。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值