当前位置:   article > 正文

【Matting】MODNet:实时人像抠图模型-NCNN C++量化部署_ncnn 人体分割模型

ncnn 人体分割模型

相关链接:

【Matting】MODNet:实时人像抠图模型-onnx python部署

【Matting】MODNet:实时人像抠图模型-笔记

【Matting】MODNet:实时人像抠图模型-onnx C++部署

MODNet是一个轻量级Matting模型,之前已经使用python部署MODNet的onnx模型,本章节将使用NCNN部署MODNet,除此之外,对模型进行静态量化,使其大小降低原模型的为1/4。Matting效果如下:

完整的代码和所需权重在文末链接。


一、NCNN编译

具体步骤可参考:官方编译教程

1、编译protobuf

下载protobuf:https://github.com/google/protobuf/archive/v3.4.0.zip

打开开始菜单中的x64 Native Tools Command Prompt for VS 2017命令行工具(更高级的版本也可以,我用2022成功了),编译protobuf。

  1. cd <protobuf-root-dir>
  2. mkdir build
  3. cd build
  4. cmake -G"NMake Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=%cd%/install -Dprotobuf_BUILD_TESTS=OFF -Dprotobuf_MSVC_STATIC_RUNTIME=OFF ../cmake
  5. nmake
  6. nmake install

2、编译NCNN

克隆NCNN仓库:

git clone https://github.com/Tencent/ncnn.git

编译NCNN(我这里没用Vulkan,需要的话参考官方教程),取代命令中的路径为自己的路径:

  1. cd <ncnn-root-dir>
  2. mkdir -p build
  3. cd build
  4. cmake -G"NMake Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=%cd%/install -DProtobuf_INCLUDE_DIR=<protobuf-root-dir>/build/install/include -DProtobuf_LIBRARIES=<protobuf-root-dir>/build/install/lib/libprotobuf.lib -DProtobuf_PROTOC_EXECUTABLE=<protobuf-root-dir>/build/install/bin/protoc.exe -DNCNN_VULKAN=OFF.. -DOpenCV_DIR=C:/opencv/opencv/build
  5. nmake
  6. nmake install

二、NCNN部署

1、环境

windows cpu

opencv 4.5.5

visual studio 2019

2、onnx转ncnn模型

首先将上面得到的简化后的onnx模型转换为ncnn模型(得到param和bin后缀的2个文件),注意转换的时候要没有报错,不然后续加载会出错:

可以在这里下载转换好的模型:传送门

../ncnn/build/tools/onnx/onnx2ncnn simple_modnet.onnx simple_modnet.param simple_modnet.bin 

3、C++代码

代码结构:

 MODNet.h代码:

  1. #pragma once
  2. #include <string>
  3. #include "net.h"
  4. #include <opencv.hpp>
  5. #include "time.h"
  6. class MODNet
  7. {
  8. private:
  9. std::string param_path;
  10. std::string bin_path;
  11. std::vector<int> input_shape;
  12. ncnn::Net net;
  13. const float norm_vals[3] = { 1 / 177.5, 1 / 177.5, 1 / 177.5 };
  14. const float mean_vals[3] = { 175.5, 175.5, 175.5 };
  15. cv::Mat normalize(cv::Mat& image);
  16. public:
  17. MODNet() = delete;
  18. MODNet(const std::string param_path, const std::string bin_path, std::vector<int> input_shape);
  19. ~MODNet();
  20. cv::Mat predict_image(cv::Mat& image);
  21. void predict_image(const std::string& src_image_path, const std::string& dst_path);
  22. void predict_camera();
  23. };

 MODNet.cpp代码:

  1. #include "MODNet.h"
  2. MODNet::MODNet(const std::string param_path, const std::string bin_path, std::vector<int> input_shape)
  3. :param_path(param_path), bin_path(bin_path), input_shape(input_shape) {
  4. net.load_param(param_path.c_str());
  5. net.load_model(bin_path.c_str());
  6. }
  7. MODNet::~MODNet() {
  8. net.clear();
  9. }
  10. cv::Mat MODNet::normalize(cv::Mat& image) {
  11. std::vector<cv::Mat> channels, normalized_image;
  12. cv::split(image, channels);
  13. cv::Mat r, g, b;
  14. b = channels.at(0);
  15. g = channels.at(1);
  16. r = channels.at(2);
  17. b = (b / 255. - 0.5) / 0.5;
  18. g = (g / 255. - 0.5) / 0.5;
  19. r = (r / 255. - 0.5) / 0.5;
  20. normalized_image.push_back(r);
  21. normalized_image.push_back(g);
  22. normalized_image.push_back(b);
  23. cv::Mat out = cv::Mat(image.rows, image.cols, CV_32F);
  24. cv::merge(normalized_image, out);
  25. return out;
  26. }
  27. cv::Mat MODNet::predict_image(cv::Mat& image) {
  28. cv::Mat rgbImage;
  29. cv::cvtColor(image, rgbImage, cv::COLOR_BGR2RGB);
  30. ncnn::Mat in = ncnn::Mat::from_pixels_resize(rgbImage.data, ncnn::Mat::PIXEL_RGB, image.cols, image.rows, input_shape[3], input_shape[2]);
  31. in.substract_mean_normalize(mean_vals, norm_vals);
  32. ncnn::Extractor ex = net.create_extractor();
  33. ex.set_num_threads(4);
  34. ex.input("input", in);
  35. ncnn::Mat out;
  36. ex.extract("output", out);
  37. cv::Mat mask(out.h, out.w, CV_8UC1);
  38. const float* probMap = out.channel(0);
  39. for (int i{ 0 }; i < out.h; i++) {
  40. for (int j{ 0 }; j < out.w; ++j) {
  41. mask.at<uchar>(i, j) = probMap[i * out.w + j] > 0.5 ? 255 : 0;
  42. }
  43. }
  44. cv::resize(mask, mask, cv::Size(image.cols, image.rows), 0, 0);
  45. cv::Mat segFrame;
  46. cv::bitwise_and(image, image, segFrame, mask = mask);
  47. return segFrame;
  48. }
  49. void MODNet::predict_image(const std::string& src_image_path, const std::string& dst_path) {
  50. cv::Mat image = cv::imread(src_image_path);
  51. cv::Mat segFrame = predict_image(image);
  52. cv::imwrite(dst_path, segFrame);
  53. }
  54. void MODNet::predict_camera() {
  55. cv::Mat frame;
  56. cv::VideoCapture cap;
  57. int deviceID{ 0 };
  58. int apiID{ cv::CAP_ANY };
  59. cap.open(deviceID, apiID);
  60. if (!cap.isOpened()) {
  61. std::cout << "Error, cannot open camera!" << std::endl;
  62. return;
  63. }
  64. //--- GRAB AND WRITE LOOP
  65. std::cout << "Start grabbing" << std::endl << "Press any key to terminate" << std::endl;
  66. int count{ 0 };
  67. clock_t start{ clock() }, end{ 0 };
  68. double fps{ 0 };
  69. for (;;)
  70. {
  71. // wait for a new frame from camera and store it into 'frame'
  72. cap.read(frame);
  73. // check if we succeeded
  74. if (frame.empty()) {
  75. std::cout << "ERROR! blank frame grabbed" << std::endl;
  76. break;
  77. }
  78. cv::Mat segFrame = predict_image(frame);
  79. // fps
  80. ++count;
  81. end = clock();
  82. fps = count / (float(end - start) / CLOCKS_PER_SEC);
  83. if (count >= 50) {
  84. count = 0; //防止计数溢出
  85. start = clock();
  86. }
  87. std::cout << "FPS: " << fps << " Seg Image Number: " << count << " time consume:" << (float(end - start) / CLOCKS_PER_SEC) << std::endl;
  88. //设置绘制文本的相关参数
  89. std::string text{ std::to_string(fps) };
  90. int font_face = cv::FONT_HERSHEY_COMPLEX;
  91. double font_scale = 1;
  92. int thickness = 2;
  93. int baseline;
  94. cv::Size text_size = cv::getTextSize(text, font_face, font_scale, thickness, &baseline);
  95. //将文本框居中绘制
  96. cv::Point origin;
  97. origin.x = 20;
  98. origin.y = 20;
  99. cv::putText(segFrame, text, origin, font_face, font_scale, cv::Scalar(0, 255, 255), thickness, 8, 0);
  100. // show live and wait for a key with timeout long enough to show images
  101. imshow("Live", segFrame);
  102. if (cv::waitKey(5) >= 0)
  103. break;
  104. }
  105. cap.release();
  106. cv::destroyWindow("Live");
  107. return;
  108. }

