CT 重建从零开始(一):Radon 变换、正弦图与 FBP

做 CT 重建,绕不开三个词:Radon 变换、正弦图、FBP。后面的迭代重建、稀疏视角、扩散模型,几乎每篇论文都拿 FBP 当基线。这篇用最少的公式把它们讲清楚,最后给一段可以直接运行的 Python 代码,亲手看一遍"投影 → 正弦图 → 重建"的全过程。

这是「CT 重建从零开始」系列的第一篇。

一、CT 到底测了什么

X 射线穿过物体会衰减。沿一条射线 L,探测器读到的强度满足 Beer--Lambert 定律:

I = I 0 exp ⁡ ( − ∫ L μ ( x , y )   d l ) I = I_0 \exp\left(-\int_L \mu(x,y)\,dl\right) I=I0exp(−∫Lμ(x,y)dl)

其中 μ \mu μ 是线性衰减系数,也就是我们想重建的图像。两边取对数:

p = − ln ⁡ I I 0 = ∫ L μ ( x , y )   d l p = -\ln\frac{I}{I_0} = \int_L \mu(x,y)\,dl p=−lnI0I=∫Lμ(x,y)dl

所以 CT 每一个探测器读数,经过对数变换后,就是图像沿一条直线的线积分。重建要回答的问题是:已知大量线积分,反推出图像本身。

二、Radon 变换:把所有线积分排好

用角度 θ \theta θ 和到中心的距离 s s s 描述一条直线,平行束下的投影就是 Radon 变换:

p ( θ , s ) = ∬ f ( x , y )   δ ( x cos ⁡ θ + y sin ⁡ θ − s )   d x   d y p(\theta, s) = \iint f(x,y)\,\delta(x\cos\theta + y\sin\theta - s)\,dx\,dy p(θ,s)=∬f(x,y)δ(xcosθ+ysinθ−s)dxdy

固定一个角度 θ \theta θ,扫过所有 s s s,得到一条一维投影;角度从 0° 转到 180°(平行束转半圈就够,另一半是重复的),就得到全部数据。

三、正弦图:为什么叫"正弦"

把 p ( θ , s ) p(\theta, s) p(θ,s) 画成一张二维图,横轴角度、纵轴探测器位置,就是正弦图(sinogram)。

原因很直观:图像里一个点 ( x 0 , y 0 ) (x_0, y_0) (x0,y0),在角度 θ \theta θ 下投影到的位置是 s = x 0 cos ⁡ θ + y 0 sin ⁡ θ s = x_0\cos\theta + y_0\sin\theta s=x0cosθ+y0sinθ,这正是一条正弦曲线。整幅图像是无数个点叠加,所以正弦图就是无数条正弦曲线叠在一起。离中心越远的点,曲线振幅越大。

四、直接反投影为什么是糊的

最朴素的想法:把每个角度的投影值沿原路"抹"回图像,所有角度叠加起来,叫反投影(backprojection)。

问题在于,中心附近每个角度都会被抹到,越靠近中心叠加得越多。可以证明,直接反投影的结果等于真实图像和 1 / r 1/r 1/r 核做卷积,所以低频被严重放大,图像一片模糊(见下图第三张)。

五、中心切片定理与斜坡滤波

解决方法来自中心切片定理:一个角度投影的一维傅里叶变换,等于图像二维傅里叶变换中,过原点、同一方向的那条切片。

也就是说,转半圈采集投影,相当于在频域里沿放射线采样。放射状采样在中心密、在外围稀,密度正比于 1 / ∣ ω ∣ 1/|\omega| 1/∣ω∣。要补偿这种不均匀,就要给每个频率乘上 ∣ ω ∣ |\omega| ∣ω∣,这就是斜坡滤波器(ramp filter)。

于是 FBP(Filtered Back-Projection,滤波反投影)只有两步:

  1. 对每个角度的投影做一维斜坡滤波;
  2. 把滤波后的投影反投影回图像。

斜坡滤波会放大高频,也就会放大噪声。所以实际中常给它乘一个窗函数,比如 Shepp--Logan、Cosine、Hamming、Hann,牺牲一点分辨率换取更低的噪声。

六、代码:30 行跑通 FBP

只依赖 numpy、matplotlib 和 scikit-image(pip install scikit-image matplotlib):

python 复制代码
import numpy as np
import matplotlib.pyplot as plt
from skimage.data import shepp_logan_phantom
from skimage.transform import radon, iradon, rescale
from skimage.metrics import peak_signal_noise_ratio as psnr

img = rescale(shepp_logan_phantom(), 0.64)          # 256x256 Shepp-Logan 体模
theta = np.linspace(0., 180., 180, endpoint=False)  # 180 个角度,步长 1°
sino = radon(img, theta=theta)                      # 正弦图:探测器位置 x 角度

bp  = iradon(sino, theta=theta, filter_name=None)    # 直接反投影(不滤波)
fbp = iradon(sino, theta=theta, filter_name="ramp")  # 滤波反投影

sparse = theta[::6]                                  # 只保留 30 个角度
fbp30 = iradon(radon(img, theta=sparse), theta=sparse, filter_name="ramp")

score = lambda x: psnr(img, np.clip(x, 0, 1), data_range=1)
print(f"FBP 180 views: {score(fbp):.2f} dB")
print(f"FBP  30 views: {score(fbp30):.2f} dB")

