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

相关推荐
SomeB1oody8 小时前
【RustyML入门】6.3. 并行归约
开发语言·后端·机器学习·rust·教程
Profile排查笔记9 小时前
指纹浏览器哪个好?从 Profile、代理、权限和自动化能力判断是否适合
前端·人工智能·后端·自动化
用户938515635079 小时前
用 AI 结对编程从 0 搭一个"单词后台管理系统":Next.js + Supabase + Drizzle + shadcn/ui 全记录
后端·postgresql·next.js
爱学习的小邓同学10 小时前
Golang --- (1)第一个Golang程序
开发语言·后端·golang
小灰灰搞电子11 小时前
Rust+Slint 实现的“DNA双螺旋”加载动画源码分享
后端·rust·slint·加载动画
面向Google编程11 小时前
向量库不再囤数据:Milvus 3.0 零拷贝直读数据湖
后端
IT_陈寒12 小时前
Redis内存警告竟是因为这个不起眼的配置项
前端·人工智能·后端
Python私教13 小时前
AI 漫剧角色一进分镜就变脸?把提示词升级成“角色 ID + 镜头合同”
后端
Python私教13 小时前
一次生成 20 个角色却全都撞脸:我用“角色合同”重做了批量生成流程
后端
用户5944041035613 小时前
Go 语言高性能 Web 服务开发:基于 Gin + GORM + Redis 构建 RESTful API
后端