main.cpp代码:

  1. #include <opencv.hpp>
  2. #include <iostream>
  3. #include "MODNet.h"
  4. #include <vector>
  5. #include "net.h"
  6. #include "time.h"
  7. int main() {
  8. std::string param_path{ "onnx_model\\simple_modnet.param" };
  9. std::string bin_path{ "onnx_model\\simple_modnet.bin" };
  10. std::vector<int> input_shape{ 1, 3, 512, 512 };
  11. MODNet model(param_path, bin_path, input_shape);
  12. // 预测并显示
  13. cv::Mat image = cv::imread("C:\\Users\\langdu\\Pictures\\test.png");
  14. cv::Mat segFrame = model.predict_image(image);
  15. cv::imshow("1", segFrame);
  16. cv::waitKey(0);
  17. // 摄像头
  18. //model.predict_camera();
  19. return -1;
  20. }

三、NCNN量化

对于移动设备,对模型大小要求非常苛刻,需要有效地方法来降低其存储空间,量化是一种有效地方法降低模型大小,量化资料可参考:【深度学习】模型量化-笔记/实验

这里使用的是静态量化的方法,量化后模型大小对比:

bin(KB)param(KB)
量化前2523622
量化后644224

由上表看出,模型经过量化后,其大小仅为原来的1/4左右(预测会有一定的精度损失)。下面开始量化教程,参考官方教程

1、模型优化

使用ncnnoptimize(在build\tools目录下)优化模型。

./ncnnoptimize simple_modnet.param simple_modnet.bin quantize_modnet.param quantize_modnet 0

2、创建calibration表

ncnn创建calibration表时,mean和norm参数可以参考这里修改,另外注意pixel设置和MODNet官方repo一致,为BGR。images文件夹下存放用来校准的图片。

  1. find images/ -type f > imagelist.txt
  2. ./ncnn2table quantize_modnet.param quantize_modnet.bin imagelist.txt modnet.table mean=[177.5, 177.5, 177.5] norm=[0.00784, 0.00784, 0.00784] shape=[512, 512, 3] pixel=BGR thread=8 method=kl

3、量化

使用下面的命令得到int8模型。

./ncnn2int8 quantize_modnet.param quantize_modnet-opt.bin modnet_int8.param modnet_int8.bin modnet.table

4、使用

得到了int8模型,使用非常简单,只需要把上面的代码中,bin和param路径替换为生成的int8模型路径即可。

5、量化后预测结果

首先给出未量化的预测结果:

 量化后结果(精度有损失,鞋子预测全了,地板错误多了):

四、相关链接

1、NCNN部署全部代码+所有模型权重

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/284934?site
推荐阅读
相关标签
  

闽ICP备14008679号