Usando o algoritmo não supervisionado padim do projeto Anomalib para treinamento de modelo e implantação ONNX de conjuntos de dados de defeitos industriais feitos por você mesmo (3) - implantação C++

prefácio

        Do treinamento do projeto à interpretação do código-fonte, finalmente chegou à fase de implantação final. Os alunos que não conhecem o histórico do projeto podem ler os dois primeiros blogs. Aqui usamos a implantação C++ do sistema Windows como exemplo, e o código no sistema Linux é semelhante. Este blog é baseado principalmente em código C++, tente escrever menos palavras e mais comentários, os leitores podem praticar por conta própria.

1. Preparação do ambiente de implantação

       IDE: VS2022; Mecanismo de raciocínio: OnnxRuntime (versão CPU 1.14.1); Linguagem de desenvolvimento: C++

        Para o modelo ONNX treinado, é muito conveniente usar o mecanismo OnnxRuntime desenvolvido pela Microsoft para a implantação do raciocínio. Primeiro precisamos configurar a biblioteca OnnxRuntime no VS2022. Site oficial do Github do OnnxRuntime:

GitHub - microsoft/onnxruntime: ONNX Runtime: acelerador de treinamento e inferência de ML de plataforma cruzada e alto desempenho ONNX Runtime: acelerador de treinamento e inferência de ML de plataforma cruzada e alto desempenho - GitHub - microsoft/onnxruntime: ONNX Runtime: plataforma cruzada, alto desempenho Acelerador de inferência e treinamento de ML https://github.com/microsoft/onnxruntime         Vá para o diretório de lançamentos e baixe o pacote OnnxRuntime correspondente à sua máquina (x64 para sistema Windows, e preste atenção se sua arquitetura é aarch64 ou x64, etc. .para sistemas Linux). Após a conclusão do download, configure o diretório de inclusão e o diretório da biblioteca no VS e copie o arquivo de biblioteca dinâmica dll OnnxRuntime correspondente para a pasta em execução do projeto. Após a conclusão do trabalho de preparação, você pode começar a escrever o código. Observe aqui que, além da biblioteca OnnxRuntime, se o seu projeto não configurar OpenCV, siga o tutorial online para configurá-lo. OpenCV também depende da implantação do modelo ONNX.

Dois, código de implantação C++

        Todas as bobagens foram finalizadas nos dois primeiros blogs, e o código é fornecido diretamente aqui. A ideia é a mesma da implementação do código Python no projeto Anomalib (portanto, ler e compreender o código Python é o único caminho). O código é interceptado do projeto Qt implementado pelo próprio blogueiro, que define principalmente a classe Inferencer para completar a tarefa de raciocínio. O código é dividido no arquivo de cabeçalho Inferencer.h e no arquivo fonte Inferencer.cpp, e a explicação do código é dada na forma de comentários. código mostrado abaixo:

Inferenciador.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);

};

Inferenciador.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);
	}
}

       Depois de usar Qt para escrever um programa GUI, o efeito é mostrado na figura:

         O efeito ainda parece bom.

3. Resumo e perspectivas

        Este é o fim do grande projeto ~ Espalhando flores ~ Ainda é uma certa melhoria para mim realizar um projeto do começo ao fim. Aqui apenas o raciocínio e a implantação do modelo pela CPU são implementados. Fica a cargo dos alunos explorar o lançamento de tijolos e jade, a aceleração da GPU e a escrita de programas GUI.

Acho que você gosta

Origin blog.csdn.net/m0_57315535/article/details/131749856
Recomendado
Clasificación