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 的 lib 和 include 文件
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 协议 ,转载请注明出处!