titles = ["Phantom", "Sinogram", "Backprojection", "FBP 180 views", "FBP 30 views"]
fig, ax = plt.subplots(1, 5, figsize=(17, 3.6))
for a, t, x in zip(ax, titles, [img, sino, bp, fbp, fbp30]):
    a.imshow(x, cmap="gray", aspect="auto" if t == "Sinogram" else "equal")
    a.set_title(t); a.axis("off")
plt.tight_layout(); plt.show()

运行结果(scikit-image 0.26):

  • 180 个角度的 FBP:PSNR 30.91 dB,和原图几乎一样;
  • 不滤波的直接反投影:一团模糊,只能看出轮廓;
  • 只用 30 个角度:PSNR 掉到 22.14 dB,满图放射状的条纹伪影。

七、换个滤波器会怎样

同样 180 个角度、无噪声数据,换不同窗函数:

滤波器 PSNR (dB)
Ramp 30.91
Shepp--Logan 29.94
Cosine 28.19
Hamming 27.14
Hann 26.85

无噪声时,纯斜坡最好,窗越"软"越模糊。

再模拟一次低剂量。注意体模的灰度只是相对值,直接拿它算正弦图,线积分最大约 66,按 e − 66 e^{-66} e−66 衰减几乎没有光子能到达探测器。所以先把体模缩小 10 倍,让线积分最大约 6.6,回到真实 CT 的量级,再按入射光子数 I 0 = 10 4 I_0 = 10^4 I0=104 加泊松噪声:

python 复制代码
mu = img / 10                                   # 缩到真实衰减量级,线积分最大约 6.6
sino_mu = radon(mu, theta=theta)

rng = np.random.default_rng(0)                  # 固定随机种子,结果可复现
I0 = 1e4                                        # 入射光子数
counts = rng.poisson(I0 * np.exp(-sino_mu))     # 探测器计数(泊松分布)
sino_noisy = -np.log(np.maximum(counts, 1) / I0)

for f in ["ramp", "shepp-logan"]:
    rec = 10 * iradon(sino_noisy, theta=theta, filter_name=f)   # 乘回 10
    print(f, f"{score(rec):.2f} dB")

结果:Ramp 27.66 dB ,Shepp--Logan 27.95 dB,Shepp--Logan 反超了 Ramp。换随机种子,数字会有 0.1 dB 左右的浮动,但排序不变。

这就是窗函数存在的意义:噪声越大,越需要压高频。

八、FBP 的天花板,就是后面所有方向的起点

FBP 快、稳定、可解释,至今仍是临床 CT 的重要基础。但它对数据的要求很高:

  • 角度不够(稀疏视角):条纹伪影,就是上面 30 个角度那张图;
  • 剂量太低:噪声被斜坡滤波放大;
  • 角度范围不足(有限角):某些方向的边缘直接丢失;
  • 几何不是平行束:实际 CT 多是扇束和锥束,需要扇束 FBP 和 FDK 算法。

这几个问题,分别对应了稀疏视角 CT、低剂量 CT、有限角层析、锥束 CT 几个研究方向。这些方向的代表论文、开源代码和数据集,我整理在了一个开源列表里,可以按方向筛选、支持中文搜索:

下一篇

「CT 重建从零开始(二)」会讲迭代重建:把 CT 写成线性方程组 A x = b Ax = b Ax=b,从 ART、SART 到加入全变分(TV)正则,看看它们怎么在 30 个角度下把条纹压下去。

如果这篇对你有帮助,欢迎收藏,有问题可以在评论区讨论。

相关推荐
hakuii5 个月前
plucker射线
三维生成·重建
Evand J6 个月前
【MATLAB代码介绍】三种CT模型的IMM(交互式多模型)对目标高精度定位
开发语言·matlab·ct·imm·交互式多模型·多模型·转弯
大数据AI人工智能培训专家培训讲师叶梓6 个月前
Merlin:面向腹部 CT 的三维视觉语言基础模型
人工智能·计算机视觉·大模型·医疗·ct·视觉大模型·医疗人工智能
AI医影跨模态组学6 个月前
J Transl Med(IF=7.5)苏州大学附属第一医院秦颂兵教授等团队:基于机器学习影像组学的食管鳞癌预后评估列线图
人工智能·深度学习·机器学习·ct·医学·医学影像
心 爱心 爱9 个月前
Point-SRA: Self-Representation Alignment for 3D Representation Learning 论文精读
计算机视觉·3d·重建·三维点云表征学习·流建模
weixin_468466851 年前
电磁波成像(X射线、CT成像)原理简介
图像处理·相机·ct·视觉·pet·x射线·成像原理
劳尔的狙击镜1 年前
CT影像寻找皮肤轮廓预处理
python·opencv·findcontours·ct·皮肤轮廓·皮肤表皮建模·医学影像处理
心 爱心 爱1 年前
DAS3D: Dual-modality Anomaly Synthesis for 3D Anomaly Detection 论文精读
计算机视觉·异常检测·重建·判别器·多模态目标异常检测·三维异常检测·异常合成
小冷爱读书1 年前
3D高斯破溅和4D高斯
3dgs·高斯·重建·高斯破溅