CT(DCM)外轮廓实时识别 → 逐层拼3D → 导出STL到Blender

医院 CT(DCM)切片如何逐层找出外轮廓得到有序外周点, 以及多切片如何实时拼成三维模型。

一、看「过程」

三维重建的成品往往很「安静」:一个光滑的表面模型静静摆在屏幕上。 但真正有趣的地方,常常发生在它「长出来」之前------ 每一层 CT 图像里,边缘点如何被发现?杂质如何被清掉?外周点如何变得有序?


二、整体流程

  1. 查找关键点:在单张切片上找到「看起来像边缘」的亮点集合。
  2. 排除无效区域:去掉不属于目标外轮廓的连通块/噪声区域。
  3. 查找外周点:从剩余点中进一步收敛到真正的外周候选。
  4. 排序并闭合:让点按周向有序排列,形成可闭合的轮廓环。
  5. 切片合成:多层轮廓按层间距堆叠、缝合,实时生成 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

四、步骤二:排除无效区域

原理

关键点会自然聚成许多连通区域(八邻域连通即可)。 其中只有一部分对应我们关心的外层结构;更多可能是内部空洞边缘、局部噪声块、次要组织边界。

排除无效区域的核心思路通常是:

  1. 对关键点做连通分量分组,得到若干「区块」;
  2. 用外接矩形、是否靠近图像中心、是否被更大区块包含等几何关系判断「外层 / 内层」;
  3. 保留目标相关区块,清除其余区块中的点(演示里常做成飞散消失,便于观看)。

直观理解:先把「一整片可疑边缘」分成若干团,再看哪一团才像主体外轮廓?哪一团只是里面的杂质?

效果

排除无效区域:非目标区块被清除,主体相关点保留

伪代码

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 都得到一条(或一组)二维闭环轮廓后,就可以进入三维阶段:

  1. 把像素坐标换算到毫米坐标(像素间距、层厚/层间距来自 DICOM 几何信息);
  2. 按切片顺序把轮廓放到对应的高度(Z);
  3. 在相邻两层之间做缝合:连接对应点,生成三角面片;
  4. 随着新切片不断识别完成,网格逐步「长高」,实现实时预览。

缝合策略可以很简单(例如两层都重采样到相同点数,再按索引连接), 也可以更激进(全点就近匹配)。演示里常见的「平均等分」就是为了让对应关系稳定、易于理解。

效果

切片合成:多层轮廓实时堆叠、缝合为三维模型

伪代码

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文件后,我们可以查看到完全一致的效果:

相关推荐
foggyprojects1 小时前
同一条销售查询,两个用户为什么看到不同结果?
后端
梅头脑1 小时前
Java镜像1.2GB→200MB,K8s Pod OOMKilled 137半夜报警——容器化的真实踩坑记录
后端
netCode1 小时前
IDEA使用 Alibaba Cloud Toolkit
后端
foggyprojects1 小时前
业务要给发票明细加 8 个筛选条件,我没有再写 8 个查询接口
后端
爱勇宝2 小时前
人到了一定年纪,才会看懂这些人性真相
前端·后端
阿标在干嘛2 小时前
从RESTful到GraphQL:政策快报平台接口设计的演进
后端·restful·graphql
万少3 小时前
用 TraeWork 给小孩做一个家庭工作台
前端·人工智能·后端
2601_963870183 小时前
【计算机毕业设计】基于Vue + Spring Boot的社区养老服务管理平台设计与实现
spring boot·后端·课程设计
前端开发张小七3 小时前
Java 学习笔记 · 第二课:面向对象核心(封装、继承、多态)及接口与异常
java·后端·程序员