从这章开始,准备把哪个超分辨率重建SRCNN (Matlab程序) 改编为 纯C++程序。
主要是每步实现一下能熟悉SRCNN重建运行机理,也方便在其它电脑上使用(不用安装Matlab什么的)。
图像显示部分用一个EasyX 库(EasyX 库采用静态链接方式,不会为您的程序增加任何额外的 DLL 依赖)
只有几个文件(easyx.h ,easyx.lib ,easyxw.lib)还有一个不错的中文帮助文件(EasyX_Help.chm)。
由于不涉及训练,核心只是一个卷积。
流程:
1。在Matlab导出卷积核(即网络权重和偏移)
2。输入图像
3。双三次放大
4。取单色转为单精度矩阵
5。三层卷积
6。转回单色图并显示和保存图像

这里先实现核心卷积:
搜索到一个《c++实现卷积过程》博文,就以这个为模板吧
c++实现卷积过程
#include<iostream>
#include<vector>
using namespace std;
int main()
{
//定义被卷积的矩阵(其实是一个数组,数组元素的个数8*8)
int const map = 8;
float A[map*map] =
{
1, 2, 3, 4, 5, 6, 7, 8,
1, 2, 3, 4, 5, 6, 7, 8,
1, 2, 3, 4, 5, 6, 7, 8,
1, 2, 3, 4, 5, 6, 7, 8,
1, 2, 3, 4, 5, 6, 7, 8,
1, 2, 3, 4, 5, 6, 7, 8,
1, 2, 3, 4, 5, 6, 7, 8,
1, 2, 3, 4, 5, 6, 7, 8
};
//定义卷积核矩阵(其实也是一个数组,数组元素的个数3*3)
int const kernel = 3;
float B[kernel*kernel] =
{
1, 1, 1,
1, 1, 1,
1, 1, 1
};
//计算卷积输出矩阵的维数(其实是输出数组元素个数的开根号)
int const outm = map - kernel + 1; //被卷积矩阵的维数-卷积核的维数+1 即8-3+1=6
//计算卷积过程中的被卷积矩阵的宽和高(就是把宽拉成和卷积核的高一样,这样才好对应相乘)
int const convAw = kernel*kernel;//3*3=9
int const convAh = map*map;//8*8=64
float A_convert[convAh*convAw] = { 0 };//定义一个卷积过程中的矩阵(也就是被拉长过后的矩阵)
for (int i = 0; i < outm; i++)
{
for (int j = 0; j < outm; j++)
{
int wh = i * outm * convAw + j * convAw;
int col1 = i * map + j;
A_convert[wh] = A[col1]; //第一次循环时把A[0] 的值赋给 A_convert[0]
A_convert[wh + 1] = A[col1 + 1];//第一次循环时把A[1] 的值赋给 A_convert[1]
A_convert[wh + 2] = A[col1 + 2];//第一次循环时把A[2] 的值赋给 A_convert[2]
int col2 = (i + 1) * map + j;
A_convert[wh + 3] = A[col2]; //第一次循环时把A[8] 的值赋给 A_convert[3]
A_convert[wh + 4] = A[col2 + 1];//第一次循环时把A[9] 的值赋给 A_convert[4]
A_convert[wh + 5] = A[col2 + 2];//第一次循环时把A[10] 的值赋给 A_convert[5]
int col3 = (i + 2) * map + j;
A_convert[wh + 6] = A[col3]; //第一次循环时把A[16] 的值赋给 A_convert[6]
A_convert[wh + 7] = A[col3 + 1]; //第一次循环时把A[17] 的值赋给 A_convert[7]
A_convert[wh + 8] = A[col3 + 2]; //第一次循环时把A[18] 的值赋给 A_convert[8]
}
}
vector<int> C;
for (int i = 0; i < outm; i++)
{
for (int j = 0; j < outm; j++)
{
int a = 0;
int wh = i * outm * convAw + j * convAw;
for (int m =0; m < convAw; m++)
{
a += A_convert[wh + m] * B[m] ;
}
C.push_back(a); //在C中添加数据a
}
}
//输出被卷积矩阵
cout << "被卷积矩阵 :" << endl;
for (int i = 0; i < map; i++)
{
for (int j = 0; j < map; j++)
{
cout << A[i*map + j] << " ";
}
cout << endl;
}
cout << endl;
//输出卷积核矩阵
cout << "卷积核矩阵:" << endl;
for (int i = 0; i < kernel; i++)
{
for (int j = 0; j < kernel; j++)
{
cout << B[i*kernel + j] << " ";
}
cout << endl;
}
cout << endl;
//输出卷积后输出矩阵
cout << "卷积后输出矩阵:" << endl;
for (int i = 0; i < outm; i++)
{
for (int j = 0; j < outm; j++)
{
cout << C[i*outm + j] << " ";
}
cout << endl;
}
system("pause");
return 0;
}抄了这一段只是便于后面对比理解
由于不算复杂,直接看注解吧,主要是easyx相关的
实现:
//c++实现卷积
#define USE_EGE 0 //选择ege (1)或 easyx(0)的开关
#if USE_EGE
#include <ege.h> //使用ege库 和 easyx 差不多,这里没有实现
using namespace ege;
#else
#include <easyx.h>//使用easyx库
#include<conio.h>
#endif
#include<iostream>
#include<vector>
using namespace std;
#define SCREEN_WIDTH 1100 //窗口大小
#define SCREEN_HEIGHT 600
#define byte unsigned char
struct 卷积矩阵
{
int width; //宽
int height; //高
//数据
byte * data;
//构造函数
卷积矩阵(int iwidth,int iheight);
};
IMAGE jpg;//一张原图
//构成一个卷积过程中的矩阵
卷积矩阵::卷积矩阵(int iwidth,int iheight): width(iwidth),
height(iheight)
{
int size=sizeof(byte)*width*iheight;
data=(byte*)malloc(size);
memset(data, 0,sizeof(size));
}
void error(char *s)
{
printf("%s\n",s);
getch();
exit(1);
}
void loadjpg(char * jpgname)
{
loadimage(&jpg,jpgname);//载入图像
}
bool 卷积(byte *A,int map,float *B,int kernel,vector<int> & C)
{
//计算卷积输出矩阵的维数(其实是输出数组元素个数的开根号)
int outm = map - kernel + 1; //被卷积矩阵的维数-卷积核的维数+1 即8-3+1=6
//计算卷积过程中的被卷积矩阵的宽和高(就是把宽拉成和卷积核的高一样,这样才好对应相乘)
int const convAw = kernel*kernel;//3*3=9
int convAh = map*map;//8*8=64
卷积矩阵 isA_convert(convAh,convAw);
byte *A_convert=isA_convert.data;//(byte*)malloc(sizeof(byte)*convAh*convAw);//[convAh*convAw] = { 0 };//定义一个卷积过程中的矩阵(也就是被拉长过后的矩阵) //用float 内存不够用
for (int i = 0; i < outm; i++)
{
for (int j = 0; j < outm; j++)
{
int wh = i * outm * convAw + j * convAw;
int col1 = i * map + j;
A_convert[wh] = A[col1]; //第一次循环时把A[0] 的值赋给 A_convert[0]
A_convert[wh + 1] = A[col1 + 1];//第一次循环时把A[1] 的值赋给 A_convert[1]
A_convert[wh + 2] = A[col1 + 2];//第一次循环时把A[2] 的值赋给 A_convert[2]
int col2 = (i + 1) * map + j;
A_convert[wh + 3] = A[col2]; //第一次循环时把A[8] 的值赋给 A_convert[3]
A_convert[wh + 4] = A[col2 + 1];//第一次循环时把A[9] 的值赋给 A_convert[4]
A_convert[wh + 5] = A[col2 + 2];//第一次循环时把A[10] 的值赋给 A_convert[5]
int col3 = (i + 2) * map + j;
A_convert[wh + 6] = A[col3]; //第一次循环时把A[16] 的值赋给 A_convert[6]
A_convert[wh + 7] = A[col3 + 1]; //第一次循环时把A[17] 的值赋给 A_convert[7]
A_convert[wh + 8] = A[col3 + 2]; //第一次循环时把A[18] 的值赋给 A_convert[8]
}
}
//卷积后输出矩阵
//vector<int> C;
int imin=0,imax=255;
for (int i = 0; i < outm; i++)
{
for (int j = 0; j < outm; j++)
{
int a = 0;
int wh = i * outm * convAw + j * convAw;
for (int m =0; m < convAw; m++)
{
a += A_convert[wh + m] * B[m] ;
}
C.push_back(a); //在C中添加数据a
//-=-=-==-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=归一化 开始
//找最小值
if(a<imin) imin=a;
//找最大值
if(a>imax)imax=a;
}
}
//注意该滤波器没有归一化(和不是1.0),故滤出来的值可能不在[0,255]之内。通过减去最小值、除以最大/最小值之差、再乘以255并取整,
//把结果值归一到[0,255]之内,使之成为一幅灰度图像。
#if 1 //'sobel' 需要这个,否则得不到哪个效果
float s;
for (int i = 0; i < C.size (); i++)
{
s=C[i];
s=(s-imin)/(imax-imin)*255;
C[i]=(int)s;
}
#endif
//-=-=-==-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=归一化 结束
return true;
}
int main()
{
initgraph(SCREEN_WIDTH, SCREEN_HEIGHT,SHOWCONSOLE);//初始化图形环境
char jpgname[]="lena.jpg";
//载入图片
loadjpg(jpgname);
SetWorkingImage(&jpg);//设置已经加载图片为要扫描的图片
int height = jpg.getheight();
int width = jpg.getwidth();
if(height!=width)
error("出错了!这里需要方形的图");
// 1。定义被卷积的矩阵(其实是一个数组)
// 这里用一张单色图代替
int map = width;
卷积矩阵 isA(width,width);
byte *A=isA.data;//(byte*)malloc(sizeof(byte)*map*map);
//图像转化单色并保存结果
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
A[i*width+j]= GetRValue(RGBtoGRAY(getpixel(i , j)));//返回指定颜色 范围0-255
}
}
SetWorkingImage();//还原为屏幕
byte c;
//显示原图
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
c=A[i*width+j];//返回指定颜色 范围0-255
putpixel(i, j, RGB(c,c,c));
}
}
getch();
// 2。定义卷积核矩阵(其实也是一个数组,数组元素的个数3*3)
int const kernel = 3;
float B[kernel*kernel] =
{
#if 1
//'sobel' 索贝尔水平边缘增强滤波器
//-1, 0, 1,
//-2, 0, 2,
//-1, 0, 1
1.2886529 , 0.04068733 , -1.3082279,
1.43157125, 0.01173212 , -1.45389295,
1.34158182, -0.07245208, -1.27504027
#else
// 'unsharp' 反锐化对比度增强滤波器
-0.1667, -0.6667,-0.1667,
-0.6667, 4.3333,-0.6667,
-0.1667, -0.6667,-0.1667
#endif
};
//计算卷积输出矩阵的维数(其实是输出数组元素个数的开根号)
int outm = map - kernel + 1; //被卷积矩阵的维数-卷积核的维数+1 即8-3+1=6
//3。卷积后输出矩阵
vector<int> C;
//实现卷积,输出矩阵大小为 outm
卷积(A, map, B, kernel, C);
//输出卷积核矩阵
cout << "卷积核矩阵:" << endl;
for (int i = 0; i < kernel; i++)
{
for (int j = 0; j < kernel; j++)
{
cout << B[i*kernel + j] << " ";
}
cout << endl;
}
cout << endl;
//显示卷积图
//cout << "卷积后输出矩阵:" << endl;
for (int i = 0; i < outm; i++)
{
for (int j = 0; j < outm; j++)
{
c= C[i*outm + j];
putpixel(i+width+5, j, RGB(c,c,c));
}
}
getch();
closegraph();
system("pause");
return 0;
}效果图:

其中加了归一化,相当于网络权重的偏置
注意这里只能是方形图
vs2008+easyx +win8.1
本文介绍如何将SRCNN超分辨率重建的Matlab程序转换为C++程序,并详细展示了图像卷积处理的具体步骤,包括卷积核的定义、图像预处理、卷积操作及结果展示。
1439

被折叠的 条评论
为什么被折叠?



