1、NCNN简介
腾讯推出的 NCNN(Ncnn Convolutional Neural Network)是一个轻量级的高性能深度学习框架,专门为移动设备和嵌入式设备优化,旨在提供高效的推理性能和低延迟。NCNN 具有高度优化的计算图、快速有效的算法实现、灵活的编程接口等特点,适用于各种常见的深度学习模型,如卷积神经网络(CNN)、循环神经网络(RNN)和变分自编码器(VAE)等。
NCNN 的主要特点有以下几点:
高性能:NCNN 针对移动设备进行了专门的优化,采用了一些高效的算法实现,如 Winograd、量化、bf16 和异构计算等,以提高推理性能和降低延迟。
轻量级:NCNN 框架本身具有较小的体积,可以在资源有限的移动设备和嵌入式设备上快速运行。
灵活性:NCNN 提供了灵活的编程接口,开发者可以根据需求自由选择模型结构和参数配置,进行定制化开发。
易用性:NCNN 具有简单的使用流程,开发者可以快速地将自己的模型导入框架中进行推理。
开源:NCNN 是开源的,具有活跃的社区支持,可以方便地获取到最新的技术发展和优化方案。
2、项目结构
3、模型转换与量化
3.1、ONNX模型转NCNN
对上图中的baseline_sim.onnx进行转换
D:\Documents\cxx_packages\ncnn-20230816-windows-vs2022-shared\x64\bin\onnx2ncnn.exe .\assets\model\baseline_sim.onnx .\assets\ncnn_model\baseline_sim.param .\assets\ncnn_model\baseline_sim.bin
在指定的路径下生成了ncnn的模型文件
3.2、NCNN模型优化
D:\Documents\cxx_packages\ncnn-20230816-windows-vs2022-shared\x64\bin\ncnnoptimize.exe .\assets\ncnn_model\baseline_sim.param .\assets\ncnn_model\baseline_sim.bin .\assets\ncnn_model\baseline_sim_opt.param .\assets\ncnn_model\baseline_sim_opt.bin 0
生成优化后的模型文件
3.3、生成校准数据集
D:\Documents\cxx_packages\ncnn-20230816-windows-vs2022-shared\x64\bin\ncnn2table ./assets/ncnn_model/baseline_sim_opt.param ./assets/ncnn_model/baseline_sim_opt.bin .\assets\images.txt ./assets/yolox.table mean=[104,117,123] norm=[0.017,0.017,0.017] shape=[640,480,3] pixel=BGR thread=8 method=kl
生成的校准数据集文件
3.4、int8量化
D:\Documents\cxx_packages\ncnn-20230816-windows-vs2022-shared\x64\bin\ncnn2int8.exe ./assets/ncnn_model/baseline_sim_opt.param ./assets/ncnn_model/baseline_sim_opt.bin ./assets/ncnn_model/baseline_sim_opt_int8.param ./assets/ncnn_model/baseline_sim_opt_int8.bin ./assets/yolox.table
量化后的模型文件
4、部署
4.1、主函数
#include <opencv2/opencv.hpp>
#include <ncnn/net.h>
#include <iostream>
#include <fstream>
#include <stdio.h>
#include <algorithm>
#include <vector>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include "opencv2/imgproc/imgproc.hpp"
using namespace std;
//这个函数是官方提供的用于打印输出的tensor
void pretty_print(const ncnn::Mat &m) {
for (int q = 0; q < m.c; q++) {
const float *ptr = m.channel(q);
for (int y = 0; y < m.h; y++) {
for (int x = 0; x < m.w; x++) {
printf("%f ", ptr[x]);
}
ptr += m.w;
printf("\n");
}
printf("------------------------\n");
}
}
void visualize(const char* title, const ncnn::Mat& m)
{
std::vector<cv::Mat> normed_feats(m.c);
for (int i=0; i<m.c; i++)
{
cv::Mat tmp(m.h, m.w, CV_32FC1, (void*)(const float*)m.channel(i));
cv::normalize(tmp, normed_feats[i], 0, 255, cv::NORM_MINMAX, CV_8U);
cv::cvtColor(normed_feats[i], normed_feats[i], cv::COLOR_GRAY2BGR);
// check NaN
for (int y=0; y<m.h; y++)
{
const float* tp = tmp.ptr<float>(y);
uchar* sp = normed_feats[i].ptr<uchar>(y);
for (int x=0; x<m.w; x++)
{
float v = tp[x];
if (v != v)
{
sp[0] = 0;
sp[1] = 0;
sp[2] = 255;
}
sp += 3;
}
}
}
int tw = m.w < 10 ? 32 : m.w < 20 ? 16 : m.w < 40 ? 8 : m.w < 80 ? 4 : m.w < 160 ? 2 : 1;
int th = (m.c - 1) / tw + 1;
cv::Mat show_map(m.h * th, m.w * tw, CV_8UC3);
show_map = cv::Scalar(127);
// tile
for (int i=0; i<m.c; i++)
{
int ty = i / tw;
int tx = i % tw;
normed_feats[i].copyTo(show_map(cv::Rect(tx * m.w, ty * m.h, m.w, m.h)));
}
cv::resize(show_map, show_map, cv::Size(0,0), 2, 2, cv::INTER_NEAREST);
cv::imshow(title, show_map);
}
float sigmoid(float x) {
return (1 / (1 + exp(-x)));
}
float desigmoid(float x) {
return -log(1 / x - 1);
}
//main函数模板
int main() {
float deconf = desigmoid(0.5);
const float mean_vals[3] = { 103.94f, 116.78f, 123.68f };
const float norm_vals[3] = { 0.017f, 0.017f, 0.017f };
// 加载转换并且量化后的yolox网络
ncnn::Net net;
net.load_param("D:\\WorkSpace\\PySolution\\ncnntest\\assets\\ncnn_model\\baseline_sim_opt_int8.param");
net.load_model("D:\\WorkSpace\\PySolution\\ncnntest\\assets\\ncnn_model\\baseline_sim_opt_int8.bin");
// ncnn前向计算器
// 设置输入输出
ncnn::Mat input;
ncnn::Mat output[3];//取决于模型的输出有几个
// 遍历文件夹
std::string src_path = "D:\\WorkSpace\\PySolution\\ncnntest\\assets\\images\\";
std::vector<cv::String> file_vec;
cv::glob(src_path + "*.jpg", file_vec, false);//遍历满足条件的文件并添加到列表中
std::random_shuffle(file_vec.begin(), file_vec.end());//对文件进行洗牌乱序
for (std::string file_name: file_vec) {
ncnn::Extractor extractor = net.create_extractor();
// 读图
cv::Mat img = cv::imread(file_name, cv::IMREAD_COLOR);
// resize
cv::Mat img2, src;
int input_width = 640;//转onnx时指定的输入大小
int input_height = 480;
if (!img.isContinuous()) {
return -1;
}
float scale_w = 640.0 / img.cols;
float scale_h = 480.0 / img.rows;
float resize_scale = min(scale_h, scale_w);
// resize
cv::resize(img, src, cv::Size(), resize_scale, resize_scale);
img2 = cv::Mat::zeros(cv::Size(640, 480), CV_8UC3);
src.copyTo(img2(cv::Rect(0, 0, src.cols, src.rows)));
// 把opencv的mat转换成ncnn的mat
input = ncnn::Mat::from_pixels(img2.data, ncnn::Mat::PIXEL_BGR, img2.cols, img2.rows);
// 差一步归一化
input.substract_mean_normalize(mean_vals, norm_vals);
int64 start = cv::getTickCount();
extractor.input("input_node", input);
extractor.extract("l", output[0]);
extractor.extract("m", output[1]);
extractor.extract("s", output[2]);
visualize("l",output[0]);
//结果解析
vector<cv::Rect> o_rect;
vector<float> o_rect_cof;
vector<int> class_ids;
float scales[] = {8.0f, 16.0f, 32.0f};
for (int i = 0; i < 3; i++) {
float scale = scales[i];
int feature_w = input_width / scale;
int feature_h = input_height / scale;
float *outdata = (float *) output[i].data;
for (int row = 0; row < feature_h; ++row) {
for (int col = 0; col < feature_w; ++col) {
float isObj = outdata[feature_h * feature_w * 4 + row * feature_w + col];
if (isObj < deconf) {
continue;
}
isObj = sigmoid(isObj);
float conf[5];
for (int cls_id = 0; cls_id < 5; cls_id++) {
conf[cls_id] = outdata[feature_h * feature_w * (5 + cls_id) + row * feature_w + col];
conf[cls_id] = sigmoid(conf[cls_id]);
}
int idx = distance(conf, max_element(conf, conf + sizeof(conf) / sizeof(conf[0])));
float max = conf[idx];
float cof = max * isObj;
//注意此处输出为中心点坐标,需要转化为角点坐标
float x = outdata[feature_h * feature_w * 0 + row * feature_w + col];
x += col;
x *= scale;
x /= resize_scale;
float y = outdata[feature_h * feature_w * 1 + row * feature_w + col];
y += row;
y *= scale;
y /= resize_scale;
float w = outdata[feature_h * feature_w * 2 + row * feature_w + col];
w = exp(w);
w *= scale;
w /= resize_scale;
float h = outdata[feature_h * feature_w * 3 + row * feature_w + col];
h = exp(h);
h *= scale;
h /= resize_scale;
float r_x = (x - w / 2);
float r_y = (y - h / 2);
cv::Rect rect = cv::Rect(round(r_x), round(r_y), round(w), round(h));
o_rect.push_back(rect);
o_rect_cof.push_back(cof);
class_ids.push_back(idx);
}
}
}
vector<int> final_id;
cv::dnn::NMSBoxes(o_rect, o_rect_cof, 0.1, 0.5, final_id);
float fps = cv::getTickFrequency() / (cv::getTickCount() - start);
float time = (cv::getTickCount() - start) / cv::getTickFrequency();
std::ostringstream ss1;
ss1 << "FPS : " << fps << " detection time: " << time * 1000 << " ms";
cv::putText(img, ss1.str(), cv::Point(20, 50), cv::FONT_HERSHEY_DUPLEX, 0.5, cv::Scalar(0, 0, 255), 2);
for (int i = 0; i < final_id.size(); ++i) {
cv::Scalar color = cv::Scalar(100, 255, 50);
if (class_ids[final_id[i]] == 0) {
color = cv::Scalar(0, 0, 250);
}
int xmin = o_rect[final_id[i]].x;
int ymin = o_rect[final_id[i]].y;
int width = o_rect[final_id[i]].width;
int height = o_rect[final_id[i]].height;
cv::Rect rect(xmin, ymin, width, height);//左上坐标(x,y)和矩形的长(x)宽(y)
cv::rectangle(img, rect, color, 1, cv::LINE_8, 0);
cv::rectangle(img, cv::Rect(xmin, ymin - 20, 120, 20), color, -1);
std::ostringstream ss;
ss << class_ids[final_id[i]] << ":" << "(" << o_rect_cof[final_id[i]] << ")";
cv::putText(img, ss.str(), cv::Point(xmin, ymin - 5), cv::FONT_HERSHEY_DUPLEX, 0.5,
cv::Scalar(255, 255, 255), 1, 8);
}
cv::imshow("result", img);
cv::waitKey(0);
}
cout << "done" << endl;
return 0;
}
4.2、CMakeLists.txt
cmake_minimum_required(VERSION 3.8)
project(ncnn_test)
set(OpenCV_DIR "D:/Documents/cxx_packages/opencv/x64/vc16/lib")
find_package(OpenCV REQUIRED)
include_directories("D:/Documents/cxx_packages/ncnn-20230816-windows-vs2022-shared/x64/include"
"C:/ProgramData/VulkanSDK/1.3.261.1/Include")
link_directories("D:/Documents/cxx_packages/ncnn-20230816-windows-vs2022-shared/x64/lib")
add_executable(main "${PROJECT_SOURCE_DIR}/src/cpp/source/main.cpp"
)
target_link_libraries(main ncnn.lib
${OpenCV_LIBS})