一、绪论
从零开始配置Qt-YOLO-OpenCV。
二、配置OpenCV
网址:

三、配置YOLO
网址:
https://github.com/ultralytics/ultralytics
模型和推理的关系
| 维度 | 模型 (Model) | 推理 (Inference) |
|---|---|---|
| 本质 | 静态的知识库(数据文件) | 动态的计算过程(程序运行) |
| 类比 | 菜谱 | 按菜谱烹饪 |
| 存在形式 | .pt, .onnx, .bin 等文件 |
一段代码或一个正在运行的程序 |
| 内容 | 网络结构、权重参数 | 加载、计算、输出等逻辑 |
| 作用 | 定义"做什么"和"怎么做"的规则 | 执行规则,完成具体任务 |
| 依赖关系 | 推理的基础和依据 | 模型的使用者和实现者 |
3.1下载模型
点击YOLO11s,下载好就这样

将pt格式转换为onnx
| 维度 | PyTorch (.pt) 格式 | ONNX (.onnx) 格式 |
|---|---|---|
| 性质与目的 | 研发框架专用格式,用于保存完整的训练状态。 | 跨平台交换标准,用于在不同框架和硬件间部署。 |
| 主要用途 | 模型训练、微调、继续训练和PyTorch环境内推理。 | 模型跨平台部署、性能优化(如TensorRT/OpenVINO转换)。 |
| 包含内容 | 完整状态:网络结构、权重、优化器状态、epoch等。 | 精简计算图:网络结构、权重、固定输入输出节点。 |
| 平台依赖 | 强依赖PyTorch及其Python生态。 | 弱依赖,任何支持ONNX标准的运行时均可加载。 |
| 可编辑性 | 高,可直接加载并修改结构、继续训练。 | 极低,通常视为只读的推理模型,难以直接修改或训练。 |
| 性能优化 | 依赖PyTorch自身优化和TorchScript。 | 作为中间格式,可被专用推理引擎(如ONNX Runtime、TensorRT)深度优化。 |
| 标准化程度 | 由PyTorch定义,是事实标准。 | 由开放联盟定义,是行业开放标准。 |
具体步骤:
新建python文件复制下面的代码
cpp
from ultralytics import YOLO
model = YOLO("yolo11n.pt")
export_path = model.export(format="onnx")
运行后会生成onnx格式文件

3.2下载推理代码
将整个项目下载后找到这个路径

四、配置cmake,将OpenCV和YOLO集成到qt
OpenCV(注意这个对应的编译器是mscv2019)
cpp
# 替换为你实际的OpenCV lib目录(包含OpenCVConfig.cmake的路径)
set(OpenCV_DIR "D:/opencv/build/x64/vc16/lib")
# 查找OpenCV库(基于上面设置的绝对路径)
find_package(OpenCV REQUIRED)
# 链接OpenCV库
target_link_libraries(demo PRIVATE
${OpenCV_LIBS}
)
# 添加OpenCV头文件路径
target_include_directories(demo PRIVATE
${OpenCV_INCLUDE_DIRS}
)
五、ui设计

添加inference文件

