Super_Resolution.md 8.24 KB
Newer Older
lijian6's avatar
lijian6 committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# 超分辨率重建

## 模型简介

图像超分辨率重建技术是指设计并采用某种算法,使得可以通过观测到的低分辨率(Low Resolution, LR)图像重建出近似真实高分辨率(High Resolution , HR)图像的方法。本次部署的超分辨率模型是2016提出的ESPCN网络,主要通过一系列卷积层不断提取图像特征,最后通过sub-pixel亚像素卷积层提高图像分辨率,从而实现图像超分辨率的方法。ESPCN网络结构如下图所示
![Superresolution_01](../Images/Superresolution_01.png)

本示例采用onnx官方提供的ESPCN模型示例:https://github.com/onnx/models/tree/main/vision/super_resolution/sub_pixel_cnn_2016,将super.onnx文件保存在Resource\Models\Super_Resolution文件夹下。

## 参数设置

samples工程中的Resource/Configuration.xml文件的Espcn节点表示超分辨率模型Espcn的参数,主要包括模型存放路径。

```xml
<!--超分辨率重建-->
<Espcn>
	<ModelPath>"../Resource/Models/Super_Resolution/super.onnx"</ModelPath>
</Espcn>
```

## 模型初始化

首先,通过parse_onnx()函数加载图像超分辨率ESPCN的onnx模型,并可以通过program的get_parameter_shapes()函数获取网络的输入属性。完成模型加载之后需要使用compile()方法编译模型,编译模式使用migraphx::gpu::target{}设为GPU模式,编译过程主要基于MIGraphX IR完成各种优化。

```C++
ErrorCode Espcn::Initialize(InitializationParameterOfSuperresolution initParamOfSuperresolutionESPCN)
{
    
    ...  
        
    // 加载模型
    net = migraphx::parse_onnx(modelPath);       // 根据提供的模型地址,去加载模型文件
    LOG_INFO(logFile,"succeed to load model: %s\n",GetFileName(modelPath).c_str());

    // 获取模型输入属性
    std::pair<std::string, migraphx::shape> inputAttribute=*(net.get_parameter_shapes().begin());
    inputName=inputAttribute.first;
    inputShape=inputAttribute.second;
    inputSize=cv::Size(inputShape.lens()[3],inputShape.lens()[2]);     

    // 设置模型为GPU模式
    migraphx::target gpuTarget = migraphx::gpu::target{};

    // 编译模型
    migraphx::compile_options options;
    options.device_id=0;                          // 设置GPU设备,默认为0号设备(>=1.2版本中支持)
    options.offload_copy=true;                    // 设置offload_copy
    net.compile(gpuTarget,options);               
    LOG_INFO(logFile,"succeed to compile model: %s\n",GetFileName(modelPath).c_str());
    
    ...
        
}
```

## 预处理

完成模型初始化之后,需要将输入数据进行如下预处理:

​      1.分离图像通道,变为B、G、R三个通道图像;

​      2.BGR格式通过公式转换为YCbCr格式,得到Y、Cb、Cr三通道图像;

​      3.Y通道图像resize到224x224,变为浮点型数据,并转换数据排布为NCHW;

本示例代码主要采用如下方式完成预处理操作:

```c++
ErrorCode Espcn::Super(const cv::Mat &srcImage, cv::Mat &superImage)
{
    ...
        
    // 图像预处理(分离图像通道,只处理Y通道的图像,进行模型推理)
    // 分离图像通道
    std::vector<cv::Mat> channels;
    cv::split(srcImage, channels);     // B通道=channels[0],G通道=channels[1],R通道=channels[2]

    // 将BGR格式转换为YCbCr格式,采用opencv官方转换公式
    cv::Mat Y_channels = 0.299*channels[2] + 0.587*channels[1] + 0.114*channels[0];
    cv::Mat Cb_channels = (channels[0]-Y_channels) * 0.564 + 128;
    cv::Mat Cr_channels = (channels[2]-Y_channels) * 0.713 + 128;

    // 将Y通道图像resize为224x224,变为浮点型数据,并转换数据排布为NCHW
    cv::Mat inputBlob;
    cv::dnn::blobFromImage(Y_channels,    // 输入数据,支持多张图像
                    inputBlob,            // 输出数据
                    1/255.0,              // 缩放系数
                    inputSize,            // 模型输入大小,这里为672x672
                    Scalar(0, 0, 0),      // 均值,这里不需要减均值,所以设置为0
                    false,                // 通道转换,因为只处理Y通道数据,不需要通道转换,所以设置为false
                    false);
    
     ...
         
}
```

