(四)里我们实测过:剂量越低,投影里的泊松噪声越大,FBP 出来的图像噪声也越重。降低剂量又不想让图像变差,有三条路:在投影域滤波、做迭代重建,或者先照常做 FBP,再在图像上去噪。
2017 年,四川大学的陈虎、张意等人和 RPI 的王革等合作者,把第三条路用深度学习推到了一个新高度:5 层卷积 + 5 层反卷积对称排列,中间不做任何池化,再用三条残差捷径把输入和中间特征直接加到后面。这就是 RED-CNN。它结构简单、参数不到 200 万,此后大量低剂量 CT 去噪论文都把它列为对比方法。
这篇按"要解决什么问题 → 网络怎么搭 → 官方代码里怎么写 → 怎么训练 → 实验说明了什么 → 读的时候要注意什么"的顺序讲。文中代码全部原样摘自作者公开的仓库,数字全部来自论文原文。
- 论文:H. Chen, Y. Zhang, M. K. Kalra, F. Lin, Y. Chen, P. Liao, J. Zhou, G. Wang. Low-Dose CT With a Residual Encoder-Decoder Convolutional Neural Network. IEEE Transactions on Medical Imaging, 2017, 36(12): 2524--2535(arXiv:1702.00288)
- 代码:https://github.com/maybe198376/Low-Dose-CT-With-a-Residual-Encoder-Decoder-Convolutional-Neural-Network(Caffe + MATLAB,GPLv3;README 留的联系人是通讯作者 Yi Zhang)
先说清楚这个仓库有什么:只有测试部分 ,即网络定义 Red_CNN.prototxt、训练好的权重 Red_CNN.caffemodel、一张示例 CT 切片和一个 17 行的 MATLAB 演示脚本,没有训练代码。所以下文讲网络结构时引用官方代码,讲训练细节时只能依据论文。
一、要解决的问题:只拿得到图像时怎么去噪
论文开头把低剂量 CT 的方法分成三类:
- 投影域滤波:在原始数据或取对数后的投影上滤波,再做 FBP。好处是投影域噪声的统计特性清楚;问题是投影上的边缘保不住时,空间分辨率会下降。
- 迭代重建:把投影统计特性、图像先验和系统参数写进一个目标函数((二)讲过的 TV 就是一种)。问题有两个:厂商的迭代重建依赖不公开的扫描几何和校正步骤,别人用不了;计算量大。
- 图像后处理:直接处理重建好的图像,不需要原始数据,可以接到任何 CT 系统后面。难点是图像域的噪声分布没法准确建模,去噪和保结构很难兼顾。
RED-CNN 走第三条路。论文把问题写得很直接:设 X ∈ R m × n X \in \mathbb{R}^{m \times n} X∈Rm×n 是低剂量图像, Y ∈ R m × n Y \in \mathbb{R}^{m \times n} Y∈Rm×n 是对应的常规剂量图像,两者的关系是 X = σ ( Y ) X = \sigma(Y) X=σ(Y), σ \sigma σ 代表包含量子噪声等因素的复杂退化过程。于是只需要找一个函数
arg min f ∥ f ( X ) − Y ∥ 2 2 \arg\min_f \; \lVert f(X) - Y \rVert_2^2 argfmin∥f(X)−Y∥22
让 f f f 近似 σ \sigma σ 的逆,用深度网络去学。
和前面精读过的 FBPConvNet 比:两者都是"先 FBP,再用网络处理图像"。FBPConvNet 针对稀疏视角的条纹伪影,用带池化的多尺度 U-Net;RED-CNN 针对低剂量噪声,故意去掉了池化,所有层的步长都是 1。
二、网络怎么搭
1. 三个部件
论文把网络拆成三部分(论文 Fig. 1):
- 堆叠编码器(去噪声和伪影):5 层卷积,每层后接 ReLU。论文认为池化(下采样)会丢掉重要的结构细节,所以编码器里只有卷积和 ReLU 两种层。
- 堆叠解码器(恢复结构细节):5 层反卷积,和卷积层一一对称。论文的说法是:卷积本质上是噪声滤波器,一连串卷积会把细节也滤掉,反卷积相当于"从特征重建图像",把细节找回来。数据流遵循"先进后出":第 1 个卷积层对应最后 1 个反卷积层,第 5 个卷积层对应第 1 个反卷积层。
- 残差补偿(捷径连接) :把前面的特征直接加到对应的后层上。设输入为 I I I、输出为 O O O,让中间层去拟合残差 F ( I ) = O − I F(I) = O - I F(I)=O−I,输出写成 R ( I ) = F ( I ) + I R(I) = F(I) + I R(I)=F(I)+I。论文给出两个理由:残差比直接映射好优化,网络加深时不容易梯度消失;只让卷积、反卷积处理残差,能在输出里保住更多结构和对比度细节。
超参数(论文 III-B 节):所有层卷积核 5 × 5 5 \times 5 5×5,最后一层 1 个滤波器、其余各层 96 个,卷积和反卷积的步长都是 1,不补零(no padding)。
论文还说明,这个结构源自 Mao 等人的 RED-Net(NIPS 2016),做了两处修改:(a) 去掉了和残差相加之前的 ReLU,不再强制学到的残差为正;(b) 加入捷径连接来改善学习过程。
2. 官方代码:网络定义
网络定义在 Red_CNN.prototxt 里,共 228 行。开头是输入和第一个卷积层:
protobuf
name: "Red_CNN"
input: "data"
input_dim: 1
input_dim: 1
input_dim: 512
input_dim: 512
layer {
name: "conv1"
type: "Convolution"
bottom: "data"
top: "conv1"
convolution_param {
num_output: 96
kernel_size: 5
stride: 1
pad: 0
}
}
layer {
name: "relu1"
type: "ReLU"
bottom: "conv1"
top: "conv1r"
}
后面 conv2 到 conv5 的参数和 conv1 只差输入通道(都是 96 个 5 × 5 5 \times 5 5×5 核、步长 1、不补零),每层后面同样接一个 ReLU。编码器结束后,第一条捷径出现在 deconv5 之后:
protobuf
layer {
name: "deconv5"
type: "Deconvolution"
bottom: "conv5r"
top: "deconv5"
convolution_param {
num_output: 96
kernel_size: 5
stride: 1
pad: 0
}
}
layer{
name: "etlwise"
type: "Eltwise"
bottom: "conv4r"
bottom: "deconv5"
top: "eltwise"
eltwise_param {
operation: SUM
}
}
layer {
name: "etRelu"
type: "ReLU"
bottom: "eltwise"
top: "eltwiser"
}
最后一层反卷积把 96 个通道合成 1 个,再和网络输入 data 相加:
protobuf
layer {
name: "deconv1"
type: "Deconvolution"
bottom: "deconv2r"
top: "deconv1"
convolution_param {
num_output: 1
kernel_size: 5
stride: 1
pad: 0
}
}
layer{
name: "etlwise2"
type: "Eltwise"
bottom: "data"
bottom: "deconv1"
top: "eltwise2"
eltwise_param {
operation: SUM
}
}
layer {
name: "etRelu2"
type: "ReLU"
bottom: "eltwise2"
top: "eltwise2"
}
对着代码看,有几点论文正文没有写得这么细:
- 不补零,尺寸怎么还原? 每个 5 × 5 5 \times 5 5×5、步长 1、不补零的卷积让边长减 4: 512 → 508 → 504 → 500 → 496 → 492 512 \to 508 \to 504 \to 500 \to 496 \to 492 512→508→504→500→496→492;每个同样设置的反卷积让边长加 4,五层之后正好回到 512。这也是论文强调"卷积层和反卷积层必须用相同核大小"的原因。
- 三条捷径,尺寸严格对齐。
conv4r(496)加到deconv5的输出( 492 + 4 = 496 492+4=496 492+4=496)上;conv2r(504)加到deconv3的输出( 500 + 4 = 504 500+4=504 500+4=504)上;输入data(512)加到deconv1的输出( 508 + 4 = 512 508+4=512 508+4=512)上。最后一条就是"输出 = 输入 + 学到的残差"。 - ReLU 放在相加之后。 论文写的是"每一层后面都接 ReLU",但代码里
deconv5、deconv3、deconv1这三个紧接捷径的反卷积层后面没有 ReLU ,ReLU 放在相加之后(etRelu、etRelu1、etRelu2);只有deconv4、deconv2直接接 ReLU。这正对应上面的修改 (a):残差本身可以是负的,相加之后才截断。 - 最后还有一个 ReLU (
etRelu2),所以网络输出不会小于 0。结合下一节的输入归一化,0 对应 CT 值 − 1024 -1024 −1024 HU,这个截断对人体组织没有影响。 - 测试时输入整张图。
input_dim是1, 1, 512, 512:训练用 55 × 55 55 \times 55 55×55 的小块,测试直接喂整张 512 × 512 512 \times 512 512×512 切片。网络是全卷积的,所以可以这样做,论文也明确说测试图像"不做分块,直接输入"。
3. 参数量和感受野
两个数都能直接从 prototxt 算出来:
- 参数量 :
conv1是 1 × 96 × 25 + 96 = 2496 1 \times 96 \times 25 + 96 = 2496 1×96×25+96=2496;中间 8 层(conv2--conv5、deconv5--deconv2)各是 96 × 96 × 25 + 96 = 230496 96 \times 96 \times 25 + 96 = 230496 96×96×25+96=230496;deconv1是 96 × 1 × 25 + 1 = 2401 96 \times 1 \times 25 + 1 = 2401 96×1×25+1=2401。合计 2496 + 8 × 230496 + 2401 = 1848865 2496 + 8 \times 230496 + 2401 = 1848865 2496+8×230496+2401=1848865,约 185 万。我把官方caffemodel里每层的权重形状解析出来核对过,和这个结果一致。 - 感受野 :每个 5 × 5 5 \times 5 5×5 的卷积或反卷积让感受野边长加 4,10 层下来,输出的一个像素最多依赖输入中 1 + 10 × 4 = 41 1 + 10 \times 4 = 41 1+10×4=41,即 41 × 41 41 \times 41 41×41 的邻域。训练块 55 × 55 55 \times 55 55×55 已经比它大,这可以帮助理解论文里"块大小从 55 加到 100 没有明显差别"的实验结果。
三、怎么训练(依据论文,官方未公开训练代码)
损失函数是均方误差(论文式 (5)):
F ( D ; Θ ) = 1 N ∑ i = 1 N ∥ X i − M ( Y i ) ∥ 2 F(D; \Theta) = \frac{1}{N} \sum_{i=1}^{N} \big\lVert X_i - M(Y_i) \big\rVert^2 F(D;Θ)=N1i=1∑N Xi−M(Yi) 2
M M M 是网络, Θ \Theta Θ 是所有卷积、反卷积的权重和偏置。注意论文在这里把记号反过来了:式 (5) 前后的文字说 X i X_i Xi 是常规剂量块、 Y i Y_i Yi 是低剂量块,而第二节开头 X X X 是低剂量、 Y Y Y 是常规剂量。按含义理解就是"网络输出和常规剂量图像的均方误差"。
其余设置(论文 II-B-5 和 III-B 节):
| 项目 | 设置 |
|---|---|
| 优化器 | Adam |
| 学习率 | 基础学习率 10 − 4 10^{-4} 10−4,逐渐降到 10 − 5 10^{-5} 10−5 |
| 初始化 | 零均值高斯分布,标准差 0.01 |
| 训练块 | 55 × 55 55 \times 55 55×55,滑动间隔 4 像素,约 10 6 10^6 106 对 |
| 数据增强 | 旋转 45°、上下和左右翻转、缩放(2 倍和 0.5 倍) |
| 框架和硬件 | Caffe,GTX 1080 |
训练块是按固定间隔重叠切出来的。论文的理由是:医学图像样本少,切块能把样本数量放大很多,同时让网络关注局部细节。
四、官方演示脚本:数据怎么喂进去
Red_CNN.m 全文如下:
matlab
clear all;
caffe.reset_all();
% MODEL
model = ['Red_CNN.prototxt'];
weights ='Red_CNN.caffemodel';
%Init Net
net = caffe.Net(model,weights,'test');
pfile = ['L506_QD_3_1.CT.0003.0035.2015.12.22.20.45.42.541197.358791561.IMA'];
X = dicomread(pfile);
im_input=double(X)/3000;
input_data = {im_input};
out_data = net.forward(input_data);
im_output = cell2mat(out_data);
subplot(1,2,1);
imshow(im_input,[850/3000 1250/3000]);
subplot(1,2,2);
imshow(im_output,[850/3000 1250/3000]);
这 17 行里最值得注意的是数据归一化:
- 输入是 DICOM 的存储值除以 3000。
dicomread读出来的是存储值,不是 CT 值。示例切片的 DICOM 头里RescaleIntercept是 − 1024 -1024 −1024、RescaleSlope是 1,即存储值 = CT 值 + 1024。所以网络的输入是 ( HU + 1024 ) / 3000 (\text{HU} + 1024) / 3000 (HU+1024)/3000。自己用这份权重时,必须按同样的方式归一化,否则输入分布对不上。 - 显示窗口
[850/3000 1250/3000]换回 CT 值是 − 174 , 226 -174, 226 −174,226 HU,即窗位 26、窗宽 400,接近常用的腹部软组织窗。 - 示例切片来自 Mayo 数据。 文件名
L506_QD_...里,L506 是 2016 NIH-AAPM-Mayo 低剂量 CT 挑战赛公开的 10 个训练病例之一,QD 是 quarter dose(四分之一剂量);DICOM 头里层厚 3 mm。README 没有说明这份权重是用哪些病人训练的,所以这张切片只能用来演示,不能当成公平的测试结果。
运行它需要 Caffe 的 MATLAB 接口,现在配起来比较麻烦。为了确认上面对网络结构的理解没有偏差,我用 NumPy 按 prototxt 的层顺序复现了一遍前向(卷积是互相关、反卷积是转置卷积,按 Caffe 的定义),载入官方权重跑这张切片,输出和输入一样是 512 × 512 512 \times 512 512×512,噪声明显减少。社区里常用的 PyTorch 版本(例如 SSinyu/RED-CNN)是第三方复现,不是官方代码,它的 README 里自己列了和原文的不同之处。
五、实验
1. 两组数据
| 模拟数据 | 临床数据 | |
|---|---|---|
| 来源 | NBIA,165 名患者的 7015 张常规剂量图像, 256 × 256 256 \times 256 256×256,覆盖身体不同部位 | 2016 NIH-AAPM-Mayo 低剂量 CT 挑战赛,10 名患者的 2378 张 3 mm 层厚全剂量与四分之一剂量图像, 512 × 512 512 \times 512 512×512 |
| 低剂量怎么来 | 从常规剂量图像模拟扇束投影(Siddon 射线追踪),加泊松噪声,空扫因子 b 0 = 10 5 b_0 = 10^5 b0=105 | Mayo 在真实全剂量扫描的投影数据上,用经过验证的噪声插入方法模拟出四分之一剂量 |
| 划分 | 随机选 200 对训练、100 对测试,训练集患者不出现在测试集 | 留一病人交叉验证:测某个病人时,用其他 9 个病人训练 |
模拟的扇束几何:源到旋转中心 40 cm,探测器到旋转中心 40 cm,成像区域 20 × 20 20 \times 20 20×20 cm,探测器宽 41.3 cm、512 个单元,全扫描 1024 个角度。
对比方法有五个:TV-POCS(迭代重建)、K-SVD(字典学习)、BM3D,以及两个 CNN:CNN10 是去掉捷径和反卷积层的 RED-CNN 简化版,也可以看作作者之前轻量 CNN 的加深版;KAIST-Net 是 Kang 等人基于方向小波的网络,在 2016 年挑战赛中获得第二名。对比方法的参数按各自原论文的建议设置。
2. 模拟数据:100 张测试图的均值(论文 Table II)
| 方法 | PSNR (dB) | RMSE | SSIM |
|---|---|---|---|
| LDCT | 36.3975±5.24 | 0.0158±0.0062 | 0.8644±0.0754 |
| TV-POCS | 41.5021±2.11 | 0.0087±0.0010 | 0.9498±0.0126 |
| K-SVD | 40.8445±2.54 | 0.0096±0.0013 | 0.9447±0.0168 |
| BM3D | 41.5358±2.09 | 0.0088±0.0010 | 0.9509±0.0127 |
| CNN10 | 41.9892±2.10 | 0.0082±0.0011 | 0.9658±0.0129 |
| KAIST-Net | 42.2746±2.05 | 0.0078±0.0009 | 0.9688±0.0098 |
| RED-CNN | 43.7871±2.01 | 0.0069±0.0007 | 0.9754±0.0086 |
RED-CNN 比 KAIST-Net 高约 1.5 dB,比 CNN10 高约 1.8 dB。CNN10 和 RED-CNN 层数相同,区别只在反卷积和捷径,所以这 1.8 dB 可以看作这两个设计的贡献。
3. 临床数据:留一交叉验证(论文 Table IV)
| 方法 | PSNR (dB) | RMSE | SSIM |
|---|---|---|---|
| LDCT | 39.4314±1.5206 | 0.0109±0.0021 | 0.9122±0.0280 |
| TV-POCS | 41.7496±1.1522 | 0.0083±0.0012 | 0.9535±0.0143 |
| K-SVD | 42.7203±1.4260 | 0.0074±0.0014 | 0.9531±0.0167 |
| BM3D | 42.7661±1.0471 | 0.0073±0.0009 | 0.9563±0.0125 |
| CNN10 | 43.6561±1.1323 | 0.0066±0.0009 | 0.9664±0.0100 |
| KAIST-Net | 43.9668±1.2169 | 0.0064±0.0009 | 0.9688±0.0110 |
| RED-CNN | 44.4187±1.2118 | 0.0060±0.0009 | 0.9705±0.0087 |
Mayo 数据上,RED-CNN 仍然三项最好,但领先 KAIST-Net 的幅度缩小到约 0.45 dB,SSIM 只高 0.0017。
4. 医生读片(论文 Table V)
两名分别有 6 年和 8 年经验的放射科医生,对 10 张含病灶图像的各方法结果独立打分(5 分制,1 = 不可接受,5 = 优秀),以常规剂量图像为金标准。"整体质量"一项:NDCT 3.39,RED-CNN 3.25,KAIST-Net 3.08,CNN10 2.99,BM3D 2.72,K-SVD 2.64,TV-POCS 2.33,LDCT 1.91。
表注只说明 * 表示 P < 0.05 P < 0.05 P<0.05、差异显著,标了 * 的是 LDCT、TV-POCS、K-SVD、BM3D 四列,CNN10、KAIST-Net、RED-CNN 三列都没有标。论文的结论也比较克制:KAIST-Net 和 RED-CNN 的得分明显高于其他方法,RED-CNN 略好于 KAIST-Net。
5. 消融和其他分析(论文 III-D 节)
- 反卷积:和不带反卷积的 CNN10 比,训练过程中测试集上的 PSNR、RMSE 都更好(Fig. 14)。
- 捷径:带捷径的版本 PSNR、RMSE 更好,收敛也更快(Fig. 15)。
- 层数:试了 10、20、30 层,差别"不容易察觉"(Fig. 16)。作者的解读是,低层视觉任务里,网络更深不一定更好。
- 块大小:从 55 加到 100,没有明显差别(Fig. 17),所以选 55 兼顾训练时间。
- 噪声水平的鲁棒性 (Table VI):只用 b 0 = 10 5 b_0 = 10^5 b0=105 训练的 RED-CNN,在 b 0 = 5 × 10 5 b_0 = 5 \times 10^5 b0=5×105 和 5 × 10 4 5 \times 10^4 5×104 的测试数据上仍有竞争力;用三种噪声水平混合训练的 RED-CNN+ 在多数情况下最好。作者据此建议:噪声水平不确定时,用可能的噪声水平混合训练。
- 计算时间 :约 10 6 10^6 106 个训练块时训练约 4 小时, 10 7 10^7 107 个约 12 小时;CNN10 层数相同但没有捷径,分别要 6 和 15 小时;KAIST-Net 是 26 层、15 通道输入,分别要 12 和 30 小时。单张图像平均处理时间:ASD-POCS 21.36 s、K-SVD 38.45 s、BM3D 4.22 s、CNN10 3.22 s、KAIST-Net 30.22 s、RED-CNN 3.68 s。
六、读这篇论文时要注意的几点
- 官方只公开了测试部分。 网络结构以 prototxt 为准;训练的切块、增强、学习率调度都只有论文里的文字描述,复现时会有自由度。
- 论文文字和代码有出入时,看代码。 "每层后接 ReLU"是简化说法,实际是三个接捷径的反卷积层先相加再 ReLU。式 (5) 前后 X X X、 Y Y Y 的含义也和第二节开头相反。
- PSNR 不能跨论文直接比。 论文的 RMSE 在 0.01 量级,说明图像做过归一化,但没写明计算 PSNR 用的峰值或灰度范围。(四)里实测过,
data_range一变,同一张图的 PSNR 就会差很多。这篇的数字只适合看同一张表里各方法的相对高低。 - 两组低剂量数据都不是另扫一次得到的。 NBIA 组是在 256 × 256 256 \times 256 256×256 图像的模拟投影上加泊松噪声,比较理想;Mayo 组的四分之一剂量也是模拟的,但它是在真实扫描的原始投影上插入噪声,更接近临床。论文正文只写了"全剂量和四分之一剂量图像",没有提这一点。Mayo 数据上 RED-CNN 的领先幅度明显缩小(1.5 dB 对 0.45 dB),读结果时更应该看这一组。
- 均方误差损失天然偏向平滑。 论文自己在腹部图像里指出 CNN10 和 KAIST-Net 有些过平滑;RED-CNN 同样只用 MSE 训练,它在像素意义上最优,不代表纹理和噪声质感最接近常规剂量图像。这也是后来很多工作改用感知损失或对抗损失的出发点。
- 对比方法用的是原论文推荐参数。 没有针对这两组数据重新调参,传统方法的结果可能偏保守。
七、它为什么重要
RED-CNN 把低剂量 CT 去噪做成了一个非常干净的模板:不池化保细节、对称的卷积和反卷积、残差捷径、整图推理。网络只有 185 万参数,可以处理任意尺寸的图像,训练成本也不高。它用的 2016 Mayo 挑战赛数据,后来也成了低剂量 CT 去噪论文最常用的数据之一。
它的局限同样清楚:只在图像域工作,FBP 里已经丢掉的信息补不回来;只用 MSE 损失,结果偏平滑。前者正是前面精读过的 Learned Primal-Dual 这类"把投影算子放进网络"的方法要解决的问题,后者则引出了后来基于感知损失、对抗训练的去噪网络。
下一篇回到基础系列:CT 重建从零开始(六):ODL。Learned Primal-Dual 的代码就是用 ODL 写的,它用"算子"的抽象把 ASTRA 这类后端包装起来,正投影、反投影、梯度、近端算子都能像数学公式一样组合。
低剂量 CT 去噪的代表论文、开源代码和数据集(包括 Mayo 的低剂量 CT 数据),我整理在了一个开源列表里,可以按方向筛选、支持中文搜索:
参考文献
- Chen H, Zhang Y, Kalra M K, et al. Low-dose CT with a residual encoder-decoder convolutional neural network. IEEE Transactions on Medical Imaging, 2017, 36(12): 2524--2535.
- Mao X J, Shen C, Yang Y B. Image restoration using very deep convolutional encoder-decoder networks with symmetric skip connections. Advances in Neural Information Processing Systems (NIPS), 2016.
如果这篇对你有帮助,欢迎收藏,有问题可以在评论区讨论。