1. supervision 是什么
supervision 是 Roboflow 开源的 Python 计算机视觉工具库。
它本身不是一个 YOLO 模型,也不是必须依赖云端的服务。
可以把它理解成:
1 2 3 4 5 6 7 YOLO / 其他检测模型 ↓ 输出检测框、类别、置信度 ↓ supervision.Detections ↓ 过滤、追踪、画框、计数、区域判断、视频保存
也就是说:
1 2 模型负责识别 supervision 负责把识别结果变成可用的业务逻辑
例如 YOLO 每一帧会输出:
1 2 3 bbox: 框坐标 class_id: 类别 ID confidence: 置信度
supervision 可以继续帮你做:
1 2 3 4 5 6 7 8 9 画框 显示标签 显示轨迹 给目标分配 tracker_id 判断是否进入某个区域 判断是否越过某条线 统计数量 保存视频 处理摄像头 / RTSP / 本地视频
2. 使用 uv 创建项目
1 2 3 4 5 mkdir supervision-democd supervision-demouv init uv add ultralytics supervision opencv-python numpy
如果你没有自己的模型,可以先用官方 YOLO 小模型测试:
如果你已经训练好了模型,直接把:
改成:
3. supervision 常用功能总览
功能
作用
典型场景
画框 / 标签 / 轨迹 / 热力图
把检测结果画到画面上
调试、监控画面、录像回放
过滤检测结果
按类别、置信度、面积、框大小、区域过滤
只看某类物品、过滤误检、小目标过滤
ByteTrack 追踪
给连续帧里的同一个物体分配固定 ID
人员追踪、车辆追踪、商品追踪
LineZone 越线计数
判断物体是否穿过一条线,并统计进出数量
门口进出、传送带计数、车辆通行
PolygonZone 区域判断
判断物体是否在一个多边形区域内
货架区域、危险区域、工作区检测
InferenceSlicer 切图推理
把大图切成小块推理,再合并结果
小物体检测、高清摄像头画面
DetectionsSmoother 平滑框
减少检测框抖动
画面更稳定,追踪显示更自然
视频工具
读视频帧、写视频、统计 FPS
离线视频测试、保存检测后视频
数据集处理 / 格式转换
COCO、VOC、YOLO 等格式导入导出
训练数据整理、模型评估
模型评估指标
mAP、Precision、Recall、F1 等
比较不同 YOLO 模型效果
4. 核心数据结构:sv.Detections
YOLO 原始输出不能直接做很多业务判断,所以通常要先转成 sv.Detections:
1 2 3 import supervision as svdetections = sv.Detections.from_ultralytics(result)
转完后,常用字段有:
1 2 3 4 5 detections.xyxy detections.class_id detections.confidence detections.tracker_id detections.area
完整流程通常是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 frame ↓ YOLO 推理 ↓ sv.Detections.from_ultralytics(result) ↓ 过滤 detections ↓ ByteTrack 追踪 ↓ 区域判断 / 越线计数 ↓ 画框 / 标签 / 轨迹 ↓ 显示或保存
5. 最小核心处理函数
建议先写一个统一的 process_frame(),以后摄像头、RTSP、本地视频都可以复用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 import cv2import numpy as npimport supervision as svfrom ultralytics import YOLOmodel = YOLO("yolov8n.pt" ) tracker = sv.ByteTrack() smoother = sv.DetectionsSmoother() box_annotator = sv.BoundingBoxAnnotator() label_annotator = sv.LabelAnnotator() trace_annotator = sv.TraceAnnotator() CONFIDENCE_THRESHOLD = 0.35 CLASS_WHITELIST = None def detect (frame ): """ 对单帧图像做 YOLO 检测,并转成 supervision.Detections """ result = model(frame, verbose=False )[0 ] detections = sv.Detections.from_ultralytics(result) detections = detections[detections.confidence >= CONFIDENCE_THRESHOLD] if CLASS_WHITELIST is not None and len (detections) > 0 : detections = detections[np.isin(detections.class_id, CLASS_WHITELIST)] return detections def build_labels (detections ): """ 给每个框生成显示文字 """ labels = [] for class_id, confidence, tracker_id in zip ( detections.class_id, detections.confidence, detections.tracker_id, ): class_name = model.names[int (class_id)] if tracker_id is None : label = f"{class_name} {confidence:.2 f} " else : label = f"#{tracker_id} {class_name} {confidence:.2 f} " labels.append(label) return labels def process_frame (frame ): """ 输入一帧图像,输出标注后的图像和 detections """ detections = detect(frame) detections = tracker.update_with_detections(detections) detections = smoother.update_with_detections(detections) labels = build_labels(detections) annotated_frame = frame.copy() annotated_frame = trace_annotator.annotate( scene=annotated_frame, detections=detections, ) annotated_frame = box_annotator.annotate( scene=annotated_frame, detections=detections, ) annotated_frame = label_annotator.annotate( scene=annotated_frame, detections=detections, labels=labels, ) return annotated_frame, detections
6. 怎么传入摄像头
摄像头最常见就是 OpenCV:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import cv2cap = cv2.VideoCapture(0 ) if not cap.isOpened(): print ("无法打开摄像头" ) exit() while True : ret, frame = cap.read() if not ret: print ("读取摄像头失败" ) break annotated_frame, detections = process_frame(frame) cv2.imshow("Camera Demo" , annotated_frame) if cv2.waitKey(1 ) & 0xFF == ord ("q" ): break cap.release() cv2.destroyAllWindows()
运行:
7. 怎么传入 RTSP 流
RTSP 也是用 OpenCV 的 VideoCapture。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import cv2rtsp_url = "rtsp://username:password@192.168.1.100:554/stream1" cap = cv2.VideoCapture(rtsp_url) if not cap.isOpened(): print ("无法打开 RTSP 流" ) exit() while True : ret, frame = cap.read() if not ret: print ("RTSP 读取失败,可能是网络断开或地址错误" ) break annotated_frame, detections = process_frame(frame) cv2.imshow("RTSP Demo" , annotated_frame) if cv2.waitKey(1 ) & 0xFF == ord ("q" ): break cap.release() cv2.destroyAllWindows()
边缘设备上如果没有桌面环境,cv2.imshow() 可能不能用,可以改成:
1 2 3 4 1. 保存视频 2. 推送到 Web 页面 3. 推送到 RTSP 4. 只输出检测事件到后端
8. 怎么传入本地视频并保存结果
本地视频推荐用 supervision 自带的视频工具:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import supervision as svSOURCE_VIDEO_PATH = "source.mp4" TARGET_VIDEO_PATH = "result.mp4" video_info = sv.VideoInfo.from_video_path(SOURCE_VIDEO_PATH) frames_generator = sv.get_video_frames_generator(SOURCE_VIDEO_PATH) with sv.VideoSink( target_path=TARGET_VIDEO_PATH, video_info=video_info, ) as sink: for frame in frames_generator: annotated_frame, detections = process_frame(frame) sink.write_frame(frame=annotated_frame) print ("处理完成:" , TARGET_VIDEO_PATH)
这个适合:
1 2 3 4 测试模型效果 离线分析视频 保存识别后的视频 做 demo 演示
9. 用 sv.process_video 简化本地视频处理
如果只是本地视频输入、本地视频输出,也可以用 sv.process_video()。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import numpy as npimport supervision as svdef callback (frame: np.ndarray, index: int ) -> np.ndarray: annotated_frame, detections = process_frame(frame) return annotated_frame sv.process_video( source_path="source.mp4" , target_path="result.mp4" , callback=callback, )
这个写法更简洁。
注意:
1 2 sv.process_video 更适合本地视频文件 摄像头 / RTSP 实时流更建议用 cv2.VideoCapture
10. 保存摄像头或 RTSP 流处理结果
摄像头和 RTSP 没有固定的视频文件信息,所以需要自己从第一帧取宽高和 FPS。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 import cv2import supervision as svSOURCE = 0 cap = cv2.VideoCapture(SOURCE) if not cap.isOpened(): print ("无法打开视频源" ) exit() fps = cap.get(cv2.CAP_PROP_FPS) if fps is None or fps <= 1 : fps = 25 ret, frame = cap.read() if not ret: print ("无法读取第一帧" ) cap.release() exit() height, width = frame.shape[:2 ] video_info = sv.VideoInfo( width=width, height=height, fps=int (fps), total_frames=None , ) with sv.VideoSink( target_path="stream_result.mp4" , video_info=video_info, ) as sink: while True : annotated_frame, detections = process_frame(frame) sink.write_frame(frame=annotated_frame) cv2.imshow("Stream Save Demo" , annotated_frame) if cv2.waitKey(1 ) & 0xFF == ord ("q" ): break ret, frame = cap.read() if not ret: break cap.release() cv2.destroyAllWindows()
11. 功能一:画框 / 标签 / 轨迹 / 热力图
11.1 画框和标签
1 2 3 4 5 6 7 8 9 10 11 12 13 box_annotator = sv.BoundingBoxAnnotator() label_annotator = sv.LabelAnnotator() annotated_frame = box_annotator.annotate( scene=frame.copy(), detections=detections, ) annotated_frame = label_annotator.annotate( scene=annotated_frame, detections=detections, labels=labels, )
11.2 画轨迹
轨迹需要先有 tracker_id,所以要先经过 ByteTrack。
1 2 3 4 5 6 7 8 9 tracker = sv.ByteTrack() trace_annotator = sv.TraceAnnotator() detections = tracker.update_with_detections(detections) annotated_frame = trace_annotator.annotate( scene=frame.copy(), detections=detections, )
11.3 热力图
热力图适合分析一段时间内物体经常出现的位置。
1 2 3 4 5 6 heatmap_annotator = sv.HeatMapAnnotator() annotated_frame = heatmap_annotator.annotate( scene=frame.copy(), detections=detections, )
典型场景:
1 2 3 4 门店顾客停留区域 教室人员活动区域 车辆经常经过的位置 工厂高频作业区域
12. 功能二:过滤检测结果
YOLO 可能会识别出很多类别,但业务上通常只关心一部分。
12.1 按置信度过滤
1 detections = detections[detections.confidence > 0.5 ]
12.2 按单个类别过滤
例如只保留 class_id == 0 的目标:
1 detections = detections[detections.class_id == 0 ]
如果用 COCO 模型,通常:
1 2 3 4 person = 0 car = 2 bus = 5 truck = 7
12.3 按多个类别过滤
1 2 3 4 import numpy as npselected_classes = [0 , 2 , 3 ] detections = detections[np.isin(detections.class_id, selected_classes)]
12.4 按检测框面积过滤
过滤太小的误检框:
1 detections = detections[detections.area > 1000 ]
12.5 按框宽高过滤
1 2 3 4 w = detections.xyxy[:, 2 ] - detections.xyxy[:, 0 ] h = detections.xyxy[:, 3 ] - detections.xyxy[:, 1 ] detections = detections[(w > 50 ) & (h > 50 )]
12.6 混合过滤
例如只保留:
1 2 3 置信度 > 0.6 类别是 person 面积 > 2000
1 2 3 4 5 detections = detections[ (detections.confidence > 0.6 ) & (detections.class_id == 0 ) & (detections.area > 2000 ) ]
13. 功能三:ByteTrack 追踪
ByteTrack 的作用是:
1 2 3 4 第 1 帧看到一个人 第 2 帧这个人移动了一点 第 3 帧这个人继续移动 系统知道这是同一个目标
使用方式:
1 2 3 4 tracker = sv.ByteTrack() detections = sv.Detections.from_ultralytics(result) detections = tracker.update_with_detections(detections)
经过追踪后,detections.tracker_id 会有值。
例如标签可以这样写:
1 2 3 4 5 labels = [ f"#{tracker_id} {model.names[int (class_id)]} {confidence:.2 f} " for class_id, confidence, tracker_id in zip (detections.class_id, detections.confidence, detections.tracker_id) ]
显示效果类似:
1 2 3 #1 person 0.86 #2 cup 0.72 #3 bottle 0.80
注意:
1 2 3 4 ByteTrack 是视频内追踪 不是跨天识别身份 不是人脸识别 不是长期身份识别
如果程序重启,ID 通常会重新分配。
14. 功能四:LineZone 越线计数
LineZone 用来判断目标是否穿过一条线。
典型场景:
1 2 3 4 门口进出人数 车辆进出 传送带物品计数 工厂流水线计数
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 import cv2import supervision as svline_zone = sv.LineZone( start=sv.Point(100 , 400 ), end=sv.Point(1100 , 400 ), ) tracker = sv.ByteTrack() def process_frame_with_line (frame ): result = model(frame, verbose=False )[0 ] detections = sv.Detections.from_ultralytics(result) detections = tracker.update_with_detections(detections) crossed_in, crossed_out = line_zone.trigger(detections) labels = build_labels(detections) annotated_frame = frame.copy() annotated_frame = box_annotator.annotate( scene=annotated_frame, detections=detections, ) annotated_frame = label_annotator.annotate( scene=annotated_frame, detections=detections, labels=labels, ) cv2.line( annotated_frame, (100 , 400 ), (1100 , 400 ), (0 , 255 , 255 ), 2 , ) cv2.putText( annotated_frame, f"in: {line_zone.in_count} out: {line_zone.out_count} " , (30 , 50 ), cv2.FONT_HERSHEY_SIMPLEX, 1 , (0 , 255 , 255 ), 2 , ) return annotated_frame, detections
重点:
1 2 LineZone 依赖 tracker_id 所以要先 ByteTrack,再 line_zone.trigger(detections)
15. 功能五:PolygonZone 区域判断
PolygonZone 用来判断目标是否在指定多边形区域内。
典型场景:
1 2 3 4 货架区域有没有商品 危险区域有没有人 教室某个座位区域有没有人 仓库某个区域是否有叉车
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 import numpy as npimport supervision as svpolygon = np.array([ [100 , 100 ], [600 , 100 ], [600 , 500 ], [100 , 500 ], ]) zone = sv.PolygonZone( polygon=polygon, frame_resolution_wh=(1280 , 720 ), ) zone_annotator = sv.PolygonZoneAnnotator(zone=zone) def process_frame_with_polygon (frame ): result = model(frame, verbose=False )[0 ] detections = sv.Detections.from_ultralytics(result) detections = tracker.update_with_detections(detections) mask = zone.trigger(detections=detections) detections_in_zone = detections[mask] labels = build_labels(detections_in_zone) annotated_frame = frame.copy() annotated_frame = box_annotator.annotate( scene=annotated_frame, detections=detections_in_zone, ) annotated_frame = label_annotator.annotate( scene=annotated_frame, detections=detections_in_zone, labels=labels, ) annotated_frame = zone_annotator.annotate( scene=annotated_frame, label=f"count: {zone.current_count} " , ) return annotated_frame, detections_in_zone
注意:
1 2 PolygonZone 默认通常使用检测框的底部中心点判断目标是否在区域内 适合判断人、车、物体是否进入某个地面区域
如果是教室座位检测,可以给每个座位画一个 PolygonZone。
例如:
1 2 3 seat_01 区域有人 -> 学生 1 到课 seat_02 区域有人 -> 学生 2 到课 seat_03 区域有人 -> 学生 3 到课
16. 功能六:InferenceSlicer 切图推理
InferenceSlicer 适合小目标检测。
例如:
1 2 3 4 4K 摄像头画面 货架上的小商品 工业零件 远处的人或车
直接整张图送 YOLO,目标可能太小,容易漏检。
InferenceSlicer 会:
1 2 3 4 5 6 7 8 9 大图 ↓ 切成多个小图 ↓ 每个小图单独推理 ↓ 把检测框合并回原图 ↓ 做 NMS 去重
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 import numpy as npimport supervision as svfrom ultralytics import YOLOmodel = YOLO("best.pt" ) def callback (image_slice: np.ndarray ) -> sv.Detections: result = model(image_slice, verbose=False )[0 ] return sv.Detections.from_ultralytics(result) slicer = sv.InferenceSlicer( callback=callback, slice_wh=(640 , 640 ), overlap_ratio_wh=(0.2 , 0.2 ), iou_threshold=0.5 , thread_workers=1 , ) def process_frame_with_slicer (frame ): detections = slicer(frame) detections = detections[detections.confidence > 0.35 ] labels = [ f"{model.names[int (class_id)]} {confidence:.2 f} " for class_id, confidence in zip (detections.class_id, detections.confidence) ] annotated_frame = box_annotator.annotate( scene=frame.copy(), detections=detections, ) annotated_frame = label_annotator.annotate( scene=annotated_frame, detections=detections, labels=labels, ) return annotated_frame, detections
注意:
1 2 3 切图推理会更准,但会更慢 适合小目标,不一定适合强实时场景 边缘设备上要测试 FPS
17. 功能七:DetectionsSmoother 平滑框
YOLO 在视频里有时会出现框抖动:
DetectionsSmoother 可以让框更稳定。
1 2 3 4 5 tracker = sv.ByteTrack() smoother = sv.DetectionsSmoother() detections = tracker.update_with_detections(detections) detections = smoother.update_with_detections(detections)
注意:
1 2 DetectionsSmoother 依赖 tracker_id 所以必须先 ByteTrack
常见顺序:
1 2 3 4 5 6 7 8 9 10 11 YOLO 检测 ↓ Detections ↓ 过滤 ↓ ByteTrack ↓ DetectionsSmoother ↓ 画框
18. 功能八:FPS 统计
边缘设备部署时,一定要看 FPS。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 import cv2import supervision as svfps_monitor = sv.FPSMonitor() cap = cv2.VideoCapture(0 ) while True : ret, frame = cap.read() if not ret: break annotated_frame, detections = process_frame(frame) fps_monitor.tick() fps = fps_monitor() cv2.putText( annotated_frame, f"FPS: {fps:.1 f} " , (30 , 50 ), cv2.FONT_HERSHEY_SIMPLEX, 1 , (0 , 255 , 0 ), 2 , ) cv2.imshow("FPS Demo" , annotated_frame) if cv2.waitKey(1 ) & 0xFF == ord ("q" ): break cap.release() cv2.destroyAllWindows()
边缘设备上一般要关注:
1 2 3 4 5 6 输入分辨率 模型大小 是否使用 GPU 是否使用 TensorRT / ONNX / OpenVINO 是否需要切图 是否每帧都推理
19. 数据集处理 / 格式转换
这个功能主要用于训练前后的数据管理。
例如你有 YOLO 格式数据集,可以用 supervision 读取,然后做拆分、合并、导出等操作。
概念流程:
1 2 3 4 5 6 7 YOLO 数据集 ↓ supervision.DetectionDataset ↓ 数据集检查 / 合并 / 拆分 ↓ 导出为 YOLO / COCO / VOC
这个和实时边缘部署关系不大,但是和模型训练关系很大。
如果你现在重点是摄像头识别和追踪,可以先不深入这个部分。
20. 模型评估指标
模型训练完成后,可以用指标比较不同模型效果:
1 2 3 4 mAP Precision Recall F1 Score
简单理解:
1 2 3 4 Precision:识别出来的结果有多少是真的 Recall:真实目标里有多少被识别出来了 mAP:综合评估检测框和类别预测质量 F1:Precision 和 Recall 的平衡
业务上常见选择:
1 2 3 误报不能太多 -> 关注 Precision 漏检不能太多 -> 关注 Recall 整体检测效果 -> 关注 mAP
例如:
1 2 3 安全帽检测:漏检很危险,更关注 Recall 商品计数:误检会影响库存,更关注 Precision 车辆检测:Precision 和 Recall 都要看
21. 一个完整 main.py 示例:摄像头 / RTSP / 视频都能改
下面是一个比较完整的版本。
你只需要改 SOURCE:
可以用摄像头。
改成:
1 SOURCE = "rtsp://username:password@192.168.1.100:554/stream1"
可以用 RTSP。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 import cv2import numpy as npimport supervision as svfrom ultralytics import YOLOMODEL_PATH = "yolov8n.pt" SOURCE = 0 CONFIDENCE_THRESHOLD = 0.35 CLASS_WHITELIST = None model = YOLO(MODEL_PATH) tracker = sv.ByteTrack() smoother = sv.DetectionsSmoother() box_annotator = sv.BoundingBoxAnnotator() label_annotator = sv.LabelAnnotator() trace_annotator = sv.TraceAnnotator() fps_monitor = sv.FPSMonitor() def detect (frame ): result = model(frame, verbose=False )[0 ] detections = sv.Detections.from_ultralytics(result) detections = detections[detections.confidence >= CONFIDENCE_THRESHOLD] if CLASS_WHITELIST is not None and len (detections) > 0 : detections = detections[np.isin(detections.class_id, CLASS_WHITELIST)] return detections def build_labels (detections ): labels = [] for class_id, confidence, tracker_id in zip ( detections.class_id, detections.confidence, detections.tracker_id, ): class_name = model.names[int (class_id)] if tracker_id is None : labels.append(f"{class_name} {confidence:.2 f} " ) else : labels.append(f"#{tracker_id} {class_name} {confidence:.2 f} " ) return labels def process_frame (frame ): detections = detect(frame) detections = tracker.update_with_detections(detections) detections = smoother.update_with_detections(detections) labels = build_labels(detections) annotated_frame = frame.copy() annotated_frame = trace_annotator.annotate( scene=annotated_frame, detections=detections, ) annotated_frame = box_annotator.annotate( scene=annotated_frame, detections=detections, ) annotated_frame = label_annotator.annotate( scene=annotated_frame, detections=detections, labels=labels, ) fps_monitor.tick() fps = fps_monitor() cv2.putText( annotated_frame, f"FPS: {fps:.1 f} " , (30 , 50 ), cv2.FONT_HERSHEY_SIMPLEX, 1 , (0 , 255 , 0 ), 2 , ) return annotated_frame, detections def main (): cap = cv2.VideoCapture(SOURCE) if not cap.isOpened(): print ("无法打开视频源" ) return while True : ret, frame = cap.read() if not ret: print ("读取视频帧失败" ) break annotated_frame, detections = process_frame(frame) cv2.imshow("Supervision Demo" , annotated_frame) if cv2.waitKey(1 ) & 0xFF == ord ("q" ): break cap.release() cv2.destroyAllWindows() if __name__ == "__main__" : main()
运行:
22. 完整视频文件处理示例
如果你要处理 source.mp4 并生成 result.mp4:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 import cv2import numpy as npimport supervision as svfrom ultralytics import YOLOMODEL_PATH = "yolov8n.pt" SOURCE_VIDEO_PATH = "source.mp4" TARGET_VIDEO_PATH = "result.mp4" model = YOLO(MODEL_PATH) tracker = sv.ByteTrack() smoother = sv.DetectionsSmoother() box_annotator = sv.BoundingBoxAnnotator() label_annotator = sv.LabelAnnotator() trace_annotator = sv.TraceAnnotator() def process_frame (frame ): result = model(frame, verbose=False )[0 ] detections = sv.Detections.from_ultralytics(result) detections = detections[detections.confidence > 0.35 ] detections = tracker.update_with_detections(detections) detections = smoother.update_with_detections(detections) labels = [ f"#{tracker_id} {model.names[int (class_id)]} {confidence:.2 f} " for class_id, confidence, tracker_id in zip (detections.class_id, detections.confidence, detections.tracker_id) ] annotated_frame = frame.copy() annotated_frame = trace_annotator.annotate( scene=annotated_frame, detections=detections, ) annotated_frame = box_annotator.annotate( scene=annotated_frame, detections=detections, ) annotated_frame = label_annotator.annotate( scene=annotated_frame, detections=detections, labels=labels, ) return annotated_frame def main (): video_info = sv.VideoInfo.from_video_path(SOURCE_VIDEO_PATH) frames_generator = sv.get_video_frames_generator(SOURCE_VIDEO_PATH) with sv.VideoSink( target_path=TARGET_VIDEO_PATH, video_info=video_info, ) as sink: for frame in frames_generator: annotated_frame = process_frame(frame) sink.write_frame(frame=annotated_frame) print ("视频处理完成:" , TARGET_VIDEO_PATH) if __name__ == "__main__" : main()
运行:
1 uv run python video_demo.py
23. 边缘设备上的推荐架构
边缘设备部署时,可以这样设计:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 摄像头 / RTSP ↓ OpenCV 读取帧 ↓ YOLO 本地推理 ↓ sv.Detections ↓ 过滤 ↓ ByteTrack ↓ LineZone / PolygonZone ↓ 业务事件 ↓ 本地 SQLite / HTTP 上报 / MQTT 上报
例如教室人数统计:
1 2 3 4 5 6 7 8 9 摄像头 ↓ YOLO person 检测 ↓ ByteTrack 临时追踪 ↓ PolygonZone 判断座位区域 ↓ 记录 seat_01 / seat_02 / seat_03 是否有人
例如传送带商品计数:
1 2 3 4 5 6 7 8 9 摄像头 ↓ YOLO 商品检测 ↓ ByteTrack 追踪 ↓ LineZone 判断是否过线 ↓ 计数 +1
例如危险区域检测:
1 2 3 4 5 6 7 摄像头 ↓ YOLO person 检测 ↓ PolygonZone 判断是否进入危险区域 ↓ 触发报警
24. 常见问题
24.1 supervision 是不是必须联网?
不是。
如果你使用本地 YOLO 权重,例如:
就可以本地运行。
但是如果你用 Roboflow 云端 API 或 Roboflow Inference 的云模型,可能需要 API Key。
24.2 YOLO 拉框做好后是不是就能接进来?
是的。
只要 YOLO 能输出:
就可以转成:
1 detections = sv.Detections.from_ultralytics(result)
然后接入 supervision 的后处理能力。
24.3 ByteTrack 是不是人脸识别?
不是。
ByteTrack 只是视频内目标追踪。
它只能判断连续帧里哪个框大概率是同一个目标。
它不能跨天识别身份,也不能知道这个人是谁。
24.4 追踪 ID 会不会一直固定?
不会。
tracker_id 通常只在当前视频流或当前程序运行期间有效。
如果程序重启,ID 可能重新分配。
如果你要长期识别同一个人,需要额外身份机制,例如:
1 2 3 4 5 6 刷卡 扫码 固定座位 老师确认 人脸识别 人体 ReID
24.5 为什么检测到了但越线没有计数?
常见原因:
1 2 3 4 5 6 7 1. 没有先做 ByteTrack 2. detections.tracker_id 是 None 3. 检测框不稳定 4. 线画的位置不对 5. 目标没有真正穿过线 6. 视频 FPS 太低 7. 置信度阈值太高导致中间帧漏检
正确顺序:
1 2 3 4 5 6 7 YOLO ↓ Detections ↓ ByteTrack ↓ LineZone.trigger()
24.6 为什么 PolygonZone 判断不准?
常见原因:
1 2 3 4 1. 多边形坐标写错 2. frame_resolution_wh 和真实视频分辨率不一致 3. 默认判断点是框的底部中心点 4. 摄像头角度导致人或物体框位置和地面区域不匹配
建议先把多边形区域画出来调试。
24.7 小物体检测不好怎么办?
可以尝试:
1 2 3 4 5 6 1. 提高输入分辨率 2. 训练更适合小目标的数据集 3. 使用 InferenceSlicer 切图推理 4. 降低置信度阈值 5. 使用更大的 YOLO 模型 6. 调整摄像头距离和角度
25. 推荐学习顺序
建议按这个顺序学习:
1 2 3 4 5 6 7 8 9 10 第一步:跑通 YOLO 单帧检测 第二步:转成 sv.Detections 第三步:画框和标签 第四步:接摄像头 / RTSP 第五步:接 ByteTrack 追踪 第六步:加 PolygonZone 区域判断 第七步:加 LineZone 越线计数 第八步:保存结果视频 第九步:优化 FPS 第十步:部署到边缘设备
26. 最重要的一句话
supervision 的重点不是训练模型,而是把模型输出的检测框变成业务结果。
核心公式:
1 2 3 4 YOLO 负责识别 supervision 负责后处理 OpenCV 负责读流和显示 你的业务代码负责保存、报警、统计、上报