Main Content

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

为图像到图像的回归准备数据存储

此示例说明如何准备数据存储,以便使用 ImageDatastoretransformcombine 函数来训练图像到图像的回归网络。

此示例说明如何使用适于训练去噪网络的管道预处理数据。然后,此示例使用预处理后的含噪数据来训练简单的卷积自编码器网络,以去除图像噪声。

使用预处理管道准备数据

此示例使用椒盐噪声模型,其中一部分输入图像像素设置为 01(分别表示黑色和白色)。含噪图像充当网络输入。原始图像充当预期的网络响应。网络学习检测和消除椒盐噪声。

将数字数据集中的原始图像加载为 imageDatastore。该数据存储包含 10,000 个数字 0 至 9 的合成图像。这些图像是通过对使用不同字体创建的数字图像应用随机变换生成的。每个数字图像为 28×28 像素。该数据存储包含的每个类别都有相同数量的图像。

digitDatasetPath = fullfile(matlabroot,"toolbox","nnet", ...
    "nndemos","nndatasets","DigitDataset");
imds = imageDatastore(digitDatasetPath, ...
    IncludeSubfolders=true,LabelSource="foldernames");

指定较大的读取大小,以最小化文件 I/O 的成本。

imds.ReadSize = 500;

在训练前,使用 shuffle 函数打乱数字数据。

imds = shuffle(imds);

使用 splitEachLabel 函数将 imds 划分为三个图像数据存储,分别包含用于训练、验证和测试的原始图像。

[imdsTrain,imdsVal,imdsTest] = splitEachLabel(imds,0.95,0.025);

使用 transform 函数创建每个输入图像的含噪版本,它们将用作网络输入。transform 函数从基础数据存储中读取数据,并使用在辅助函数 addNoise(在本示例末尾定义)中定义的操作来处理数据。transform 函数的输出是一个 TransformedDatastore

dsTrainNoisy = transform(imdsTrain,@addNoise);
dsValNoisy = transform(imdsVal,@addNoise);
dsTestNoisy = transform(imdsTest,@addNoise);

使用 combine 函数将含噪图像和原始图像合并到一个数据存储中,该数据存储将数据馈送到 trainNetwork。正如 trainNetwork 所预期的那样,该合并后的数据存储将数据批次读入一个两列元胞数组。combine 函数的输出是一个 CombinedDatastore

dsTrain = combine(dsTrainNoisy,imdsTrain);
dsVal = combine(dsValNoisy,imdsVal);
dsTest = combine(dsTestNoisy,imdsTest);

使用 transform 函数执行对输入和响应数据存储通用的其他预处理操作。commonPreprocessing 辅助函数(在本例末尾定义)将输入和响应图像的大小调整为 32×32 像素以匹配网络的输入大小,并将每个图像中的数据归一化到范围 [0, 1]。

dsTrain = transform(dsTrain,@commonPreprocessing);
dsVal = transform(dsVal,@commonPreprocessing);
dsTest = transform(dsTest,@commonPreprocessing);

最后,使用 transform 函数将随机化的增强添加到训练集中。augmentImages 辅助函数(在本示例末尾定义)对数据应用随机化的 90 度旋转。相同的旋转应用于网络输入和对应的预期响应。

dsTrain = transform(dsTrain,@augmentImages);

增强可减少过拟合,让经过训练的网络能够更好地处理经过旋转的数据。验证数据集或测试数据集不需要随机化的增强。

预览经过预处理的数据

由于准备训练数据需要几个预处理操作,请在训练前预览经过预处理的数据以确认它看起来是正确的。使用 preview 函数预览数据。

使用 montage (Image Processing Toolbox) 函数可视化成对的含噪和原始图像的示例。训练数据看起来是正确的。盐和胡椒噪声显示在左列的输入图像中。除了增加噪声外,输入图像和响应图像是相同的。以相同的方式同时对输入图像和响应图像随机旋转 90 度。

