当前位置:   article > 正文

使用Anomalib项目的padim无监督算法 进行自制工业缺陷数据集的模型训练和ONNX部署(三)——C++部署篇_anomalib训练自己的数据集

anomalib训练自己的数据集

前言

        从项目的训练到源码解读,终于到了最后部署的阶段。不了解项目背景的同学可以上翻前两篇博客。这里我们使用Windows系统的C++部署作为示例,在Linux系统下的代码大同小异。本篇博客以C++代码为主,尽量少写字多写注释,读者可以自行动手实践。

一、部署环境准备

        IDE:VS2022;推理引擎:OnnxRuntime(1.14.1 CPU版);开发语言:C++

        对于训练得到的ONNX模型,使用微软开发的OnnxRuntime引擎进行推理部署是十分方便的。首先我们需要在VS2022中配置OnnxRuntime库。OnnxRuntime的Github官网:

GitHub - microsoft/onnxruntime: ONNX Runtime: cross-platform, high performance ML inferencing and training acceleratorONNX Runtime: cross-platform, high performance ML inferencing and training accelerator - GitHub - microsoft/onnxruntime: ONNX Runtime: cross-platform, high performance ML inferencing and training acceleratorhttps://github.com/microsoft/onnxruntime        前往releases目录下,下载自己机器对应的OnnxRuntime包(windows系统就下x64,linux系统也请注意自己的架构是aarch64还是x64等)。下载完成后,在VS中配置包含目录和库目录,并把相应OnnxRuntime的dll动态库文件拷贝到项目运行文件夹下。准备工作完成后就可以开始写代码了。此处注意,除了OnnxRuntime库,若你的项目没有配置OpenCV,也请按照网上教程进行配置,OpenCV也是部署ONNX模型所依赖的。

二、C++部署代码

        所有的废话都在前两篇博客里说完了,这里直接给出代码。思路和Anomalib项目中Python代码的实现相同(所以说Python代码的阅读理解是必经之路)。代码截取于博主本人实现的Qt项目,主要定义了Inferencer类,完成推理任务。代码分头文件Inferencer.h和源文件Inferencer.cpp,对代码的解释以注释形式给出。代码如下:

Inferencer.h

  1. #pragma once
  2. #include<onnxruntime_cxx_api.h>
  3. #include <opencv2/core.hpp>
  4. #include <opencv2/imgcodecs.hpp>
  5. #include <opencv2/opencv.hpp>
  6. #include <opencv2/highgui.hpp>
  7. #include <opencv2/core/core.hpp>
  8. #include <opencv2/imgproc/imgproc_c.h>
  9. #include <opencv2/dnn.hpp>
  10. #include <iostream>
  11. #include <assert.h>
  12. #include <vector>
  13. #include <fstream>
  14. using namespace std;
  15. //定义推理器类
  16. class Inferencer
  17. {
  18. private:
  19. Ort::Session *session; //onnx运行会话
  20. void preProcess(const cv::Mat& image, cv::Mat& image_blob); //预处理
  21. vector<int64_t> input_dims;
  22. vector<int64_t> output_dims;
  23. vector<char*> input_node_names;
  24. vector<char*> output_node_names;
  25. public:
  26. Inferencer(const wchar_t* modelPath); //使用模型路径构造推推理器
  27. void InitOnnxEnv(); //初始化环境
  28. //分别生成概率热图、二值化图、缺陷边缘图和检测框图
  29. void generateHeatMap(cv::Mat& input, cv::Mat& heatMap, cv::Mat& predMask, cv::Mat& contourMap, cv::Mat& boxMap);
  30. };

