从这章开始,准备把哪个超分辨率重建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