MTCNN(六)c代码网络结构的更改

版权声明:转载注明出处:邢翔瑞的技术博客https://blog.csdn.net/weixin_36474809/article/list/1 https://blog.csdn.net/weixin_36474809/article/details/83056795

背景:将MTCNN部署在FPGA上需要将其代码设计为C代码,c代码的网络结构需要与python代码保持一致。

目的:将MTCNN的c代码网络结构转为与python代码一致。

一、相关代码与含义

1.1 相关知识

类对象

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完成分配空间的任务)。既可以在声明对象时实例化(创建)对象,也可以先声明对象,然后再创建。

this指针

https://baike.baidu.com/item/C++this%E6%8C%87%E9%92%88/637012

this作用域是在类内部,当在类的非静态成员函数中访问类的非静态成员的时候,编译器会自动将对象本身的地址作为一个隐含参数传递给函数。

1.2 与网络结构相关的数据

pBox.h程序之中,均为struct结构体

  • pBox 理解为三维的picture box,包含了数据指针,宽,高,channel
  • pRelu 相关prelu的斜率参数,包含了数据指针,宽度
  • Weight 权重参数,包含了两个数据指针:1指向卷积核的数据,2与卷积核的偏置,其他参数为lastChannel上一层的featureMap数量,selfChannel本层输出的featureMap数量,卷积核大小,步长,pad信息
  • Bbox,bounding box, 包含了得分score,四个顶点的坐标,area,exist,关键点的坐标ppoint[2*NumPoint],和regrecoord
  • orderScore,包含了指向结构的指针,score以及初始的顺序。

1.3 相关的函数及含义

在network.cpp之中,

  • 所有的init函数都是malloc相应的空间,然后数据指针指向的部分全部置0.
  • 所有的__toMatirx函数都是将相应的数据转为矩阵形式。
  • 具体名字的函数是具体的前馈运算

具体系列的函数

  • initConvAndFc函数,初始化卷积层与Fc层,把相应的权重参数传给Weight结构体,然后返回值该层权重的个数,指针指向的权重值先置为0。

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);
  • image2Matrix,将Mat格式读入的图像转为pBox格式的图像
  • featurePad, 将pBox格式的图像进行pad
  • feature2Matrix,将featureMap转为矩阵形式用于后面的矩阵乘
  • convolution,第一个参数为权重(权重自读取进来之后就始终没有变化,矩阵形式的权重与三维权重大小相同,而featureMap的大小在转换时就需要发生相应的变化),第二个参数的目的仅仅是传递网络结构给卷积函数。最后一个参数为矩阵形式的featureMap。

二、与网络结构定义相关的语句

2.1 mtcnn.h

定义了Pnet,Rnet,Onet的class,class中定义了网络的结构

mtcnn.cpp

2.2 Pnet::Pnet

定义Pnet::Pnet函数,在函数中实例化Pnet的class,将其中指针指向具体的struct 的pBox,weight

初始化conv与fc层

根据层参数初始化权重指针组,读取权重

2.3 Pnet::~Pnet

释放Pnet之前的指针指向的内存

2.4 Pnet::run

Init系函数,转化为matrix系列函数,卷积与池化init系列函数

然后是网络具体的前馈计算

2.5 Rnet::,Onet::系列相关

与上面这些一致

2.6 mtcnn.cpp中与网络结构无关的函数

Pnet::generateBbox,根据相应score生成备选框,无需更改

mtcnn::detectObject应该不涉及关于网络结构的更改相关的问题。

三、Pnet结构的更改

3.1 python代码前后结构

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

   

3.2 c代码中变量及含义

原始结构: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

   

3.3 相关函数参数

init系列

image2MatrixInit(输入mat格式图片,输出rgb格式图片)

feature2MatrixInit(该层输入,该层需转化为的matrix格式的输入,该层权重)

convolutionInit(该层权重,该层输入,该层输出,该层matrix格式的输入)

maxPoolingInit(该层输入,该层输出,kernelSize,Stride)

运算系列

feature2matrix(该层输入,该层需转化的matrix格式的输入,该层权重)

convolution(该层权重,该层输入,该层输出,该层marix格式输入)

prelu(该层输入输出,上层卷积的偏置,权重斜率)

maxpooling(该层输入,该层输出,kernelSize,Stride)

3.4 更改顺序

mtcnn.h中pnet中的private成员定义

mtcnn.cpp中

Pnet函数中this指针指向的量

dataNumber中数值的值

pointTeam中数值指针值

readData的读入的值

~Pnet中的free的指针值

四、卷积运算相关代码

原版的代码之中只有形式为valid的卷积,我们需要加入padding以及stride的卷积。代码之中关于卷积以及stride和padding的代码在network.cpp之中,我们需要搞懂此代码。

4.1 pad运算

原版的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) <leftPad || (row % outpBox->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

4.2 feature转为矩阵形式

卷积的矩阵乘法运用的是二维矩阵乘,所以需要将feature转换为二维形式与权重对应。以下为图像转为矩阵乘的参考。

//image2Matrix  network.cpp
for (int rowI = 0; rowI < image.rows; rowI++){
	for (int colK = 0; colK < image.cols; colK++){
		*p = (image.at<Vec3b>(rowI, colK)[0] - 127.5)*0.0078125;//opencvµÄͨµÀÅÅÐòÊÇRGB
		*(p + image.rows*image.cols) = (image.at<Vec3b>(rowI, colK)[1] - 127.5)*0.0078125;
		*(p + 2*image.rows*image.cols) = (image.at<Vec3b>(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转为矩阵形式,

4.3 convolution

// -------------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->selfChannel,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是对应矩阵还没变换之前,在主维度方向的维度。(如果是行优先就是列数)。

猜你喜欢

转载自blog.csdn.net/weixin_36474809/article/details/83056795
今日推荐