Inferencer.cpp

  1. #include"Inferencer.h"
  2. //此处注意,在Linux系统下,输入参数modelPath应为const char*类型,而不是const wchar_t*
  3. Inferencer::Inferencer(const wchar_t* modelPath)
  4. {
  5. Ort::Env env(ORT_LOGGING_LEVEL_WARNING);
  6. Ort::SessionOptions session_options;
  7. session_options.SetIntraOpNumThreads(1);
  8. session_options.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_ALL);
  9. cout << "正在使用Onnxruntime C++ API\n";
  10. session = new Ort::Session(env, modelPath, session_options);
  11. }
  12. void Inferencer::preProcess(const cv::Mat& image, cv::Mat& image_blob)
  13. {
  14. cv::Mat input;
  15. image.copyTo(input);
  16. //数据处理 标准化
  17. std::vector<cv::Mat> channels, channel_p;
  18. split(input, channels);
  19. cv::Mat R, G, B;
  20. B = channels.at(0);
  21. G = channels.at(1);
  22. R = channels.at(2);
  23. //按照ImageNet的均值和方差进行标准化预处理
  24. B = (B / 255. - 0.406) / 0.225;
  25. G = (G / 255. - 0.456) / 0.224;
  26. R = (R / 255. - 0.485) / 0.229;
  27. channel_p.push_back(R);
  28. channel_p.push_back(G);
  29. channel_p.push_back(B);
  30. cv::Mat outt;
  31. merge(channel_p, outt);
  32. image_blob = outt;
  33. }
  34. void Inferencer::InitOnnxEnv()
  35. {
  36. //打印模型的各项信息
  37. auto num_input_nodes = session->GetInputCount();
  38. auto num_output_nodes = session->GetOutputCount();
  39. cout << "Number of inputs = " << num_input_nodes << endl;
  40. cout << "Number of outputs = " << num_output_nodes << endl;
  41. this->input_dims = session->GetInputTypeInfo(0).GetTensorTypeAndShapeInfo().GetShape();
  42. this->output_dims = session->GetOutputTypeInfo(0).GetTensorTypeAndShapeInfo().GetShape();
  43. cout << "input_dims:" << this->input_dims[3] << endl;
  44. cout << "output_dims:" << this->output_dims[3] << endl;
  45. Ort::AllocatorWithDefaultOptions allocator;
  46. //此处早期版本的onnxruntime的API不同
  47. auto input_name_ptr = session->GetInputNameAllocated(0, allocator);
  48. this->input_node_names.push_back(input_name_ptr.get());
  49. auto output_name_ptr = session->GetOutputNameAllocated(0, allocator);
  50. this->output_node_names.push_back(output_name_ptr.get());
  51. }
  52. void Inferencer::generateHeatMap(cv::Mat& input, cv::Mat& heatMap, cv::Mat& predMask, cv::Mat& contourMap, cv::Mat& boxMap)
  53. {
  54. //metadata.json中的信息
  55. float image_threshold = 13.702226638793945;
  56. float pixel_threshold = 13.702226638793945;
  57. float min_val = 5.296699047088623;
  58. float max_val = 22.767864227294922;
  59. cv::Mat det1, det2;
  60. cv::resize(input, det1, cv::Size(256, 256), cv::INTER_AREA);
  61. det1.convertTo(det1, CV_32FC3);
  62. //标准化处理
  63. Inferencer::preProcess(det1, det2);
  64. cv::Mat blob = cv::dnn::blobFromImage(det2, 1., cv::Size(256, 256), cv::Scalar(0, 0, 0), false, true);
  65. cout << "加载成功!" << endl;
  66. clock_t startTime, endTime;
  67. //创建输入tensor
  68. auto memory_info = Ort::MemoryInfo::CreateCpu(OrtAllocatorType::OrtArenaAllocator, OrtMemType::OrtMemTypeDefault);
  69. vector<Ort::Value> input_tensors;
  70. input_tensors.push_back(Ort::Value::CreateTensor<float>(memory_info, blob.ptr<float>(), blob.total(), input_dims.data(), input_dims.size()));
  71. startTime = clock();
  72. //auto output_tensors = session->Run(Ort::RunOptions{ nullptr }, input_node_names.data(), input_tensors.data(), input_node_names.size(), output_node_names.data(), output_node_names.size());
  73. const char* ch_in = "input";
  74. const char* const* p_in = &ch_in;
  75. const char* ch_out = "output";
  76. const char* const* p_out = &ch_out;
  77. //output_tensors这里直接固定输入和输出的结点名p_in和p_out
  78. auto output_tensors = session->Run(Ort::RunOptions{ nullptr }, p_in, input_tensors.data(), 1, p_out, 1);
  79. endTime = clock();
  80. assert(output_tensors.size() == 1 && output_tensors.front().IsTensor());
  81. float* floatarr = output_tensors[0].GetTensorMutableData<float>();
  82. cv::Mat anomalyMap = cv::Mat_<float>(256, 256);
  83. int k = 0;
  84. for (int i = 0; i < 256; i++)
  85. {
  86. for (int j = 0; j < 256; j++) //矩阵列数循环
  87. {
  88. float val = floatarr[++k];
  89. anomalyMap.at<float>(i, j) = val;
  90. }
  91. }
  92. //标准化处理
  93. cv::Mat norm = ((anomalyMap - pixel_threshold) / (max_val - min_val)) + 0.5;
  94. //double minValue, maxValue;
  95. //cv::Point minIdx, maxIdx;
  96. //cv::minMaxLoc(norm, &minValue, &maxValue, &minIdx, &maxIdx);
  97. norm *= 255;
  98. //转换为uint8灰度图
  99. cv::Mat normUint8;
  100. norm.convertTo(normUint8, CV_8UC1);
  101. //转换为伪彩色图
  102. cv::Mat colorMap;
  103. cv::applyColorMap(normUint8, colorMap, cv::COLORMAP_JET);
  104. //与原图叠加生成热图
  105. cv::resize(input, input, cv::Size(256, 256));
  106. cv::addWeighted(colorMap, 0.4, input, 0.6, 0, heatMap);
  107. //生成二值区域
  108. cv::threshold(anomalyMap, predMask, pixel_threshold, 255, CV_THRESH_BINARY);
  109. predMask.convertTo(predMask, CV_8UC1);
  110. //生成缺陷轮廓
  111. cv::resize(input, contourMap, cv::Size(256, 256));
  112. vector<vector<cv::Point>> contours;
  113. cv::findContours(predMask, contours, cv::noArray(), cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
  114. cv::drawContours(contourMap, contours, -1, cv::Scalar(0, 0, 255), 2);
  115. //生成缺陷检测框和尺寸信息
  116. cv::resize(input, boxMap, cv::Size(256, 256));
  117. for (int i = 0; i < contours.size(); i++)
  118. {
  119. cv::Rect rect = cv::boundingRect(contours[i]);
  120. cv::rectangle(boxMap, rect, cv::Scalar(0, 255, 0), 1);
  121. }
  122. }

       使用Qt编写了GUI程序后,其效果如图:

         效果看起来还是可以的。

三、总结与展望

        大项目到此结束了~撒花~从头到尾拉通一个项目对自己还是有一定的提升的。这里只实现了CPU对模型的推理和部署。抛砖引玉,GPU的加速以及GUI程序的编写留给同学们自己探索了。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/691978
推荐阅读
相关标签
  

闽ICP备14008679号