背景:将MTCNN部署在FPGA上需要将其代码设计为C代码,c代码的网络结构需要与python代码保持一致。
目的:将MTCNN的c代码网络结构转为与python代码一致。
目录
一、相关代码与含义
1.1 相关知识
类对象
this指针
1.2 与网络结构相关的数据
1.3 相关的函数及含义
在network.cpp之中,
具体系列的函数
二、与网络结构定义相关的语句
2.1 mtcnn.h
mtcnn.cpp
2.2 Pnet::Pnet
2.3 Pnet::~Pnet
2.4 Pnet::run
2.5 Rnet::,Onet::系列相关
2.6 mtcnn.cpp中与网络结构无关的函数
三、Pnet结构的更改
3.1 python代码前后结构
3.2 c代码中变量及含义
3.3 相关函数参数
init系列
运算系列
3.4 参数更改顺序
四、卷积运算相关代码
4.1 pad运算
4.2 feature转为矩阵形式
4.3 convolution
五、结构更改顺序
5.1 mtcnn.h中的定义
5.2 mtcnn.cpp
https://blog.csdn.net/qq_32583189/article/details/52412369
http://www.baike.com/wiki/%E7%B1%BB%E5%92%8C%E5%AF%B9%E8%B1%A1
实例化一个对象就是通过new运算符为对象分配空间(类属于复合数据类型,在声明对象时,系统并没有为对象分配空间,用户需要应用new完成分配空间的任务)。既可以在声明对象时实例化(创建)对象,也可以先声明对象,然后再创建。
https://baike.baidu.com/item/C++this%E6%8C%87%E9%92%88/637012
this作用域是在类内部,当在类的非静态成员函数中访问类的非静态成员的时候,编译器会自动将对象本身的地址作为一个隐含参数传递给函数。
pBox.h程序之中,均为struct结构体
Pnet::run
image2Matrix(image, this->rgb);
feature2Matrix(this->rgb, this->conv1_matrix, this->conv1_wb);
convolution(this->conv1_wb, this->rgb, this->conv1, this->conv1_matrix);
定义了Pnet,Rnet,Onet的class,class中定义了网络的结构
定义Pnet::Pnet函数,在函数中实例化Pnet的class,将其中指针指向具体的struct 的pBox,weight
初始化conv与fc层
根据层参数初始化权重指针组,读取权重
释放Pnet之前的指针指向的内存
Init系函数,转化为matrix系列函数,卷积与池化init系列函数
然后是网络具体的前馈计算
与上面这些一致
Pnet::generateBbox,根据相应score生成备选框,无需更改
mtcnn::detectObject应该不涉及关于网络结构的更改相关的问题。
Pnet原始结构
Feature size |
name | Kernel size |
Stride |
Padding |
12*12*3 |
conv1 PReLU1 |
3*3*10 |
1 |
Valid |
10*10*10 |
pool1 | Maxpool 2*2 |
2 |
Same |
5*5*10 |
conv2 PReLU2 |
3*3*16 |
1 |
Valid |
3*3*16 |
conv3 PReLU3 |
3*3*32 |
1 |
Valid |
1*1*32 |
|
|
Pnet 最终结构,只有3×3的卷积(为保证输出的得分图与输入的映射,需要same与valid)
Feature size |
name | Kernel size |
Stride |
Padding |
12*12*3 |
conv1 PReLU1 |
3*3*10 |
1 |
Valid |
10*10*10 |
pool1_conv1 pool1_PReLU1 |
3*3*16 |
2 |
Same |
5*5*16 |
conv2 PReLU2 |
3*3*32 |
2 |
Valid |
3*3*32 |
conv3 PReLU3 |
3*3*32 |
1 |
Valid |
1*1*32 |
|
|
原始结构:mtcnn.h与mtcnn.cpp
layer name | input and output | weight |
conv1 PReLU1 |
rgb:矩阵格式的rgb图像 conv1_matrixI_in conv1 |
conv1_wb prelu_gmma1 |
pool1 | maxPooling1 |
|
conv2 PReLU2 |
maxPooling_matrix conv2 |
conv2_wb prelu_gmma2 |
conv3 PReLU3 |
conv3_matrix conv3 |
conv3_wb prelu_gmma3 |
post conv layers |
image2MatrixInit(输入mat格式图片,输出rgb格式图片)
feature2MatrixInit(该层输入,该层需转化为的matrix格式的输入,该层权重)
convolutionInit(该层权重,该层输入,该层输出,该层matrix格式的输入)
maxPoolingInit(该层输入,该层输出,kernelSize,Stride)
feature2matrix(该层输入,该层需转化的matrix格式的输入,该层权重)
convolution(该层权重,该层输入,该层输出,该层marix格式输入)
prelu(该层输入输出,上层卷积的偏置,权重斜率)
maxpooling(该层输入,该层输出,kernelSize,Stride)
mtcnn.h中pnet中的private成员定义
mtcnn.cpp中
Pnet函数中this指针指向的量
dataNumber中数值的值
pointTeam中数值指针值
readData的读入的值
~Pnet中的free的指针值
init系列开辟内存空间的值
run内运行相应的运算的值
原版的代码之中只有形式为valid的卷积,我们需要加入padding以及stride的卷积。代码之中关于卷积以及stride和padding的代码在network.cpp之中,我们需要搞懂此代码。
原版的pad是左右两边都加相同的pad,并且嵌套在convolution之中,顺序并不对。
原始的pad代码是两边都pad相同的尺寸,我们需要更改尺寸,我们加入leftPad与rightPad。(此处留有隐患就是左右顺序是否是正确的,但我们可以大致看出,feature的排列顺序是先行后列后channel)
//network.cpp in featurePad
for (int row = 0; row < outpBox->channel*outpBox->height;row++){
if ((row%outpBox->height) height >(outpBox->height-rightPad-1))){
p += outpBox->width;
continue;
}
p += leftPad;
memcpy(p, pIn, pbox->width*sizeof(mydataFmt));
p += pbox->width + rightPad;
pIn += pbox->width;
}
据此可以大致看出循环是for channel,for row,for col
卷积的矩阵乘法运用的是二维矩阵乘,所以需要将feature转换为二维形式与权重对应。以下为图像转为矩阵乘的参考。
//image2Matrix network.cpp
for (int rowI = 0; rowI < image.rows; rowI++){
for (int colK = 0; colK < image.cols; colK++){
*p = (image.at(rowI, colK)[0] - 127.5)*0.0078125;//opencvµÄͨµÀÅÅÐòÊÇRGB
*(p + image.rows*image.cols) = (image.at(rowI, colK)[1] - 127.5)*0.0078125;
*(p + 2*image.rows*image.cols) = (image.at(rowI, colK)[2] - 127.5)*0.0078125;
p++;
}
}
读取图像时的形式,将二维图像读取为线性的存储。
//network.cpp feature2matrix
for (int row = 0; row< h_out; row ++){
for (int col = 0; col < w_out; col++){
pIn = pboxIn->pdata + row*stride*pboxIn->width + col*stride;
for (int channel = 0; channel < pboxIn->channel; channel++){
ptemp = pIn + channel*pboxIn->height*pboxIn->width;
for (int kernelRow = 0; kernelRow < kernelSize; kernelRow++){
memcpy(p, ptemp, kernelSize*sizeof(mydataFmt));//from ptemp to p
p += kernelSize;
ptemp += pboxIn->width;
}
}
}
}
将feature转为矩阵形式,
// -------------convulution in 2D matrix format-------------------
// input kernel matrix * input feature matrix(Trans) = output feature matrix
// height (outChannels) height (3D_KernelSize) height (outChannels)
// width (3D_KernelSize) width (outFeatureSize) width (outFeatureSize)
//C=αAB + βC : outpBox=weightIn*matrixIn(T)
// RowMajor(c code) A no trans B trans
cblas_sgemm(CblasRowMajor, CblasNoTrans, CblasTrans, \
//A row C row B col C col A col B row alpha
weightIn->out_ChannelNum,matrixIn->height,matrixIn->width, 1,\
//A* A'col B* B'col beta C* C'col
weightIn->pdata,matrixIn->width,matrixIn->pdata,matrixIn->width,0,outpBox->pdata,matrixIn->height);
cblas_segmm的参数
https://blog.csdn.net/u012235274/article/details/52769682
cblas_sgemm(order, transA, transB, M, N, K, ALPHA, A, LDA, B, LDB, BETA, C, LDA);
第一个参数的函数是存储的有限性,有行优先和列优先(c语言是行优先)
第二个参数和第三个参数是是否转置
A矩阵经过transA之后的维度是M×K
B矩阵经过transB之后的维度是K×N
C矩阵的维度是M×N
LDA和LDB是对应矩阵还没变换之前,在主维度方向的维度。(如果是行优先就是列数)。