Utilisation de l'algorithme non supervisé padim du projet Anomalib pour la formation de modèles et le déploiement ONNX d'ensembles de données de défauts industriels faits maison (3) - Déploiement C++

avant-propos

        De la formation du projet à l'interprétation du code source, il atteint enfin le stade du déploiement final. Les étudiants qui ne connaissent pas le contexte du projet peuvent lire les deux premiers blogs. Ici, nous utilisons le déploiement C++ du système Windows comme exemple, et le code sous le système Linux est similaire. Ce blog est principalement basé sur du code C++, essayez d'écrire moins de mots et plus de commentaires, les lecteurs peuvent s'entraîner par eux-mêmes.

1. Préparation de l'environnement de déploiement

       IDE : VS2022 ; Moteur de raisonnement : OnnxRuntime (version CPU 1.14.1); Langage de développement : C++

        Pour le modèle ONNX formé, il est très pratique d'utiliser le moteur OnnxRuntime développé par Microsoft pour le déploiement de raisonnement. Nous devons d'abord configurer la bibliothèque OnnxRuntime dans VS2022. Site officiel Github d'OnnxRuntime :

GitHub - microsoft/onnxruntime : ONNX Runtime : accélérateur d'inférence et de formation ML multiplateforme et hautes performances ONNX Runtime : accélérateur d'inférence et de formation ML multiplateforme hautes performances - GitHub - microsoft/onnxruntime : ONNX Runtime : multiplateforme, hautes performances Accélérateur d'inférence et de formation ML https://github.com/microsoft/onnxruntime         Accédez au répertoire des versions et téléchargez le package OnnxRuntime correspondant à votre machine (x64 pour le système Windows, et veuillez faire attention si votre architecture est aarch64 ou x64, etc. .pour les systèmes Linux). Une fois le téléchargement terminé, configurez le répertoire d'inclusion et le répertoire de bibliothèque dans VS, et copiez le fichier de bibliothèque dynamique OnnxRuntime dll correspondant dans le dossier en cours d'exécution du projet. Une fois le travail de préparation terminé, vous pouvez commencer à écrire du code. Notez ici, en plus de la bibliothèque OnnxRuntime, si votre projet ne configure pas OpenCV, merci de suivre le tutoriel en ligne pour le configurer. OpenCV dépend également du déploiement du modèle ONNX.

Deuxièmement, le code de déploiement C++

        Toutes les bêtises ont été finies dans les deux premiers blogs, et le code est directement donné ici. L'idée est la même que l'implémentation du code Python dans le projet Anomalib (donc la lecture et la compréhension du code Python sont le seul moyen). Le code est intercepté du projet Qt implémenté par le blogueur lui-même, qui définit principalement la classe Inferencer pour accomplir la tâche de raisonnement. Le code est divisé en fichier d'en-tête Inferencer.h et en fichier source Inferencer.cpp, et l'explication du code est donnée sous forme de commentaires. le code s'affiche comme ci-dessous :

Inférenceur.h

#pragma once
#include<onnxruntime_cxx_api.h>
#include <opencv2/core.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/opencv.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc_c.h>
#include <opencv2/dnn.hpp>
#include <iostream>
#include <assert.h>
#include <vector>
#include <fstream>

using namespace std;

//定义推理器类
class Inferencer
{
private:
	Ort::Session *session;                                         //onnx运行会话
	void preProcess(const cv::Mat& image, cv::Mat& image_blob);    //预处理
	vector<int64_t> input_dims;
	vector<int64_t> output_dims;
	vector<char*> input_node_names;
	vector<char*> output_node_names;

public:
	Inferencer(const wchar_t* modelPath);                         //使用模型路径构造推推理器
	void InitOnnxEnv();                                           //初始化环境
	//分别生成概率热图、二值化图、缺陷边缘图和检测框图
	void generateHeatMap(cv::Mat& input, cv::Mat& heatMap, cv::Mat& predMask, cv::Mat& contourMap, cv::Mat& boxMap);

};

Inférence.cpp

#include"Inferencer.h"

//此处注意,在Linux系统下,输入参数modelPath应为const char*类型,而不是const wchar_t*
Inferencer::Inferencer(const wchar_t* modelPath)
{
	Ort::Env env(ORT_LOGGING_LEVEL_WARNING);
	Ort::SessionOptions session_options;
	session_options.SetIntraOpNumThreads(1);
	session_options.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_ALL);
	cout << "正在使用Onnxruntime C++ API\n";
	session = new Ort::Session(env, modelPath, session_options);
}

void Inferencer::preProcess(const cv::Mat& image, cv::Mat& image_blob)
{
	cv::Mat input;
	image.copyTo(input);


	//数据处理 标准化
	std::vector<cv::Mat> channels, channel_p;
	split(input, channels);
	cv::Mat R, G, B;
	B = channels.at(0);
	G = channels.at(1);
	R = channels.at(2);

	//按照ImageNet的均值和方差进行标准化预处理
	B = (B / 255. - 0.406) / 0.225;
	G = (G / 255. - 0.456) / 0.224;
	R = (R / 255. - 0.485) / 0.229;

	channel_p.push_back(R);
	channel_p.push_back(G);
	channel_p.push_back(B);

	cv::Mat outt;
	merge(channel_p, outt);
	image_blob = outt;
}

