
简介这是一套面向人工智能初学者与计算机视觉开发者的YOLOv8实战演示系统聚焦目标检测与图像分割任务通过Gradio构建零前端基础的交互式界面让非专业用户也能快速体验前沿模型效果。资源包共32个文件含6个核心Python脚本含Docker部署与FastAPI服务扩展、11个YAML配置文件覆盖多语言类别名、自定义模型结构及训练参数、6张实测样例图片如zidane.jpg、bus.jpg等典型场景以及Dockerfile、requirements.txt、LICENSE等工程化配套文件整体仅2.59MB轻量易部署。已有1362人学习下载适合教学演示、课程实验或快速原型验证。读者可直接运行gradio_yolov8_det.py启动本地Web界面上传任意图像即可实时获取边界框标注与分割掩码同时获得完整可复现的工程目录结构、多语言类别支持方案及容器化部署能力显著降低YOLOv8落地门槛。1. 为什么一个 Gradio 界面能撑起 YOLOv8 的完整推理闭环从模型加载、图像/视频输入、检测分割双路输出到结果可视化与导出全链路可复现、可调试、可交付这不是一个“用 Gradio 包个 predict 函数”的玩具 demo。它是一套面向工程落地的轻量级视觉 AI 交互系统支持单图、批量图、本地视频、摄像头流输入自动适配 YOLOv8 的detect和segment两种任务模式输出带置信度标签的检测框 像素级掩码mask叠加图同时生成结构化 JSON 结果含类别、坐标、面积、置信度和可直接用于标注回传的 COCO-style.json或 VOC-style.xml文件。它不依赖 Flask/Gunicorn/Nginx 复杂部署也不需要前端写 Vue/React——Gradio 自带响应式 UI、文件上传控件、进度条、错误弹窗和跨域处理真正实现「写完 model.predict() 就能发给同事试用」。适合算法工程师快速验证模型效果、产品团队做原型评审、客户现场演示也适合作为训练后 pipeline 的标准推理入口。尤其对刚跑通yolov8n.pt的新手它把「模型能不能用」和「别人怎么用」彻底分开——你只管调好权重界面逻辑、输入校验、后处理、格式转换全由这套结构兜住。2. 搭建最小可行系统从 ultralytics 安装到 Gradio 界面启动三步跑通 detect segment 双任务2.1 环境隔离与核心依赖安装避开 ultralytics 与 torch 版本冲突的玄学坑YOLOv8 官方推荐使用ultralytics库非yolov8pip 包且必须严格匹配 PyTorch 版本。常见翻车点是pip install ultralytics自动拉取最新版而该版本可能要求torch2.0.0但你的 CUDA 环境只支持torch1.13.1cu117。血泪经验永远显式指定 torch torchvision ultralytics 三者兼容组合。以主流 CUDA 11.7 环境为例# 创建干净虚拟环境推荐 conda避免 pip 混乱 conda create -n yolov8-gradio python3.9 conda activate yolov8-gradio # 先装指定 torch官方 wheel 地址见 pytorch.org选 cu117 pip install torch1.13.1cu117 torchvision0.14.1cu117 --extra-index-url https://download.pytorch.org/whl/cu117 # 再装 ultralytics固定 v8.0.200此版本对 torch 1.13 支持最稳v8.1.x 开始强制要求 torch2.0 pip install ultralytics8.0.200 # Gradio 必须 ≥ 4.25.0支持 streaming video input 和 mask overlay 动态渲染 pip install gradio4.25.0提示ultralytics8.0.200是当前2024 Q2最稳定的生产级版本。不要贪新——v8.1.x 引入了Predictor类重构导致大量旧 demo 报AttributeError: Predictor object has no attribute modelv8.2.x 则默认启用amp混合精度在 GTX 1660 Ti 等无 Tensor Core 的卡上直接 OOM。版本锁死是第一道防线。2.2 构建双任务模型加载器统一接口封装 detect/segment支持 .pt/.onnx/.engine 多格式Gradio 要求所有操作函数fn必须是纯 Python 函数不能在每次调用时重复YOLO(model_path)初始化。我们封装一个ModelManager类实现模型单例缓存、任务自动识别、设备自动分配# model_loader.py from ultralytics import YOLO import torch class ModelManager: _instance None _model None _task None # detect or segment def __new__(cls): if cls._instance is None: cls._instance super().__new__(cls) return cls._instance def load_model(self, model_path: str): 加载模型并自动推断任务类型 if self._model is not None and self._model.model_path model_path: return self._model, self._task # 加载模型支持 .pt / .onnx / .engine self._model YOLO(model_path) # 推断任务类型检查模型配置中是否有 seg 相关层 try: if hasattr(self._model.model, m) and len(self._model.model.m) 0: # YOLOv8 seg 模型最后一层是 Segment 分支 if seg in str(type(self._model.model.m[-1])).lower(): self._task segment else: self._task detect else: # fallback检查模型名称或配置文件 if seg in model_path.lower() or segment in model_path.lower(): self._task segment else: self._task detect except Exception: self._task detect # 默认安全降级 # 设备选择优先 GPU无则 CPU device cuda if torch.cuda.is_available() else cpu self._model.to(device) return self._model, self._task # 全局实例 model_manager ModelManager()这个封装解决了三个关键问题避免重复加载同一模型路径第二次调用直接返回缓存实例防止 Gradio 多次请求触发多次YOLO()初始化耗时且显存泄漏任务自动识别不依赖用户手动选择 “detect/segment”而是通过模型结构或文件名智能判断降低 UI 复杂度设备自适应自动检测 CUDA 可用性GTX 1660 Ti 用户无需改代码RTX 4090 用户也无需手动设devicecuda:0。2.3 编写 Gradio 核心推理函数支持图像/视频/摄像头输入统一输出格式Gradio 的gr.Interface需要一个fn函数输入是 Gradio 组件如gr.Image,gr.Video输出是 Gradio 组件如gr.Image,gr.JSON。我们定义run_inference函数它接收原始输入调用model.predict()再组织多路输出# inference.py import cv2 import numpy as np from PIL import Image import json from model_loader import model_manager def run_inference( input_type: str, # image, video, webcam image_input: np.ndarray None, video_input: str None, webcam_enabled: bool False, model_path: str yolov8n-seg.pt, conf_threshold: float 0.25, iou_threshold: float 0.7 ): Gradio 主推理函数 :param input_type: 输入类型标识 :param image_input: PIL.Image 或 np.ndarray (HWC, BGR) :param video_input: 视频文件路径str :param webcam_enabled: 是否启用摄像头bool :param model_path: 模型路径 :param conf_threshold: 置信度阈值 :param iou_threshold: NMS IoU 阈值 :return: (output_image, output_json, output_coco_json, output_xml) # 1. 加载模型 model, task model_manager.load_model(model_path) # 2. 构造输入源 if input_type image and image_input is not None: # Gradio 图像输入是 RGB numpy array需转 BGR 供 OpenCV/YOLO 使用 img_bgr cv2.cvtColor(image_input, cv2.COLOR_RGB2BGR) source [img_bgr] elif input_type video and video_input: source video_input elif input_type webcam and webcam_enabled: source 0 # 默认摄像头 else: raise ValueError(Invalid input combination) # 3. 执行预测关键设置 saveFalse, verboseFalse 避免日志污染 Gradio 输出 results model.predict( sourcesource, confconf_threshold, iouiou_threshold, saveFalse, verboseFalse, streamFalse, # 关键streamTrue 会返回 generatorGradio 不支持 devicemodel.device # 显式传入避免 predict 内部重选设备 ) # 4. 处理单帧结果视频/摄像头取首帧实际项目中可扩展为逐帧处理 if isinstance(results, list): result results[0] # 取第一帧 else: result results # 5. 构建可视化图像带 bbox mask label vis_img result.plot() # ultralytics 内置 plot已含 mask 叠加seg 模型或 bboxdet 模型 # 6. 构建结构化 JSON 输出 boxes [] masks [] for box, cls_id, conf in zip(result.boxes.xyxy.cpu().numpy(), result.boxes.cls.cpu().numpy(), result.boxes.conf.cpu().numpy()): x1, y1, x2, y2 map(int, box) label model.names[int(cls_id)] boxes.append({ label: label, confidence: float(conf), bbox: [x1, y1, x2, y2], area: int((x2-x1)*(y2-y1)) }) # 如果是 segmentation 任务提取 mask 并编码为 base64供前端显示或下载 if task segment and hasattr(result, masks) and result.masks is not None: for mask_tensor in result.masks.data: mask_np mask_tensor.cpu().numpy().astype(np.uint8) * 255 masks.append(mask_np.tolist()) # 简单转 list生产环境建议 base64 encode json_output { task: task, input_type: input_type, num_detections: len(boxes), detections: boxes, masks: masks } # 7. 生成 COCO-style JSON用于后续标注回传 coco_json generate_coco_json(result, model.names) # 8. 生成 VOC XML兼容老系统 voc_xml generate_voc_xml(result, model.names) return ( vis_img, # Gradio Image 组件 json.dumps(json_output, indent2, ensure_asciiFalse), # Gradio JSON 组件 json.dumps(coco_json, indent2, ensure_asciiFalse), # Gradio Text 组件可下载 voc_xml # Gradio Text 组件可下载 ) # 辅助函数生成 COCO JSON 格式简化版仅含 annotations def generate_coco_json(result, class_names): annotations [] for i, (box, cls_id, conf) in enumerate(zip(result.boxes.xyxy.cpu().numpy(), result.boxes.cls.cpu().numpy(), result.boxes.conf.cpu().numpy())): x1, y1, x2, y2 box w, h x2 - x1, y2 - y1 ann { id: i1, image_id: 1, category_id: int(cls_id) 1, # COCO 从 1 开始编号 bbox: [float(x1), float(y1), float(w), float(h)], area: float(w * h), score: float(conf), segmentation: [] } # 若有 mask添加 RLE 编码此处省略实际需 pycocotools if hasattr(result, masks) and result.masks is not None: mask result.masks.data[i].cpu().numpy() # 实际应转 RLE此处 placeholder ann[segmentation] mask.tolist() annotations.append(ann) return {annotations: annotations, categories: [{id: i1, name: n} for i, n in enumerate(class_names)]} # 辅助函数生成 VOC XML简化版 def generate_voc_xml(result, class_names): from xml.etree.ElementTree import Element, SubElement, tostring import xml.dom.minidom root Element(annotation) folder SubElement(root, folder) folder.text unknown filename SubElement(root, filename) filename.text input.jpg for box, cls_id, conf in zip(result.boxes.xyxy.cpu().numpy(), result.boxes.cls.cpu().numpy(), result.boxes.conf.cpu().numpy()): x1, y1, x2, y2 map(int, box) obj SubElement(root, object) name SubElement(obj, name) name.text class_names[int(cls_id)] bndbox SubElement(obj, bndbox) xmin SubElement(bndbox, xmin) xmin.text str(x1) ymin SubElement(bndbox, ymin) ymin.text str(y1) xmax SubElement(bndbox, xmax) xmax.text str(x2) ymax SubElement(bndbox, ymax) ymax.text str(y2) rough_string tostring(root, utf-8) reparsed xml.dom.minidom.parseString(rough_string) return reparsed.toprettyxml(indent )这段代码的核心设计逻辑输入归一化无论图像/视频/摄像头最终都转成model.predict(source...)可接受的格式输出解耦vis_img供 Gradio 渲染json_output供用户读取结构化数据coco_json/voc_xml供下游系统消费性能控制streamFalse是 Gradio 必须项否则 predict 返回 generator 导致界面卡死verboseFalse防止日志刷屏mask 处理result.plot()已内置 mask 可视化YOLOv8 seg 模型无需额外 opencv 合成这是 ultralytics 的隐藏优势。3. 构建生产级 Gradio UI支持模型热切换、参数实时调节、多输入源切换与结果导出3.1 定义 Gradio Blocks 界面比 Interface 更灵活的布局与状态管理gr.Interface简单但僵硬无法实现「模型加载中」状态、按钮禁用、多 tab 切换。我们用gr.Blocks构建模块化 UI关键组件包括顶部模型选择区下拉菜单 模型路径输入框支持自定义路径中间输入控制区Radio 选择输入类型图像/视频/摄像头对应显示不同组件右侧参数面板滑块调节conf_threshold/iou_threshold实时反馈底部结果展示区四栏布局 —— 原图、结果图、JSON 结构、COCO/VOC 下载按钮。# app.py import gradio as gr from inference import run_inference with gr.Blocks(titleYOLOv8 Detection Segmentation Demo) as demo: gr.Markdown(## YOLOv8 通用目标检测与图像分割演示系统) # 模型加载区域 with gr.Row(): model_dropdown gr.Dropdown( choices[ yolov8n.pt, yolov8s.pt, yolov8m.pt, yolov8n-seg.pt, yolov8s-seg.pt ], valueyolov8n-seg.pt, label预置模型, info选择检测或分割模型带 -seg 后缀为分割模型 ) model_path_input gr.Textbox( valueyolov8n-seg.pt, label模型路径, info支持本地 .pt / .onnx / .engine 文件 ) # 输入类型选择 input_type gr.Radio( choices[image, video, webcam], valueimage, label输入源类型 ) # 条件化输入组件根据 input_type 动态显示 with gr.Group(visibleTrue) as image_group: image_input gr.Image(typenumpy, label上传图像) with gr.Group(visibleFalse) as video_group: video_input gr.Video(label上传视频) with gr.Group(visibleFalse) as webcam_group: webcam_checkbox gr.Checkbox(label启用摄像头, valueFalse) # 参数调节区 with gr.Accordion(⚙️ 推理参数, openFalse): conf_slider gr.Slider(0.01, 0.99, value0.25, label置信度阈值 (conf)) iou_slider gr.Slider(0.1, 0.99, value0.7, labelNMS IoU 阈值) # 结果输出区 with gr.Tab(可视化结果): output_image gr.Image(label检测/分割结果, interactiveFalse) with gr.Tab(结构化数据): output_json gr.JSON(labelJSON 结果含 bbox/mask) with gr.Tab(导出格式): with gr.Row(): coco_download gr.File(label下载 COCO JSON, file_countsingle) voc_download gr.File(label下载 VOC XML, file_countsingle) # 事件绑定input_type 改变时切换组件可见性 def update_input_visibility(choice): return ( gr.update(visible(choice image)), gr.update(visible(choice video)), gr.update(visible(choice webcam)) ) input_type.change( fnupdate_input_visibility, inputsinput_type, outputs[image_group, video_group, webcam_group] ) # 主提交按钮 submit_btn gr.Button( 开始推理, variantprimary) # 提交逻辑整合所有输入 def process_all( model_choice, model_custom_path, input_type_val, image_data, video_data, webcam_flag, conf_val, iou_val ): # 优先使用下拉选择若为空则用自定义路径 model_path model_custom_path if model_custom_path.strip() else model_choice # 构建输入参数 kwargs { input_type: input_type_val, model_path: model_path, conf_threshold: conf_val, iou_threshold: iou_val } if input_type_val image: kwargs[image_input] image_data elif input_type_val video: kwargs[video_input] video_data elif input_type_val webcam: kwargs[webcam_enabled] webcam_flag # 调用推理函数 try: vis_img, json_str, coco_str, voc_str run_inference(**kwargs) # 生成可下载文件Gradio File 组件需 bytes import io coco_bytes io.BytesIO(coco_str.encode(utf-8)) voc_bytes io.BytesIO(voc_str.encode(utf-8)) return ( vis_img, json.loads(json_str), # JSON 组件需 dict非 str gr.File(valuecoco_bytes, labelCOCO JSON), gr.File(valuevoc_bytes, labelVOC XML) ) except Exception as e: gr.Error(f推理失败{str(e)}) return (None, {}, None, None) # 绑定提交事件 submit_btn.click( fnprocess_all, inputs[ model_dropdown, model_path_input, input_type, image_input, video_input, webcam_checkbox, conf_slider, iou_slider ], outputs[output_image, output_json, coco_download, voc_download] ) # 启动服务 if __name__ __main__: demo.launch( server_name0.0.0.0, # 允许局域网访问 server_port7860, shareFalse, # 关闭公网分享安全考虑 debugTrue )这个 Blocks 界面的关键优势动态组件input_type切换时gr.Group的visible属性实时更新避免无效组件干扰参数联动conf_slider/iou_slider直接传入run_inference无需额外 state 管理错误防御try/except包裹整个推理流程gr.Error显示具体异常比黑屏友好得多安全默认shareFalse禁用 Gradio 自动生成的公网链接防止模型被未授权访问若需远程演示应配合 nginx basic auth而非依赖 Gradio share。3.2 支持摄像头实时推理解决 OpenCV 与 Gradio 的线程冲突Gradio 的gr.Video组件本身不支持实时摄像头流它只处理录制好的视频文件。要实现「点击按钮 → 启动摄像头 → 实时检测」必须用gr.Stategr.Timercv2.VideoCapture手动轮询。但直接在fn中开cap cv2.VideoCapture(0)会导致线程阻塞Gradio 主线程卡死。正确做法用后台线程持续读帧Gradio 通过gr.State共享最新帧UI 每 100ms 刷新一次# 在 app.py 中追加位于 Blocks 定义之后 import threading import time from queue import Queue # 全局帧队列与状态 frame_queue Queue(maxsize1) camera_active False cap None def camera_worker(): global cap, camera_active while True: if camera_active and cap is not None: ret, frame cap.read() if ret: # 转 RGB 供 Gradio 显示 frame_rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) if not frame_queue.full(): frame_queue.put(frame_rgb) time.sleep(0.03) # ~30 FPS # 启动后台线程 camera_thread threading.Thread(targetcamera_worker, daemonTrue) camera_thread.start() # 摄像头开关函数 def toggle_camera(enable: bool): global camera_active, cap if enable and not camera_active: cap cv2.VideoCapture(0) if not cap.isOpened(): raise RuntimeError(无法打开摄像头) camera_active True return ✅ 摄像头已启动 elif not enable and camera_active: cap.release() cap None camera_active False return ⏹️ 摄像头已关闭 return ⚠️ 状态未改变 # 添加摄像头控制按钮 with gr.Row(): start_cam_btn gr.Button(▶️ 启动摄像头, variantsecondary) stop_cam_btn gr.Button(⏹️ 停止摄像头, variantstop) cam_status gr.Textbox(label摄像头状态, interactiveFalse) start_cam_btn.click( fnlambda: toggle_camera(True), inputs[], outputscam_status ) stop_cam_btn.click( fnlambda: toggle_camera(False), inputs[], outputscam_status ) # 实时刷新摄像头画面每 100ms def get_latest_frame(): if not frame_queue.empty(): return frame_queue.get() return None demo.load( fnget_latest_frame, inputs[], outputsimage_input, # 复用 image_input 组件显示摄像头帧 every0.1 # 100ms 刷新一次 )注意此方案绕过了 Gradio 的gr.Webcam组件它在 4.25.0 版本中仍不稳定常报MediaStreamTrack ended错误用 OpenCV 底层控制更可靠。every0.1是 Gradio 的定时刷新机制比 JavaScriptsetInterval更精准且不依赖浏览器端。3.3 模型热切换与缓存清理避免内存泄漏与 stale model 问题Gradio 默认不会在模型切换时释放旧模型显存。连续加载 5 个.pt模型后GPU 显存可能爆满。我们在model_loader.py中增强ModelManager# 追加到 model_loader.py import gc class ModelManager: # ... 原有代码 ... def unload_model(self): 主动卸载当前模型释放显存 if self._model is not None: # 清空模型引用 del self._model self._model None self._task None # 强制垃圾回收 gc.collect() if torch.cuda.is_available(): torch.cuda.empty_cache() def load_model(self, model_path: str): # 卸载旧模型 self.unload_model() # ... 后续加载逻辑 ...并在app.py的模型切换事件中调用# 在 model_dropdown.change 事件中 def on_model_change(new_model): model_manager.unload_model() # 切换前先清内存 return new_model model_dropdown.change( fnon_model_change, inputsmodel_dropdown, outputsmodel_dropdown )这步看似简单却是 RK3588 等边缘设备部署的关键——它们只有 4GB GPU 显存不主动empty_cache()会导致第二次加载直接 OOM。4. 避坑指南YOLOv8 Gradio 联合部署的 5 个真实翻车现场与解法4.1 现象Gradio 界面点击「开始推理」后长时间无响应终端日志卡在Loading weights from...原因模型文件过大如yolov8x-seg.pt1.8GB且未启用torch.compile或export优化首次加载耗时超 60 秒Gradio 默认超时时间为 60 秒触发TimeoutError。解决对大模型提前导出为 ONNX 或 TensorRT engine# 导出 ONNX支持 dynamic batch, dynamic H/W yolo export modelyolov8x-seg.pt formatonnx opset12 dynamicTrue # 导出 TensorRT需安装 tensorrt8.6 yolo export modelyolov8x-seg.pt formatengine halfTrue在model_loader.py中对.onnx/.engine模型使用cv2.dnn或tensorrt原生加载绕过 ultralytics 的 Python 解析开销Gradio 启动时加server_timeout300参数延长超时。4.2 现象上传视频后结果图只显示第一帧且output_json中num_detections0原因model.predict(sourcevideo_path)默认返回generator节省内存但 Gradio 的fn函数要求同步返回generator被当作空对象处理。解决强制streamFalse已在run_inference中体现若需处理长视频改用results list(model.predict(...))但注意内存爆炸风险更优方案用cv2.VideoCapture逐帧读取 model.predict(sourceframe)自行控制帧率与缓存。4.3 现象分割结果 mask 显示为黑色块或与 bbox 错位原因YOLOv8 的result.plot()方法在segment模式下mask 叠加逻辑依赖result.orig_img的尺寸但 Gradio 上传的图像可能被自动 resize如gr.Image(height480)导致orig_img尺寸与result中的orig_shape不一致。解决禁用 Gradio 图像自动 resizegr.Image(typenumpy, image_modeRGB, tooleditor)不设height/width或在run_inference中对输入图像做cv2.resize至模型输入尺寸如 640x640再传入model.predict()确保orig_img与result尺寸严格一致检查result.masks.data的 shape 是否与result.orig_img.shape[:2]匹配不匹配则用cv2.resize(mask, (w,h))插值。4.4 现象在 RK3588 上运行报错OSError: libcudnn.so.8: cannot open shared object file原因RK3588 使用 NPU不是 CUDA但ultralytics默认尝试加载 CUDA 库即使torch.cuda.is_available()False。解决安装 Rockchip 官方rknn-toolkit2并导出 RKNN 模型pip install rknn-toolkit2 yolo export modelyolov8n-seg.pt formatrknn imgsz640修改model_loader.py当检测到platform.machine() aarch64且not torch.cuda.is_available()时自动加载 RKNN 模型调用rknn.inference()替代model.predict()或直接使用ultralytics的rknndevice后端v8.1.0 支持。4.5 现象多人同时访问时模型预测结果串扰A 用户上传图B 用户看到 A 的结果原因model_manager是全局单例但model.predict()的result对象在多线程下可能被覆盖尤其result.plot()内部使用静态变量。解决为每个请求创建独立result拷贝# 在 run_inference 中 result results[0].cpu().clone() # 强制拷贝到 CPU避免 GPU 张量共享或更彻底禁用全局model_manager改为每次fn调用时YOLO(model_path)但需配合torch.jit.script编译模型提升加载速度生产环境推荐用gr.State为每个 session 维护独立模型实例Gradio 4.20 支持state参数。5. 进阶技巧让这套系统真正「可交付」——身份验证、日志审计、结果回传与离线部署5.1 添加基础身份验证不用 Nginx纯 Gradio 实现登录保护Gradio 4.20 原生支持auth参数但仅限用户名密码不支持 OAuth。对于内部系统足够安全# 在 demo.launch() 中添加 demo.launch( auth[(admin, SecurePass2024!)], # 用户名密码元组列表 auth_message请输入管理员凭据, # ... 其他参数 )注意密码明文写在代码里不安全。生产环境应从环境变量读取auth[(os.getenv(GRADIO_USER), os.getenv(GRADIO_PASS))]启动前export GRADIO_USERadmin; export GRADIO_PASSxxx。5.2 记录每次推理日志谁、何时、用什么模型、输入什么、输出多少目标Gradio 不提供内置审计日志。我们在run_inference函数开头添加结构化日志import logging from datetime import datetime # 配置日志 logging.basicConfig( levellogging.INFO, format%(asctime)s - %(levelname)s - %(message)s, handlers[ logging.FileHandler(inference.log, encodingutf-8), logging.StreamHandler() # 同时输出到终端 ] ) def run_inference(...): # ... 前置代码 ... # 记录审计日志 log_data { timestamp: datetime.now().isoformat(), client_ip: unknown, # Gradio 不暴露 IP需前置 nginx 传递 X-Forwarded-For model_path: model_path, input_type: input_type, conf_threshold: conf_threshold, iou_threshold: iou_threshold, num_detections: len(boxes), classes: list(set([b[label] for b in boxes])) } logging.info(fINFER: {json.dumps(log_data, ensure_asciiFalse)}) # ... 后续代码 ...日志文件inference.log可被 ELK 或 Grafana Loki 采集实现「谁在什么时候检测到了什么」的全链路追溯。5.3 实现结果一键回传将检测框/掩码自动写入 Label Studio 或 CVAT与其让用户手动下载 JSON 再导入标注平台不如在 UI 中集成「回传」按钮。以 Label Studio 为例其 API 要求 POST 到/api/projects/{id}/import# 在 app.py 中追加 import requests def upload_to_labelstudio( project_id: str, api_token: str, json_result: str, task_name: str auto_upload ): 将 YOLOv8 结果 JSON 转为 Label Studio 格式并上传 # p a hrefhttps://download.csdn.net/download/qq_41701956/87693023 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