Main Content

本页翻译不是最新的。点击此处可查看最新英文版本。

使用深度学习进行 JPEG 图像去块

此示例说明如何使用去噪卷积神经网络 (DnCNN) 来减少图像中的 JPEG 压缩伪影。

图像压缩用于减少图像的内存占用。JPEG 图像格式采用一种流行且强大的压缩方法,它使用质量因子来指定压缩量。降低质量值会导致更高的压缩比和更小的内存占用,但会牺牲图像的视觉质量。

JPEG 压缩是有损的,这意味着压缩过程会导致图像丢失信息。对于 JPEG 图像,这种信息丢失表现为图像中的块伪影。如图所示,更多压缩会导致更多信息丢失和更强的伪影。具有高频成分的纹理区域(例如草和云)看起来模糊不清。锐边(例如屋顶和灯塔顶部护栏)会呈现振铃效应。

JPEG 去块是减少 JPEG 图像中压缩伪影影响的过程。有几种方法可以执行 JPEG 去块,其中一些较为高效的方法使用深度学习。此示例实现一种基于深度学习的方法,它尝试将 JPEG 压缩伪影的影响降到最低。

DnCNN 网络

此示例使用一种内置深度前馈卷积神经网络,称为 DnCNN。该网络主要是为了去除图像中的噪声而设计的。不过,也可以训练 DnCNN 架构以去除 JPEG 压缩伪影或提高图像分辨率。

参考论文 [1] 采用残差学习策略,这意味着 DnCNN 网络学习估计残差图像。残差图像是原始图像和图像的失真副本之间的差异。残差图像包含关于图像失真的信息。在本示例中,失真显示为 JPEG 块伪影。

DnCNN 网络被训练用于通过彩色图像的亮度来检测残差图像。图像的亮度通道 Y 通过红色、绿色和蓝色像素值的线性组合来表示每个像素的亮度。另一方面,图像的两个色度通道 CbCr 使用红色、绿色和蓝色像素值的其他线性组合来表示色差信息。DnCNN 仅使用亮度通道进行训练,因为人类对亮度变化的感知比对颜色变化更敏感。

如果 YOriginal 是原始图像的亮度,而 YCompressed 是包含 JPEG 压缩伪影的图像的亮度,则 DnCNN 网络的输入是 YCompressed 并且网络学习基于训练数据预测 YResidual=YCompressed-YOriginal

一旦 DnCNN 网络学会如何估计残差图像,它就可以通过将残差图像添加到压缩的亮度通道、然后将图像转换回 RGB 颜色空间来重新构造压缩的 JPEG 图像的未失真版本。

下载训练数据

下载包含 20,000 个静态自然图像的 IAPR TC-12 Benchmark [2]。该数据集包括人、动物、城市等的照片。数据文件的大小约为 1.8 GB。如果您不想下载训练数据或训练网络,可以通过在命令行中键入 load("trainedJPEGDnCNN.mat") 来加载预训练的 DnCNN 网络。然后,直接转至本示例中的使用 DnCNN 网络执行 JPEG 去块部分。

使用辅助函数 downloadIAPRTC12Data 下载数据。此函数作为支持文件包含在本示例中。指定 dataDir 作为数据的存放位置。

dataDir = tempdir;
downloadIAPRTC12Data(dataDir);

此示例将使用 IAPR TC-12 Benchmark 数据的一个小型子集来训练网络。加载 imageCLEF 训练数据。所有图像均为 32 位 JPEG 彩色图像。

trainImagesDir = fullfile(dataDir,"iaprtc12","images","00");
exts = [".jpg",".bmp",".png"];
imdsPristine = imageDatastore(trainImagesDir,FileExtensions=exts);

列出训练图像的数量。

numel(imdsPristine.Files)
ans = 251

准备训练数据

要创建训练数据集,请读入原始图像,并以 JPEG 文件格式输出不同压缩级别的图像。

