YOLOv8工业落地四大断层:环境、数据、训练、推理全链路避坑指南
简介本资源是一套基于YOLOv8的图像识别完整实践项目面向计算机视觉初学者与Python深度学习开发者聚焦目标检测任务的端到端实现适用于安全监控、工业质检、智能相册等典型应用场景。压缩包共53个文件含10个核心Python脚本如train.py、imgPredict.py、video_detection.py、11张测试样图jpg、1个预训练模型yolov8n.pt、多语言README文档及环境配置文件覆盖数据准备、模型训练、图像/视频/摄像头多模态推理全流程。资源大小为18.4MB结构清晰模块分离明确——PhotoRecnition主目录下分设test_images、models、examples等子路径便于快速定位与复现。目前已有150人学习下载用户可直接运行代码完成本地部署获取开箱即用的检测能力并通过benchmark.py评估性能、exportModel.py导出适配格式同时参考中英文文档理解工程组织逻辑与关键参数调优思路。1. YOLOv8 图像识别不是调个库就完事为什么你跑通了 demo 却在真实产线里集体失效YOLOv8 图像识别这个标题背后藏着大量被低估的工程断层——它不是“装好 ultralytics 就能识别猫狗”的玩具级任务而是横跨数据质量、模型轻量化、部署适配、推理稳定性四大关卡的落地闭环。我见过太多团队在本地 Jupyter 里用model.predict()跑出 98% mAP 就宣布成功结果一上工控机CPU 占用飙到 100%、帧率跌到 2fps、漏检关键缺陷还查不出原因。根本矛盾在于YOLOv8 官方代码Python 实现默认面向科研验证而工业图像识别要的是可复现、可监控、可嵌入、可长期运行的确定性输出。它适合三类人正在做视觉质检/安防巡检/农业分拣等边缘部署的工程师手握私有小样本但被标注噪声折磨的算法同学以及想绕过 PyTorch 黑匣子、真正看懂“预测框怎么出来的”技术负责人。本文不讲论文复现只拆解从 pip install 到产线稳定跑满 7×24 小时的完整链路——包括你不会在官方文档里看到的 3 个核心参数陷阱、2 种必做的后处理加固、以及让模型在低光照/遮挡/反光场景下不翻车的实操补丁。2. 从零构建可复现的 YOLOv8 图像识别环境避开 pip install ultralytics 的 5 个隐性依赖雷区YOLOv8 的 Python 代码看似开箱即用但 ultralytics 包的版本迭代极快且对底层 CUDA、OpenCV、PyTorch 的耦合度远超表面所见。直接pip install ultralytics在多数生产环境中会触发静默失败模型能加载但 predict() 返回空列表或训练 loss 不下降却无报错。这不是代码问题是环境链断裂。我一般会强制锁定四层依赖并用 conda 管理基础环境——因为 conda 对 CUDA 工具链的兼容性控制比 pip 更可靠。2.1 创建隔离环境并安装确定性依赖栈# 创建 Python 3.9 环境YOLOv8 v8.0.200 推荐 3.93.11 在某些 Linux 发行版存在 torch.compile 兼容问题 conda create -n yolov8-prod python3.9 conda activate yolov8-prod # 强制安装指定 CUDA 版本对应的 PyTorch以 CUDA 11.8 为例这是当前 NVIDIA Jetson Orin 和多数 x86 工控机的主流版本 pip3 install torch2.0.1cu118 torchvision0.15.2cu118 --extra-index-url https://download.pytorch.org/whl/cu118 # 安装 OpenCV 4.8.0避坑4.9.0 在 ARM 平台存在内存泄漏4.7.0 在 Windows 上与 ultralytics 的 cv2.dnn 模块冲突 pip install opencv-python4.8.0.74 # 安装 ultralytics v8.0.200这是目前最稳定的 LTS 版本v8.1.x 开始引入 experimental 的 RT-DETR 混合模块增加不可控变量 pip install ultralytics8.0.200提示不要用ultralytics[export]或ultralytics[dev]这类 extras 安装方式。它们会拉取未测试的 ONNX Runtime、TensorRT 预编译包在国产 ARM 芯片如瑞芯微 RK3588上极易触发 ABI 不兼容导致ImportError: libnvrtc.so.11.2: cannot open shared object file类错误。所有导出功能我们后续手动集成。2.2 验证环境是否真正就绪三行代码测透底层链路# test_env.py from ultralytics import YOLO import torch import cv2 import numpy as np # 1. 测试 PyTorch CUDA 可用性必须返回 True否则所有 GPU 加速失效 print(CUDA available:, torch.cuda.is_available()) print(CUDA device count:, torch.cuda.device_count()) if torch.cuda.is_available(): print(Current device:, torch.cuda.get_device_name(0)) # 2. 测试 OpenCV 读图能力避坑某些 OpenCV 编译版本不支持 JPEG2000导致 imread 返回 None test_img np.zeros((480, 640, 3), dtypenp.uint8) cv2.imwrite(/tmp/test_cv.jpg, test_img) loaded cv2.imread(/tmp/test_cv.jpg) print(OpenCV imread success:, loaded is not None) # 3. 测试 ultralytics 基础加载注意这里不 load 权重只验证模块导入 model YOLO(yolov8n.pt, taskdetect) # 使用官方 nano 模型做最小验证 print(YOLOv8 model loaded successfully)运行此脚本必须同时满足CUDA availableTrue、imread successTrue、model loaded successfully。任一失败说明环境未真正就绪——此时不要进入训练或推理先回退检查 CUDA 驱动版本nvidia-smi输出的驱动版本需 ≥ 对应 PyTorch 的最低要求、或更换 OpenCV 源国内用户建议用清华源pip install -i https://pypi.tuna.tsinghua.edu.cn/simple/ opencv-python4.8.0.74。2.3 为什么不用 Docker——在边缘设备上容器化反而增加故障面很多教程推荐用 Docker 封装 YOLOv8但在实际产线中我主动放弃 Docker原因有三第一Jetson 设备的 L4T 系统对 nvidia-docker 的支持不稳定--gpus all参数在 L4T 35.3.1 后频繁出现 device plugin timeout第二Docker overlayfs 在 SD 卡或 eMMC 存储上写入放大严重某农业分拣项目曾因容器日志写满 eMMC 导致整机重启第三调试时无法直接strace或gdb进入进程当出现Segmentation fault (core dumped)时排查周期从 2 小时拉长到 1 天。我的替代方案是用conda-pack打包整个环境为 tar.gz再通过 rsync 同步到目标设备解压后执行source activate.sh即可激活——它比 Docker 更轻量、更透明、更易 debug。3. 数据准备不是“放张图就行”YOLOv8 标注格式的 4 个边界陷阱与自动清洗脚本YOLOv8 要求的标签格式是class_id center_x center_y width height归一化到 0~1但真实数据集几乎 100% 存在越界、重叠、缺失、坐标系错位四类问题。这些在训练时不会报错但会导致 mAP 波动剧烈、NMS 失效、甚至模型学会“忽略小目标”。我见过一个金属件缺陷检测项目标注工具导出的 txt 文件里37% 的 bounding box 的center_x 1原因是导出时用了原始像素坐标而非归一化——模型训练时把这些点当成了“画面外目标”学习到了错误的空间先验。3.1 四类高频标注污染及自动化清洗逻辑问题类型典型现象根本原因清洗策略x/y 越界center_x 1.0或 0.0标注工具未做归一化或图像 resize 后未同步更新标签强制 clampx max(0.0, min(1.0, x))宽高越界width 1.0或height 1.0标注框跨图像边界如贴边目标被拉伸重置为min(width, 1.0 - center_x width/2)等几何约束零面积框width 0或height 0标注员单点点击误操作直接丢弃该行记录 warning坐标系错位同一图像多个 txt 中 class_id 不一致多人协作时 labelmap.txt 版本不同步统一校验class_id num_classes超限则映射到 03.2 执行清洗一个可直接运行的 validate_labels.py 脚本# validate_labels.py import os import glob import numpy as np from pathlib import Path def clean_label_file(txt_path: str, img_width: int, img_height: int): 清洗单个 label txt 文件返回清洗后行数和警告列表 warnings [] cleaned_lines [] with open(txt_path, r) as f: lines f.readlines() for i, line in enumerate(lines): parts line.strip().split() if len(parts) 5: warnings.append(fLine {i}: less than 5 values, skip) continue try: cls_id int(parts[0]) cx, cy, w, h map(float, parts[1:5]) except ValueError: warnings.append(fLine {i}: invalid float format, skip) continue # 检查 class_id 合法性假设你有 3 类0,1,2 if cls_id 0 or cls_id 2: warnings.append(fLine {i}: class_id {cls_id} out of range [0,2], set to 0) cls_id 0 # clamp center coordinates cx max(0.0, min(1.0, cx)) cy max(0.0, min(1.0, cy)) # clamp width/height with geometric constraint max_w min(1.0, 2 * min(cx, 1.0 - cx) w) # 保证框不跨左/右边界 max_h min(1.0, 2 * min(cy, 1.0 - cy) h) # 保证框不跨上/下边界 w max(0.001, min(w, max_w)) # 强制最小宽度 0.001约 1px at 1024px h max(0.001, min(h, max_h)) # 重新计算 cx,cy 以保证中心在框内避免 clamp 后中心偏移 cx max(w/2, min(1.0 - w/2, cx)) cy max(h/2, min(1.0 - h/2, cy)) cleaned_lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}\n) # 写回文件 with open(txt_path, w) as f: f.writelines(cleaned_lines) return len(cleaned_lines), warnings def main(): label_dir datasets/my_dataset/labels/train # 替换为你的 labels 路径 img_dir datasets/my_dataset/images/train # 对应 images 路径 txt_files glob.glob(os.path.join(label_dir, *.txt)) total_cleaned 0 all_warnings [] for txt_path in txt_files: # 从 txt 文件名推导对应图片尺寸需确保同名 jpg/png 存在 stem Path(txt_path).stem img_path None for ext in [.jpg, .jpeg, .png, .bmp]: candidate os.path.join(img_dir, stem ext) if os.path.exists(candidate): img_path candidate break if not img_path: print(fWarning: no image found for {txt_path}) continue # 读取图片获取尺寸不加载像素仅 header import cv2 try: img cv2.imread(img_path, cv2.IMREAD_UNCHANGED) h, w img.shape[:2] except Exception as e: print(fFailed to read {img_path}: {e}) continue cleaned_cnt, warns clean_label_file(txt_path, w, h) total_cleaned cleaned_cnt all_warnings.extend([f{txt_path}: {w} for w in warns]) print(fTotal cleaned labels: {total_cleaned}) if all_warnings: print(fWarnings ({len(all_warnings)}):) for w in all_warnings[:10]: # 只打印前 10 条 print(f {w}) if len(all_warnings) 10: print(f ... and {len(all_warnings)-10} more) if __name__ __main__: main()参数说明脚本中的num_classes3需按你实际类别数修改min(w,h)0.001是经验值对应 1024px 图像上的 1px 宽度低于此值的目标在 YOLOv8 中基本无法学习max_w/max_h的几何约束公式来自 YOLO 的 anchor-free 设计原理——中心点必须落在(w/2, h/2)到(1-w/2, 1-h/2)区域内否则框必然超出图像。3.3 验证清洗效果用可视化脚本确认“肉眼不可见”的修复清洗后必须可视化验证否则可能引入新 bug。以下脚本将清洗前后的 label 叠加到原图上对比# visualize_cleaning.py import cv2 import numpy as np from pathlib import Path def draw_boxes(img, labels, color(0,255,0), thickness2): h, w img.shape[:2] for line in labels: parts line.strip().split() if len(parts) 5: continue cls_id, cx, cy, w_norm, h_norm int(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) # 归一化转像素 x1 int((cx - w_norm/2) * w) y1 int((cy - h_norm/2) * h) x2 int((cx w_norm/2) * w) y2 int((cy h_norm/2) * h) cv2.rectangle(img, (x1,y1), (x2,y2), color, thickness) cv2.putText(img, str(cls_id), (x1,y1-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1) # 加载原图和原始/清洗后 label img_path datasets/my_dataset/images/train/001.jpg orig_txt datasets/my_dataset/labels/train/001.txt clean_txt datasets/my_dataset/labels/train/001.txt # 清洗后覆盖原文件 img cv2.imread(img_path) img_orig img.copy() img_clean img.copy() with open(orig_txt, r) as f: orig_labels f.readlines() with open(clean_txt, r) as f: clean_labels f.readlines() draw_boxes(img_orig, orig_labels, color(0,0,255)) # 红色原始 draw_boxes(img_clean, clean_labels, color(0,255,0)) # 绿色清洗后 # 拼接对比图 vis np.hstack([img_orig, img_clean]) cv2.imwrite(/tmp/clean_compare.jpg, vis) print(Comparison saved to /tmp/clean_compare.jpg)运行后打开/tmp/clean_compare.jpg左侧红框是原始标注常有飘出画布的框右侧绿框是清洗后结果——你会直观看到那些“玄学消失”的漏检框其实是被修正回图像内的合理位置。这一步不能跳过它是后续训练稳定性的基石。4. 训练不是“改个 config 就跑”YOLOv8 的 3 个必调参数与早停策略设计YOLOv8 的model.train()接口封装了太多默认行为新手常陷入“训了 100 epoch 但 val mAP 卡在 0.6 不动”的困境。问题往往不在模型结构而在三个被忽略的参数box,cls,dfl的损失权重以及patience早停阈值。官方默认box7.5, cls0.5, dfl1.5是为 COCO 大数据集设计的而你的工业小样本5k 图像需要完全不同的平衡。4.1 为什么 box 权重要从 7.5 降到 3.0——小样本下定位比分类更难在缺陷检测场景中同类缺陷如划痕、凹坑外观差异极大但位置信息在哪一排焊点、距边缘多远具有强工艺约束。若box权重过高模型会过度优化 bbox 回归导致分类头欠拟合——表现为 val_loss 下降但 cls_acc 不升反降。我做过 A/B 测试在 2k 张 PCB 焊点图像上box3.0比box7.5的最终 mAP 提升 11.2%且收敛速度加快 40%。# train_custom.py from ultralytics import YOLO # 加载预训练模型不加载分类头避免小样本过拟合 model YOLO(yolov8n.pt) # 关键自定义损失权重针对小样本优化 model.train( datadatasets/my_dataset/data.yaml, epochs100, imgsz640, batch16, nameyolov8n_defect_small, # —— 以下三行是核心调整 —— box3.0, # 定位损失权重从 7.5 降至 3.0 cls1.2, # 分类损失权重从 0.5 提至 1.2小样本更需区分细粒度缺陷 dfl0.8, # DFL 损失权重从 1.5 降至 0.8DFL 对小目标增益有限且易震荡 # ———————————————————————— patience15, # 早停耐心值val mAP 连续 15 epoch 不提升则停止 save_period10, # 每 10 epoch 保存一次便于回溯最佳 checkpoint projectruns/train )参数逻辑说明box3.0并非拍脑袋而是基于损失曲线分析——在 epoch 20 后loss_box通常已降至loss_cls的 1/3 以下此时继续加权 box 会拖慢 cls 收敛cls1.2是为了对抗小样本的类别不平衡如“正常”样本占 80%缺陷仅 20%提高分类头梯度强度dfl0.8是因为 DFLDistribution Focal Loss在大目标上效果显著但小目标32px的分布建模收益低且其计算开销大在边缘设备上易引发显存碎片。4.2 早停不是“设个 patience 就完事”动态阈值与双指标监控YOLOv8 默认只监控metrics/mAP50-95(B)但工业场景更关注mAP50严格 IoU0.5和Recall漏检率。若只按 mAP50-95 早停模型可能在高 IoU0.7上表现差——这在质检中意味着把合格品判为缺陷误杀比漏检更致命。因此我改用双指标早停# 自定义早停回调需 patch ultralytics/engine/trainer.py或在训练后脚本中实现 # 此处给出 post-train 验证逻辑用于选择最佳 epoch import json import pandas as pd def select_best_epoch(run_dir: str): 从 runs/train/yolov8n_defect_small/results.csv 中选出最优 epoch csv_path os.path.join(run_dir, results.csv) df pd.read_csv(csv_path) # 定义综合得分mAP50 权重 0.6Recall 权重 0.4因漏检比误杀代价更高 df[score] df[metrics/mAP50(B)] * 0.6 df[metrics/recall(B)] * 0.4 best_idx df[score].idxmax() best_epoch int(df.iloc[best_idx][epoch]) print(fBest epoch: {best_epoch}, mAP50{df.iloc[best_idx][metrics/mAP50(B)]:.4f}, fRecall{df.iloc[best_idx][metrics/recall(B)]:.4f}, score{df.iloc[best_idx][score]:.4f}) # 复制最佳权重 best_weights os.path.join(run_dir, fweights/best.pt) final_weights os.path.join(run_dir, weights/final_best.pt) import shutil shutil.copy(best_weights, final_weights) return best_epoch # 调用 select_best_epoch(runs/train/yolov8n_defect_small)4.3 避坑常见问题与血泪排查经验现象 1训练 loss 下降但 val mAP 为 0原因验证集路径错误或标签格式未清洗导致val_loader读取空标签mAP 计算时分母为 0返回 NaN 后被 ultralytics 静默置为 0。解决在data.yaml中确认val路径正确运行validate_labels.py清洗验证集添加--verbose参数启动训练观察val阶段是否打印Found X labels。现象 2GPU 显存占用持续增长几小时后 OOM原因YOLOv8 默认启用torch.compilev8.0.190在某些 CUDA 版本下存在内存泄漏。解决训练命令中添加--compile False或在代码中设置model.model torch.compile(model.model, backendinductor)前加内存监控。现象 3同一配置两次训练 mAP 相差 5%原因未固定随机种子且batch16时数据增强mosaic的随机性放大了波动。解决在训练前插入import random import numpy as np import torch seed 42 random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed(seed) torch.backends.cudnn.deterministic True现象 4训练后期 loss 震荡剧烈mAP 不收敛原因学习率衰减策略不匹配小样本。YOLOv8 默认lr00.01 cosine annealing但小样本易过拟合需更激进衰减。解决改用cosinelrf0.01最终学习率设为初始的 1%或手动在train.py中修改scheduler.last_epoch 0强制重置。现象 5model.val()返回的 mAP 比训练日志高 3%原因训练日志的 mAP 是每个 epoch 结束时在验证集子集默认 50%上计算的而model.val()是全量验证。解决训练时加val_fraction1.0参数或接受该差异以model.val()结果为准。5. 推理不是“predict() 一行代码”工业级图像识别的 4 层后处理加固model.predict()返回的Results对象只是 raw output直接用于产线等于裸奔。工业场景要求抗干扰光照变化、反光、防抖动同一目标连续帧 bbox 跳变、可解释为什么判为缺陷、可审计每帧输出带时间戳和置信度分布。我一般在 predict() 后叠加四层后处理形成Raw → Filtered → Stabilized → Annotated的流水线。5.1 第一层置信度过滤与 NMS 重调——不是用默认 0.25 IoUYOLOv8 默认iou0.7但工业场景中相邻缺陷如 PCB 上的连锡、桥接间距常小于 20px0.7 会导致它们被合并为一个框。而conf0.25又太宽松小目标噪声多。我的实践是对小目标面积1024px²用iou0.3conf0.45对大目标1024px²用iou0.6conf0.35。# inference_pipeline.py from ultralytics import YOLO import numpy as np model YOLO(runs/train/yolov8n_defect_small/weights/final_best.pt) def smart_nms(results, img_area: int): 根据图像中目标面积动态调整 NMS 参数 boxes results.boxes.xyxy.cpu().numpy() # [N,4] scores results.boxes.conf.cpu().numpy() # [N,] classes results.boxes.cls.cpu().numpy() # [N,] if len(boxes) 0: return results # 计算每个框面积 areas (boxes[:,2] - boxes[:,0]) * (boxes[:,3] - boxes[:,1]) median_area np.median(areas) # 动态 IoU 阈值小目标更严格 iou_thres 0.3 if median_area 1024 else 0.6 # 动态 conf 阈值小目标要求更高置信度 conf_thres 0.45 if median_area 1024 else 0.35 # 手动调用 torchvision.ops.nms比 ultralytics 内置更可控 from torchvision.ops import nms boxes_tensor torch.from_numpy(boxes) scores_tensor torch.from_numpy(scores) keep nms(boxes_tensor, scores_tensor, iou_thres) # 过滤 filtered_boxes boxes[keep] filtered_scores scores[keep] filtered_classes classes[keep] # 更新 Results 对象需 patch此处简化为返回新对象 return { boxes: filtered_boxes, scores: filtered_scores, classes: filtered_classes, orig_shape: results.orig_shape } # 使用 results model.predict(test.jpg, verboseFalse) post_results smart_nms(results, img_area640*480)5.2 第二层时序稳定性加固——用卡尔曼滤波平滑 bbox 坐标单帧检测的 bbox 坐标抖动是产线最大痛点。例如传送带上同一金属件连续 5 帧的x1值为[120,123,118,125,121]这种抖动会让下游 PLC 误判位置。我用 1D 卡尔曼滤波分别平滑x1,y1,x2,y2四个维度状态向量为[position, velocity]观测仅为 position。# kalman_filter.py import numpy as np class KalmanBoxFilter: def __init__(self, init_pos, dt1.0): self.dt dt # 状态向量 [x, vx] self.x np.array([init_pos, 0.0]) # 状态协方差 self.P np.eye(2) * 100.0 # 过程噪声 self.Q np.eye(2) * 0.1 # 观测噪声假设观测误差 std2px self.R 4.0 def predict(self): # 状态转移x x vx*dt, vx vx F np.array([[1, self.dt], [0, 1]]) self.x F self.x self.P F self.P F.T self.Q def update(self, z): # z 是观测值当前帧 bbox 坐标 H np.array([1, 0]).reshape(1, -1) # 观测矩阵 y z - H self.x # 创新 S H self.P H.T self.R K self.P H.T / S # 卡尔曼增益 self.x self.x K * y self.P (np.eye(2) - K H) self.P # 在推理循环中使用 kalman_filters [KalmanBoxFilter(0) for _ in range(4)] # x1,y1,x2,y2 for frame in video_stream: results model.predict(frame, verboseFalse) if len(results.boxes) 0: raw_box results.boxes.xyxy[0].cpu().numpy() # 取第一个目标 smoothed [] for i, coord in enumerate(raw_box): kalman_filters[i].predict() kalman_filters[i].update(coord) smoothed.append(kalman_filters[i].x[0]) smoothed_box np.array(smoothed)5.3 第三层光照鲁棒性补丁——直方图匹配预处理YOLOv8 训练时用HSV颜色空间增强但真实产线中相机白平衡漂移、LED 光源频闪会导致图像整体偏色。单纯靠数据增强无法覆盖所有偏色组合。我的补丁是在推理前将当前帧与一张“标准光照”参考图做直方图匹配Histogram Matching强制颜色分布对齐。# hist_match.py import cv2 import numpy as np def match_histograms(source: np.ndarray, reference: np.ndarray) - np.ndarray: 将 source 图像的直方图匹配到 reference # 转 HSV只匹配 V 通道亮度 src_hsv cv2.cvtColor(source, cv2.COLOR_BGR2HSV) ref_hsv cv2.cvtColor(reference, cv2.COLOR_BGR2HSV) # 计算 V 通道的 CDF src_v src_hsv[:,:,2] ref_v ref_hsv[:,:,2] src_cdf, src_bins np.histogram(src_v.flatten(), 256, [0,256], densityTrue) ref_cdf, ref_bins np.histogram(ref_v.flatten(), 256, [0,256], densityTrue) # 累积 CDF src_cdf src_cdf.cumsum() ref_cdf ref_cdf.cumsum() # 查找映射表 lookup_table np.zeros(256) for i in range(256): # 找到 ref_cdf 中最接近 src_cdf[i] 的索引 idx np.argmin(np.abs(ref_cdf - src_cdf[i])) lookup_table[i] idx # 应用映射 matched_v cv2.LUT(src_v, lookup_table.astype(np.uint8)) src_hsv[:,:,2] matched_v return cv2.cvtColor(src_hsv, cv2.COLOR_HSV2BGR) # 使用加载一张在标准光照下拍摄的“白板图”作为 reference ref_img cv2.imread(calibration/whiteboard_ref.jpg) for frame in camera_stream: fixed_frame match_histograms(frame, ref_img) results model.predict(fixed_frame, verboseFalse)5.4 第四层可审计输出生成——不只是画框还要带决策依据产线系统需要知道“为什么判为缺陷”而不仅是“是什么”。我在后处理中加入置信度热力图生成和 top-3 类别概率输出# audit_output.py def generate_audit_report(results, frame, class_names[normal, scratch, dent]): 生成含热力图和概率分布的审计报告 from PIL import Image, ImageDraw, ImageFont import matplotlib.pyplot as plt # 1. 置信度热力图对每个检测框用高斯核生成 confidence heatmap heatmap np.zeros(frame.shape[:2], dtypenp.float32) for i, box in enumerate(results.boxes.xyxy): x1, y1, x2, y2 map(int, box) conf results.boxes.conf[i].item() # 在框内生成高斯热力 y_grid, x_grid np.ogrid[y1:y2, x1:x2] center_y, center_x (y1y2)//2, (x1x2)//2 sigma max(5, (y2-y1)//6) # 自适应 sigma gaus np.exp(-((x_grid-center_x)**2 (y_grid-center_y)**2) / (2*sigma**2)) heatmap[y1:y2, x1:x2] np.maximum(heatmap[y1:y2, x1:x2], gaus * conf) # 2. 叠加热力图到原图 heatmap_colored cv2.applyColorMap((heatmap*255).astype(np.uint8), cv2 p a hrefhttps://download.csdn.net/download/m0_74712453/90330912 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p