C代码版本的MTCNN 从tensorflow权重参数生成bin文件

版权声明:版权所有,转载请申明并附加链接,谢谢! https://blog.csdn.net/qqqzmy/article/details/85066137

摘要

MTCNN是一个优秀的人脸检测模型,在网上有各种框架下的版本,在项目中需要使用MTCNN的C代码版本,该版本的作者并没有提供生成代码运行的txt权重参数文件的程序,同时使用txt文件来存参数,文件的体积比较大,在项目中我们需要使用tensorflow训练出来的权重参数给模型运行,因此就涉及到如何把tensorflow训练出来的权重文件转化为bin文件,用于替代程序中的txt文件

如何生成bin文件

以pnet.bin的生成为例:

PnetOutFile = "Pnet.bin"
PnetBinFile = open(PnetOutFile,'wb')
PnetBinFile.write(struct.pack('f', Pconv1_w[y,x,i,j]))
PnetBinFile.close()

其中的“f”是写入浮点数的意思,Pconv1_w[y,x,i,j]是我们从tensorflow权重文件中取出来的参数,每执行一次则向bin文件中存入一个参数,存取都是按顺序的。最后记得close文件

权重文件写入顺序

1、conv层权重参数

Pconv1_w = sess.run(pnet_var[0])
ky, kx, num_in_map, num_out_kerns = Pconv1_w.shape
print(0,Pconv1_w.shape)
    for j in range(num_out_kerns):
        for i in range(num_in_map):
            for y in range(ky):
                for x in range(kx):
                    PnetBinFile.write(struct.pack('f', Pconv1_w[y,x,i,j]))

卷积层通过sess.run读取到了一个权重矩阵Pconv1_w[y,x,i,j],这个矩阵是四维的,维度分别为y方向权重、x方向权重、输入通道、输出通道。
我们需要把这个四维矩阵转化为一列,存入bin文件中,用于给c代码读取。

2、conv层偏置、prelu层的写入

#Rconv1_b
    Rconv1_b = sess.run(rnet_var[1])
    print(1,Rconv1_b.shape)
    for item in range(0,len(Rconv1_b[:])):
        RnetBinFile.write(struct.pack('f',Rconv1_b[item]))
#RPReLU1
    RPReLU1 = sess.run(rnet_var[2])
    print(2,RPReLU1.shape)
    for item in range(0,len(RPReLU1[:])):
        RnetBinFile.write(struct.pack('f',RPReLU1[item]))

这两个层都是一维的原理相同,比较简单,直接按顺序写就好了

3、fc层

#Rconv4_w  (576,128)
    Rconv4_w = sess.run(rnet_var[9])
    print(9,Rconv4_w.shape)
    num_in_map, num_out_kerns = Rconv4_w.shape
    RfcTemp = []
    RfcTemp2 = []
    for j in range(num_out_kerns):#128
        for i in range(num_in_map):#576
            RfcTemp.append(Rconv4_w[i,j])
            for n in range(64):
                for k in range(9):
                    RfcTemp2.append(RfcTemp[n + k*64])
            for l in range(576):
                RnetBinFile.write(struct.pack('f', RfcTemp2[l]))
            RfcTemp = []
            RfcTemp2 = []    

这一层比较难,需要做顺序的调换
做调换的原因是Tensorflow框架和C代码下的全连接输出一直不一致,原因在于把上一层卷积出来的结果reshape成一列的过程不正确,C代码中并没有写这个reshape成一列的函数,在存储中上一层输出的数据conv3_out->pdata在内存中就是存层一列的,C代码把它直接送入fc层,导致了reshape顺序的错误。
详细的排列顺序如下:在TF或C代码中,Rnet的FC前一层(PReLU3)的输出为(3,3, 64),对其进行重排如下:每个channel有9个参数,一共64个kernel,把每个kernel同个位置的参数取出,按顺序排列在一起。如下图:
在这里插入图片描述
知道了c代码和TF的排列顺序,就可以按照顺序去做转化了

调试方法介绍

在调试过程中需要我们需要一层一层调,先保证TF和C代码的输入一致,通过相同的层,如果输出一致的话,那么权重的顺序就排列对了,在c代码中直接使用自带的权重打印函数pBoxShow把数据打印出来:

    feature2Matrix(this->rgb, this->conv1_matrix, this->conv1_wb);
    convolution(this->conv1_wb, this->rgb, this->conv1_out, this->conv1_matrix);
    prelu(this->conv1_out, this->conv1_wb->pbias, this->prelu_gmma1->pdata);
    pBoxShow(this->conv1_out);

在TF中,我们采用这样的策略,把我们要看的层后面的操作屏蔽,然后输出我们要看的层的输出作为最后的网络输出:
在这里插入图片描述
这样网络输出就是prelu1层操作后的结果,然后再打印出来:

           out = pnet(img)
           im_data = np.array(out)
           for c in range(3):
               print("channel:",c)
               tempImg = []
               for y in range(17):
                   for x in range(21):
                       tempImg.append(im_data[y,x,c])
                   print(tempImg)
                   tempImg = []
               print("\n")

C代码修改

网络的输出结果一致后,运行程序发现框可以准确预测,可是关键并不能准确预测。
这是因为在TF框架和C代码中对于网络最终输出数据转换成坐标的程序有差异,直接使用会造成关键点不正确
解决方法:修改Onet中的关键点的计算代码
在这里插入图片描述
在这里插入图片描述

其他问题

C代码中也有一些缺陷:
network.cpp代码中的maxpooling()函数有缺陷,在pooling过程中如果遇见需要补零的feature map,只对x方向补零,却没有y方向补零,导致最后一行的maxpooling输出有差错
解决办法:在network.cpp中增加了y方向的补零代码
在这里插入图片描述

没有更多推荐了,返回首页