论文精读|RED-CNN:残差编解码网络,低剂量 CT 去噪的经典基线

(四)里我们实测过:剂量越低,投影里的泊松噪声越大,FBP 出来的图像噪声也越重。降低剂量又不想让图像变差,有三条路:在投影域滤波、做迭代重建,或者先照常做 FBP,再在图像上去噪。

2017 年,四川大学的陈虎、张意等人和 RPI 的王革等合作者,把第三条路用深度学习推到了一个新高度:5 层卷积 + 5 层反卷积对称排列,中间不做任何池化,再用三条残差捷径把输入和中间特征直接加到后面。这就是 RED-CNN。它结构简单、参数不到 200 万,此后大量低剂量 CT 去噪论文都把它列为对比方法。

这篇按"要解决什么问题 → 网络怎么搭 → 官方代码里怎么写 → 怎么训练 → 实验说明了什么 → 读的时候要注意什么"的顺序讲。文中代码全部原样摘自作者公开的仓库,数字全部来自论文原文。

先说清楚这个仓库有什么:只有测试部分 ,即网络定义 Red_CNN.prototxt、训练好的权重 Red_CNN.caffemodel、一张示例 CT 切片和一个 17 行的 MATLAB 演示脚本,没有训练代码。所以下文讲网络结构时引用官方代码,讲训练细节时只能依据论文。

一、要解决的问题:只拿得到图像时怎么去噪

论文开头把低剂量 CT 的方法分成三类:

  1. 投影域滤波:在原始数据或取对数后的投影上滤波,再做 FBP。好处是投影域噪声的统计特性清楚;问题是投影上的边缘保不住时,空间分辨率会下降。
  2. 迭代重建:把投影统计特性、图像先验和系统参数写进一个目标函数((二)讲过的 TV 就是一种)。问题有两个:厂商的迭代重建依赖不公开的扫描几何和校正步骤,别人用不了;计算量大。
  3. 图像后处理:直接处理重建好的图像,不需要原始数据,可以接到任何 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):

  1. 堆叠编码器(去噪声和伪影):5 层卷积,每层后接 ReLU。论文认为池化(下采样)会丢掉重要的结构细节,所以编码器里只有卷积和 ReLU 两种层。
  2. 堆叠解码器(恢复结构细节):5 层反卷积,和卷积层一一对称。论文的说法是:卷积本质上是噪声滤波器,一连串卷积会把细节也滤掉,反卷积相当于"从特征重建图像",把细节找回来。数据流遵循"先进后出":第 1 个卷积层对应最后 1 个反卷积层,第 5 个卷积层对应第 1 个反卷积层。
  3. 残差补偿(捷径连接) :把前面的特征直接加到对应的后层上。设输入为 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"
}

对着代码看,有几点论文正文没有写得这么细:

  1. 不补零,尺寸怎么还原? 每个 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。这也是论文强调"卷积层和反卷积层必须用相同核大小"的原因。
  2. 三条捷径,尺寸严格对齐。 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)上。最后一条就是"输出 = 输入 + 学到的残差"。
  3. ReLU 放在相加之后。 论文写的是"每一层后面都接 ReLU",但代码里 deconv5、deconv3、deconv1 这三个紧接捷径的反卷积层后面没有 ReLU ,ReLU 放在相加之后(etRelu、etRelu1、etRelu2);只有 deconv4、deconv2 直接接 ReLU。这正对应上面的修改 (a):残差本身可以是负的,相加之后才截断。
  4. 最后还有一个 ReLU (etRelu2),所以网络输出不会小于 0。结合下一节的输入归一化,0 对应 CT 值 − 1024 -1024 −1024 HU,这个截断对人体组织没有影响。
  5. 测试时输入整张图。 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 行里最值得注意的是数据归一化:

  1. 输入是 DICOM 的存储值除以 3000。 dicomread 读出来的是存储值,不是 CT 值。示例切片的 DICOM 头里 RescaleIntercept 是 − 1024 -1024 −1024、RescaleSlope 是 1,即存储值 = CT 值 + 1024。所以网络的输入是 ( HU + 1024 ) / 3000 (\text{HU} + 1024) / 3000 (HU+1024)/3000。自己用这份权重时,必须按同样的方式归一化,否则输入分布对不上。
  2. 显示窗口 [850/3000 1250/3000] 换回 CT 值是 − 174 , 226 -174, 226 −174,226 HU,即窗位 26、窗宽 400,接近常用的腹部软组织窗。
  3. 示例切片来自 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。

