yolov8 opencv dnn
yolov8 opencv dnn
1 源码地址
2 运行github代码
2.1 推理条件
- windows 10
- Visual Studio 2019
- Nvidia GeForce GTX 1070
- opencv4.7.0 (opencv4.5.5在别的地方看到不支持yolov8的推理,所以只使用opencv4.7.0)
2.2 c++部署
先将源码复制到下图位置中
环境和代码的大致步骤跟yolov5 opencv dnn部署 github代码一样,环境部署好之后
选择“release x64”
在选择编译环境之后,还需要对文件路径进行修改
"Resources/classes.txt" -> "YOLO_cpp/Resurces/classes.txt"
另外还需要特别需要修改的一个地方是load_net,如果不修改这个代码,使用cpu可以运行程序,使用gpu可以运行程序,但是gpu运行的速度特别慢。
void load_net(cv::dnn::Net& net, bool is_cuda)
{
auto result = cv::dnn::readNet("YOLO_cpp/Resources/yolov8s.onnx");
if (is_cuda)
{
std::cout << "Attempty to use CUDA\n";
result.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA);
result.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA_FP16); // 这里一定要修改为下面的这行
// result.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA)
}
else
{
std::cout << "Running on CPU\n";
result.setPreferableBackend(cv::dnn::DNN_BACKEND_OPENCV);
result.setPreferableTarget(cv::dnn::DNN_TARGET_CPU);
}
net = result;
}
然后直接运行yolov8.cpp,不用做任何修改
2.3 c++推理结果

3 部署custom yolov8
3.1 推理条件
同2.1
3.2 导出yolov8模型
yolov8版本: version = ‘8.0.110’
首先将default.yaml中的一些配置修改以下,将只修改的部分贴上去,注意下面的batch一定要设置为1
task: detect # YOLO task, i.e. detect, segment, classify, pose
mode: export # YOLO mode, i.e. train, val, predict, export, track, benchmark
# Train settings -------------------------------------------------------------------------------------------------------
# model: C:\Users\HUST\Desktop\yolov8_ultralytics\ultralytics\models\v8\yolov8.yaml # path to model file, i.e. yolov8n.pt, yolov8n.yaml
model: C:\Users\Administrator\Desktop\yolov8_ultralytics\runs\detect\yolov8n\weights\best.pt # path to model file, i.e. yolov8n.pt, yolov8n.yaml
data: C:\Users\Administrator\Desktop\yolov8_ultralytics/ultralytics/datasets/custom.yaml # path to data file, i.e. coco128.yaml
weights: yolov8n.pt
epochs: 1 # number of epochs to train for
patience: 50 # epochs to wait for no observable improvement for early stopping of training
batch: 1 # number of images per batch (-1 for AutoBatch)
default.yaml中的export部分的配置也需要修改
# Export settings ------------------------------------------------------------------------------------------------------
format: onnx # format to export to
keras: False # use Keras
optimize: False # TorchScript: optimize for mobile
int8: False # CoreML/TF INT8 quantization
dynamic: False # ONNX/TF/TensorRT: dynamic axes
simplify: False # ONNX: simplify model
opset: 12 # ONNX: opset version (optional)
workspace: 4 # TensorRT: workspace size (GB)
nms: False # CoreML: add NMS
然后直接运行ultralytics/yolo/engine/exporter.py
测试一下导出的best.onnx可不可用,直接正常的val即可
将best.onnx模型放入netron中,onnx的输入和输出如下图1所示
图 1 图1 图1
3.3 c++部署
先将源码复制到下图位置中
环境和代码的大致步骤跟yolov5 opencv dnn部署 github代码一样
由于源码中使用的输入尺寸如图2是640 * 480的,我导出模型时使用的模型的输入如图1是640 * 640,所以需要对尺寸的那一部分需要进行修改,修改为640 * 640
const float INPUT_WIDTH = 640.0;
const float INPUT_HEIGHT = 640.0;
const float SCORE_THRESHOLD = 0.45;
const float NMS_THRESHOLD = 0.5;
const float CONFIDENCE_THRESHOLD = 0.25;