1.在预处理过程中,重要的是BGR格式转换为YCbCr格式,主要是因为相较于色差,人类视觉对亮度变化更为敏感。因此,在处理的过程中感兴趣的不是颜色变化(存储在 CbCr 通道中的信息),而只是其亮度(Y 通道),所以需要将BGR转换为YCbCr,转换公式采用的是opencv官方提供的公式,链接:https://docs.opencv.org/3.4.11/de/d25/imgproc_color_conversions.html。

2.得到Y通道图像后,需要resize为固定尺寸并转换为NCHW,主要采用cv::dnn::blobFromImage()函数。首先将输入图像resize到inputSize,然后减去均值,其次乘以缩放系数1/255.0并转换为NCHW,最终将转换好的数据保存到inputBlob作为输入数据执行推理。

## 推理

完成图像预处理后,就可以执行推理,得到推理结果。

```C++
ErrorCode Espcn::Super(const cv::Mat &srcImage, cv::Mat &superImage)
{
    
    ...
        
    // 输入数据
    migraphx::parameter_map inputData;
    inputData[inputName]= migraphx::argument{inputShape, (float*)inputBlob.data};

    // 推理
    std::vector<migraphx::argument> results = net.eval(inputData);

    // 获取输出节点的属性
    migraphx::argument result = results[0];                 // 获取第一个输出节点的数据
    migraphx::shape outputShape=result.get_shape();         // 输出节点的shape
    std::vector<std::size_t> outputSize=outputShape.lens(); // 每一维大小,维度顺序为(N,C,H,W) 
    int numberOfOutput=outputShape.elements();              // 输出节点元素的个数
    float *data = (float *)result.data();                   // 输出节点数据指针

    // 获取推理结果,是重建后每个像素点的像素值,创建一个cv::Mat,依次在对应位置处赋予像素值。
    cv::Mat output_Y = cv::Mat_<float>(Size(outputShape.lens()[3], outputShape.lens()[2]), CV_32F);   //规定尺寸大小
    for(int i=0;i<outputShape.lens()[2];++i)
    {
        for(int j=0;j<outputShape.lens()[3];++j){
            output_Y.at<float>(i,j)=data[672*i+j];   // 其中,672代表了outputShape.lens()[3]的值
        }
    }

    // 将32F格式的数据变为8U格式的数据
    cv::Mat output_Y8;
    output_Y.convertTo(output_Y8, CV_8U, 255.0);      // 转换为无符号8位像素数据类型,0-255是大多数图像和视频格式的正常范围

    ...
    
    // 将YCbCr转换为BGR
    cv::Mat B_output = output_Y8 + 1.773 * (Cb_output - 128);
    cv::Mat G_output = output_Y8 - 0.714 * (Cr_output - 128) - 0.344 * (Cb_output - 128);
    cv::Mat R_output = output_Y8 + 1.403 * (Cr_output - 128);

    // 将BGR三通道图像合并到一起
    std::vector<cv::Mat> channels2;
    channels2.push_back(B_output);
    channels2.push_back(G_output);
    channels2.push_back(R_output);
    cv::merge(channels2, superImage);
    
    return SUCCESS;
}
```

1.inputData表示MIGraphX的输入数据,这里表示为Y通道图像数据,inputName表示输入节点名,migraphx::argument{inputShape, (float*)inputBlob.data}表示该节点名对应的数据,这里通过前面的预处理数据inputBlob来创建的,第一个参数表示数据的shape,第二个参数表示数据指针。

2.net.eval(inputData)返回模型的推理结果,由于这里只有一个输出节点,所以std::vector中只有一个数据,results[0]表示第一个输出节点,获取输出数据之后,就可以对输出数据执行相关后处理操作。

3.推理结果为重建后的图像像素点值,创建一个cv::Mat,尺寸为672x672,通过output_Y.at<float>(i,j)=data[672*i+j]按行赋相应像素值。最后,将YCbCr转换为BGR,并将三通道合并到一起,得到最终的重建图像。

## 运行示例

根据samples工程中的README.md构建成功C++ samples后,在build目录下输入如下命令运行该示例:

```Python
./ MIGraphX_Samples 7
```

会在当前目录中生成检测结果图像Result.jpg。
结果如下,左图为原图,有图为重建过后的图像,分辨率提高了三倍。
![Superresolutin_02](../Images/Superresolution_02.png)