指定用于呈现图像压缩伪影的 JPEG 图像质量值。质量值必须在 [0, 100] 范围内。较小的质量值会导致更多压缩和更强的压缩伪影。使用更密集的小质量值来采样,以使训练数据具有广泛的压缩伪影。

JPEGQuality = [5:5:40 50 60 70 80];

压缩图像以 MAT 文件形式存储在磁盘的 compressedImagesDir 目录中。计算出的残差图像以 MAT 文件形式存储在磁盘的 residualImagesDir 目录中。这些 MAT 文件以 double 数据类型存储,以在训练网络时实现更高的精度。

compressedImagesDir = fullfile(dataDir,"iaprtc12","JPEGDeblockingData","compressedImages");
residualImagesDir = fullfile(dataDir,"iaprtc12","JPEGDeblockingData","residualImages");

使用辅助函数 createJPEGDeblockingTrainingSet 预处理训练数据。此函数作为支持文件包含在本示例中。

对于每个原始训练图像,辅助函数输出质量因子为 100 的图像副本用作参考图像,并且输出分别采用各个质量因子的图像副本用作网络输入。该函数以 double 数据类型计算参考图像和压缩图像的亮度 (Y) 通道,以更加精确地计算残差图像。压缩图像以 .MAT 文件形式存储在磁盘的 compressedDirName 目录中。计算出的残差图像以 .MAT 文件的形式存储在磁盘的 residualDirName 目录中。

[compressedDirName,residualDirName] = createJPEGDeblockingTrainingSet(imdsPristine,JPEGQuality);

为训练创建随机补片提取数据存储

使用随机补片提取数据存储将训练数据馈送给网络。此数据存储从包含网络输入和预期网络响应的两个图像数据存储中提取随机的对应补片。

在本示例中,网络输入是压缩图像。所需的网络响应是残差图像。从压缩图像文件集合中创建名为 imdsCompressed 的图像数据存储。从计算的残差图像文件集合中创建名为 imdsResidual 的图像数据存储。两个数据存储都需要使用辅助函数 matRead 从图像文件中读取图像数据。此函数作为支持文件包含在本示例中。

imdsCompressed = imageDatastore(compressedDirName,FileExtensions=".mat",ReadFcn=@matRead);
imdsResidual = imageDatastore(residualDirName,FileExtensions=".mat",ReadFcn=@matRead);

创建指定数据增强参数的 imageDataAugmenter (Deep Learning Toolbox)。在训练期间使用数据增强来更改训练数据,这可以有效地增加可用的训练数据量。此处,增强器指定 90 度的随机旋转和 x 方向上的随机翻转。

augmenter = imageDataAugmenter( ...
    RandRotation=@()randi([0,1],1)*90, ...
    RandXReflection=true);

基于这两个图像数据存储创建 randomPatchExtractionDatastore。指定 50×50 像素的补片大小。每个图像生成 128 个大小为 50×50 像素的随机补片。指定小批量大小为 128。

patchSize = 50;
patchesPerImage = 128;
dsTrain = randomPatchExtractionDatastore(imdsCompressed,imdsResidual,patchSize, ...
    PatchesPerImage=patchesPerImage, ...
    DataAugmentation=augmenter);
dsTrain.MiniBatchSize = patchesPerImage;

随机补片提取数据存储 dsTrain 在一轮训练的迭代中向网络提供小批量数据。预览从数据存储中读取的结果。

inputBatch = preview(dsTrain);
disp(inputBatch)
      InputImage      ResponseImage 
    ______________    ______________

    {50×50 double}    {50×50 double}
    {50×50 double}    {50×50 double}
    {50×50 double}    {50×50 double}
    {50×50 double}    {50×50 double}
    {50×50 double}    {50×50 double}
    {50×50 double}    {50×50 double}
    {50×50 double}    {50×50 double}
    {50×50 double}    {50×50 double}

设置 DnCNN 层

使用 dnCNNLayers 函数创建内置 DnCNN 网络的层。默认情况下,网络深度(卷积层个数)为 20。