图 2 图2 图2在进行修改之后,就可以直接运行yolo.cpp
3.4 c++推理结果
yolov8_deploy_fire
4 github代码介绍
4.1 yolov8与yolov5对比
SCORE_THRESHOLD, NMS_THRESHOLD的阈值选择,由于yolov8是free anchor,因此yolov8的输出没有conf,只有xyxy+cls_score,因此,是nms直接将cls_socre中最大的且大于SCORE_THRESHOLD当作conf处理。因此在github代码中post_process函数中已经不需要CONFIDENCE_THRESHOLD。
yolov8
const std::vector<cv::Scalar> colors = { cv::Scalar(255, 255, 0), cv::Scalar(0, 255, 0), cv::Scalar(0, 255, 255), cv::Scalar(255, 0, 0) };
const float INPUT_WIDTH = 640.0;
const float INPUT_HEIGHT = 640.0;
const float SCORE_THRESHOLD = 0.45;
const float NMS_THRESHOLD = 0.5;
const float CONFIDENCE_THRESHOLD = 0.25; // 不需要这个, free anchor没有conf,是将cls_score当作conf
Mat post_process(Mat& input_image, vector<Mat>& outputs, const vector<string>& class_name)
{
// Initialize vectors to hold respective outputs while unwrapping detections.
vector<int> class_ids;
vector<float> confidences;
vector<Rect> boxes;
int rows = outputs[0].size[2];
int dimensions = outputs[0].size[1];
outputs[0] = outputs[0].reshape(1, dimensions);
cv::transpose(outputs[0], outputs[0]);
float* data = (float*)outputs[0].data;
// Resizing factor.
float x_factor = input_image.cols / INPUT_WIDTH;
float y_factor = input_image.rows / INPUT_HEIGHT;
// Iterate through detections.
//cout << "num detections : " << rows << " " << dimensions << endl;
for (int i = 0; i < rows; ++i)
{
float* classes_scores = data + 4;
cv::Mat scores(1, class_name.size(), CV_32FC1, classes_scores);
cv::Point class_id;
double maxClassScore;
minMaxLoc(scores, 0, &maxClassScore, 0, &class_id); // 这里选取cls_score中的max
if (maxClassScore > SCORE_THRESHOLD) // 只保留大于SCORE_THRESHOLD的预测框
{
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) * x_factor);
int top = int((y - 0.5 * h) * y_factor);
int width = int(w * x_factor);
int height = int(h * y_factor);
boxes.push_back(cv::Rect(left, top, width, height));
}
data += dimensions;
}
// Perform Non-Maximum Suppression and draw predictions.
vector<int> indices;
NMSBoxes(boxes, confidences, SCORE_THRESHOLD, NMS_THRESHOLD, indices);
//cout << "num detections finally : "<< indices.size() <<endl;
for (int i = 0; i < indices.size(); i++)
{
int idx = indices[i];
Rect box = boxes[idx];
int left = box.x;
int top = box.y;
int width = box.width;
int height = box.height;
// Draw bounding box.
rectangle(input_image, Point(left, top), Point(left + width, top + height), BLUE, 3 * THICKNESS);
// Get the label for the class name and its confidence.
string label = format("%.2f", confidences[idx]);
label = class_name[class_ids[idx]] + ":" + label;
// Draw class labels.
draw_label(input_image, label, left, top);
}
return input_image;
}
yolov5
const std::vector<cv::Scalar> colors = {cv::Scalar(255, 255, 0), cv::Scalar(0, 255, 0), cv::Scalar(0, 255, 255), cv::Scalar(255, 0, 0)};
const float INPUT_WIDTH = 640.0;
const float INPUT_HEIGHT = 640.0;
const float SCORE_THRESHOLD = 0.2;
const float NMS_THRESHOLD = 0.4;
const float CONFIDENCE_THRESHOLD = 0.4;
void detect(cv::Mat &image, cv::dnn::Net &net, std::vector<Detection> &output, const std::vector<std::string> &className) {
cv::Mat blob;
auto input_image = format_yolov5(image);
cv::dnn::blobFromImage(input_image, blob, 1./255., cv::Size(INPUT_WIDTH, INPUT_HEIGHT), cv::Scalar(), true, false);
net.setInput(blob);
std::vector<cv::Mat> outputs;
// 添加代码,使得opencv4.7.0可以使用
net.enableWinograd(false);
net.forward(outputs, net.getUnconnectedOutLayersNames());
float x_factor = input_image.cols / INPUT_WIDTH;
float y_factor = input_image.rows / INPUT_HEIGHT;
float *data = (float *)outputs[0].data;
const int dimensions = 85;
const int rows = 25200;
const int max_wh = 768; // 这个值是偏移量,这个酌情选择,不然太大会导致dnn:nms不工作
// 添加代码
int out_dim2 = outputs[0].size[2]; // 这里的是class+conf+xywh,相当于COCO的指标的85
std::vector<int> class_ids;
std::vector<float> confidences;
std::vector<cv::Rect> boxes;
std::vector<cv::Rect> boxes_muti;
for (int i = 0; i < rows; ++i) {
// 添加代码
int index = i * out_dim2; // 每一次循环索引都是下一个pre_box的初始位置
float confidence = data[4 + index]; // 修改代码 这样读取的值就是下一个的pre_box的conf
if (confidence >= CONFIDENCE_THRESHOLD) {
// 修改代码 这样读取的值就是下一个的pre_box的class
float * classes_scores = data + 5 + index;
cv::Mat scores(1, className.size(), CV_32FC1, classes_scores);
cv::Point class_id;
double max_class_score;
minMaxLoc(scores, 0, &max_class_score, 0, &class_id);
max_class_score *= confidence; // conf = obj_conf * cls_conf
if (max_class_score > SCORE_THRESHOLD) {
confidences.push_back(max_class_score);
class_ids.push_back(class_id.x);
// 修改代码,这样读取的值就是下一个的pre_box的xywh
float x = data[0 + index];
float y = data[1 + index];
float w = data[2 + index];
float h = data[3 + index];
int left = int((x - 0.5 * w) * x_factor);
int top = int((y - 0.5 * h) * y_factor);
int width = int(w * x_factor);
int height = int(h * y_factor);
boxes.push_back(cv::Rect(left, top, width, height));
// 实现多分类NMS,如果不需要实现,就直接删掉该部分
// 在这里添加的是类似yolov5nms的class_id位置偏移
int left_muti = int((x - 0.5 * w) * x_factor + class_id.x * max_wh);
int top_muti = int((y - 0.5 * h) * y_factor + class_id.x * max_wh);
int width_muti = int(w * x_factor + class_id.x * max_wh);
int height_muti = int(h * y_factor + class_id.x * max_wh);
boxes_muti.push_back(cv::Rect(left_muti, top_muti, width_muti, height_muti));
}
}
}
std::vector<int> nms_result;
cv::dnn::NMSBoxes(boxes_muti, confidences, SCORE_THRESHOLD, NMS_THRESHOLD, nms_result);
for (int i = 0; i < nms_result.size(); i++) {
int idx = nms_result[i];
Detection result;
result.class_id = class_ids[idx];
result.confidence = confidences[idx];
result.box = boxes[idx];
output.push_back(result);
}
}
4.2 blobFromImage 与 blobFromImages
blobFromImage是将单张图片转换成dnn可以推理的输入格式(1X3X640X640),使用下面代码可以打印出img经过cv::dnn::blobFromImage变换后的blob信息。blobFromImages是将vector中的多张图片转化为dnn可以推理的输入格式(NXCX640X640)。
int main() {
int INPUT_WIDTH = 640;
int INPUT_HEIGHT = 640;
cv::Mat blob;
cv::Mat img = cv::imread("C:\\Users\\Administrator\\Desktop\\1.png", cv::IMREAD_COLOR);
std::cout << "img shape: " << img.size << std::endl; // 644 x 287 (W x H)
std::cout << "img channels: " << img.channels() << std::endl; // 打印通道数,彩色为3
cv::dnn::blobFromImage(img, blob, 1. / 255., cv::Size(INPUT_WIDTH, INPUT_HEIGHT), cv::Scalar(), true, false);
std::cout << "Blob shape: " << blob.size << std::endl; // 1 x 3 x 640 x640 (N x C x W x H)
std::cout << "Blob channels: " << blob.channels() << std::endl; // 打印通道数, 为1
const float* blob_data = reinterpret_cast<const float*>(blob.ptr<float>(0));
int blob_size = blob.total() * blob.elemSize1();
// 仅打印前10个元素
for (int i = 0; i < std::min(blob_size, 100); ++i) {
std::cout << blob_data[i] << " ";
}
std::cout << std::endl;
//std::cout << "==Python风格==\n" << cv::format(img.rowRange(0, 1), cv::Formatter::FMT_PYTHON) << std::endl;
cv::imshow("Image", img);
cv::waitKey(0);
}
reshape的用法, cv::Mat::reshape(int cn, int rows = 0) const, cn是新的通道数,rows是新的行数。
int main() {
// 创建一个 1x3x640x640 的矩阵
int sizes[] = { 1, 3, 640, 640 };
cv::Mat blob(4, sizes, CV_32F); // 创建一个 1x3x640x640 的矩阵
// 重塑为单通道矩阵
cv::Mat reshaped1 = blob.reshape(1); // 1 x 3 x 640 x 640
std::cout << "Reshaped to 1 channel: " << reshaped1.size << std::endl;
// 重塑为 3 个通道和 640 行的矩阵
cv::Mat reshaped2 = blob.reshape(1, 3); // 3 x 409600
std::cout << "Reshaped to 1 channels and 3 rows: " << reshaped2.size << std::endl;
cv::Mat reshaped3 = blob.reshape(1, std::vector<int>({ 3, 640, 640 })); // 3 x 640 x640
std::cout << "Reshaped to 1 channels: " << reshaped3.size << std::endl;
return 0;
}
5 参考链接
另外两个yolov8推理的代码参考链接
更多推荐
所有评论(0)