void Inferencer::InitOnnxEnv()
{
	//打印模型的各项信息
	auto num_input_nodes = session->GetInputCount();
	auto num_output_nodes = session->GetOutputCount();
	cout << "Number of inputs = " << num_input_nodes << endl;
	cout << "Number of outputs = " << num_output_nodes << endl;

	this->input_dims = session->GetInputTypeInfo(0).GetTensorTypeAndShapeInfo().GetShape();
	this->output_dims = session->GetOutputTypeInfo(0).GetTensorTypeAndShapeInfo().GetShape();
	cout << "input_dims:" << this->input_dims[3] << endl;
	cout << "output_dims:" << this->output_dims[3] << endl;
	Ort::AllocatorWithDefaultOptions allocator;
	//此处早期版本的onnxruntime的API不同
	auto input_name_ptr = session->GetInputNameAllocated(0, allocator);
	this->input_node_names.push_back(input_name_ptr.get());
	auto output_name_ptr = session->GetOutputNameAllocated(0, allocator);
	this->output_node_names.push_back(output_name_ptr.get());
}

void Inferencer::generateHeatMap(cv::Mat& input, cv::Mat& heatMap, cv::Mat& predMask, cv::Mat& contourMap, cv::Mat& boxMap)
{
	//metadata.json中的信息
	float image_threshold = 13.702226638793945;
	float pixel_threshold = 13.702226638793945;
	float min_val = 5.296699047088623;
	float max_val = 22.767864227294922;
	cv::Mat det1, det2;
	cv::resize(input, det1, cv::Size(256, 256), cv::INTER_AREA);
	det1.convertTo(det1, CV_32FC3);
	//标准化处理
	Inferencer::preProcess(det1, det2);
	cv::Mat blob = cv::dnn::blobFromImage(det2, 1., cv::Size(256, 256), cv::Scalar(0, 0, 0), false, true);
	cout << "加载成功!" << endl;

	clock_t startTime, endTime;
	//创建输入tensor
	auto memory_info = Ort::MemoryInfo::CreateCpu(OrtAllocatorType::OrtArenaAllocator, OrtMemType::OrtMemTypeDefault);
	vector<Ort::Value> input_tensors;
	input_tensors.push_back(Ort::Value::CreateTensor<float>(memory_info, blob.ptr<float>(), blob.total(), input_dims.data(), input_dims.size()));
	startTime = clock();
	//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());
	const char* ch_in = "input";
	const char* const* p_in = &ch_in;
	const char* ch_out = "output";
	const char* const* p_out = &ch_out;
	//output_tensors这里直接固定输入和输出的结点名p_in和p_out
	auto output_tensors = session->Run(Ort::RunOptions{ nullptr }, p_in, input_tensors.data(), 1, p_out, 1);
	endTime = clock();
	assert(output_tensors.size() == 1 && output_tensors.front().IsTensor());

	float* floatarr = output_tensors[0].GetTensorMutableData<float>();
	cv::Mat anomalyMap = cv::Mat_<float>(256, 256);
	int k = 0;
	for (int i = 0; i < 256; i++)
	{
		for (int j = 0; j < 256; j++) //矩阵列数循环
		{
			float val = floatarr[++k];
			anomalyMap.at<float>(i, j) = val;

		}
	}
	//标准化处理
	cv::Mat norm = ((anomalyMap - pixel_threshold) / (max_val - min_val)) + 0.5;
	//double minValue, maxValue;
	//cv::Point minIdx, maxIdx;
	//cv::minMaxLoc(norm, &minValue, &maxValue, &minIdx, &maxIdx);
	norm *= 255;
	//转换为uint8灰度图
	cv::Mat normUint8;
	norm.convertTo(normUint8, CV_8UC1);
	//转换为伪彩色图
	cv::Mat colorMap;
	cv::applyColorMap(normUint8, colorMap, cv::COLORMAP_JET);
	//与原图叠加生成热图
	cv::resize(input, input, cv::Size(256, 256));
	cv::addWeighted(colorMap, 0.4, input, 0.6, 0, heatMap);
	//生成二值区域
	cv::threshold(anomalyMap, predMask, pixel_threshold, 255, CV_THRESH_BINARY);
	predMask.convertTo(predMask, CV_8UC1);
	//生成缺陷轮廓
	cv::resize(input, contourMap, cv::Size(256, 256));
	vector<vector<cv::Point>> contours;
	cv::findContours(predMask, contours, cv::noArray(), cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
	cv::drawContours(contourMap, contours, -1, cv::Scalar(0, 0, 255), 2);
	//生成缺陷检测框和尺寸信息
	cv::resize(input, boxMap, cv::Size(256, 256));
	for (int i = 0; i < contours.size(); i++)
	{
		cv::Rect rect = cv::boundingRect(contours[i]);
		cv::rectangle(boxMap, rect, cv::Scalar(0, 255, 0), 1);
	}
}

       Après avoir utilisé Qt pour écrire un programme GUI, l'effet est comme indiqué dans la figure :

         L'effet semble toujours correct.

3. Résumé et perspectives

        C'est la fin du grand projet ~ Disperser des fleurs ~ C'est encore une certaine amélioration pour moi de mener à bien un projet du début à la fin. Ici seul le raisonnement et le déploiement du modèle par le CPU sont implémentés. C'est aux étudiants d'explorer en jetant des briques et du jade, l'accélération du GPU et l'écriture de programmes GUI.

Je suppose que tu aimes

Origine blog.csdn.net/m0_57315535/article/details/131749856
conseillé
Classement