将这两个文件添加到你上面创建的项目中
mainwindow.h
cpp
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include"inference.h"
#include<QFileDialog>
QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_open_clicked();
private:
Ui::MainWindow *ui;
Inference *inf{nullptr};
};
#endif // MAINWINDOW_H
mainwindow.cpp
cpp
#include "mainwindow.h"
#include "./ui_mainwindow.h"
using namespace std;
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
if (inf) {
delete inf;
inf = nullptr;
}
}
cv::Mat QImageToCvMat(const QImage &qimage)
{
switch(qimage.format()) {
case QImage::Format_RGB32: // 最常见格式(ARGB,忽略Alpha)
case QImage::Format_ARGB32:
case QImage::Format_ARGB32_Premultiplied: {
cv::Mat mat(qimage.height(), qimage.width(), CV_8UC4,
(void*)qimage.constBits(), qimage.bytesPerLine());
// 移除Alpha通道,将ARGB转为BGR
cv::Mat mat_rgb;
cv::cvtColor(mat, mat_rgb, cv::COLOR_BGRA2BGR);
return mat_rgb;
}
case QImage::Format_RGB888: { // 24位RGB
cv::Mat mat(qimage.height(), qimage.width(), CV_8UC3,
(void*)qimage.constBits(), qimage.bytesPerLine());
// RGB转BGR
cv::Mat mat_bgr;
cv::cvtColor(mat, mat_bgr, cv::COLOR_RGB2BGR);
return mat_bgr;
}
case QImage::Format_Grayscale8: { // 灰度图
cv::Mat mat(qimage.height(), qimage.width(), CV_8UC1,
(void*)qimage.constBits(), qimage.bytesPerLine());
return mat.clone(); // 需要clone以确保数据连续
}
default:
// 转换为24位RGB再处理
QImage converted = qimage.convertToFormat(QImage::Format_RGB888);
return QImageToCvMat(converted);
}
}
// 在 QImageToCvMat 函数后面添加这个函数
QImage cvMatToQImage(const cv::Mat &mat)
{
switch(mat.type()) {
// 8-bit, 4 channel
case CV_8UC4: {
QImage image(mat.data, mat.cols, mat.rows,
static_cast<int>(mat.step), QImage::Format_ARGB32);
return image.copy();
}
// 8-bit, 3 channel
case CV_8UC3: {
QImage image(mat.data, mat.cols, mat.rows,
static_cast<int>(mat.step), QImage::Format_RGB888);
return image.rgbSwapped(); // BGR to RGB
}
// 8-bit, 1 channel
case CV_8UC1: {
QImage image(mat.data, mat.cols, mat.rows,
static_cast<int>(mat.step), QImage::Format_Grayscale8);
return image.copy();
}
default: {
qWarning() << "cv::Mat image type not handled in switch:" << mat.type();
return QImage();
}
}
}
void MainWindow::on_open_clicked()
{
qDebug() << "按钮被点击";
QString filePath = QFileDialog::getOpenFileName(
this,
"选择图片",
"",
"图片文件 (*.jpg *.jpeg *.png *.bmp *.gif)"
);
if (filePath.isEmpty()) return;
QImage image(filePath);
if (image.isNull()) {
return;
}
// 先显示原始图片
QPixmap originalPixmap = QPixmap::fromImage(image);
// 计算合适的尺寸,保持图片比例,同时确保按钮可见
QSize labelSize = ui->show->size();
QPixmap scaledPixmap = originalPixmap.scaled(
labelSize.width() - 20, // 留出边距
labelSize.height() - 60, // 留出按钮空间
Qt::KeepAspectRatio,
Qt::SmoothTransformation
);
// 显示原始图片
ui->show->setPixmap(scaledPixmap);
ui->show->setAlignment(Qt::AlignCenter);
// 设置按钮文本
ui->open->setText("推理中...");
ui->open->setEnabled(false);
// 立即更新界面显示
QCoreApplication::processEvents();
// 进行推理
string onnxPath = "D:/qtdemo/Fire/YOLO/yolo11s.onnx";
string classesPath = "D:/qtdemo/Fire/YOLO/classes.txt";
bool runOnGPU = false;
// 如果之前有推理器,先删除
if (inf) {
delete inf;
inf = nullptr;
}
try {
inf = new Inference(onnxPath, cv::Size(640, 640), classesPath, runOnGPU);
cv::Mat frame = QImageToCvMat(image);
// 如果图像有 alpha 通道,转换为 BGR
if (frame.channels() == 4) {
cv::cvtColor(frame, frame, cv::COLOR_BGRA2BGR);
}
std::vector<Detection> output = inf->runInference(frame);
int detections = output.size();
std::cout << "Number of detections:" << detections << std::endl;
// 在图像上绘制检测结果
for (int i = 0; i < detections; ++i) {
Detection detection = output[i];
cv::Rect box = detection.box;
cv::Scalar color = detection.color;
// 绘制检测框
cv::rectangle(frame, box, color, 2);
// 绘制检测框文本
std::string classString = detection.className + ' ' + std::to_string(detection.confidence).substr(0, 4);
cv::Size textSize = cv::getTextSize(classString, cv::FONT_HERSHEY_DUPLEX, 1, 2, 0);
cv::Rect textBox(box.x, box.y - 40, textSize.width + 10, textSize.height + 20);
cv::rectangle(frame, textBox, color, cv::FILLED);
cv::putText(frame, classString, cv::Point(box.x + 5, box.y - 10),
cv::FONT_HERSHEY_DUPLEX, 1, cv::Scalar(0, 0, 0), 2, 0);
}
// 将处理后的 OpenCV Mat 转换为 QImage
QImage resultImage = cvMatToQImage(frame);
// 计算合适的尺寸显示结果图片
QPixmap resultPixmap = QPixmap::fromImage(resultImage);
QSize labelSize = ui->show->size();
QPixmap scaledResult = resultPixmap.scaled(
labelSize.width() - 20,
labelSize.height() - 60, // 为按钮留出空间
Qt::KeepAspectRatio,
Qt::SmoothTransformation
);
// 在 QLabel 中显示结果图片
ui->show->setPixmap(scaledResult);
ui->show->setAlignment(Qt::AlignCenter);
// 恢复按钮状态
ui->open->setText("重新选择图片");
ui->open->setEnabled(true);
// 显示检测结果统计
if (detections > 0) {
QString message = QString("检测到 %1 个目标").arg(detections);
}
} catch (const std::exception& e) {
qDebug() << "推理错误:" << e.what();
// 恢复按钮状态
ui->open->setText("选择图片并识别");
ui->open->setEnabled(true);
}
}
六、效果