六、读这篇论文时要注意的几点

  1. 官方只公开了测试部分。 网络结构以 prototxt 为准;训练的切块、增强、学习率调度都只有论文里的文字描述,复现时会有自由度。
  2. 论文文字和代码有出入时,看代码。 "每层后接 ReLU"是简化说法,实际是三个接捷径的反卷积层先相加再 ReLU。式 (5) 前后 X X X、 Y Y Y 的含义也和第二节开头相反。
  3. PSNR 不能跨论文直接比。 论文的 RMSE 在 0.01 量级,说明图像做过归一化,但没写明计算 PSNR 用的峰值或灰度范围。(四)里实测过,data_range 一变,同一张图的 PSNR 就会差很多。这篇的数字只适合看同一张表里各方法的相对高低。
  4. 两组低剂量数据都不是另扫一次得到的。 NBIA 组是在 256 × 256 256 \times 256 256×256 图像的模拟投影上加泊松噪声,比较理想;Mayo 组的四分之一剂量也是模拟的,但它是在真实扫描的原始投影上插入噪声,更接近临床。论文正文只写了"全剂量和四分之一剂量图像",没有提这一点。Mayo 数据上 RED-CNN 的领先幅度明显缩小(1.5 dB 对 0.45 dB),读结果时更应该看这一组。
  5. 均方误差损失天然偏向平滑。 论文自己在腹部图像里指出 CNN10 和 KAIST-Net 有些过平滑;RED-CNN 同样只用 MSE 训练,它在像素意义上最优,不代表纹理和噪声质感最接近常规剂量图像。这也是后来很多工作改用感知损失或对抗损失的出发点。
  6. 对比方法用的是原论文推荐参数。 没有针对这两组数据重新调参,传统方法的结果可能偏保守。

七、它为什么重要

RED-CNN 把低剂量 CT 去噪做成了一个非常干净的模板:不池化保细节、对称的卷积和反卷积、残差捷径、整图推理。网络只有 185 万参数,可以处理任意尺寸的图像,训练成本也不高。它用的 2016 Mayo 挑战赛数据,后来也成了低剂量 CT 去噪论文最常用的数据之一。

它的局限同样清楚:只在图像域工作,FBP 里已经丢掉的信息补不回来;只用 MSE 损失,结果偏平滑。前者正是前面精读过的 Learned Primal-Dual 这类"把投影算子放进网络"的方法要解决的问题,后者则引出了后来基于感知损失、对抗训练的去噪网络。

下一篇回到基础系列:CT 重建从零开始(六):ODL。Learned Primal-Dual 的代码就是用 ODL 写的,它用"算子"的抽象把 ASTRA 这类后端包装起来,正投影、反投影、梯度、近端算子都能像数学公式一样组合。

低剂量 CT 去噪的代表论文、开源代码和数据集(包括 Mayo 的低剂量 CT 数据),我整理在了一个开源列表里,可以按方向筛选、支持中文搜索:

参考文献

  1. 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.
  2. 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.

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

相关推荐
X54先生(人文科技)1 小时前
ELR-SELLM_硅基伙伴通过对话碰撞个性涌现的推理架构_v10
人工智能·深度学习·架构·零知识证明
Sailing1 小时前
【封闭结束3】ReAct 循环:从一次 Function Call 到多步 Agent
人工智能·面试·agent
星恒讯工业路由器1 小时前
Modbus透传和Modbus网关有什么区别?从字节转发到协议翻译的选型思路
网络·物联网·modbus·工业物联网·工业网关·rs485·协议转换
AIyy8661 小时前
AI任务在关机后继续运行的三种实现思路
人工智能
万粉变现经纪人1 小时前
如何解决 pip install 安装报错 检测到 yanked 版本需更换可用版本 问题
人工智能·python·scrapy·numpy·pandas·pyqt·pip
mudtools1 小时前
一份声明,多份产物:飞书 SDK 的 AI 工具面是怎么生成的
人工智能·.net·ai编程
xifangge20251 小时前
把 DeepSeek 搬回自己电脑:Ollama+OpenWebUI+AnythingLLM 本地私有部署(含离线一键包)
人工智能·deepseek
kyle~1 小时前
DINOv3 --- 自监督通用视觉特征引擎
人工智能·计算机视觉·ai
李&@杰1 小时前
交换机PoE供电不足导致AP反复重启故障处理
网络·华为