mxnet2ncnn

mxnet2ncnn 部署方案

1. 下载并编译 ncnn 框架

# install xcode and protobuf
# install protobuf
$ brew install protobuf

# build & install ncnn
$ cd <ncnn-root-dir>
$ mkdir -p build
$ cd build

$ cmake -DNCNN_VULKAN=OFF ..

$ make -j4
$ make install

2. model slim

从如下地址下载 mobileface 的文件:

https://pan.baidu.com/s/1If28BkHde4fiuweJrbicVA

解压可以得到三个文件: log、model-0000.params 和 model-symbol.json

执行:

python model_slim.py  --model  ../models/model-y1-test2/model,0

此时 会生成两个文件:../models/model-y1-test2/models-

3. 使用 mxnet2ncnn 工具来转化模型

cd ncnn/build/tools/mxnet
./mxnet2ncnn models-symbol.json models-0000.params mobilefacenet.param mobilefacenet.bin

生成两个新的文件 mobilefacenet.param ,mobilefacenet.bin,这就是我们部署 ncnn 需要用到的文件。

4. 项目构建

arcface 我们的主项目文件
image  存储测试图像文件
models  存储我们的转换完成的 人脸检测、人脸识别模型
ncnn  存放 ncnn 的 libinclude 文件
Makefile 文件

其中 arcface 存放相关文件和 Makefile 文件。进入主项目文件 make 然后执行 ./main 即可。

需要重点研究的文件:(1) model_slim.py (2)arcface 下的 四个 文件 arcface/mtcnn/base/Makefile 文件。

​ ncnn只是一个前向推断的库,利用它可以快速完成前向传播。在项目中,我们将图片传给MTCNN, 分别经过三个网络Pnet、RNet、ONet,然后产生待检测的人脸, 然后再将待检测的人脸送给facent, 最后比对产生的128D向量的距离。这样就可以完成人脸的识别。具体结合代码来说:

下面这个函数是MTCNN的主要的接口文件:

vector<FaceInfo> MtcnnDetector::Detect(ncnn::Mat img){
    int img_w = img.w;  // 获取 宽度
    int img_h = img.h; // 获取高度
    vector<FaceInfo> pnet_results = Pnet_Detect(img);  // 让图片经过PNet
    doNms(pnet_results, 0.7, "union");  // 对输出的结果进行nms
    refine(pnet_results, img_h, img_w, true);  // refine,把结果放在 pnet_results 中
    vector<FaceInfo> rnet_results = Rnet_Detect(img, pnet_results);  // 让图片经过 RNet
    doNms(rnet_results, 0.7, "union"); // 对输出结果进行 nms
    refine(rnet_results, img_h, img_w, true); // refine, 把结果放在 Rnet_results中

    vector<FaceInfo> onet_results = Onet_Detect(img, rnet_results);  // 把图片 经过 Onet
    refine(onet_results, img_h, img_w, false);  // refine
    doNms(onet_results, 0.7, "min");  //  对最后的结果进行refine
    Lnet_Detect(img, onet_results);  // 后处理
    return onet_results;  // 返回检测结果
}

这个文件是 facenet 的前向推断框架:

vector<float> Arcface::getFeature(ncnn::Mat img)
{
    vector<float> feature;
    ncnn::Mat in = resize(img, 112, 112); // 将人脸调整到 112x112
    in = bgr2rgb(in); // 通道变幻
    ncnn::Extractor ex = net.create_extractor();  // 设置提取器
    ex.set_light_mode(true);  
    ex.input("data", in);  // 让图片进入网络
    ncnn::Mat out;
    ex.extract("fc1", out);  // 提取出 fc1 层的结果
    feature.resize(this->feature_dim);  // 结果resize
    for (int i = 0; i < this->feature_dim; i++) 
        feature[i] = out[i];  
    normalize(feature); // 正则化特征
    return feature; // 返回特征
}

下面这个文件是 main.cpp, 现在看来非常简单了

int main(int argc, char* argv[])
{
    Mat img1;
    Mat img2;

    img1 = imread("../image/gyy1.jpeg");  // 读取图片
    img2 = imread("../image/gyy2.jpeg");

    ncnn::Mat ncnn_img1 = ncnn::Mat::from_pixels(img1.data, ncnn::Mat::PIXEL_BGR, img1.cols, img1.rows);  // 转换为 ncnn 格式
    ncnn::Mat ncnn_img2 = ncnn::Mat::from_pixels(img2.data, ncnn::Mat::PIXEL_BGR, img2.cols, img2.rows);
    // 检测
    MtcnnDetector detector("../../models"); 

    vector<FaceInfo> results1 = detector.Detect(ncnn_img1);
    vector<FaceInfo> results2 = detector.Detect(ncnn_img2);
    // 处理检测结果,其实就是提取图片
    ncnn::Mat det1 = preprocess(ncnn_img1, results1[0]);
    ncnn::Mat det2 = preprocess(ncnn_img2, results2[0]);
 
    Arcface arc("../../models");
    // 提取特征
    vector<float> feature1 = arc.getFeature(det1);
    vector<float> feature2 = arc.getFeature(det2);

    // 特征比较
    std::cout << "Similarity: " << calcSimilar(feature1, feature2) << std::endl;;

    imshow("det1", ncnn2cv(det1));
    imshow("det2", ncnn2cv(det2));

    waitKey(0);
    return 0;
}

移动端部署要点

主要的解决方案:将MTCNN 和 facenet 的接口组织成为一个人脸 face_reg 的接口,然后使用 objective-c 调用该C++ 代码,然后在使用swift 调用 objective-c 的代码。之所以这样做是因为,swift 不能调用 C++ 呀。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!