layers = dnCNNLayers
layers = 
  1×59 Layer array with layers:

     1   'InputLayer'             Image Input           50×50×1 images
     2   'Conv1'                  Convolution           64 3×3×1 convolutions with stride [1  1] and padding [1  1  1  1]
     3   'ReLU1'                  ReLU                  ReLU
     4   'Conv2'                  Convolution           64 3×3×64 convolutions with stride [1  1] and padding [1  1  1  1]
     5   'BNorm2'                 Batch Normalization   Batch normalization with 64 channels
     6   'ReLU2'                  ReLU                  ReLU
     7   'Conv3'                  Convolution           64 3×3×64 convolutions with stride [1  1] and padding [1  1  1  1]
     8   'BNorm3'                 Batch Normalization   Batch normalization with 64 channels
     9   'ReLU3'                  ReLU                  ReLU
    10   'Conv4'                  Convolution           64 3×3×64 convolutions with stride [1  1] and padding [1  1  1  1]
    11   'BNorm4'                 Batch Normalization   Batch normalization with 64 channels
    12   'ReLU4'                  ReLU                  ReLU
    13   'Conv5'                  Convolution           64 3×3×64 convolutions with stride [1  1] and padding [1  1  1  1]
    14   'BNorm5'                 Batch Normalization   Batch normalization with 64 channels
    15   'ReLU5'                  ReLU                  ReLU
    16   'Conv6'                  Convolution           64 3×3×64 convolutions with stride [1  1] and padding [1  1  1  1]
    17   'BNorm6'                 Batch Normalization   Batch normalization with 64 channels
    18   'ReLU6'                  ReLU                  ReLU
    19   'Conv7'                  Convolution           64 3×3×64 convolutions with stride [1  1] and padding [1  1  1  1]
    20   'BNorm7'                 Batch Normalization   Batch normalization with 64 channels
    21   'ReLU7'                  ReLU                  ReLU
    22   'Conv8'                  Convolution           64 3×3×64 convolutions with stride [1  1] and padding [1  1  1  1]
    23   'BNorm8'                 Batch Normalization   Batch normalization with 64 channels
    24   'ReLU8'                  ReLU                  ReLU
    25   'Conv9'                  Convolution           64 3×3×64 convolutions with stride [1  1] and padding [1  1  1  1]
    26   'BNorm9'                 Batch Normalization   Batch normalization with 64 channels
    27   'ReLU9'                  ReLU                  ReLU
    28   'Conv10'                 Convolution           64 3×3×64 convolutions with stride [1  1] and padding [1  1  1  1]
    29   'BNorm10'                Batch Normalization   Batch normalization with 64 channels
    30   'ReLU10'                 ReLU                  ReLU
    31   'Conv11'                 Convolution           64 3×3×64 convolutions with stride [1  1] and padding [1  1  1  1]
    32   'BNorm11'                Batch Normalization   Batch normalization with 64 channels
    33   'ReLU11'                 ReLU                  ReLU
    34   'Conv12'                 Convolution           64 3×3×64 convolutions with stride [1  1] and padding [1  1  1  1]
    35   'BNorm12'                Batch Normalization   Batch normalization with 64 channels
    36   'ReLU12'                 ReLU                  ReLU
    37   'Conv13'                 Convolution           64 3×3×64 convolutions with stride [1  1] and padding [1  1  1  1]
    38   'BNorm13'                Batch Normalization   Batch normalization with 64 channels
    39   'ReLU13'                 ReLU                  ReLU
    40   'Conv14'                 Convolution           64 3×3×64 convolutions with stride [1  1] and padding [1  1  1  1]
    41   'BNorm14'                Batch Normalization   Batch normalization with 64 channels
    42   'ReLU14'                 ReLU                  ReLU
    43   'Conv15'                 Convolution           64 3×3×64 convolutions with stride [1  1] and padding [1  1  1  1]
    44   'BNorm15'                Batch Normalization   Batch normalization with 64 channels
    45   'ReLU15'                 ReLU                  ReLU
    46   'Conv16'                 Convolution           64 3×3×64 convolutions with stride [1  1] and padding [1  1  1  1]
    47   'BNorm16'                Batch Normalization   Batch normalization with 64 channels
    48   'ReLU16'                 ReLU                  ReLU
    49   'Conv17'                 Convolution           64 3×3×64 convolutions with stride [1  1] and padding [1  1  1  1]
    50   'BNorm17'                Batch Normalization   Batch normalization with 64 channels
    51   'ReLU17'                 ReLU                  ReLU
    52   'Conv18'                 Convolution           64 3×3×64 convolutions with stride [1  1] and padding [1  1  1  1]
    53   'BNorm18'                Batch Normalization   Batch normalization with 64 channels
    54   'ReLU18'                 ReLU                  ReLU
    55   'Conv19'                 Convolution           64 3×3×64 convolutions with stride [1  1] and padding [1  1  1  1]
    56   'BNorm19'                Batch Normalization   Batch normalization with 64 channels
    57   'ReLU19'                 ReLU                  ReLU
    58   'Conv20'                 Convolution           1 3×3×64 convolutions with stride [1  1] and padding [1  1  1  1]
    59   'FinalRegressionLayer'   Regression Output     mean-squared-error

