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

本示例采用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
"../Resource/Models/Super_Resolution/super.onnx"
```
## 模型初始化
首先,通过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 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 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 results = net.eval(inputData);
// 获取输出节点的属性
migraphx::argument result = results[0]; // 获取第一个输出节点的数据
migraphx::shape outputShape=result.get_shape(); // 输出节点的shape
std::vector outputSize=outputShape.lens(); // 每一维大小,维度顺序为(N,C,H,W)
int numberOfOutput=outputShape.elements(); // 输出节点元素的个数
float *data = (float *)result.data(); // 输出节点数据指针
// 获取推理结果,是重建后每个像素点的像素值,创建一个cv::Mat,依次在对应位置处赋予像素值。
cv::Mat output_Y = cv::Mat_(Size(outputShape.lens()[3], outputShape.lens()[2]), CV_32F); //规定尺寸大小
for(int i=0;i(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 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(i,j)=data[672*i+j]按行赋相应像素值。最后,将YCbCr转换为BGR,并将三通道合并到一起,得到最终的重建图像。
## 运行示例
根据samples工程中的README.md构建成功C++ samples后,在build目录下输入如下命令运行该示例:
```Python
./ MIGraphX_Samples 7
```
会在当前目录中生成检测结果图像Result.jpg。
结果如下,左图为原图,有图为重建过后的图像,分辨率提高了三倍。
