
简介基于QT与YOLOv5s的缺陷检测系统围绕图像检测与目标检测两大功能展开完整覆盖了从界面搭建到模型推理的工程流程适合作为毕业设计、课程设计或入门实战项目参考。压缩包总共76个文件类型涵盖源码中的cpp与h、QT界面ui与资源qrc、png和jpg图片素材、mp4演示视频、onnx模型权重以及names类别文件整体大小约151MB结构按功能模块划分便于检索和学习。目前已有120人学习/下载尤其适合正在学习QT GUI编程或YOLO目标检测的开发者。资料中既包含QT的界面设计、信号槽通信、多线程处理和网络通信等C核心代码也包含YOLOv5s的模型文件、推理调用和测试数据可直接运行体验也可对照源码梳理工程细节支持在此基础上继续改进和二次开发。1. 基于QT和yolov5s的缺陷检测系统是在做什么毕业设计题目一旦带上“QT”和“yolov5s”几乎就默认了要做一套带界面的视觉检测系统。这里最容易被误解的是“图像检测”和“目标检测”两个词前者指传统图像处理里的阈值、边缘、形态学分析后者指用yolo这类深度学习模型输出目标框和类别。放在同一个项目里意味着既要写OpenCV的规则逻辑也要调通yolov5的训练与推理最后还要把它们塞进一个能正常操作、不闪退的QT界面。这篇文章按“选型—环境—实现—联调”的顺序把从零跑通这套系统时最容易被卡住的细节拆开讲适合正在做毕设、以及想快速搭一套缺陷检测演示项目的人。2. 缺陷检测的两条技术路线图像检测与目标检测2.1 图像检测用OpenCV做阈值、边缘和形态学处理传统图像检测的原理是把缺陷定义为像素统计上的异常。以电机转子绕线缺陷检测为例绕线区域在灰度上通常和背景有明确分层断线、错位会表现为局部灰度突变或边缘断裂。处理流程基本都是灰度化、滤波、二值化、形态学操作、连通域分析。这套流程不依赖标注数据参数调好后能够跑到毫秒级而且在QT里用OpenCV的C接口实现非常直接。下面的代码是一个最常见的最小实现适合作为第一个验证版本。cv::Mat gray, blur, binary; cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY); cv::medianBlur(gray, blur, 5); cv::adaptiveThreshold(blur, binary, 255, cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY_INV, 31, 10); std::vectorstd::vectorcv::Point contours; cv::findContours(binary, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE); for (auto c : contours) { double area cv::contourArea(c); if (area minArea area maxArea) { cv::Rect r cv::boundingRect(c); cv::rectangle(src, r, cv::Scalar(0, 0, 255), 2); } }这段代码里adaptiveThreshold的blockSize取31表示以当前像素为中心、31×31邻域内计算局部阈值C取10让判定条件比局部均值高10个灰度级。如果检测结果出现大量细碎噪点就把中值滤波的ksize从5改到7。minArea和maxArea是两级面积过滤用来排除灰尘和反光造成的大块亮斑。实际调试时我一般会把轮廓画出来之后在界面上打印每个轮廓的面积方便确定上下限。这比一遍遍改代码重新编译高效得多。2.2 目标检测yolov5s如何定位缺陷yolov5s是yolo家族里参数量相对小、适合端侧部署的一个变体。它把输入图像划分成网格每个网格预测若干个候选框再用置信度阈值和NMS得到最终结果。与图像检测相比它的优势是能学习到缺陷的高层语义特征例如裂纹的走向、划痕和背景的纹理差异。毕设里用yolov5s不需要自己实现网络结构训练好best.pt后直接调用官方推理脚本即可。但要注意在QT里集成yolov5s时并不需要把C版的模型推理重新写一遍。常见做法是QT界面调用Python脚本脚本负责加载模型、做letterbox、前向推理、输出JSON。这样换权重文件或换检测类别时QT一侧的代码可以保持稳定。推理流程中的letterbox是不可省略的它把原图按比例缩放到640×640剩余区域用灰色填充。很多同学在C里自己写前向时忘记记录填充尺寸导致画框偏移所以稳妥起见先用QProcess调用Python是更可控的方案。2.3 为什么毕设里两个部分都要做如果只做yolov5s题目里的“图像检测”就没有落点如果只做OpenCV又体现不出深度学习的内容。两套方案放在一起其实是一件产品里经常出现的混合架构规则逻辑负责快速过滤模型负责复杂判断。对毕设项目来说这种组合天然适合写论文里的对比实验同一批样本分别用两种方法检测统计检出率和误检率。维度图像检测yolov5s目标检测数据需求无需标注需要标注框可解释性阈值、面积可调特征不可直接解释推理速度毫秒级CPU约30-50ms泛化能力依赖场景稳定对形态变化更鲁棒部署复杂度OpenCV即可需要Python运行环境另外还有一个实用点先跑图像检测得到的候选框可以直接导出成伪标签供yolov5训练前做数据清洗反过来yolov5检测出的高置信度样本也可以用来验证图像检测的判定阈值是否过松。两套方法互相补位答辩时老师问到“为什么做了两套”可以从这个角度回答。3. 搭建QTyolov5s开发环境与数据集准备3.1 环境清单和最容易踩的版本坑这里给一套经过验证的版本组合QT使用5.15.2 MSVC2019_64OpenCV使用4.5.5Python使用3.8PyTorch使用CPU版1.13yolov5以6.0版本为基准。这套组合在Windows 10/11上都能跑通Ubuntu下则需要把QT换成gcc版本的安装包。下表列出每个组件的作用和常见坑组件版本建议主要用途常见坑QT5.15.2 MSVC2019_64界面框架混用MinGW会链接失败OpenCV4.5.5 MSVC版图像检测需要把bin目录加入PATHPython3.8及以上深度学习推理必须和QT是相同位数PyTorchCPU版1.13yolov5s推理无GPU时不要装CUDA版yolov56.0训练与推理脚本6.0后的版本接口有变化安装顺序建议先装QT和OpenCV并写一个显示图片的最小程序再建Python虚拟环境安装yolov5的requirements.txt最后才做QT和Python的联调。不要一开始就把两侧工程混在一起否则排错时无法判断是哪一边的问题。这里特别强调两点一是OpenCV的bin目录要放到系统PATH里否则QT程序运行时找不到opencv_world453.dll二是Python虚拟环境创建时一定要使用和QT编译环境相同的架构x64程序去加载32位Python终端会直接崩溃。提示所有组件务必保持64位统一。调试阶段用QT的qDebug输出关键路径、模型路径和当前线程id能省下大量排查时间。如果程序启动后一闪而过优先检查OpenCV环境变量和QT插件目录。3.2 标注数据并转成yolo格式图像检测不需要标注但yolov5s必须有带标注框的数据集。标注工具推荐labelImg导出为VOC xml。建议每类缺陷至少收集500个实例实在不够就做离线增强旋转、平移、翻转、亮度变化。数据量少的时候不要一上来就训练100轮先在验证集上评估过拟合程度。import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_file, out_file, class_names): tree ET.parse(xml_file) root tree.getroot() size root.find(size) w int(size.find(width).text) h int(size.find(height).text) lines [] for obj in root.findall(object): name obj.find(name).text cls_id class_names.index(name) box obj.find(bndbox) xmin int(box.find(xmin).text) ymin int(box.find(ymin).text) xmax int(box.find(xmax).text) ymax int(box.find(ymax).text) x_center (xmin xmax) / 2 / w y_center (ymin ymax) / 2 / h bw (xmax - xmin) / w bh (ymax - ymin) / h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {bw:.6f} {bh:.6f}) with open(out_file, w) as f: f.write(\n.join(lines))转换脚本里的class_names顺序要和后续训练时的data.yaml完全一致。比如顺序写为[scratch, dirt, crack]那么txt里的0代表scratch1代表dirt2代表crack。很多人的模型训练出来类别全错就是这里顺序没对齐。转换后的txt文件应该和图片同名且放在不同目录下。3.3 训练yolov5s生成可部署的best.pt用yolov5官方仓库训练建议把数据集组织成如下目录结构。目录名不要带中文images和labels必须分开放同一个图片的标注文件不要求在同一目录但yaml配置文件里的路径必须指向正确。dataset/ images/train/ images/val/ labels/train/ labels/val/然后修改data.yaml填写nc和names。这里的names顺序必须与转换脚本保持一致否则模型会学到错误的类别映射。完成配置后在yolov5目录下执行训练命令建议先跑5个epoch确认数据路径没有报错再跑完整训练。python train.py --data data.yaml --weights yolov5s.pt --img 640 --batch 16 --epochs 100 --device cpu训练命令里的--batch 16在8G内存的CPU机器上也能跑因为CPU版PyTorch不占显存但会吃内存。如果内存不够把batch降到4。训练结束后runs/train/exp/weights/best.pt就是后续QT要调用的权重。不要用last.pt它是最后一步的权重验证效果通常比best.pt差。4. 用QT实现图像检测部分OpenCV处理4.1 在QT里加载图像并做预处理QT显示图像的常规流程是cv::imread读入BGR图像转换成QImage再设置到QLabel上。这里有几个细节要注意如果路径里有中文QString转std::string时要用toLocal8Bit()否则imread会失败QImage和cv::Mat共享数据时要拷贝防止Mat析构后QImage还在引用旧内存。QImage DefectWindow::cvMatToQImage(const cv::Mat mat) { if (mat.type() CV_8UC3) { return QImage(mat.data, mat.cols, mat.rows, mat.step, QImage::Format_RGB888).rgbSwapped().copy(); } if (mat.type() CV_8UC1) { return QImage(mat.data, mat.cols, mat.rows, mat.step, QImage::Format_Grayscale8).copy(); } return QImage(); }注意Format_RGB888对应BGR图像时需要rgbSwapped()把B和R调换。copy()是为了让QImage持有自己的像素数据避免与Mat共享内存导致的悬空指针。预处理不要放在主线程。缺陷检测在单张图上虽然只有几十毫秒但在拖动滑块时连续调用还是会阻塞UI事件循环。最简单的方式是每次图像加载后把原图存为成员变量检测时用QtConcurrent::run在后台执行再通过信号槽回传结果。4.2 缺陷判定逻辑阈值、面积和位置约束图像检测的判定逻辑不能只做一次全局阈值。生产环境下的打光、产品位置都会有微小变化所以需要加位置约束。以电机转子绕线缺陷检测为例先用Hough圆检测找到中心再以中心为圆心截取环形ROI。只在环形区域内做梯度检测能大幅减少背景干扰。cv::Mat DefectWindow::imageDetect(const cv::Mat src) { cv::Mat gray, grad, binary; cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY); cv::Mat circles; cv::HoughCircles(gray, circles, cv::HOUGH_GRADIENT, 1, 50, 150, 60, 100, 200); if (circles.empty()) return src; float cx circles.atfloat(0, 0); float cy circles.atfloat(0, 1); float r circles.atfloat(0, 2); cv::Mat mask cv::Mat::zeros(gray.size(), CV_8UC1); cv::circle(mask, cv::Point(cx, cy), (int)(r * 0.9), cv::Scalar(255), -1); cv::bitwise_and(gray, mask, gray); cv::Laplacian(gray, grad, CV_16S, 3); cv::convertScaleAbs(grad, grad); cv::threshold(grad, binary, 40, 255, cv::THRESH_BINARY); // 形态学闭运算与连通域分析略 }HoughCircles参数中param260表示累加器阈值越低检测出的圆越多。如果灰尘被误检成圆提高param2。r*0.9是为了减小边缘干扰让检测范围退进圆内。Laplacian阈值40是一个经验值背景亮度高时可以提高到60但要防止漏检。连通域分析我会使用findContours加contourArea。过滤条件包括面积下限、外接矩形的长宽比、填充率。比如线缺陷长宽比大于3点状缺陷长宽比接近1这两类缺陷可以分开计数。参数作用推荐范围Laplacian阈值梯度响应截断30~60minArea忽略过小噪声50~200像素圆形ROI半径限定检测区域0.8~0.95 R4.3 结果显示和参数调节为了让答辩时能直观演示参数影响界面上建议放三个QSlider阈值、最小面积、圆形半径。拖动阈值滑块时只需要重新执行二值化和连通域分析不需要重新读取图片。为了减少卡顿可以在滑块释放时才触发检测而不是连续滑动。绘制结果时用cv::rectangle在原图上画红框再在左上角写上缺陷类型和面积。注意cv::putText不支持中文需要显示中文时把文字信息传到QT后用QPainter绘制在QLabel上。这也是很多人忽略的坑。这一部分完成之后图像检测就已经具备了独立运行的能力。你可以先用几张典型缺陷图测试阈值再决定是否继续集成深度学习部分。接下来要做的是把yolov5s的推理结果也汇入同一个界面让两个检测器共享同一套显示控件。5. 在QT里集成yolov5s目标检测5.1 用QProcess调用Python推理脚本把yolov5s放在QT里最稳妥的集成方式是QProcess外部调用Python。这样做的好处是C侧不需要依赖libtorch模型升级只需替换Python脚本坏处是每次启动都要初始化模型第一次调用会明显慢。如果只检测单张图片或离线条目完全够用。# detect_qt.py import sys, json import cv2 import torch model torch.hub.load(ultralytics/yolov5, custom, pathbest.pt, force_reloadTrue) model.conf 0.25 def apply(img_path): img cv2.imread(img_path) results model(img) dets results.xyxy[0].cpu().numpy() boxes [] for x1, y1, x2, y2, conf, cls in dets: boxes.append({ class: int(cls), conf: float(conf), box: [int(x1), int(y1), int(x2), int(y2)] }) print(json.dumps(boxes)) if __name__ __main__: apply(sys.argv[1])上面脚本中results.xyxy返回的是已经还原到原图坐标的检测框所以QT端不需要再处理letterbox。如果你使用ONNX或自己实现前向才需要手动记录填充参数。model.conf0.25可以在脚本里设置也可以在results.pandas()里过滤。print(json.dumps(boxes))要确保只输出JSON不要调用results.print()否则会混入进度条。QT端通过QProcess启动该脚本并接收标准输出QProcess *proc new QProcess(this); QStringList args; args detect_qt.py imagePath; proc-setWorkingDirectory(yoloDir); proc-start(python, args); connect(proc, QProcess::finished, this, [](int exitCode) { if (exitCode ! 0) return; QByteArray out proc-readAllStandardOutput(); QJsonDocument doc QJsonDocument::fromJson(out); // 解析boxes数组 });QProcess的workingDirectory要设置成yolov5项目目录否则找不到best.pt。可以通过setWorkingDirectory指定。finished信号里readAllStandardOutput必须在进程结束后调用不然可能读到空数据。5.2 解析yolo输出并映射回QT图像坐标QT端收到QByteArray后用QJsonDocument解析。下面这段代码演示了从进程输出到显示框的完整路径。void DefectWindow::onInferFinished(int exitCode) { if (exitCode ! 0) return; QByteArray data proc-readAllStandardOutput(); QJsonParseError err; QJsonDocument doc QJsonDocument::fromJson(data, err); if (err.error ! QJsonParseError::NoError) return; QJsonArray arr doc.array(); for (const auto item : arr) { auto obj item.toObject(); auto box obj[box].toArray(); int x1 box[0].toInt(); int y1 box[1].toInt(); int x2 box[2].toInt(); int y2 box[3].toInt(); double conf obj[conf].toDouble(); // 在原图上画框 } }如果图像在QLabel里按等比缩放显示画框时要把原图坐标转换到控件坐标。假设QLabel显示区域比原图小需要计算scale min(labelW/oriW, labelH/oriH)再通过偏移量offsetX和offsetY把框平移到居中位置。这个映射函数在显示层会频繁用到建议单独封装。5.3 两个检测部分的切换与串联在界面上做一个模式切换常见做法是QComboBox加三个选项图像检测、目标检测、串联检测。串联模式执行顺序先跑图像检测如果找到候选框再跑目标检测确认。如果目标检测置信度超过阈值最终输出类别和定位否则标记为疑似。模式耗时参考适用场景图像检测5~10ms规则明确、背景稳定目标检测30~60ms缺陷形态多样串联检测35ms左右需要二次确认串联合并时要注意两个检测的坐标系是否一致。图像检测是在ROI内分析的输出框要加上ROI的offset再传给yolov5。否则yolov5会认为框的坐标已经对应整张原图产生矛盾结果。我一般会在数据结构里让每个检测框都携带一个source字段记录它来自哪个检测器方便排错。6. 两种检测模式的效果比对与调优技巧6.1 用同一张缺陷图对比检测结果调试阶段建议给QT加一个“对比检测”按钮。点击后把当前图片分别送入图像检测和目标检测并在界面上分栏显示两张结果图。对比时重点看三类情况两者都检出、只有图像检测检出、只有目标检测检出。只有图像检测检出多半是缺陷的对比度很强但形状不规则yolov5训练样本里缺少这类样本只有目标检测检出说明缺陷没有稳定的灰度特征传统阈值无法分离。根据这个差异决定补数据还是调阈值。6.2 针对QT界面卡顿的优化手段图像检测的Laplacian和findContours在3000×3000大图上会消耗几十毫秒连续操作会有迟滞感。优化方法是先做图像金字塔缩小到800像素宽检测后在原图上放大回坐标。yolov5推理如果每次冷启动界面会僵住1~2秒。可以在应用启动时就预加载Python进程检测时通过标准输入发送图片路径。如果只是单张检测用QProcess加环境变量OMP_NUM_THREADS4限制线程数。import os os.environ[OMP_NUM_THREADS] 4在Python脚本顶部设置这个环境变量必须在import torch之前执行否则不会生效。设置后推理线程数被限制CPU占用率下降QT界面的鼠标响应会明显好转。6.3 一个可复用的验证技巧批量输出检测可视化准备一个validate.sh或Python脚本遍历验证集图片分别调用两套检测把原图、图像检测结果、yolov5结果并排保存到HTML文件。HTML里用相对路径引用图片文件名带上参数时间戳。这样做一次就能得到几十组对比答辩时直接翻网页比现场截图更有说服力。脚本核心逻辑是在循环里读取图片、调用两个接口、用PIL或OpenCV拼接成三列长图。把脚本放在项目tools目录下每次调参后重跑一遍生成带时间戳的报告。这样答辩时老师问“你这个阈值怎么确定的”你可以直接打开HTML报告说明不同阈值下的漏检和误检比例。本文还有配套的精品资源点击获取