鉴于用python写神经网络、卷积神经网络的话,不利于框架直接用numpy等来完成,速度实在是慢的吓人,所以改用c++来写。c++的话什么操作基本都要自己定义。由于卷积神经网络主要针对图像类,故最好定义一个适用于图像类的矩阵类,我这边使用了网上一个开源的用c++写好的卷积神经网络,使用了里面定义好的矩阵类,如下。这个类个人觉得非常好用,故写成博客,收藏一波,方便以后使用时候查找。
首先导入需要的模块:
#include <math.h>
#include <string.h>
#include <string>
#include <cstdlib>
#include <random>
#include <algorithm>
然后定义一个矩阵类,主要用于图像处理方面:
里面包括了矩阵的各种属性,宽、高、通道数,大小,元素个数等,以及对矩阵的各种操作,包括padding、翻转等,还有矩阵之间的各种运算,+、-、*、+=等。。。
namespace mojo
{
enum pad_type { zero = 0, edge = 1, median_edge = 2 };
// matrix class ---------------------------------------------------
// should use opencv if available
//
class matrix
{
int _size;
int _capacity;
float *_x_mem;
void delete_x() { delete[] _x_mem; x = NULL; _x_mem = NULL; }
// 4 extra for alignment and 4 for 3 padding for SSE
//float *new_x(const int size) { _x_mem = new float[size + 4+3]; x = (float *)(((uintptr_t)_x_mem + 16) & ~(uintptr_t)0x0F); return x; }
// avx mem aligment
float *new_x(const int size) { _x_mem = new float[size + 8 + 7]; x = (float *)(((uintptr_t)_x_mem + 32) & ~(uintptr_t)0x1F); return x; }
public:
std::string _name;
int cols, rows, chans;
int chan_stride;
int chan_aligned;
float *x;
// size must be divisible by 8 for AVX
virtual int calc_chan_stride(int w, int h)
{
if (chan_aligned)
{
int s = w*h;
const int remainder = s % 8;
if (remainder > 0) s += 8 - remainder;
return s;
}
else return w*h;
}
matrix( ): cols(0), rows(0), chans(0), _size(0), _capacity(0), chan_stride(0), x(NULL), chan_aligned(0)/*, empty_chan(NULL)*/{}
matrix( int _w, int _h, int _c=1, const float *data=NULL, int align_chan=0): cols(_w), rows(_h), chans(_c)
{
chan_aligned = align_chan;
chan_stride = calc_chan_stride(cols, rows);
_size= chan_stride*chans; _capacity=_size; x = new_x(_size);
if(data!=NULL) memcpy(x,data,_size*sizeof(float));
}
// copy constructor - deep copy
matrix( const matrix &m) : cols(m.cols), rows(m.rows), chan_aligned(m.chan_aligned), chans(m.chans), chan_stride(m.chan_stride), _size(m._size), _capacity(m._size) {x = new_x(_size); memcpy(x,m.x,sizeof(float)*_size); /*empty_chan = new unsigned char[chans]; memcpy(empty_chan, m.empty_chan, chans);*/} // { v=m.v; x=(float*)v.data();}
// copy and pad constructor
matrix( const matrix &m, int pad_cols, int pad_rows, mojo::pad_type padding= mojo::zero, int threads=1) : cols(m.cols), rows(m.rows), chans(m.chans), chan_aligned(m.chan_aligned), chan_stride(m.chan_stride), _size(m._size), _capacity(m._size)
{
x = new_x(_size); memcpy(x, m.x, sizeof(float)*_size);
*this = pad(pad_cols, pad_rows, padding, threads);
}
~matrix() { if (x) delete_x(); }
matrix get_chans(int start_channel, int num_chans=1) const
{
return matrix(cols,rows,num_chans,&x[start_channel*chan_stride]);
}
// if edge_pad==0, then the padded area is just 0.
// if edge_pad==1 it fills with edge pixel colors
// if edge_pad==2 it fills with median edge pixel color
matrix pad(int dx, int dy, mojo::pad_type edge_pad = mojo::zero, int threads=1) const
{
return pad(dx, dy, dx, dy, edge_pad, threads);
}
matrix pad(int dx, int dy, int dx_right, int dy_bottom, mojo::pad_type edge_pad = mojo::zero, int threads=1) const
{
matrix v(cols+dx+dx_right,rows+dy+dy_bottom,chans);//,NULL,this->chan_aligned);
v.fill(0);
//float *new_x = new float[chans*w*h];
#pragma omp parallel for num_threads(threads)
for(int k=0; k<chans; k++)
{
const int v_chan_offset=k*v.chan_stride;
const int chan_offset=k*chan_stride;
// find median color of perimeter
float median = 0.f;
if (edge_pad == mojo::median_edge)
{
int perimeter = 2 * (cols + rows - 2);
std::vector<float> d(perimeter);
for (int i = 0; i < cols; i++)
{
d[i] = x[i+ chan_offset]; d[i + cols] = x[i + cols*(rows - 1)+ chan_offset];
}
for (int i = 1; i < (rows - 1); i++)
{
d[i + cols * 2] = x[cols*i+ chan_offset];
// file from back so i dont need to cal index
d[perimeter - i] = x[cols - 1 + cols*i+ chan_offset];
}
std::nth_element(d.begin(), d.begin() + perimeter / 2, d.end());
median = d[perimeter / 2];
//for (int i = 0; i < v.rows*v.cols; i++) v.x[v_chan_offset + i] = solid_fill;
}
for(int j=0; j<rows; j++)
{
memcpy(&v.x[dx+(j+dy)*v.cols+v_chan_offset], &x[j*cols+chan_offset], sizeof(float)*cols);
if(edge_pad== mojo::edge)
{
// do left/right side
for(int i=0; i<dx; i++) v.x[i+(j+dy)*v.cols+v_chan_offset]=x[0+j*cols+chan_offset];
for (int i = 0; i<dx_right; i++) v.x[i + dx + cols + (j + dy)*v.cols + v_chan_offset] = x[(cols - 1) + j*cols + chan_offset];
}
else if (edge_pad == mojo::median_edge)
{
for (int i = 0; i < dx; i++) v.x[i + (j + dy)*v.cols + v_chan_offset] = median;
for (int i = 0; i < dx_right; i++) v.x[i + dx + cols + (j + dy)*v.cols + v_chan_offset] = median;
}
}
// top bottom pad
if(edge_pad== mojo::edge)
{
for(int j=0; j<dy; j++) memcpy(&v.x[(j)*v.cols+v_chan_offset],&v.x[(dy)*v.cols+v_chan_offset], sizeof(float)*v.cols);
for (int j = 0; j<dy_bottom; j++) memcpy(&v.x[(j + dy + rows)*v.cols + v_chan_offset], &v.x[(rows - 1 + dy)*v.cols + v_chan_offset], sizeof(float)*v.cols);
}
if (edge_pad == mojo::median_edge)
{
for (int j = 0; j<dy; j++)
for (int i = 0; i<v.cols; i++)
v.x[i + j*v.cols + v_chan_offset] = median;
for (int j = 0; j<dy_bottom; j++)
for (int i = 0; i<v.cols; i++)
v.x[i + (j + dy + rows)*v.cols + v_chan_offset] = median;
}
}
return v;
}
matrix crop(int dx, int dy, int w, int h, int threads=1) const
{
matrix v(w,h,chans);
#pragma omp parallel for num_threads(threads)
for(int k=0; k<chans; k++)
{
for(int j=0; j<h; j++)
{
memcpy(&v.x[j*w+k*v.chan_stride], &x[dx+(j+dy)*cols+k*chan_stride], sizeof(float)*w);
}
}
return v;
}
mojo::matrix shift(int dx, int dy, mojo::pad_type edge_pad=mojo::zero)
{
int orig_cols=cols;
int orig_rows=rows;
int off_x=abs(dx);
int off_y=abs(dy);
mojo::matrix shifted= pad(off_x, off_y, edge_pad);
return shifted.crop(off_x-dx, off_y-dy,orig_cols,orig_rows);
}
mojo::matrix flip_cols()
{
mojo::matrix v(cols,rows,chans);
for(int k=0; k<chans; k++)
for(int j=0; j<rows; j++)
for(int i=0; i<cols; i++)
v.x[i+j*cols+k*chan_stride]=x[(cols-i-1)+j*cols+k*chan_stride];
return v;
}
mojo::matrix flip_rows()
{
mojo::matrix v(cols, rows, chans);
for (int k = 0; k<chans; k++)
for (int j = 0; j<rows; j++)
memcpy(&v.x[(rows-1-j)*cols + k*chan_stride],&x[j*cols + k*chan_stride], cols*sizeof(float));
return v;
}
void clip(float min, float max)
{
int s = chan_stride*chans;
for (int i = 0; i < s; i++)
{
if (x[i] < min) x[i] = min;
if (x[i] > max) x[i]=max;
}
}
void min_max(float *min, float *max, int *min_i=NULL, int *max_i=NULL)
{
int s = rows*cols;
int mini = 0;
int maxi = 0;
for (int c = 0; c < chans; c++)
{
const int t = chan_stride*c;
for (int i = t; i < t+s; i++)
{
if (x[i] < x[mini]) mini = i;
if (x[i] > x[maxi]) maxi = i;
}
}
*min = x[mini];
*max = x[maxi];
if (min_i) *min_i = mini;
if (max_i) *max_i = maxi;
}
float mean()
{
const int s = rows*cols;
int cnt = 0;// channel*s;
float average = 0;
for (int c = 0; c < chans; c++)
{
const int t = chan_stride*c;
for (int i = 0; i < s; i++)
average += x[i + t];
}
average = average / (float)(s*chans);
return average;
}
float remove_mean(int channel)
{
int s = rows*cols;
int offset = channel*chan_stride;
float average=0;
for(int i=0; i<s; i++) average+=x[i+offset];
average= average/(float)s;
for(int i=0; i<s; i++) x[i+offset]-=average;
return average;
}
float remove_mean()
{
float m=mean();
int s = chan_stride*chans;
//int offset = channel*s;
for(int i=0; i<s; i++) x[i]-=m;
return m;
}
void fill(float val) { for(int i=0; i<_size; i++) x[i]=val;
}
void fill_random_uniform(float range)
{
std::mt19937 gen(0);
std::uniform_real_distribution<float> dst(-range, range);
for (int i = 0; i<_size; i++) x[i] = dst(gen);
}
void fill_random_normal(float std)
{
std::mt19937 gen(0);
std::normal_distribution<float> dst(0, std);
for (int i = 0; i<_size; i++) x[i] = dst(gen);
}
// deep copy
inline matrix& operator =(const matrix &m)
{
resize(m.cols, m.rows, m.chans, m.chan_aligned);
memcpy(x,m.x,sizeof(float)*_size);
// memcpy(empty_chan, m.empty_chan, chans);
return *this;
}
int size() const {return _size;}
void resize(int _w, int _h, int _c, int align_chans=0) {
chan_aligned = align_chans;
int new_stride = calc_chan_stride(_w,_h);
int s = new_stride*_c;
if(s>_capacity)
{
if(_capacity>0) delete_x(); _size = s; _capacity=_size; x = new_x(_size);
}
cols = _w; rows = _h; chans = _c; _size = s; chan_stride = new_stride;
}
// dot vector to 2d mat
inline matrix dot_1dx2d(const matrix &m_2d) const
{
mojo::matrix v(m_2d.rows, 1, 1);
for(int j=0; j<m_2d.rows; j++) v.x[j]=dot(x,&m_2d.x[j*m_2d.cols],_size);
return v;
}
// +=
inline matrix& operator+=(const matrix &m2){
for(int i = 0; i < _size; i++) x[i] += m2.x[i];
return *this;
}
// -=
inline matrix& operator-=(const matrix &m2) {
for (int i = 0; i < _size; i++) x[i] -= m2.x[i];
return *this;
}
// *= float
inline matrix operator *=(const float v) {
for (int i = 0; i < _size; i++) x[i] = x[i] * v;
return *this;
}
inline matrix operator *=(const matrix &v) {
for (int i = 0; i < _size; i++) x[i] = x[i] * v.x[i];
return *this;
}
inline matrix operator *(const matrix &v) {
matrix T(cols, rows, chans);
for (int i = 0; i < _size; i++) T.x[i] = x[i] * v.x[i];
return T;
}
// * float
inline matrix operator *(const float v) {
matrix T(cols, rows, chans);
for (int i = 0; i < _size; i++) T.x[i] = x[i] * v;
return T;
}
// + float
inline matrix operator +(const float v) {
matrix T(cols, rows, chans);
for (int i = 0; i < _size; i++) T.x[i] = x[i] + v;
return T;
}
// +
inline matrix operator +(matrix m2)
{
matrix T(cols,rows,chans);
for(int i = 0; i < _size; i++) T.x[i] = x[i] + m2.x[i];
return T;
}
// -
inline matrix operator -(matrix m2)
{
matrix T(cols, rows, chans);
for (int i = 0; i < _size; i++) T.x[i] = x[i] - m2.x[i];
return T;
}
};
}// namespace