文章目录
-
- 一、Supervision 是什么
- 二、为什么要用 Supervision
- 三、环境安装
-
- 1. 基础安装
- 2. 配合 YOLO 使用
- 3. Conda 安装
- 4. 检查版本
- 四、核心概念:Detections
-
- 1. Detections 常见字段
- 2. xyxy 坐标格式
- 五、Supervision 和 OpenCV 的区别
-
- 1. OpenCV 画框
- 2. Supervision 画框
- 3. 怎么选择
- 六、快速上手:YOLO 检测并画框
-
- 1. 安装依赖
- 2. 示例代码
- 3. 核心代码解释
- 七、手动构造 Detections
-
- 1. 单个目标
- 2. 多个目标
- 3. 必须注意 shape
- 八、常用 Annotator 标注器
-
- 1. BoxAnnotator
- 2. LabelAnnotator
- 3. MaskAnnotator
- 4. TraceAnnotator
- 九、过滤检测结果
-
- 1. 按置信度过滤
- 2. 按类别过滤
- 3. 多条件过滤
- 十、NMS 去重
- 十一、视频处理
-
- 1. 获取视频信息
- 2. 逐帧读取视频
- 3. 保存视频
- 4. 使用 process_video
- 十二、目标跟踪
-
- 1. YOLO + ByteTrack 视频跟踪
- 2. 注意
- 十三、线计数 LineZone
-
- 1. 基本概念
- 2. 线计数需要 tracker_id
- 3. 示例代码
- 十四、区域过滤 PolygonZone
-
- 1. 定义多边形区域
- 2. 过滤区域内目标
- 3. 画出区域
- 4. 完整示例
- 十五、小目标检测:InferenceSlicer
-
- 1. 基本思路
- 2. 示例代码
- 十六、和昇腾 310B / OM 推理结果结合
-
- 1. 从 OM 后处理结果构造 Detections
- 2. 画框
- 3. 为什么 OpenCV 能画,Supervision 不显示
一、Supervision 是什么
Supervision 是 Roboflow 开源的 Python 计算机视觉工具库。
它不是一个训练框架,也不是一个模型本身,而是一个用于构建计算机视觉应用的辅助库。
它主要解决这些问题:
模型输出格式不统一
检测框可视化麻烦
视频逐帧处理重复代码多
目标跟踪、计数、区域判断要自己写很多逻辑
数据集格式转换和评估流程繁琐
Supervision 官方文档中提到,它提供了统一的 Detections 对象,可以兼容 Ultralytics、Roboflow Inference、Transformers、SAM、Detectron2、MMDetection、YOLO-NAS、PaddleDet、NCNN 等多种模型输出。
简单说:
Supervision = 检测结果管理 + 可视化 + 跟踪 + 计数 + 视频处理 + 数据集工具
二、为什么要用 Supervision
如果你只想画一个框,用 OpenCV 就够了:
cv2.rectangle(image, (x1, y1), (x2, y2), (0, 255, 0), 2)
但是实际项目里经常要做:
画检测框
画类别名和置信度
不同类别不同颜色
视频逐帧处理
目标跟踪 ID
画运动轨迹
统计越线数量
判断目标是否在区域内
保存处理后视频
过滤低置信度目标
对检测结果做 NMS
这些如果都用 OpenCV 手写,会越来越乱。
Supervision 的价值就是把这些常见流程封装好。
| 画框 | 可以,底层直接 | 可以,封装更方便 |
| 画标签 | 要自己写文字位置 | 内置 LabelAnnotator |
| 多类别颜色 | 要自己维护颜色表 | 内置颜色策略 |
| YOLO 结果转换 | 要手动解析 | Detections.from_ultralytics() |
| 视频处理 | 要自己写循环和保存 | process_video()、VideoSink |
| 目标跟踪 | 需要额外算法 | 可接 ByteTrack |
| 区域计数 | 要自己写几何判断 | PolygonZone |
| 越线计数 | 要自己维护 ID 和方向 | LineZone |
三、环境安装
1. 基础安装
pip install supervision
Supervision 要求 Python 版本通常为:
Python >= 3.9
2. 配合 YOLO 使用
如果你要结合 Ultralytics YOLO:
pip install ultralytics supervision opencv-python
3. Conda 安装
conda install -c conda-forge supervision
4. 检查版本
import supervision as sv
print(sv.__version__)
四、核心概念:Detections
sv.Detections 是 Supervision 最核心的对象。
它的作用是:
把不同模型的检测结果,统一成同一种格式
1. Detections 常见字段
| xyxy | 检测框坐标 | (N, 4) |
| confidence | 置信度 | (N,) |
| class_id | 类别 ID | (N,) |
| tracker_id | 跟踪 ID | (N,) |
| mask | 分割 mask | (N, H, W) |
| data | 额外数据 | 字典 |
其中最常用的是:
xyxy
confidence
class_id
2. xyxy 坐标格式
Supervision 使用的检测框格式是:
[x1, y1, x2, y2]
含义:
x1, y1 = 左上角坐标
x2, y2 = 右下角坐标
示例:
xyxy = [100, 50, 300, 220]
表示:
左上角:(100, 50)
右下角:(300, 220)
注意,它不是 YOLO 标签里的:
x_center, y_center, width, height
也不是 OpenCV 里有时用的:
x, y, w, h
五、Supervision 和 OpenCV 的区别
1. OpenCV 画框
OpenCV 更底层:
import cv2
cv2.rectangle(image, (x1, y1), (x2, y2), (0, 255, 0), 2)
cv2.putText(
image,
"person 0.90",
(x1, y1 – 10),
cv2.FONT_HERSHEY_SIMPLEX,
0.6,
(0, 255, 0),
2
)
优点:
直接
灵活
依赖少
调试方便
缺点:
代码重复
标签布局要自己处理
多类别颜色要自己维护
跟踪、计数、区域判断都要自己写
2. Supervision 画框
Supervision 更高级:
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
image = box_annotator.annotate(scene=image, detections=detections)
image = label_annotator.annotate(scene=image, detections=detections, labels=labels)
优点:
结果对象统一
可视化方便
扩展到视频、跟踪、计数很自然
代码结构更清晰
缺点:
要求 Detections 格式正确
坐标、shape、dtype 错了可能不显示
版本 API 变化需要注意
3. 怎么选择
| 只想快速画一个框 | OpenCV |
| YOLO 结果可视化 | Supervision |
| 视频跟踪 | Supervision |
| 区域计数 | Supervision |
| 昇腾 OM 推理调试坐标 | 先 OpenCV,再 Supervision |
| 正式工程封装 | Supervision |
六、快速上手:YOLO 检测并画框
1. 安装依赖
pip install ultralytics supervision opencv-python
2. 示例代码
import cv2
import supervision as sv
from ultralytics import YOLO
IMAGE_PATH = "bus.jpg"
MODEL_PATH = "yolov8s.pt"
model = YOLO(MODEL_PATH)
image = cv2.imread(IMAGE_PATH)
results = model(image)[0]
detections = sv.Detections.from_ultralytics(results)
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
labels = [
f"{model.names[class_id]} {confidence:.2f}"
for class_id, confidence
in zip(detections.class_id, detections.confidence)
]
annotated_image = image.copy()
annotated_image = box_annotator.annotate(
scene=annotated_image,
detections=detections
)
annotated_image = label_annotator.annotate(
scene=annotated_image,
detections=detections,
labels=labels
)
cv2.imshow("result", annotated_image)
cv2.waitKey(0)
cv2.destroyAllWindows()
3. 核心代码解释
results = model(image)[0]
使用 YOLO 对图片进行推理。
detections = sv.Detections.from_ultralytics(results)
把 Ultralytics 的结果转换为 Supervision 的统一 Detections 对象。
box_annotator.annotate(...)
在图片上画检测框。
label_annotator.annotate(...)
在图片上画类别标签和置信度。
七、手动构造 Detections
有时你的模型不是 Ultralytics,比如你在昇腾 310B 上跑 .om,得到的是自己解析出来的框。
这时可以手动构造 Detections。
1. 单个目标
import numpy as np
import supervision as sv
detections = sv.Detections(
xyxy=np.array([
[100, 50, 300, 220]
], dtype=np.float32),
confidence=np.array([0.92], dtype=np.float32),
class_id=np.array([0], dtype=np.int32)
)
2. 多个目标
import numpy as np
import supervision as sv
xyxy = np.array([
[100, 50, 300, 220],
[400, 80, 520, 260]
], dtype=np.float32)
confidence = np.array([0.92, 0.81], dtype=np.float32)
class_id = np.array([0, 1], dtype=np.int32)
detections = sv.Detections(
xyxy=xyxy,
confidence=confidence,
class_id=class_id
)
3. 必须注意 shape
| xyxy | (N, 4) |
| confidence | (N,) |
| class_id | (N,) |
错误示例:
xyxy = np.array([100, 50, 300, 220])
这是 (4,),不是 (1, 4)。
正确写法:
xyxy = np.array([[100, 50, 300, 220]])
八、常用 Annotator 标注器
Annotator 的作用是把检测结果画到图片或视频帧上。
1. BoxAnnotator
画矩形检测框。
box_annotator = sv.BoxAnnotator(thickness=2)
image = box_annotator.annotate(scene=image, detections=detections)
常用参数:
| thickness | 框线粗细 |
| color | 框颜色或颜色表 |
| color_lookup | 按类别、索引或跟踪 ID 映射颜色 |
2. LabelAnnotator
画类别文本。
label_annotator = sv.LabelAnnotator()
image = label_annotator.annotate(
scene=image,
detections=detections,
labels=labels
)
标签通常这样构造:
labels = [
f"{class_names[class_id]} {confidence:.2f}"
for class_id, confidence
in zip(detections.class_id, detections.confidence)
]
3. MaskAnnotator
用于实例分割结果。
mask_annotator = sv.MaskAnnotator()
image = mask_annotator.annotate(scene=image, detections=detections)
前提:
detections.mask 不为空
如果你是普通 YOLO 检测模型,只有框,没有 mask,就不要用它。
4. TraceAnnotator
用于画目标运动轨迹。
通常配合跟踪器使用:
trace_annotator = sv.TraceAnnotator()
image = trace_annotator.annotate(scene=image, detections=detections)
前提:
detections.tracker_id 不为空
九、过滤检测结果
Detections 支持用布尔条件过滤。
1. 按置信度过滤
detections = detections[detections.confidence > 0.5]
意思是只保留置信度大于 0.5 的目标。
2. 按类别过滤
假设 0 是 person:
detections = detections[detections.class_id == 0]
只保留人。
3. 多条件过滤
detections = detections[
(detections.confidence > 0.5) &
(detections.class_id == 0)
]
意思是:
只保留 class_id 为 0 且置信度大于 0.5 的目标
十、NMS 去重
NMS 全称:
Non-Maximum Suppression
非极大值抑制
作用是去掉重复框。
如果同一个目标被预测出多个重叠框,只保留最可信的框。
Supervision 中可以这样调用:
detections = detections.with_nms(threshold=0.5)
参数:
| threshold | IoU 阈值,越小去重越严格 |
| class_agnostic | 是否忽略类别做 NMS |
常见设置:
detections = detections.with_nms(threshold=0.45)
如果你导出 YOLO ONNX 时用了:
nms=False
那么后处理时就需要在代码里做置信度过滤和 NMS。
十一、视频处理
Supervision 提供了几个视频处理工具:
| VideoInfo | 获取视频宽、高、FPS、总帧数 |
| get_video_frames_generator | 逐帧读取视频 |
| VideoSink | 保存视频 |
| process_video | 用 callback 快速处理视频 |
| FPSMonitor | 统计处理 FPS |
1. 获取视频信息
import supervision as sv
video_info = sv.VideoInfo.from_video_path("input.mp4")
print(video_info.width)
print(video_info.height)
print(video_info.fps)
print(video_info.total_frames)
print(video_info.resolution_wh)
2. 逐帧读取视频
import supervision as sv
frames_generator = sv.get_video_frames_generator("input.mp4")
for frame in frames_generator:
# frame 是 OpenCV BGR 图像
pass
3. 保存视频
import supervision as sv
video_info = sv.VideoInfo.from_video_path("input.mp4")
frames_generator = sv.get_video_frames_generator("input.mp4")
with sv.VideoSink(target_path="output.mp4", video_info=video_info) as sink:
for frame in frames_generator:
sink.write_frame(frame)
注意:
VideoSink 写入的 frame 应该是 BGR 格式
4. 使用 process_video
import numpy as np
import supervision as sv
def callback(frame: np.ndarray, frame_index: int) –> np.ndarray:
# 在这里处理每一帧
return frame
sv.process_video(
source_path="input.mp4",
target_path="output.mp4",
callback=callback
)
callback 的输入:
| frame | 当前视频帧 |
| frame_index | 当前帧编号 |
返回值:
处理后的 frame
十二、目标跟踪
检测只能告诉你:
这一帧有哪些目标
跟踪可以告诉你:
这一帧的目标和上一帧是不是同一个目标
跟踪后每个目标会有一个:
tracker_id
例如:
person #1
person #2
car #3
1. YOLO + ByteTrack 视频跟踪
import numpy as np
import supervision as sv
from ultralytics import YOLO
model = YOLO("yolov8s.pt")
tracker = sv.ByteTrack()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
trace_annotator = sv.TraceAnnotator()
def callback(frame: np.ndarray, frame_index: int) –> np.ndarray:
results = model(frame)[0]
detections = sv.Detections.from_ultralytics(results)
detections = detections[detections.confidence > 0.5]
detections = tracker.update_with_detections(detections)
labels = [
f"#{tracker_id} class:{class_id} {confidence:.2f}"
for tracker_id, class_id, confidence
in zip(detections.tracker_id, detections.class_id, detections.confidence)
]
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
)
annotated_frame = trace_annotator.annotate(
scene=annotated_frame,
detections=detections
)
return annotated_frame
sv.process_video(
source_path="input.mp4",
target_path="tracked_output.mp4",
callback=callback
)
2. 注意
官方文档中提到,Supervision 内置的 sv.ByteTrack 包装后续更推荐使用外部 trackers 包的新实现。你学习和快速实验时可以先用 sv.ByteTrack(),正式项目要关注版本变更。
十三、线计数 LineZone
LineZone 用于统计目标穿过一条线的数量。
典型场景:
统计车辆进出
统计人流量
统计物体通过输送线
1. 基本概念
一条线由两个点构成:
start = sv.Point(x=100, y=400)
end = sv.Point(x=1000, y=400)
创建线计数器:
line_zone = sv.LineZone(start=start, end=end)
2. 线计数需要 tracker_id
LineZone 要判断“同一个目标从线的一边移动到另一边”,所以必须依赖跟踪 ID。
也就是说:
只检测不跟踪,不能可靠做越线计数
正确流程:
YOLO 检测
-> Detections
-> ByteTrack 分配 tracker_id
-> LineZone.trigger()
-> LineZoneAnnotator 画线和计数
3. 示例代码
import numpy as np
import supervision as sv
from ultralytics import YOLO
model = YOLO("yolov8s.pt")
tracker = sv.ByteTrack()
line_zone = sv.LineZone(
start=sv.Point(x=100, y=400),
end=sv.Point(x=1000, y=400)
)
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
line_annotator = sv.LineZoneAnnotator()
def callback(frame: np.ndarray, frame_index: int) –> np.ndarray:
results = model(frame)[0]
detections = sv.Detections.from_ultralytics(results)
detections = detections[detections.confidence > 0.5]
detections = tracker.update_with_detections(detections)
line_zone.trigger(detections=detections)
labels = [
f"#{tracker_id}"
for tracker_id in detections.tracker_id
]
annotated_frame = frame.copy()
annotated_frame = box_annotator.annotate(annotated_frame, detections)
annotated_frame = label_annotator.annotate(annotated_frame, detections, labels)
annotated_frame = line_annotator.annotate(annotated_frame, line_counter=line_zone)
return annotated_frame
sv.process_video(
source_path="input.mp4",
target_path="line_count_output.mp4",
callback=callback
)
十四、区域过滤 PolygonZone
PolygonZone 用于判断目标是否在一个多边形区域内。
典型场景:
只统计停车区内的车辆
只检测禁区内的人
只保留某个 ROI 区域中的目标
1. 定义多边形区域
import numpy as np
import supervision as sv
polygon = np.array([
[100, 100],
[600, 100],
[600, 500],
[100, 500]
])
zone = sv.PolygonZone(polygon=polygon)
2. 过滤区域内目标
mask = zone.trigger(detections=detections)
detections_in_zone = detections[mask]
3. 画出区域
zone_annotator = sv.PolygonZoneAnnotator(zone=zone)
frame = zone_annotator.annotate(scene=frame)
4. 完整示例
import cv2
import numpy as np
import supervision as sv
from ultralytics import YOLO
model = YOLO("yolov8s.pt")
image = cv2.imread("street.jpg")
results = model(image)[0]
detections = sv.Detections.from_ultralytics(results)
polygon = np.array([
[100, 100],
[600, 100],
[600, 500],
[100, 500]
])
zone = sv.PolygonZone(polygon=polygon)
mask = zone.trigger(detections=detections)
detections = detections[mask]
box_annotator = sv.BoxAnnotator()
zone_annotator = sv.PolygonZoneAnnotator(zone=zone)
annotated = image.copy()
annotated = zone_annotator.annotate(scene=annotated)
annotated = box_annotator.annotate(scene=annotated, detections=detections)
cv2.imshow("zone", annotated)
cv2.waitKey(0)
cv2.destroyAllWindows()
十五、小目标检测:InferenceSlicer
小目标检测经常遇到一个问题:
整张大图缩放到 640×640 后,小目标变得更小,模型很难识别
解决思路:
把大图切成多个小块
每个小块分别推理
再把结果合并回原图
Supervision 提供了 InferenceSlicer 来处理这个流程。
1. 基本思路
原图
-> 切片
-> 每个切片推理
-> 合并检测框
-> NMS/NMM 去重
-> 返回完整图检测结果
2. 示例代码
import cv2
import supervision as sv
from ultralytics import YOLO
model = YOLO("yolov8s.pt")
def callback(image_slice):
results = model(image_slice)[0]
return sv.Detections.from_ultralytics(results)
slicer = sv.InferenceSlicer(callback=callback)
image = cv2.imread("large_image.jpg")
detections = slicer(image)
box_annotator = sv.BoxAnnotator()
annotated = box_annotator.annotate(scene=image.copy(), detections=detections)
cv2.imshow("sliced result", annotated)
cv2.waitKey(0)
cv2.destroyAllWindows()
适合场景:
无人机小目标
高分辨率监控
遥感图像
密集小物体检测
十六、和昇腾 310B / OM 推理结果结合
如果你的模型已经转成 .om 在昇腾 310B 上推理,Supervision 不能直接帮你跑 OM。
Supervision 负责的是:
拿到后处理后的框
转换成 Detections
画框、跟踪、计数、可视化
你的完整流程应该是:
读取图像/视频帧
-> 前处理 letterbox / resize
-> OM 推理
-> 解析输出
-> 置信度过滤
-> NMS
-> 坐标还原到原图
-> 构造 sv.Detections
-> 使用 Supervision 画框/跟踪/计数
1. 从 OM 后处理结果构造 Detections
假设你的后处理已经得到:
boxes = [
[100, 50, 300, 220],
[400, 80, 520, 260]
]
scores = [0.92, 0.81]
class_ids = [0, 0]
转换成 Supervision:
import numpy as np
import supervision as sv
detections = sv.Detections(
xyxy=np.array(boxes, dtype=np.float32),
confidence=np.array(scores, dtype=np.float32),
class_id=np.array(class_ids, dtype=np.int32)
)
2. 画框
class_names = ["drone"]
labels = [
f"{class_names[class_id]} {confidence:.2f}"
for class_id, confidence in zip(detections.class_id, detections.confidence)
]
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
frame = box_annotator.annotate(scene=frame, detections=detections)
frame = label_annotator.annotate(scene=frame, detections=detections, labels=labels)
3. 为什么 OpenCV 能画,Supervision 不显示
常见原因:
| xyxy 形状错 | 必须是 (N, 4) |
| 坐标格式错 | Supervision 要 [x1, y1, x2, y2] |
| 坐标没有映射回原图 | 框可能在错误位置 |
| 坐标超出图像范围 | 建议 np.clip |
| confidence 被过滤没了 | 检查阈值 |
| class_id 和标签不匹配 | class_id 不能越界 |
| 传入的是灰度图 | 建议用 BGR 三通道 |
建议加一段检查:
print("xyxy:", detections.xyxy)
print("confidence:", detections.confidence)
print("class_id:", detections.class_id)
print("len:", len(detections))
print("frame shape:", frame.shape)



