超详细YOLOv8/11图像菜品分类全程概述:环境、数据准备、训练、验证/预测、onnx部署(c++/python)详解
文章目录
- 一、环境准备
- 二、数据准备
- 三、训练
- 四、验证与预测
- 五、模型部署
一、环境准备
我的都是在Linux系统下,训练部署的;模型训练之前,需要配置好环境,Anaconda、显卡驱动、cuda、cudnn、pytorch等;
参考:Ubuntu/Debian小白从零开始配置深度学习环境和各种软件库(显卡驱动、CUDA、CUDNN、Pytorch、OpenCv、PCL、Cmake …)【持续维护】
使用清华镜像,下载。
git clone https://github.com/ultralytics/ultralytics.git
conda create -n yolov11 python==3.10 -y
conda activate yolov11
# 换成适配电脑自己的版本
#pip install torch==2.0.0+cu118 torchvision==0.15.1+cu118 --extra-index-url https://download.pytorch.org/whl/cu118
#pip install ultralytics
pip install ultralytics -i https://pypi.tuna.tsinghua.edu.cn/simple
二、数据准备
分类模型准备比较简单,不需要数据转换成txt啥的;分类与检测不同,在数据集准备过程中无需标签labels文件,也无需配置.yaml文件。都是图片,不同类型的图片文件夹放到同一个文件里面,然后分为train和val;例如,caipin_data包含train和val,train文件夹下有000-207个文件夹,就是有208类目标,每个文件(000-2007)下有对应训练的图片,都是一种类型的,比如000都是麻婆豆腐的图片,001下是西红柿鸡蛋图片等。
三、训练
下载预训练模型 点击 【ultralytics 11说明文档】 训练时候不指定版本的话,默认是YOLO11n-cls;选择n/s/m/l/x哪个看自己的需求;
关键的训练参数说明
参数 | 描述 |
---|---|
data | 数据集路径,配置文件coco128.yaml |
model | 模型文件路径,如yolov8n_cls.pt |
epochs | 训练的周期数 |
batch | 每批次的图像数量,一般选1 2 4 8 16,由大到小逐一尝试,大内存不够,小未充分利用内存 |
imgsz | 输入图像的尺寸,默认预训练模型输入大小 |
lr0 | 学习率,模型效果不好,可以调低一些,默认0.01 |
device | 若设备只有一个,默认,若多个,可以指定,device=0,1,2,3 |
optimizer | 使用的优化器,选项包括 [SGD, Adam, Adamax, AdamW, NAdam, RAdam, RMSProp, auto] |
patience | 早停训练等待的最大无改善周期数 |
workers | 数据加载的工作线程数,默认为8 |
cd到下载的yolo/ultralytics-main文件夹里面,在终端执行训练命令,我一般习惯终端执行
yolo classify train data=./datasets/skin-cancer-detection model=yolov8n-cls.pt epochs=100
#自定义数据集,模型是之前训练过的模型,用的是这个
yolo classify train data=./caipin_data model=./yolov11_me/runs/classify/train2/weights/best.pt epochs=100 imgsz=608 amp=False batch=4 lr0=0.001 mosaic=0.2 patience=60
如果没有指定输出的文件夹,默认在runs/文件里面生成训练好的模型,分类、检测、分割、姿态都在这个目录里面;
进入classify里面,可以看到训练的参数,包括权重,损失曲线,评估的图像等;
四、验证与预测
在训练过程中,实际已经边训练边评估了,模型输出,就有评估的文件,预测也是终端执行,预测完后还是在runs/classify文件夹里面有predict文件。评估,可以生成评估的数据,比如召回率、准确率等。
#分类评估
yolo task=classify data=./caipin_data model=./yolov11_me/runs/classify/train2/weights/best.pt device=0
#分类预测
yolo classify predict model=./yolov11_me/runs/classify/train3/weights/best.pt source=./yolov11_me/caipin_data/test
五、模型部署
训练完毕后,一般要部署到电脑、嵌入式设备中,这是导出onnx模型进行部署;onnx版本是13,看你自己安装的版本;在weights文件夹里面,生成对应的best.onnx文件。opset=13是指定的版本;
yolo export model=./yolov11_me/runs/classify/train3/weights/best.pt format=onnx opset=13 simplify=True
c++推理
下载对应的onnxruntime包,官网 https://github.com/microsoft/onnxruntime/releases/,目前最新的出了1.22版本,我用的是1.13,但是最新的ultralytics8.3.158版本得需要大于1.14.1;看自己的配置需求,必须与上述导出的模型一致;
主要是参考官网的 https://github.com/ultralytics/ultralytics/tree/v8.3.158/examples/YOLOv8-ONNXRuntime-CPP deploy的内容;直接上代码,主要有inference.h 、inference.cpp、main.cpp三个文件。
//inference.h
#pragma once
#define RET_OK nullptr#ifdef _WIN32
#include <Windows.h>
#include <direct.h>
#include <io.h>
#endif#include <string>
#include <vector>
#include <cstdio>
#include <opencv2/opencv.hpp>
#include "onnxruntime_cxx_api.h"#ifdef USE_CUDA
#include <cuda_fp16.h>
#endifenum MODEL_TYPE
{//FLOAT32 MODELYOLO_DETECT_V8 = 1,YOLO_POSE = 2,YOLO_CLS = 3,//FLOAT16 MODELYOLO_DETECT_V8_HALF = 4,YOLO_POSE_V8_HALF = 5,YOLO_CLS_HALF = 6
};typedef struct _DL_INIT_PARAM
{std::string modelPath;MODEL_TYPE modelType = YOLO_DETECT_V8;//std::vector<int> imgSize = { 640, 640 };std::vector<int> imgSize = { 608, 608 };float rectConfidenceThreshold = 0.5;float iouThreshold = 0.5;int keyPointsNum = 2;//Note:kpt number for posebool cudaEnable = false;int logSeverityLevel = 3;int intraOpNumThreads = 1;
} DL_INIT_PARAM;typedef struct _DL_RESULT
{int classId;float confidence;cv::Rect box;std::vector<cv::Point2f> keyPoints;
} DL_RESULT;class YOLO_V8
{
public:YOLO_V8();~YOLO_V8();public:char* CreateSession(DL_INIT_PARAM& iParams);char* RunSession(cv::Mat& iImg, std::vector<DL_RESULT>& oResult);char* WarmUpSession();template<typename N>char* TensorProcess(clock_t& starttime_1, cv::Mat& iImg, N& blob, std::vector<int64_t>& inputNodeDims,std::vector<DL_RESULT>& oResult);char* PreProcess(cv::Mat& iImg, std::vector<int> iImgSize, cv::Mat& oImg);std::vector<std::string> classes{"Mapo Tofu","Home style sauteed Tofu","Fried Tofu","Bean curd","Stinky tofu",
"Potato silk","Pan fried potato","Pan fried potato","Braised beans with potato","Fried Potato, Green Pepper & Eggplant","French fries","Yu-Shiang Eggplant","Mashed garlic eggplant","Eggplant with mince pork","Spicy cabbage",
"Sour cabbage","Steamed Baby Cabbage","Shredded cabbage","Sauteed Lettuce in Oyster Sauce","Saute vegetable","tumis kangkung","Lettuce with smashed garlic","Sauteed spainch","Sauteed bean sprouts","Sauteed broad beans",
"Soybean","Broccoli with Oyster Sauce","Deep Fried lotus root","Lotus root","Tomato salad","Gizzard","Black Fungus in Vinegar Sauce","Cucumber in Sauce","peanut","Seaweed salad","Chinese Yam in Hot Toffee",
"Fried Yam","Fried beans","Oyster mushroom","stuffed bitter melon","sauteed bitter melon","pepper with tiger skin","Yuba salad","fried cauliflower","Sauteed Sweet Corn with Pine Nuts","Sauted Chinese Greens with Mushrooms",
"Spiced mushroom","Celery and tofu","Sauteed Lily Bulbs and Celery","Leak and tofu","Scrambled egg with tomato","Scrambled Egg with Leek","Scrambled Egg with cucumber","Steamed egg custard","Pork liver","Pig ears","roast pork",
"Steamed pork with rice powder","Sweet and sour spareribs","Braised spareribs with kelp","Cola Chicken wings","Chicken Feet with Pickled Peppers","Chicken Feet with black bean sauce","Steamed Chicken with Chili Sauce","Roast goose",
"Boiled chicken","Saute Spicy Chicken","Steamed Chicken with Mushroom","chicken braised with brown sauce","Soy sauce chicken","Spicy Chicken","Kung Pao Chicken","Stewed Chicken with Three Cups Sauce","Shredded chicken","Fried chicken drumsticks","Beer duck","Scalloped pork or lamb kidneys","Braised pork","Braised beef","Beef Seasoned with Soy Sauce","Sirloin tomatoes",
"Stewed sirloin potatoes","Sauteed Beef Fillet with Hot Green Pepper","Pork with salted vegetable","Double cooked pork slices","Braised Pork with Vermicelli","Boiled Shredded pork in chili oil","Fried Sweet and Sour Tenderloin","Cripsy sweet & sour pork slices","Pot bag meat","Shredded Pork with Vegetables","Tiger lily buds in Baconic","Sauteed Shredded Pork in Sweet Bean Sauce","Shredded pork with bean","Braised pig feet with soy sauce","Tripe","Shredded pork and green pepper","Yu-Shiang Shredded Pork",
"Braised Fungus with pork slice","Sauteed Sliced Pork,Eggs and Black Fungus","Lettuce shredded meat","Sauteed Vermicelli with Spicy Minced Pork","Fried Lamb with Cumin","Lamb shashlik","Sauteed Sliced Lamb with Scallion","Stewed Pork Ball in Brown Sauce","Boiled Fish with Picked Cabbage and Chili","grilled fish","Sweet and sour fish","Sweet and Sour Mandarin Fish",
"Braised Hairtail in Brown Sauce","Steamed Fish Head with Diced Hot Red Peppers","Fish Filets in Hot Chili Oil","Steamed Perch","Cheese Shrimp Meat","Shrimp broccoli","Braised Shrimp in chili oil","Spicy shrimp","Spicy crayfish","Shrimp Duplings","Steamed shrimp with garlic and vermicelli","Sauteed Shrimp meat","Pipi shrimp","Scallop in Shell","Oysters","squid","Abalone",
"Crab","Turtle","eel","Yangzhou fried rice","Omelette","Steamed Bun Stuffed","Steamed Pork Dumplings","egg omelet","Potato omelet","Egg pie cake","Marinated Egg","Poached Egg","Pine cake with Diced Scallion","Sesame seed cake","Chinese hamburger","Leek box","steamed bun with purple potato and pumpkin","steamed bun","Steamed stuffed bun","Pumpkin pie","Pizza","Deep-Fried Dough Sticks","sauteed noodles with minced meat","Chongqing Hot and Sour Rice Noodles","Cold noodles","Noodles with egg and tomato","spaghetti with meat sauce","Noodles with tomato sauce","Cold Rice Noodles","Sichuan noodles with peppery sauce","Qishan noodles","fried noodles",
"Dumplings","Corn Cob","Braised beef noodle","fried rice noodles","Steamed vermicelli roll","Pork wonton","Fried Dumplings","Tang-yuan","Millet congee","Sweet potato porridge","Jellyfish","Minced Pork Congee with Preserved Egg","Rice porridge","Rice","Laver rice","Stone pot of rice","Black bone chicken soup","Crucian and Bean Curd Soup","Dough Drop and Assorted Vegetable Soup","Hot and Sour Soup","Pork ribs soup with radish","Tomato and Egg Soup","West Lake beef soup","Lotus Root and Rib soup",
"Seaweed and Egg Soup","Seaweed tofu soup","Corn and sparerib soup","Spinach and pork liver soup","Borsch","White fungus soup","White gourd soup","Miso soup","Duck Blood in Chili Sauce","Pork Lungs in Chili Sauce","Spicy pot","Golden meat rolls","Chiffon Cake","Egg Tart","Bread","Croissant","toast","Biscuits","cookies","Soda biscuit","Double skin milk","ice cream","Egg pudding","Sweet stewed snow pear","Fruit salad"};private:Ort::Env env;Ort::Session* session;bool cudaEnable;Ort::RunOptions options;std::vector<const char*> inputNodeNames;std::vector<const char*> outputNodeNames;MODEL_TYPE modelType;std::vector<int> imgSize;float rectConfidenceThreshold;float iouThreshold;float resizeScales;//letterbox scale
};//inference.cpp
#include "inference.h"
#include <regex>#define benchmark
#define min(a,b) (((a) < (b)) ? (a) : (b))
YOLO_V8::YOLO_V8() {}YOLO_V8::~YOLO_V8() {delete session;
}#ifdef USE_CUDA
namespace Ort
{template<>struct TypeToTensorType<half> { static constexpr ONNXTensorElementDataType type = ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT16; };
}
#endiftemplate<typename T>
char* BlobFromImage(cv::Mat& iImg, T& iBlob) {int channels = iImg.channels();int imgHeight = iImg.rows;int imgWidth = iImg.cols;for (int c = 0; c < channels; c++){for (int h = 0; h < imgHeight; h++){for (int w = 0; w < imgWidth; w++){iBlob[c * imgWidth * imgHeight + h * imgWidth + w] = typename std::remove_pointer<T>::type((iImg.at<cv::Vec3b>(h, w)[c]) / 255.0f);}}}return RET_OK;
}char* YOLO_V8::PreProcess(cv::Mat& iImg, std::vector<int> iImgSize, cv::Mat& oImg)
{if (iImg.channels() == 3){oImg = iImg.clone();cv::cvtColor(oImg, oImg, cv::COLOR_BGR2RGB);}else{cv::cvtColor(iImg, oImg, cv::COLOR_GRAY2RGB);}switch (modelType){case YOLO_DETECT_V8:case YOLO_POSE:case YOLO_DETECT_V8_HALF:case YOLO_POSE_V8_HALF://LetterBox{if (iImg.cols >= iImg.rows){resizeScales = iImg.cols / (float)iImgSize.at(0);cv::resize(oImg, oImg, cv::Size(iImgSize.at(0), int(iImg.rows / resizeScales)));}else{resizeScales = iImg.rows / (float)iImgSize.at(0);cv::resize(oImg, oImg, cv::Size(int(iImg.cols / resizeScales), iImgSize.at(1)));}cv::Mat tempImg = cv::Mat::zeros(iImgSize.at(0), iImgSize.at(1), CV_8UC3);oImg.copyTo(tempImg(cv::Rect(0, 0, oImg.cols, oImg.rows)));oImg = tempImg;break;}case YOLO_CLS://CenterCrop{int h = iImg.rows;int w = iImg.cols;int m = min(h, w);int top = (h - m) / 2;int left = (w - m) / 2;cv::resize(oImg(cv::Rect(left, top, m, m)), oImg, cv::Size(iImgSize.at(0), iImgSize.at(1)));break;}}return RET_OK;
}char* YOLO_V8::CreateSession(DL_INIT_PARAM& iParams) {char* Ret = RET_OK;std::regex pattern("[\u4e00-\u9fa5]");bool result = std::regex_search(iParams.modelPath, pattern);if (result){Ret = "[YOLO_V8]:Your model path is error.Change your model path without chinese characters.";std::cout << Ret << std::endl;return Ret;}try{rectConfidenceThreshold = iParams.rectConfidenceThreshold;iouThreshold = iParams.iouThreshold;imgSize = iParams.imgSize;modelType = iParams.modelType;env = Ort::Env(ORT_LOGGING_LEVEL_WARNING, "Yolo");Ort::SessionOptions sessionOption;if (iParams.cudaEnable){cudaEnable = iParams.cudaEnable;OrtCUDAProviderOptions cudaOption;cudaOption.device_id = 0;sessionOption.AppendExecutionProvider_CUDA(cudaOption);}sessionOption.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_ALL);sessionOption.SetIntraOpNumThreads(iParams.intraOpNumThreads);sessionOption.SetLogSeverityLevel(iParams.logSeverityLevel);#ifdef _WIN32int ModelPathSize = MultiByteToWideChar(CP_UTF8, 0, iParams.modelPath.c_str(), static_cast<int>(iParams.modelPath.length()), nullptr, 0);wchar_t* wide_cstr = new wchar_t[ModelPathSize + 1];MultiByteToWideChar(CP_UTF8, 0, iParams.modelPath.c_str(), static_cast<int>(iParams.modelPath.length()), wide_cstr, ModelPathSize);wide_cstr[ModelPathSize] = L'\0';const wchar_t* modelPath = wide_cstr;
#elseconst char* modelPath = iParams.modelPath.c_str();
#endif // _WIN32session = new Ort::Session(env, modelPath, sessionOption);Ort::AllocatorWithDefaultOptions allocator;size_t inputNodesNum = session->GetInputCount();for (size_t i = 0; i < inputNodesNum; i++){Ort::AllocatedStringPtr input_node_name = session->GetInputNameAllocated(i, allocator);char* temp_buf = new char[50];strcpy(temp_buf, input_node_name.get());inputNodeNames.push_back(temp_buf);}size_t OutputNodesNum = session->GetOutputCount();for (size_t i = 0; i < OutputNodesNum; i++){Ort::AllocatedStringPtr output_node_name = session->GetOutputNameAllocated(i, allocator);char* temp_buf = new char[10];strcpy(temp_buf, output_node_name.get());outputNodeNames.push_back(temp_buf);}options = Ort::RunOptions{ nullptr };WarmUpSession();return RET_OK;}catch (const std::exception& e){const char* str1 = "[YOLO_V8]:";const char* str2 = e.what();std::string result = std::string(str1) + std::string(str2);char* merged = new char[result.length() + 1];std::strcpy(merged, result.c_str());std::cout << merged << std::endl;delete[] merged;return "[YOLO_V8]:Create session failed.";}}char* YOLO_V8::RunSession(cv::Mat& iImg, std::vector<DL_RESULT>& oResult) {
#ifdef benchmarkclock_t starttime_1 = clock();
#endif // benchmarkchar* Ret = RET_OK;cv::Mat processedImg;PreProcess(iImg, imgSize, processedImg);if (modelType < 4){float* blob = new float[processedImg.total() * 3];BlobFromImage(processedImg, blob);std::vector<int64_t> inputNodeDims = { 1, 3, imgSize.at(0), imgSize.at(1) };TensorProcess(starttime_1, iImg, blob, inputNodeDims, oResult);}else{
#ifdef USE_CUDAhalf* blob = new half[processedImg.total() * 3];BlobFromImage(processedImg, blob);std::vector<int64_t> inputNodeDims = { 1,3,imgSize.at(0),imgSize.at(1) };TensorProcess(starttime_1, iImg, blob, inputNodeDims, oResult);
#endif}return Ret;
}template<typename N>
char* YOLO_V8::TensorProcess(clock_t& starttime_1, cv::Mat& iImg, N& blob, std::vector<int64_t>& inputNodeDims,std::vector<DL_RESULT>& oResult) {Ort::Value inputTensor = Ort::Value::CreateTensor<typename std::remove_pointer<N>::type>(Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU), blob, 3 * imgSize.at(0) * imgSize.at(1),inputNodeDims.data(), inputNodeDims.size());
#ifdef benchmarkclock_t starttime_2 = clock();
#endif // benchmarkauto outputTensor = session->Run(options, inputNodeNames.data(), &inputTensor, 1, outputNodeNames.data(),outputNodeNames.size());
#ifdef benchmarkclock_t starttime_3 = clock();
#endif // benchmarkOrt::TypeInfo typeInfo = outputTensor.front().GetTypeInfo();auto tensor_info = typeInfo.GetTensorTypeAndShapeInfo();std::vector<int64_t> outputNodeDims = tensor_info.GetShape();auto output = outputTensor.front().GetTensorMutableData<typename std::remove_pointer<N>::type>();delete[] blob;switch (modelType){case YOLO_DETECT_V8:case YOLO_DETECT_V8_HALF:{int strideNum = outputNodeDims[1];//8400int signalResultNum = outputNodeDims[2];//84std::vector<int> class_ids;std::vector<float> confidences;std::vector<cv::Rect> boxes;cv::Mat rawData;if (modelType == YOLO_DETECT_V8){// FP32rawData = cv::Mat(strideNum, signalResultNum, CV_32F, output);}else{// FP16rawData = cv::Mat(strideNum, signalResultNum, CV_16F, output);rawData.convertTo(rawData, CV_32F);}//Note://ultralytics add transpose operator to the output of yolov8 model.which make yolov8/v5/v7 has same shape//https://github.com/ultralytics/assets/releases/download/v8.2.0/yolov8n.pt//rowData = rowData.t();float* data = (float*)rawData.data;for (int i = 0; i < strideNum; ++i){float* classesScores = data + 4;cv::Mat scores(1, this->classes.size(), CV_32FC1, classesScores);cv::Point class_id;double maxClassScore;cv::minMaxLoc(scores, 0, &maxClassScore, 0, &class_id);if (maxClassScore > rectConfidenceThreshold){confidences.push_back(maxClassScore);class_ids.push_back(class_id.x);float x = data[0];float y = data[1];float w = data[2];float h = data[3];int left = int((x - 0.5 * w) * resizeScales);int top = int((y - 0.5 * h) * resizeScales);int width = int(w * resizeScales);int height = int(h * resizeScales);boxes.push_back(cv::Rect(left, top, width, height));}data += signalResultNum;}std::vector<int> nmsResult;cv::dnn::NMSBoxes(boxes, confidences, rectConfidenceThreshold, iouThreshold, nmsResult);for (int i = 0; i < nmsResult.size(); ++i){int idx = nmsResult[i];DL_RESULT result;result.classId = class_ids[idx];result.confidence = confidences[idx];result.box = boxes[idx];oResult.push_back(result);}#ifdef benchmarkclock_t starttime_4 = clock();double pre_process_time = (double)(starttime_2 - starttime_1) / CLOCKS_PER_SEC * 1000;double process_time = (double)(starttime_3 - starttime_2) / CLOCKS_PER_SEC * 1000;double post_process_time = (double)(starttime_4 - starttime_3) / CLOCKS_PER_SEC * 1000;if (cudaEnable){std::cout << "[YOLO_V8(CUDA)]: " << pre_process_time << "ms pre-process, " << process_time << "ms inference, " << post_process_time << "ms post-process." << std::endl;}else{std::cout << "[YOLO_V8(CPU)]: " << pre_process_time << "ms pre-process, " << process_time << "ms inference, " << post_process_time << "ms post-process." << std::endl;}
#endif // benchmarkbreak;}case YOLO_CLS:case YOLO_CLS_HALF:{cv::Mat rawData;if (modelType == YOLO_CLS) {// FP32rawData = cv::Mat(1, this->classes.size(), CV_32F, output);} else {// FP16rawData = cv::Mat(1, this->classes.size(), CV_16F, output);rawData.convertTo(rawData, CV_32F);}float *data = (float *) rawData.data;DL_RESULT result;for (int i = 0; i < this->classes.size(); i++){result.classId = i;result.confidence = data[i];oResult.push_back(result);}break;}default:std::cout << "[YOLO_V8]: " << "Not support model type." << std::endl;}return RET_OK;}char* YOLO_V8::WarmUpSession() {clock_t starttime_1 = clock();cv::Mat iImg = cv::Mat(cv::Size(imgSize.at(0), imgSize.at(1)), CV_8UC3);cv::Mat processedImg;PreProcess(iImg, imgSize, processedImg);if (modelType < 4){float* blob = new float[iImg.total() * 3];BlobFromImage(processedImg, blob);std::vector<int64_t> YOLO_input_node_dims = { 1, 3, imgSize.at(0), imgSize.at(1) };Ort::Value input_tensor = Ort::Value::CreateTensor<float>(Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU), blob, 3 * imgSize.at(0) * imgSize.at(1),YOLO_input_node_dims.data(), YOLO_input_node_dims.size());auto output_tensors = session->Run(options, inputNodeNames.data(), &input_tensor, 1, outputNodeNames.data(),outputNodeNames.size());delete[] blob;clock_t starttime_4 = clock();double post_process_time = (double)(starttime_4 - starttime_1) / CLOCKS_PER_SEC * 1000;if (cudaEnable){std::cout << "[YOLO_V8(CUDA)]: " << "Cuda warm-up cost " << post_process_time << " ms. " << std::endl;}}else{
#ifdef USE_CUDAhalf* blob = new half[iImg.total() * 3];BlobFromImage(processedImg, blob);std::vector<int64_t> YOLO_input_node_dims = { 1,3,imgSize.at(0),imgSize.at(1) };Ort::Value input_tensor = Ort::Value::CreateTensor<half>(Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU), blob, 3 * imgSize.at(0) * imgSize.at(1), YOLO_input_node_dims.data(), YOLO_input_node_dims.size());auto output_tensors = session->Run(options, inputNodeNames.data(), &input_tensor, 1, outputNodeNames.data(), outputNodeNames.size());delete[] blob;clock_t starttime_4 = clock();double post_process_time = (double)(starttime_4 - starttime_1) / CLOCKS_PER_SEC * 1000;if (cudaEnable){std::cout << "[YOLO_V8(CUDA)]: " << "Cuda warm-up cost " << post_process_time << " ms. " << std::endl;}
#endif}return RET_OK;
}//main.cpp
#include <iostream>
#include <iomanip>
#include "inference.h"
#include <filesystem>
#include <fstream>
#include <random>void Detector(YOLO_V8*& p) {std::filesystem::path current_path = std::filesystem::current_path();std::filesystem::path imgs_path = current_path / "images";for (auto& i : std::filesystem::directory_iterator(imgs_path)){if (i.path().extension() == ".jpg" || i.path().extension() == ".png" || i.path().extension() == ".jpeg"){std::string img_path = i.path().string();cv::Mat img = cv::imread(img_path);std::vector<DL_RESULT> res;p->RunSession(img, res);for (auto& re : res){cv::RNG rng(cv::getTickCount());cv::Scalar color(rng.uniform(0, 256), rng.uniform(0, 256), rng.uniform(0, 256));cv::rectangle(img, re.box, color, 3);float confidence = floor(100 * re.confidence) / 100;std::cout << std::fixed << std::setprecision(2);std::string label = p->classes[re.classId] + " " +std::to_string(confidence).substr(0, std::to_string(confidence).size() - 4);cv::rectangle(img,cv::Point(re.box.x, re.box.y - 25),cv::Point(re.box.x + label.length() * 15, re.box.y),color,cv::FILLED);cv::putText(img,label,cv::Point(re.box.x, re.box.y - 5),cv::FONT_HERSHEY_SIMPLEX,0.75,cv::Scalar(0, 0, 0),2);}std::cout << "Press any key to exit" << std::endl;cv::imshow("Result of Detection", img);cv::waitKey(0);cv::destroyAllWindows();}}
}void Classifier(YOLO_V8*& p)
{//获取当前工作目录std::filesystem::path current_path = std::filesystem::current_path();//设置要访问的图片目录路径std::filesystem::path imgs_path = "/home/yaokunwei/CaiPin_CLS/build/test";// / "images"//p->classes={"person","normal","0"};//生成随机数引擎std::random_device rd;std::mt19937 gen(rd());std::uniform_int_distribution<int> dis(0, 255);int k = 0;for (auto& i : std::filesystem::directory_iterator(imgs_path)){if (i.path().extension() == ".jpg" || i.path().extension() == ".png"){//获取图片路径并读取图像std::string img_path = i.path().string();//std::cout << img_path << std::endl;cv::Mat img = cv::imread(img_path);std::vector<DL_RESULT> res;//运行推理会话以进行图像分类char* ret = p->RunSession(img, res);//std::cout<<"class: "<<res.size()<<std::endl;float positionY = 50;int best_ID=0;float best_confidence=0.0;for (int i = 0; i < res.size(); i++){if(best_confidence<res[i].confidence){best_ID=i;best_confidence=res[i].confidence;}}//best_ID=best_ID+1;///std::cout<<"class: "<<res.size()<<std::endl;std::string labels=std::to_string(best_ID+1)+" "+p->classes[res[best_ID].classId] + ": " + std::to_string(res[best_ID].confidence);cv::putText(img, labels, cv::Point(10, 50), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(255, 0, 0), 2);// for (int i = 0; i < res.size(); i++)// {// int r = dis(gen);// int g = dis(gen);// int b = dis(gen);// // 获取类别名称和置信度// std::string label;// if (res[i].classId >= 0 && res[i].classId < p->classes.size()) {// label = p->classes[res[i].classId] + ": " + std::to_string(res[i].confidence);// }// else {// label="Unknown";// }// //cv::putText(img, std::to_string(i) + ":", cv::Point(10, positionY), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(b, g, r), 2);// cv::putText(img, label, cv::Point(10, positionY), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(b, g, r), 2);// positionY += 50;// }//cv::imshow("TEST_CLS", img);//cv::waitKey(0);cv::destroyAllWindows();cv::imwrite(std::to_string(k) + ".png", img);k++;}}
}int ReadCocoYaml(YOLO_V8*& p) {// Open the YAML filestd::ifstream file("coco.yaml");if (!file.is_open()){std::cerr << "Failed to open file" << std::endl;return 1;}// Read the file line by linestd::string line;std::vector<std::string> lines;while (std::getline(file, line)){lines.push_back(line);}// Find the start and end of the names sectionstd::size_t start = 0;std::size_t end = 0;for (std::size_t i = 0; i < lines.size(); i++){if (lines[i].find("names:") != std::string::npos){start = i + 1;}else if (start > 0 && lines[i].find(':') == std::string::npos){end = i;break;}}// Extract the namesstd::vector<std::string> names;for (std::size_t i = start; i < end; i++){std::stringstream ss(lines[i]);std::string name;std::getline(ss, name, ':'); // Extract the number before the delimiterstd::getline(ss, name); // Extract the string after the delimiternames.push_back(name);}p->classes = names;return 0;
}void DetectTest()
{YOLO_V8* yoloDetector = new YOLO_V8;ReadCocoYaml(yoloDetector);DL_INIT_PARAM params;params.rectConfidenceThreshold = 0.1;params.iouThreshold = 0.5;params.modelPath = "yolov8n.onnx";params.imgSize = { 640, 640 };
#ifdef USE_CUDAparams.cudaEnable = true;// GPU FP32 inferenceparams.modelType = YOLO_DETECT_V8;// GPU FP16 inference//Note: change fp16 onnx model//params.modelType = YOLO_DETECT_V8_HALF;
#else// CPU inferenceparams.modelType = YOLO_DETECT_V8;params.cudaEnable = false;#endifyoloDetector->CreateSession(params);Detector(yoloDetector);
}void ClsTest()
{YOLO_V8* yoloDetector = new YOLO_V8;std::string model_path = "./models/cls_13.onnx";//ReadCocoYaml(yoloDetector);//初始化推理参数//DL_INIT_PARAM params{ model_path, YOLO_CLS, {224, 224} };DL_INIT_PARAM params{ model_path, YOLO_CLS, {608, 608} }; //换成模型输入大小yoloDetector->CreateSession(params);Classifier(yoloDetector);
}
int main()
{//DetectTest();ClsTest();
}
上述代码里面包含了分类和检测的代码,这只用分类。再附赠一个CMakeLists.txt,若pcl不需要,可以删除pcl部分。
CMAKE_MINIMUM_REQUIRED(VERSION 3.0.0)
project(YOLOv8_cls)
# -------------- Support C++17 for using filesystem ------------------#
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)#换成自己的路径
SET (ONNXRUNTIME_DIR /xxx/src/onnxruntime-1.13.1-gpu)find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
# 打印opencv信息
message(STATUS "OpenCV library status:")
message(STATUS " config: ${OpenCV_DIR}")
message(STATUS " version: ${OpenCV_VERSION}")
message(STATUS " libraries: ${OpenCV_LIBS}")
message(STATUS " include path: ${OpenCV_INCLUDE_DIRS}")# 添加PCL环境
find_package(PCL REQUIRED)
add_definitions(${PCL_DEFINITIONS})
include_directories(${PCL_INCLUDE_DIRS})
link_directories(${PCL_LIBRARY_DIRS})
find_package(VTK REQUIRED)ADD_EXECUTABLE(YOLOv8_cls main.cpp inference.cpp inference.h)
SET(CMAKE_CXX_STANDARD 14)
SET(CMAKE_CXX_STANDARD_REQUIRED ON)TARGET_INCLUDE_DIRECTORIES(YOLOv8_cls PRIVATE "${ONNXRUNTIME_DIR}/include")
TARGET_COMPILE_FEATURES(YOLOv8_cls PRIVATE cxx_std_14)
TARGET_LINK_LIBRARIES(YOLOv8_cls ${OpenCV_LIBS}
)
TARGET_LINK_LIBRARIES(YOLOv8_cls "${ONNXRUNTIME_DIR}/lib/libonnxruntime.so")
TARGET_LINK_LIBRARIES(YOLOv8_cls "${ONNXRUNTIME_DIR}/lib/libonnxruntime.so.1.13.1")
target_link_libraries(YOLOv8_cls /usr/lib/gcc/x86_64-linux-gnu/7/libstdc++fs.a)
if (WIN32)TARGET_LINK_LIBRARIES(YOLOv8_cls "${ONNXRUNTIME_DIR}/lib/onnxruntime.lib")
endif(WIN32)if (UNIX)TARGET_LINK_LIBRARIES(YOLOv8_cls "${ONNXRUNTIME_DIR}/lib/libonnxruntime.so")
endif(UNIX)
整个基于yolo11n的菜品分类模型全部部署工程文件 参考 https://download.csdn.net/download/qq_61812715/90867328;注意修改模型和预测图片路径和输入的模型大小。
使用cmake make生成;cmake使用方法,参考 这个,这里不详细叙述;
#创建一个文件夹,把inference.h 、inference.cpp、main.cpp CMakeLists.txt 放到这里面
mkdir build &cd build
cmake ..
make
./YOLOv8_cls
python版本
python 推理预测相对简单
import cv2
import numpy as np
import onnxruntime as ort
from PIL import Image
import timeclass YOLOv11Classifier:def __init__(self, onnx_path, class_names=None):"""初始化分类器参数:onnx_path: ONNX模型文件路径class_names: 可选,类别名称列表"""# 创建ONNX运行时会话self.session = ort.InferenceSession(onnx_path)# 获取输入输出信息self.input_name = self.session.get_inputs()[0].nameself.output_name = self.session.get_outputs()[0].nameself.input_shape = self.session.get_inputs()[0].shape# 预期的输入尺寸 (通常为 224x224 或 256x256)self.input_size = (self.input_shape[2], self.input_shape[3])#self.input_size = (608, 608)# 类别名称self.class_names = class_names or [f"class_{i}" for i in range(self.session.get_outputs()[0].shape[1])]def preprocess(self, image):"""预处理输入图像参数:image: 输入图像 (PIL Image 或 numpy数组)返回:预处理后的numpy数组"""# 如果输入是numpy数组,转换为PIL Imageif isinstance(image, np.ndarray):image = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))# 调整大小并保持宽高比 (中心裁剪)width, height = image.sizeratio = min(self.input_size[0] / width, self.input_size[1] / height)new_size = (int(width * ratio), int(height * ratio))image = image.resize(new_size, Image.BILINEAR)# 中心裁剪left = (new_size[0] - self.input_size[0]) / 2top = (new_size[1] - self.input_size[1]) / 2right = (new_size[0] + self.input_size[0]) / 2bottom = (new_size[1] + self.input_size[1]) / 2image = image.crop((left, top, right, bottom))# 转换为numpy数组并归一化#image = np.array(image).astype(np.float32) / 255.0image = np.array(image, dtype=np.float32) / 255.0 # 关键修改在这里# 归一化 (使用ImageNet均值和标准差)mean = np.array([0.485, 0.456, 0.406])std = np.array([0.229, 0.224, 0.225])image = (image - mean) / std# 调整维度顺序为 CHW 并添加batch维度image = np.transpose(image, (2, 0, 1))image = np.expand_dims(image, axis=0)return imagedef predict(self, image, topk=5):"""执行分类预测参数:image: 输入图像 (文件路径/PIL Image/numpy数组)topk: 返回前k个预测结果返回:list: 包含(topk类别, 概率)的列表"""# 如果输入是文件路径,则读取图像if isinstance(image, str):image = Image.open(image)# 预处理图像input_tensor = self.preprocess(image)# 确保输入数据类型正确if input_tensor.dtype != np.float32:input_tensor = input_tensor.astype(np.float32)# 执行推理outputs = self.session.run([self.output_name], {self.input_name: input_tensor})probs = outputs[0][0] # 获取第一个(也是唯一一个)batch的输出# 获取topk预测topk_indices = np.argsort(probs)[-topk:][::-1]topk_probs = probs[topk_indices]# 返回结果results = [(self.class_names[i], float(probs[i])) for i in topk_indices]return resultsdef predict_with_visualization(self, image_path, topk=5):"""执行预测并可视化结果参数:image_path: 输入图像路径topk: 显示前k个预测结果"""# 读取图像img = cv2.imread(image_path)img_display = img.copy()# 执行预测start_time = time.time()results = self.predict(img, topk=topk)inference_time = time.time() - start_time# 显示结果cv2.putText(img_display, f"Inference: {inference_time*1000:.1f}ms", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)y_offset = 60for i, (class_name, prob) in enumerate(results):text = f"{i+1}. {class_name}: {prob*100:.2f}%"cv2.putText(img_display, text, (10, y_offset), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 255), 2)y_offset += 30# 显示图像cv2.imwrite("Classification_Result.png",img_display)cv2.imshow("Classification Result", img_display)cv2.waitKey(0)cv2.destroyAllWindows()# 使用示例
if __name__ == "__main__":# 示例类别 (替换为你的实际类别)class_names = ["Mapo Tofu","Home style sauteed Tofu","Fried Tofu","Bean curd","Stinky tofu",
"Potato silk","Pan fried potato","Pan fried potato","Braised beans with potato",
"Fried Potato, Green Pepper & Eggplant","French fries","Yu-Shiang Eggplant","Mashed garlic eggplant",
"Eggplant with mince pork","Spicy cabbage",
"Sour cabbage","Steamed Baby Cabbage","Shredded cabbage","Sauteed Lettuce in Oyster Sauce","Saute vegetable",
"tumis kangkung","Lettuce with smashed garlic","Sauteed spainch","Sauteed bean sprouts","Sauteed broad beans",
"Soybean","Broccoli with Oyster Sauce","Deep Fried lotus root","Lotus root","Tomato salad","Gizzard",
"Black Fungus in Vinegar Sauce","Cucumber in Sauce","peanut","Seaweed salad","Chinese Yam in Hot Toffee",
"Fried Yam","Fried beans","Oyster mushroom","stuffed bitter melon","sauteed bitter melon","pepper with tiger skin",
"Yuba salad","fried cauliflower","Sauteed Sweet Corn with Pine Nuts","Sauted Chinese Greens with Mushrooms",
"Spiced mushroom","Celery and tofu","Sauteed Lily Bulbs and Celery","Leak and tofu","Scrambled egg with tomato",
"Scrambled Egg with Leek","Scrambled Egg with cucumber","Steamed egg custard","Pork liver","Pig ears","roast pork",
"Steamed pork with rice powder","Sweet and sour spareribs","Braised spareribs with kelp","Cola Chicken wings",
"Chicken Feet with Pickled Peppers","Chicken Feet with black bean sauce","Steamed Chicken with Chili Sauce",
"Roast goose","Boiled chicken","Saute Spicy Chicken","Steamed Chicken with Mushroom","chicken braised with brown sauce","Soy sauce chicken","Spicy Chicken","Kung Pao Chicken","Stewed Chicken with Three Cups Sauce","Shredded chicken","Fried chicken drumsticks","Beer duck","Scalloped pork or lamb kidneys","Braised pork","Braised beef","Beef Seasoned with Soy Sauce","Sirloin tomatoes",
"Stewed sirloin potatoes","Sauteed Beef Fillet with Hot Green Pepper","Pork with salted vegetable","Double cooked pork slices","Braised Pork with Vermicelli","Boiled Shredded pork in chili oil","Fried Sweet and Sour Tenderloin","Cripsy sweet & sour pork slices","Pot bag meat","Shredded Pork with Vegetables","Tiger lily buds in Baconic","Sauteed Shredded Pork in Sweet Bean Sauce","Shredded pork with bean","Braised pig feet with soy sauce","Tripe","Shredded pork and green pepper","Yu-Shiang Shredded Pork",
"Braised Fungus with pork slice","Sauteed Sliced Pork,Eggs and Black Fungus","Lettuce shredded meat","Sauteed Vermicelli with Spicy Minced Pork","Fried Lamb with Cumin","Lamb shashlik","Sauteed Sliced Lamb with Scallion","Stewed Pork Ball in Brown Sauce","Boiled Fish with Picked Cabbage and Chili","grilled fish","Sweet and sour fish","Sweet and Sour Mandarin Fish",
"Braised Hairtail in Brown Sauce","Steamed Fish Head with Diced Hot Red Peppers","Fish Filets in Hot Chili Oil","Steamed Perch","Cheese Shrimp Meat","Shrimp broccoli","Braised Shrimp in chili oil","Spicy shrimp","Spicy crayfish","Shrimp Duplings","Steamed shrimp with garlic and vermicelli","Sauteed Shrimp meat","Pipi shrimp","Scallop in Shell","Oysters","squid","Abalone",
"Crab","Turtle","eel","Yangzhou fried rice","Omelette","Steamed Bun Stuffed","Steamed Pork Dumplings","egg omelet","Potato omelet","Egg pie cake","Marinated Egg","Poached Egg","Pine cake with Diced Scallion","Sesame seed cake","Chinese hamburger","Leek box","steamed bun with purple potato and pumpkin","steamed bun","Steamed stuffed bun","Pumpkin pie","Pizza","Deep-Fried Dough Sticks","sauteed noodles with minced meat","Chongqing Hot and Sour Rice Noodles","Cold noodles","Noodles with egg and tomato","spaghetti with meat sauce","Noodles with tomato sauce","Cold Rice Noodles","Sichuan noodles with peppery sauce","Qishan noodles","fried noodles",
"Dumplings","Corn Cob","Braised beef noodle","fried rice noodles","Steamed vermicelli roll","Pork wonton","Fried Dumplings","Tang-yuan","Millet congee","Sweet potato porridge","Jellyfish","Minced Pork Congee with Preserved Egg","Rice porridge","Rice","Laver rice","Stone pot of rice","Black bone chicken soup","Crucian and Bean Curd Soup","Dough Drop and Assorted Vegetable Soup","Hot and Sour Soup","Pork ribs soup with radish","Tomato and Egg Soup","West Lake beef soup","Lotus Root and Rib soup",
"Seaweed and Egg Soup","Seaweed tofu soup","Corn and sparerib soup","Spinach and pork liver soup","Borsch","White fungus soup","White gourd soup","Miso soup","Duck Blood in Chili Sauce","Pork Lungs in Chili Sauce","Spicy pot","Golden meat rolls","Chiffon Cake","Egg Tart","Bread","Croissant","toast","Biscuits","cookies","Soda biscuit","Double skin milk","ice cream","Egg pudding","Sweet stewed snow pear","Fruit salad"]# 初始化分类器classifier = YOLOv11Classifier("./models/cls_15.onnx", class_names)# 执行预测image_path = "./test/000013.jpg"results = classifier.predict(image_path, topk=3)# 打印结果print("Top predictions:")for i, (class_name, prob) in enumerate(results):print(f"{i+1}. {class_name}: {prob*100:.2f}%")# 可视化结果 (可选),输出前三个分数高的classifier.predict_with_visualization(image_path, topk=3)