exampleData = preview(dsTrain);
inputs = exampleData(:,1);
responses = exampleData(:,2);
minibatch = cat(2,inputs,responses);
montage(minibatch',Size=[8 2])
title("Inputs (Left) and Responses (Right)")

定义卷积自编码器网络

卷积自编码器是一种常见的图像去噪架构。卷积自编码器由两个阶段组成:编码器和解码器。编码器将原始输入图像压缩成一个潜在表示,其宽度和高度较小,但比原始输入图像更深,因为每个空间位置有更多特征映射。压缩的潜在表示在恢复原始图像中的高频特征时会损失一些空间分辨率,但它学会了在进行原始图像编码时不引入噪声伪影。解码器重复对编码信号进行上采样,以将其移回其原始宽度、高度和通道数。由于编码器可去除噪声,经过解码的最终图像具有较少的噪声伪影。

此示例使用 Deep Learning Toolbox™ 中的层定义卷积自编码器网络,这些层包括:

创建图像输入层。为了简化与以 2 为因子的下采样和上采样相关的填充问题,请选择 32×32 输入大小,因为 32 可以被 2、4 和 8 整除。

imageLayer = imageInputLayer([32,32,1]);

创建编码层。编码器中的下采样是通过池大小为 2、步幅为 2 的最大池化实现的。

encodingLayers = [ ...
    convolution2dLayer(3,8,Padding="same"), ...
    reluLayer, ...
    maxPooling2dLayer(2,Padding="same",Stride=2), ...
    convolution2dLayer(3,16,Padding="same"), ...
    reluLayer, ...
    maxPooling2dLayer(2,Padding="same",Stride=2), ...
    convolution2dLayer(3,32,Padding="same"), ...
    reluLayer, ...
    maxPooling2dLayer(2,Padding="same",Stride=2)];

创建解码层。解码器使用步幅为 2 的转置卷积层对编码信号进行上采样,上采样因子为 2。网络使用 clippedReluLayer 作为最终激活层,以强制输出在 [0, 1] 范围内。

decodingLayers = [ ...
    transposedConv2dLayer(2,32,Stride=2), ...
    reluLayer, ...
    transposedConv2dLayer(2,16,Stride=2), ...
    reluLayer, ...
    transposedConv2dLayer(2,8,Stride=2), ...
    reluLayer, ...
    convolution2dLayer(1,1,Padding="same"), ...
    clippedReluLayer(1.0), ...
    regressionLayer];    

串联图像输入层、编码层和解码层,以形成卷积自编码器网络架构。

layers = [imageLayer,encodingLayers,decodingLayers];

定义训练选项

使用 Adam 优化训练网络。使用 trainingOptions 函数指定超参数设置。进行 50 轮训练。

options = trainingOptions("adam", ...
    MaxEpochs=50, ...
    MiniBatchSize=imds.ReadSize, ...
    ValidationData=dsVal, ...
    ValidationPatience=5, ...
    Plots="training-progress", ...
    OutputNetwork="best-validation-loss", ...
    Verbose=false);

训练网络

现在已配置数据源和训练选项,接下来使用 trainNetwork 函数训练卷积自编码器网络。

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

net = trainNetwork(dsTrain,layers,options);
modelDateTime = string(datetime("now",Format="yyyy-MM-dd-HH-mm-ss"));
save("trainedImageToImageRegressionNet-"+modelDateTime+".mat","net");    

评估去噪网络的性能

使用 predict 函数从测试集中获取输出图像。

ypred = predict(net,dsTest);

使用 preview 函数从测试集中获取成对的含噪图像和原始图像。

testBatch = preview(dsTest);

可视化样本输入图像和来自网络的关联预测输出,以了解去噪效果。与预期相符,网络的输出图像已去除输入图像中的大部分噪声伪影。经过编码和解码过程后,去噪图像稍显模糊。

idx = 1;
y = ypred(:,:,:,idx);
x = testBatch{idx,1};
ref = testBatch{idx,2};
montage({x,y})

通过分析峰值信噪比 (PSNR) 评估网络性能。

psnrNoisy = psnr(x,ref)
psnrNoisy = single
    19.6457
psnrDenoised = psnr(y,ref)
psnrDenoised = single
    20.6994

输出图像的 PSNR 高于含噪输入图像,这与预期相符。

支持函数

addNoise 辅助函数使用 imnoise (Image Processing Toolbox) 函数在图像中添加椒盐噪声。addNoise 函数要求输入数据的格式为图像数据元胞数组,这与 ImageDatastoreread 函数返回的数据格式相匹配。

function dataOut = addNoise(data)

    dataOut = data;
    for idx = 1:size(data,1)
       dataOut{idx} = imnoise(data{idx},"salt & pepper");
    end

end

commonPreprocessing 辅助函数定义训练集、验证集和测试集共有的预处理。该辅助函数执行以下预处理步骤。

  1. 将图像数据转换为数据类型 single

  2. 使用 imresize 函数调整图像数据的大小以匹配输入层的大小。

  3. 使用 rescale 函数将数据归一化到范围 [0, 1]。

该辅助函数要求输入数据的格式为图像数据的两列元胞数组,这与 CombinedDatastoreread 函数返回的数据格式相匹配。

function dataOut = commonPreprocessing(data)

    dataOut = cell(size(data));
    for col = 1:size(data,2)
        for idx = 1:size(data,1)
            temp = single(data{idx,col});
            temp = imresize(temp,[32,32]);
            temp = rescale(temp);
            dataOut{idx,col} = temp;
        end
    end
end

augmentImages 辅助函数使用 rot90 函数在数据中添加随机化的 90 度旋转值。相同的旋转应用于网络输入和对应的预期响应。函数要求输入数据的格式为图像数据的两列元胞数组,这与 CombinedDatastoreread 函数返回的数据格式相匹配。

function dataOut = augmentImages(data)

    dataOut = cell(size(data));
    for idx = 1:size(data,1)
        rot90Val = randi(4,1,1)-1;
        dataOut(idx,:) = {rot90(data{idx,1},rot90Val), ...
            rot90(data{idx,2},rot90Val)};
    end
end

另请参阅

| | | |

相关示例

详细信息