赞
踩
ncnn 是一个为手机端极致优化的高性能神经网络前向计算框架。ncnn 从设计之初深刻考虑手机端的部署和使用。无第三方依赖,跨平台,手机端 cpu 的速度快于目前所有已知的开源框架。基于 ncnn,开发者能够将深度学习算法轻松移植到手机端高效执行。
之前写了2篇博客,分别介绍了轻量级人像分割PP-HumanSeg的树莓派部署和ONNX windows部署。本篇博客将使用NCNN 将PP-HumanSeg lite在windows cpu上C++部署,FPS为18左右预测效果如图所示。
目录
git clone https://github.com/PaddlePaddle/PaddleSeg.git
进入PP-HumanSeg目录,使用pip安装paddleseg库,并使用PaddleSeg提供的脚本下载预训练模型,该模型将用于后续导出为onnx。
- %cd ~/PaddleSeg/contrib/PP-HumanSeg
- !pip install paddleseg
- !python pretrained_model/download_pretrained_model.py
上一步下载的模型是paddlepaddle动态图模型,为了将其转换为onnx,先将paddlepaddle动态图模型转换为静态图模型,再利用paddle2onnx工具将其转为onnx模型。
这里提供超轻量级模型PP-HumanSeg-Lite转换示例,该模型适用于移动端实时分割场景,例如手机自拍、Web视频会议,模型输入大小(192, 192)。为了防止后续onnx模型使用报错,需要加上--input_shape参数。(命令中的文件路径如有需要请更换成自己的路径)
注意:这里加上了--without_argmax参数,因为NCNN暂时不支持argmax算子,也没有在网络最后添加上--with_softmax参数。
- %cd ~/PaddleSeg/contrib/PP-HumanSeg
- !python ../../export.py \
- --config configs/fcn_hrnetw18_small_v1_humanseg_192x192_mini_supervisely.yml \
- --model_path pretrained_model/fcn_hrnetw18_small_v1_humanseg_192x192/model.pdparams \
- --save_dir export_model/fcn_hrnetw18_small_v1_humanseg_192x192 \
- --input_shape 1 3 192 192 --without_argmax
得到paddlepaddle动态图模型后,使用paddle2onnx工具将静态图模型转换为onnx模型,首先安装paddle2onnx库。
!pip install paddle2onnx
使用paddle2onnx工具,将上一步得到的paddlepaddle静态图的模型转换为onnx。
- %cd ~/PaddleSeg/contrib/PP-HumanSeg
- ! paddle2onnx --model_dir ./export_model/fcn_hrnetw18_small_v1_humanseg_192x192/ \
- --model_filename model.pdmodel \
- --params_filename model.pdiparams \
- --save_file onnx_model/model.onnx \
- --opset_version 12
onnx模型信息如下,注意这里导出的模型是没有softmax和argmax的:
安装onnx-simplifier:
pip3 install onnx-simplifier
将上一步得到的model.onnx简化:
- # 命令格式
- onnxsim input_onnx_model output_onnx_model
- # 具体命令
- onnxsim model.onnx simple_model.onnx
简化后的onnx模型:
具体内容可参考:官方编译教程
下载protobuf:https://github.com/google/protobuf/archive/v3.4.0.zip
打开开始菜单中的x64 Native Tools Command Prompt for VS 2017命令行工具(更高级的版本也可以,我用2022成功了),编译protobuf。
- cd <protobuf-root-dir>
- mkdir build
- cd build
- cmake -G"NMake Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=%cd%/install -Dprotobuf_BUILD_TESTS=OFF -Dprotobuf_MSVC_STATIC_RUNTIME=OFF ../cmake
- nmake
- nmake install
克隆NCNN仓库:
git clone https://github.com/Tencent/ncnn.git
编译NCNN(我这里没用Vulkan,需要的话参考官方教程),取代命令中的路径为自己的路径:
- cd <ncnn-root-dir>
- mkdir -p build
- cd build
- 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
- nmake
- nmake install
代码及模型地址:代码和模型
windows cpu
visual studio 2019
首先将上面得到的简化后的onnx模型转换为ncnn模型(得到param和bin后缀的2个文件),注意转换的时候要没有报错,不然后续加载会出错:
../ncnn/build/tools/onnx/onnx2ncnn simple_model.onnx simple_model.param simple_model.bin
代码结构:
HumanSeg.h代码:
- #pragma once
- #include <string>
- #include "net.h"
- #include <opencv.hpp>
- #include "time.h"
-
-
- class HumanSeg
- {
- private:
- std::string param_path;
- std::string bin_path;
- std::vector<int> input_shape;
- ncnn::Net net;
-
- const float norm_vals[3] = { 1 / 177.5, 1 / 177.5, 1 / 177.5 };
- const float mean_vals[3] = { 175.5, 175.5, 175.5 };
-
- cv::Mat normalize(cv::Mat& image);
- public:
- HumanSeg() = delete;
- HumanSeg(const std::string param_path, const std::string bin_path, std::vector<int> input_shape);
- ~HumanSeg();
-
- cv::Mat predict_image(cv::Mat& image);
- void predict_image(const std::string& src_image_path, const std::string& dst_path);
-
- void predict_camera();
- };
-
HumanSeg.cpp代码:
- #include "HumanSeg.h"
-
-
- HumanSeg::HumanSeg(const std::string param_path, const std::string bin_path, std::vector<int> input_shape)
- :param_path(param_path), bin_path(bin_path), input_shape(input_shape) {
- net.load_param(param_path.c_str());
- net.load_model(bin_path.c_str());
- }
-
-
- HumanSeg::~HumanSeg() {
- net.clear();
- }
-
-
- cv::Mat HumanSeg::normalize(cv::Mat& image) {
- std::vector<cv::Mat> channels, normalized_image;
- cv::split(image, channels);
-
- cv::Mat r, g, b;
- b = channels.at(0);
- g = channels.at(1);
- r = channels.at(2);
- b = (b / 255. - 0.5) / 0.5;
- g = (g / 255. - 0.5) / 0.5;
- r = (r / 255. - 0.5) / 0.5;
-
- normalized_image.push_back(r);
- normalized_image.push_back(g);
- normalized_image.push_back(b);
-
- cv::Mat out = cv::Mat(image.rows, image.cols, CV_32F);
- cv::merge(normalized_image, out);
- return out;
- }
-
-
- cv::Mat HumanSeg::predict_image(cv::Mat& image) {
- cv::Mat rgbImage;
- cv::cvtColor(image, rgbImage, cv::COLOR_BGR2RGB);
- ncnn::Mat in = ncnn::Mat::from_pixels_resize(rgbImage.data, ncnn::Mat::PIXEL_RGB, image.cols, image.rows, input_shape[3], input_shape[2]);
- in.substract_mean_normalize(mean_vals, norm_vals);
- ncnn::Extractor ex = net.create_extractor();
- ex.input("x", in);
- ncnn::Mat out;
- ex.extract("bilinear_interp_v2_13.tmp_0", out);
-
- cv::Mat mask(out.h, out.w, CV_8UC1);
- const float* maskmap0 = out.channel(0);
- const float* maskmap1 = out.channel(1);
-
- for (int i{ 0 }; i < out.h; i++) {
- for (int j{ 0 }; j < out.w; ++j) {
- mask.at<uchar>(i, j) = maskmap1[i * out.w + j] > maskmap0[i * out.w + j] ? 255 : 0;
- }
- }
- cv::resize(mask, mask, cv::Size(image.cols, image.rows), 0, 0);
- cv::Mat segFrame;
- cv::bitwise_and(image, image, segFrame, mask = mask);
- return segFrame;
- }
-
-
- void HumanSeg::predict_image(const std::string& src_image_path, const std::string& dst_path) {
- cv::Mat image = cv::imread(src_image_path);
- cv::Mat segFrame = predict_image(image);
- cv::imwrite(dst_path, segFrame);
- }
-
-
- void HumanSeg::predict_camera() {
- cv::Mat frame;
- cv::VideoCapture cap;
- int deviceID{ 0 };
- int apiID{ cv::CAP_ANY };
- cap.open(deviceID, apiID);
- if (!cap.isOpened()) {
- std::cout << "Error, cannot open camera!" << std::endl;
- return;
- }
- //--- GRAB AND WRITE LOOP
- std::cout << "Start grabbing" << std::endl << "Press any key to terminate" << std::endl;
- int count{ 0 };
- clock_t start{ clock() }, end{0};
- double fps{ 0 };
- for (;;)
- {
- // wait for a new frame from camera and store it into 'frame'
- cap.read(frame);
- // check if we succeeded
- if (frame.empty()) {
- std::cout << "ERROR! blank frame grabbed" << std::endl;
- break;
- }
- cv::Mat segFrame = predict_image(frame);
-
- // fps
- ++count;
- end = clock();
- fps = count / (float(end - start) / CLOCKS_PER_SEC);
- if (count >= 50) {
- count = 0; //防止计数溢出
- start = clock();
- }
- std::cout << "FPS: " << fps << " Seg Image Number: " << count << " time consume:" << (float(end - start) / CLOCKS_PER_SEC) << std::endl;
- //设置绘制文本的相关参数
- std::string text{ std::to_string(fps) };
- int font_face = cv::FONT_HERSHEY_COMPLEX;
- double font_scale = 1;
- int thickness = 2;
- int baseline;
- cv::Size text_size = cv::getTextSize(text, font_face, font_scale, thickness, &baseline);
-
- //将文本框居中绘制
- cv::Point origin;
- origin.x = 20;
- origin.y = 20;
- cv::putText(segFrame, text, origin, font_face, font_scale, cv::Scalar(0, 255, 255), thickness, 8, 0);
-
- // show live and wait for a key with timeout long enough to show images
- imshow("Live", segFrame);
- if (cv::waitKey(5) >= 0)
- break;
-
- }
- cap.release();
- cv::destroyWindow("Live");
- return;
- }
-
main.cpp代码:
- #include <opencv.hpp>
- #include <iostream>
- #include "HumanSeg.h"
- #include <vector>
- #include "net.h"
-
-
- int main() {
- std::string param_path{ "D:\\C_code\\humanseg_ncnn\\onnx_model\\simple_model_interp.param" };
- std::string bin_path{ "D:\\C_code\\humanseg_ncnn\\onnx_model\\simple_model_interp.bin" };
- std::vector<int> input_shape{ 1, 3, 192, 192 };
- HumanSeg model(param_path,bin_path,input_shape);
- // 预测并保存
- // model.predict_image("C:\\Users\\langdu\\Pictures\\test.png", "C:\\Users\\langdu\\Pictures\\predict.png");
-
- // 预测并显示
- //cv::Mat image = cv::imread("C:\\Users\\langdu\\Pictures\\test.png");
- //cv::Mat segFrame = model.predict_image(image);
- //cv::imshow("1", segFrame);
- //cv::waitKey(0);
-
- // 摄像头
- model.predict_camera();
- return -1;
- }
-
摄像头分割效果如图所示
2、轻量级人像分割深度学习模型PP-HumanSeg树莓派部署
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。