选择训练选项

使用具有动量的随机梯度下降 (SGDM) 优化来训练网络。使用 trainingOptions (Deep Learning Toolbox) 函数指定 SGDM 的超参数设置。

训练深度网络是很费时间的。通过指定高学习率可加快训练速度。然而,这可能会导致网络的梯度爆炸或不受控制地增长,阻碍网络训练成功。要将梯度保持在有意义的范围内,请通过将 GradientThreshold" 值设置为 0.005 来启用梯度裁剪,并指定 "GradientThresholdMethod" 使用梯度的绝对值。

maxEpochs = 30;
initLearningRate = 0.1;
l2reg = 0.0001;
batchSize = 64;

options = trainingOptions("sgdm", ...
    Momentum=0.9, ...
    InitialLearnRate=initLearningRate, ...
    LearnRateSchedule="piecewise", ...
    GradientThresholdMethod="absolute-value", ...
    GradientThreshold=0.005, ...
    L2Regularization=l2reg, ...
    MiniBatchSize=batchSize, ...
    MaxEpochs=maxEpochs, ...
    Plots="training-progress", ...
    Verbose=false);

训练网络

默认情况下,该示例加载一个预训练的 DnCNN 网络。该预训练网络可让您无需等待训练完成即可执行 JPEG 去块。

要训练网络,请将以下代码中的 doTraining 变量设置为 true。使用 trainNetwork (Deep Learning Toolbox) 函数训练 DnCNN 网络。

在 GPU 上(如果有)进行训练。使用 GPU 需要 Parallel Computing Toolbox™ 和支持 CUDA® 的 NVIDIA® GPU。有关详细信息,请参阅GPU Computing Requirements (Parallel Computing Toolbox)。在 NVIDIA™ Titan X 上训练大约需要 40 个小时。

doTraining = false; 
if doTraining  
    [net,info] = trainNetwork(dsTrain,layers,options);
    modelDateTime = string(datetime("now",Format="yyyy-MM-dd-HH-mm-ss"));
    save("trainedJPEGDnCNN-"+modelDateTime+".mat","net");
else 
    load("trainedJPEGDnCNN.mat"); 
end

现在,您可以使用 DnCNN 网络从图像中去除 JPEG 压缩伪影。

使用 DnCNN 网络执行 JPEG 去块

要使用 DnCNN 执行 JPEG 去块,请按照此示例的后续步骤进行操作:

  • 用三种不同质量级别的 JPEG 压缩伪影创建示例测试图像。

  • 使用 DnCNN 网络去除压缩伪影。

  • 以可视化方式比较去块之前和之后的图像。

  • 对压缩和去块图像与未失真参考图像之间的相似性进行量化,以此评估压缩和去块图像的质量。

创建具有块伪影的示例图像

测试数据集 testImages 包含 Image Processing Toolbox™ 中提供的 20 个未失真图像。将这些图像加载到 imageDatastore 中。

fileNames = ["sherlock.jpg","peacock.jpg","fabric.png","greens.jpg", ...
    "hands1.jpg","kobi.png","lighthouse.png","office_4.jpg", ...
    "onion.png","pears.png","yellowlily.jpg","indiancorn.jpg", ...
    "flamingos.jpg","sevilla.jpg","llama.jpg","parkavenue.jpg", ...
    "strawberries.jpg","trailer.jpg","wagon.jpg","football.jpg"];
filePath = fullfile(matlabroot,"toolbox","images","imdata")+filesep;
filePathNames = strcat(filePath,fileNames);
testImages = imageDatastore(filePathNames);

将测试图像显示为蒙太奇。

montage(testImages)

选择一个测试图像用于测试 JPEG 去块网络。

testImage = "lighthouse.png";
Ireference = imread(testImage);
imshow(Ireference)
title("Uncompressed Reference Image")

使用 JPEG Quality 值 10、20 和 50 创建三个压缩测试图像。

imwrite(Ireference,fullfile(tempdir,"testQuality10.jpg"),"Quality",10);
imwrite(Ireference,fullfile(tempdir,"testQuality20.jpg"),"Quality",20);
imwrite(Ireference,fullfile(tempdir,"testQuality50.jpg"),"Quality",50);

预处理压缩图像

将图像的压缩版本读入工作区中。

I10 = imread(fullfile(tempdir,"testQuality10.jpg"));
I20 = imread(fullfile(tempdir,"testQuality20.jpg"));
I50 = imread(fullfile(tempdir,"testQuality50.jpg"));

将压缩图像显示为蒙太奇。

montage({I50,I20,I10},Size=[1 3])
title("JPEG-Compressed Images with Quality Factor: 50, 20 and 10 (left to right)")

如前文所述,DnCNN 只使用图像的亮度通道进行训练,因为人类对亮度变化的感知比对颜色变化更敏感。使用 rgb2ycbcr 函数将 JPEG 压缩图像从 RGB 颜色空间转换为 YCbCr 颜色空间。

I10ycbcr = rgb2ycbcr(I10);
I20ycbcr = rgb2ycbcr(I20);
I50ycbcr = rgb2ycbcr(I50);

应用 DnCNN 网络

为了执行网络的前向传导,请使用 denoiseImage 函数。该函数使用完全相同的训练和测试过程对图像去噪。您可以将 JPEG 压缩伪影视为一种图像噪声。

I10y_predicted = denoiseImage(I10ycbcr(:,:,1),net);
I20y_predicted = denoiseImage(I20ycbcr(:,:,1),net);
I50y_predicted = denoiseImage(I50ycbcr(:,:,1),net);

色度通道不需要处理。将去块后的亮度通道与原始色度通道串联起来,以获得 YCbCr 颜色空间中的去块图像。

I10ycbcr_predicted = cat(3,I10y_predicted,I10ycbcr(:,:,2:3));
I20ycbcr_predicted = cat(3,I20y_predicted,I20ycbcr(:,:,2:3));
I50ycbcr_predicted = cat(3,I50y_predicted,I50ycbcr(:,:,2:3));

使用 ycbcr2rgb 函数将去块 YCbCr 图像转换为 RGB 颜色空间。

I10_predicted = ycbcr2rgb(I10ycbcr_predicted);
I20_predicted = ycbcr2rgb(I20ycbcr_predicted);
I50_predicted = ycbcr2rgb(I50ycbcr_predicted);

将去块图像显示为蒙太奇。

montage({I50_predicted,I20_predicted,I10_predicted},Size=[1 3])
title("Deblocked Images with Quality Factor 50, 20 and 10 (Left to Right)")

为了在视觉上更好地理解这些改进,请检查每个图像中的一个小区域。使用向量 roi 指定感兴趣的区域 (ROI),格式为 [x y 宽度 高度]。其中的元素定义 ROI 左上角的 x 坐标和 y 坐标,以及它的宽度和高度。

roi = [30 440 100 80];

将压缩图像裁切到该 ROI,并将结果显示为蒙太奇。

i10 = imcrop(I10,roi);
i20 = imcrop(I20,roi);
i50 = imcrop(I50,roi);
montage({i50 i20 i10},Size=[1 3])
title("Patches from JPEG-Compressed Images with Quality Factor 50, 20 and 10 (Left to Right)")

将去块图像裁切到该 ROI,并将结果显示为蒙太奇。

i10predicted = imcrop(I10_predicted,roi);
i20predicted = imcrop(I20_predicted,roi);
i50predicted = imcrop(I50_predicted,roi);
montage({i50predicted,i20predicted,i10predicted},Size=[1 3])
title("Patches from Deblocked Images with Quality Factor 50, 20 and 10 (Left to Right)")

定量比较

通过四个度量量化去块图像的质量。您可以使用 jpegDeblockingMetrics 辅助函数分别计算质量因子为 10、20 和 50 时压缩图像和去块图像的这些度量。此函数作为支持文件包含在本示例中。

  • 结构相似性索引 (SSIM)。SSIM 对照参考图像评估图像三个特性的视觉效果:亮度、对比度和结构。SSIM 值越接近 1,测试图像与参考图像越一致。此处,参考图像是在 JPEG 压缩之前的未失真原始图像 Ireference。有关该度量的详细信息,请参阅 ssim

  • 峰值信噪比 (PSNR)。PSNR 值越大,信号较失真而言越强。有关该度量的详细信息,请参阅 psnr

  • 自然图像质量评价方法 (NIQE)。NIQE 使用基于自然场景训练的模型来测量图像感知质量。NIQE 分数越小,表示感知质量越好。有关该度量的详细信息,请参阅 niqe

  • 盲/无参考图像空间质量评价方法 (BRISQUE)。BRISQUE 使用基于具有图像失真的自然场景训练的模型来测量图像感知质量。BRISQUE 分数越小,表示感知质量越好。有关该度量的详细信息,请参阅 brisque

jpegDeblockingMetrics(Ireference,I10,I20,I50,I10_predicted,I20_predicted,I50_predicted)
------------------------------------------
SSIM Comparison
===============
I10: 0.90624    I10_predicted: 0.91286
I20: 0.94904    I20_predicted: 0.95444
I50: 0.97238    I50_predicted: 0.97482
------------------------------------------
PSNR Comparison
===============
I10: 26.6046    I10_predicted: 27.0793
I20: 28.8015    I20_predicted: 29.3378
I50: 31.4512    I50_predicted: 31.8584
------------------------------------------
NIQE Comparison
===============
I10: 7.2194    I10_predicted: 3.9469
I20: 4.5158    I20_predicted: 3.0681
I50: 2.8874    I50_predicted: 2.4107
NOTE: Smaller NIQE score signifies better perceptual quality
------------------------------------------
BRISQUE Comparison
==================
I10: 52.372    I10_predicted: 38.9272
I20: 45.3772    I20_predicted: 30.8993
I50: 27.7093    I50_predicted: 24.3847
NOTE: Smaller BRISQUE score signifies better perceptual quality

参考资料

[1] Zhang, K., W. Zuo, Y. Chen, D. Meng, and L. Zhang, "Beyond a Gaussian Denoiser:Residual Learning of Deep CNN for Image Denoising."IEEE® Transactions on Image Processing.Feb 2017.

[2] Grubinger, M., P. Clough, H. Müller, and T. Deselaers."The IAPR TC-12 Benchmark:A New Evaluation Resource for Visual Information Systems."Proceedings of the OntoImage 2006 Language Resources For Content-Based Image Retrieval.Genoa, Italy.Vol. 5, May 2006, p. 10.

另请参阅

| | | | (Deep Learning Toolbox) | (Deep Learning Toolbox) |

相关主题