医院 CT(DCM)切片如何逐层找出外轮廓得到有序外周点, 以及多切片如何实时拼成三维模型。
一、看「过程」
三维重建的成品往往很「安静」:一个光滑的表面模型静静摆在屏幕上。 但真正有趣的地方,常常发生在它「长出来」之前------ 每一层 CT 图像里,边缘点如何被发现?杂质如何被清掉?外周点如何变得有序?
二、整体流程
- 查找关键点:在单张切片上找到「看起来像边缘」的亮点集合。
- 排除无效区域:去掉不属于目标外轮廓的连通块/噪声区域。
- 查找外周点:从剩余点中进一步收敛到真正的外周候选。
- 排序并闭合:让点按周向有序排列,形成可闭合的轮廓环。
- 切片合成:多层轮廓按层间距堆叠、缝合,实时生成 3D 网格。
三、步骤一:在切片上查找关键点
原理
CT 灰度图上,组织与背景常存在对比。外轮廓附近的像素,往往比邻域更「亮」或更「陡」。 第一阶段要做的事情很朴素:
- 按行(或按扫描线)遍历图像有效区域;
- 对每个像素,比较它与周围邻域的平均亮度;
- 若自身相对邻域更突出,则记为一个候选关键点。
这一步得到的是「边缘感很强的点云」,数量通常很多,且可能包含内部结构、噪声、伪影。 所以它只是起点,不是终点。
效果
伪代码
css
for each pixel p in slice (with margin):
if brightness(p) > average(brightness of neighbors around p):
mark p as keypoint candidate
四、步骤二:排除无效区域
原理
关键点会自然聚成许多连通区域(八邻域连通即可)。 其中只有一部分对应我们关心的外层结构;更多可能是内部空洞边缘、局部噪声块、次要组织边界。
排除无效区域的核心思路通常是:
- 对关键点做连通分量分组,得到若干「区块」;
- 用外接矩形、是否靠近图像中心、是否被更大区块包含等几何关系判断「外层 / 内层」;
- 保留目标相关区块,清除其余区块中的点(演示里常做成飞散消失,便于观看)。
直观理解:先把「一整片可疑边缘」分成若干团,再看哪一团才像主体外轮廓?哪一团只是里面的杂质?
效果
排除无效区域:非目标区块被清除,主体相关点保留
伪代码
scss
// 连通分组 → 判定外层目标 → 丢弃其它
blocks = connected_components(keypoints)
target = select_main_outer_block(blocks)
for block in blocks:
if block is not related_to(target):
remove points of block
五、步骤三:查找真正的外周点
原理
即使去掉大量无效区域,剩余点仍可能「厚」:同一方向上既有更靠外的点,也有偏里的点。 外周点的目标是:在轮廓的周向上,尽量只保留最外侧的代表点。
一种非常适合可视化的演示策略是「中心 + 周向扫描」:
- 估计点集中心(质心);
- 把平面按角度分成很多扇区(例如每 2° 一格);
- 指针顺时针扫一周,每个角度只保留距中心最远的那一个点;
- 扇区内其它点视为扫过即可清除。
效果
查找外周点:周向扫描后收敛到外周候选点
伪代码
scss
// 按角度分箱,每箱只留最远点
center = centroid(remaining_points)
for angle_bin in 0 .. N-1:
pts = points_falling_into(angle_bin)
keep farthest_from(center, pts)
discard the others
六、步骤四:外周点排序并闭合
原理
「有一圈点」还不够,建模与展示需要它们是有序的。 有序意味着:沿着边界走一圈,点的先后关系稳定,并且首尾可以闭合。
排序的直觉很直接:
- 以中心为原点,按极角(例如从上方向起顺时针)排序;
- 必要时做等弧长重采样,让相邻点间距更均匀;
- 用方向箭头或折线连接,明确「从第 i 个点走向第 i+1 个点」;
- 最后连回起点,形成闭环轮廓。
效果
外周点排序并闭合:方向性箭头与闭环连线,强调「有序」
伪代码
ini
// 极角排序 → 连线 → 闭合
ring = sort_by_polar_angle(outer_points, center)
for i in 0 .. ring.Count-1:
connect ring[i] → ring[(i+1) % ring.Count]
七、步骤五:多切片合成三维模型
原理
每一层 CT 都得到一条(或一组)二维闭环轮廓后,就可以进入三维阶段:
- 把像素坐标换算到毫米坐标(像素间距、层厚/层间距来自 DICOM 几何信息);
- 按切片顺序把轮廓放到对应的高度(Z);
- 在相邻两层之间做缝合:连接对应点,生成三角面片;
- 随着新切片不断识别完成,网格逐步「长高」,实现实时预览。
缝合策略可以很简单(例如两层都重采样到相同点数,再按索引连接), 也可以更激进(全点就近匹配)。演示里常见的「平均等分」就是为了让对应关系稳定、易于理解。
效果
切片合成:多层轮廓实时堆叠、缝合为三维模型
伪代码
ini
// 相邻层轮廓 loft 成三角网格
for layer in 0 .. layers-2:
A = resample(contours[layer], N)
B = resample(contours[layer+1], N)
for i in 0 .. N-1:
add_quad_as_two_triangles(A[i], A[i+1], B[i+1], B[i])
八、导出与查看(STL / Blender)
当网格足够完整后,可导出为常见的 STL / PLY。 STL 记录的是三角面片汤(triangle soup),Blender、MeshLab 等软件都能直接打开。
在 Blender 中查看时,关注:
- 整体形态是否与预期解剖结构一致;
- 层与层之间是否出现明显扭曲(通常与轮廓对应或层间距有关);
- 法线朝向、封闭性是否满足后续处理需求。
九、小结
从 CT 灰度里找出「像边缘的点」,剔掉不该留下的区域, 再收敛成有序外周闭环,最后把多层闭环缝成可导出的三维网格。
导出为stl文件后,我们可以查看到完全一致的效果: