首页
/ Supervision Draw Utils 完全指南:掌握底层绘图 API,用代码亲手画出检测与跟踪可视化

Supervision Draw Utils 完全指南:掌握底层绘图 API,用代码亲手画出检测与跟踪可视化

2026-09-07 16:18:24作者:申梦珏Efrain

本文以 supervision 项目中的绘图工具模块为核心,系统梳理 supervision.draw.utilssupervision.draw.color 提供的全部底层绘制原语(线段、矩形、多边形、文字、图像叠加等)与颜色体系。读完本文,你将掌握每个 API 的参数语义、默认值与在 BGR 帧(NumPy 数组)上的就地绘制行为,并能基于这些原语构建自己的自定义可视化,或深入理解上层 Annotator 的工作机理。

从参考文档说起:本文覆盖的 API 清单

本仓库的 docs/utils/draw.md 是一份 MkDocs 自动生成的 API 参考页,它通过 :::supervision.draw.utils.xxx:::supervision.draw.color.xxx 指令将源码中的 docstring 渲染为文档。因此,该页面的真实内容即源码中的完整说明与示例。按文档骨架,本文要讲解的对象分为三类:

  • 绘制函数utils.py):draw_linedraw_rectangledraw_filled_rectangledraw_polygondraw_filled_polygondraw_textdraw_image
  • 分辨率自适应辅助函数calculate_optimal_text_scalecalculate_optimal_line_thickness
  • 颜色基础类color.py):ColorColorPalette

这些符号在包的顶层 src/supervision/init.pyfrom supervision.draw.utils import ...from supervision.draw.color import Color, ColorPalette,见第 117–127 行与第 185、243–257 行)中被统一重导出,因此日常使用时既可以写 sv.draw_line(...),也可以像官方示例那样从子模块精确导入。

动手前的三个基础概念

1. 场景(scene):绘制目标就是 NumPy 图像数组

所有绘制函数的第一个参数都叫 scene,类型为 npt.NDArray[np.uint8]。最典型的场景是 OpenCV 读取的 BGR 彩色帧,shape 为 (H, W, 3);docstring 同时声明灰度图((H, W))也可作为 draw_text 的输入。构建一张空白测试场景最直接的方式就是 NumPy 零数组:

import numpy as np

scene = np.zeros((100, 100, 3), dtype=np.uint8)

2. 坐标系与几何基元:Point 与 Rect

draw_line 的起点终点、draw_text 的锚点都是 Pointdraw_rectangledraw_filled_rectangledraw_image 的绘制区域则是 Rect。它们定义在 geometry/core.py

  • Point(x, y):持有浮点坐标,通过 as_xy_int_tuple() 转成 (int x, int y) 供 OpenCV 使用(第 31–61 行);
  • Rect(x, y, width, height):以左上角 + 宽高定义矩形,提供 top_leftbottom_right 角点属性、pad(padding)(四边同时外扩)以及 as_xyxy_int_tuple()(转 (x_min, y_min, x_max, y_max)),见第 146–209 行。

例如画矩形所需的区域与文字背景框计算都会用到 Rect 的角点与 pad 能力。

3. 颜色:永远是 RGBA,交给 OpenCV 时自动转 BGR

supervision 的颜色模型以 RGBA 表示,而在内部调用 OpenCV 时通过 color.as_bgr() 转成 BGR 元组(例如 utils.pycv2.line(..., color.as_bgr(), ...))。下面的小节详解颜色体系。

Color:RGBA 颜色对象与构造/转换方法

Color 是一个 @dataclasscolor.py 第 63–413 行),字段为 r, g, b(0–255)以及默认 255(不透明)的 a。直接在 Color(...) 构造时,__post_init__ 会校验四个通道都必须落在 0–255,否则抛出 ValueError

预设颜色常量

常量 Hex 值 RGB 元组
WHITE #FFFFFF (255, 255, 255)
BLACK #000000 (0, 0, 0)
GREY #808080 (128, 128, 128)
RED #FF0000 (255, 0, 0)
GREEN #00FF00 (0, 255, 0)
BLUE #0000FF (0, 0, 255)
YELLOW #FFFF00 (255, 255, 0)
ROBOFLOW #A351FB (163, 81, 251)

这些常量都是 classproperty(见 color.py 第 366–396 行的 WHITE/BLACK/GREY/RED/GREEN/BLUE/YELLOW/ROBOFLOW 定义),使用时无需实例化:

>>> import supervision as sv
>>> sv.Color.WHITE
Color(r=255, g=255, b=255)

值得注意的是,几乎所有绘制函数的颜色参数默认值都是 Color.ROBOFLOW(品牌紫色 #A351FB),这与 supervision 默认标注配色保持一致。

从 Hex 字符串构造(支持 3/4/6/8 位缩写)

Color.from_hex(color_hex) 接受带或不带 # 前缀的十六进制字符串。校验规则见 _validate_color_hex(color.py 第 55–60 行):只能包含合法十六进制字符,长度必须是 3、4、6、8 之一。3/4 位短码会将每个字符翻倍展开成完整 hex,因此 #f0f 等价于 #ff00ff#f0f8 等价于 #ff00ff88

>>> sv.Color.from_hex('#ff00ff')
Color(r=255, g=0, b=255)
>>> sv.Color.from_hex('#f0f')
Color(r=255, g=0, b=255)
>>> sv.Color.from_hex('#ff00ff80')   # 8 位时末两位是 alpha
Color(r=255, g=0, b=255, a=128)

从元组构造与转换输出

除 Hex 外,还提供一组对称的构造器与转换器:

  • 构造器from_rgb_tuple((r, g, b))from_bgr_tuple((b, g, r))from_rgba_tuple((r, g, b, a))from_bgra_tuple((b, g, r, a)),均会校验通道范围(越界抛 ValueError);
  • 转换器as_hex()(alpha 非 255 时输出 #RRGGBBAA)、as_rgb()as_bgr()as_rgba()as_bgra()
  • __hash__ / __eq__ 基于四个通道实现,因此 Color 可作为集合元素或字典键,便于按颜色聚合标注结果。
>>> sv.Color.from_rgb_tuple((255, 255, 0))
Color(r=255, g=255, b=0)
>>> sv.Color(r=255, g=255, b=0).as_bgr()
(0, 255, 255)
>>> sv.Color(r=255, g=0, b=255, a=128).as_hex()
'#ff00ff80'

ColorPalette:成组取色与按索引循环取色

当需要为多个检测框、多个类别或轨迹点分配不同颜色时,使用 ColorPalettecolor.py 第 416–570 行),内部即 colors: list[Color]

内置三套色板

  • ColorPalette.DEFAULT:21 色默认色板,源码常量 DEFAULT_COLOR_PALETTE(color.py 第 8–30 行),首色即 #A351FB
  • ColorPalette.ROBOFLOW:6 色 Roboflow 紫色系渐变色板,常量 ROBOFLOW_COLOR_PALETTE(第 52 行),从 #C28DFC 渐变到 #4D049A
  • ColorPalette.LEGACY:17 色旧版色板,常量 LEGACY_COLOR_PALETTE(第 32–50 行),供迁移期兼容使用。

构造与使用

>>> import supervision as sv
>>> colors = ['#ff0000', '#00ff00', '#0000ff']
>>> color_palette = sv.ColorPalette.from_hex(colors)
>>> color_palette.by_idx(1)   # 按索引取色
Color(r=0, g=255, b=0)

by_idx(idx)(color.py 第 537–561 行)的实现细节值得注意:它用 idx % len(self.colors) 取模,因此负数索引与越界索引都会被自动回绕,比如调色板只有 3 色时 by_idx(3) 会取回第 0 号颜色。这在为数量不确定的目标循环分配颜色时非常安全。空调色板调用会抛 ValueError

此外还有 ColorPalette.from_matplotlib(palette_name, color_count)(color.py 第 489–535 行):内部延迟导入 matplotlib.pyplot(注释明确说明是为了「import 颜色工具时不引入 matplotlib」),从指定 colormap 等距采样 color_count 个颜色。若 color_count < 1 会抛 ValueError;对 viridis 之类非 ListedColormap 对象按 0–1 等距位置取色,单色请求则取调色板起始色以避免除零。

线段与矩形绘制:draw_line、draw_rectangle、draw_filled_rectangle

draw_line —— 画线段

draw_line(scene, start: Point, end: Point,
          color: Color = Color.ROBOFLOW, thickness: int = 2) -> scene

在场景上从 startend 绘制线段。内部调用 cv2.line(scene, start.as_xy_int_tuple(), end.as_xy_int_tuple(), color.as_bgr(), thickness=thickness)utils.py 第 47–53 行)。

>>> import numpy as np
>>> from supervision.draw.utils import draw_line
>>> from supervision.draw.color import Color
>>> from supervision.geometry.core import Point
>>> scene = np.zeros((100, 100, 3), dtype=np.uint8)
>>> scene = draw_line(
...     scene, start=Point(x=10, y=10), end=Point(x=90, y=90), color=Color.RED
... )
>>> scene.shape
(100, 100, 3)

Point 的浮点坐标会被截断取整后传给 OpenCV;result is scene,即函数就地修改并返回同一个数组对象(测试 test_draw_line_modifies_scene 断言了 result is scene 与像素确实变化,见 tests/draw/test_utils.py 第 153–165 行)。

draw_rectangle —— 画空心矩形边框

draw_rectangle(scene, rect: Rect, color: Color = Color.ROBOFLOW,
               thickness: int = 2) -> scene

Rect 为区域绘制矩形边框,内部用 rect.top_left / rect.bottom_right 两个角点调用 cv2.rectangle(utils.py 第 89–95 行)。thickness 指定边框像素宽度,测试表明 1 像素边框不会污染矩形内部(tests/draw/test_utils.py 第 200–210 行断言内点保持 [0,0,0])。

>>> from supervision.draw.utils import draw_rectangle
>>> from supervision.geometry.core import Rect
>>> scene = np.zeros((100, 100, 3), dtype=np.uint8)
>>> rect = Rect(x=10, y=10, width=50, height=30)
>>> scene = draw_rectangle(scene, rect, color=Color.RED)

draw_filled_rectangle —— 填充矩形(支持透明度)

draw_filled_rectangle(scene, rect: Rect, color: Color = Color.ROBOFLOW,
                      opacity: float = 1) -> scene

多了一个 opacity 参数(0–1,默认 1 表示完全不透明)。其实现(utils.py 第 131–150 行)体现了两种路径:

  • opacity == 1:直接 cv2.rectangle(..., thickness=-1) 实心填充,效率最高;
  • opacity < 1:先把填充画到 scene.copy() 的副本上,再用 cv2.addWeighted(scene_with_annotations, opacity, scene, 1 - opacity, gamma=0, dst=scene) 做加权混合,dst=scene 保证结果仍写回原数组。
>>> scene = draw_filled_rectangle(scene, rect, color=Color.RED, opacity=0.5)

测试 test_draw_filled_rectangle_fills_interior(opacity=1 时内点变纯白)与 test_draw_filled_rectangle_opacity_blends(opacity=0.5 时混合像素既不是黑也不是白)分别验证了这两条路径(tests/draw/test_utils.py 第 218–242 行)。

补充说明:源码中还有 draw_rounded_rectangle(scene, rect, color, border_radius)(utils.py 第 155–234 行),即带圆角的实心矩形,实现上先按 min(border_radius, min(width, height)//2) 对半径做钳制,再画两个矩形加四个圆角圆。它虽不在 docs/utils/draw.md 参考页的清单中,但已被上层标注器使用(见下节),并配套了圆角像素级测试(tests/draw/test_utils.py 第 86–145 行)。

多边形绘制:draw_polygon 与 draw_filled_polygon

draw_polygon —— 闭合多边形轮廓

draw_polygon(scene, polygon: npt.NDArray[np.int_],
             color: Color = Color.ROBOFLOW, thickness: int = 2) -> scene

polygon 是顶点数组,例如 np.array([[x1, y1], [x2, y2], ...])(整数类型)。内部调用 cv2.polylines(scene, [polygon], isClosed=True, color=..., thickness=...)isClosed=True 意味着首尾顶点自动相连构成闭合轮廓(utils.py 第 267–269 行)。

>>> import numpy as np
>>> from supervision.draw.utils import draw_polygon
>>> scene = np.zeros((100, 100, 3), dtype=np.uint8)
>>> polygon = np.array([[10, 10], [90, 10], [90, 90], [10, 90]])
>>> scene = draw_polygon(scene, polygon, color=Color.RED)
>>> scene.shape
(100, 100, 3)

draw_filled_polygon —— 填充多边形(支持透明度)

draw_filled_polygon(scene, polygon: npt.NDArray[np.int_],
                    color: Color = Color.ROBOFLOW, opacity: float = 1) -> scene

draw_filled_rectangle 保持完全一致的设计:opacity == 1 时直接 cv2.fillPoly 实心填充;否则先在副本上 fillPoly,再用 cv2.addWeighted 按透明度混合回原图(utils.py 第 303–310 行)。这一 API 同形性让使用者无需记忆两套透明度语义。

>>> scene = draw_filled_polygon(scene, polygon, color=Color.RED, opacity=0.5)

典型应用:半透明多边形常用来标注 ROI 区域(如禁入区、统计区),既能看清区域边界又不完全遮挡底下的画面内容。

draw_text:带可选背景框与抗锯齿的文字绘制

draw_text(
    scene, text: str, text_anchor: Point,
    text_color: Color = Color.BLACK,
    text_scale: float = 0.5,
    text_thickness: int = 1,
    text_padding: int = 10,
    text_font: int = cv2.FONT_HERSHEY_SIMPLEX,
    background_color: Color | None = None,
) -> scene

参数语义(utils.py 第 315–395 行):

  • text_anchor:文字的锚点位置,文字会被以锚点为中心放置(见下方几何计算);
  • text_color:文字颜色,默认黑色;
  • text_scale:字号缩放,默认 0.5,通常结合分辨率自适应函数(见后文)取值;
  • text_thickness:笔画粗细,默认 1;
  • text_padding:当绘制背景框时文字四周留白(像素),默认 10;
  • text_font:OpenCV 字体,默认 cv2.FONT_HERSHEY_SIMPLEX
  • background_color:非 None 时先绘制实心背景矩形,再叠文字,实现「标签条」效果。

其底层几何流程非常值得拆解(utils.py 第 364–394 行):

  1. cv2.getTextSize(text, fontFace, fontScale, thickness) 量出文字宽高;
  2. text_anchor 为中心构造文字矩形 Rect(x=ax - text_width//2, y=ay - text_height//2, width=text_width, height=text_height),然后 .pad(text_padding) 四边外扩得到背景框;
  3. background_color 非空,调用上文的 draw_filled_rectangle 画背景;
  4. cv2.putText 使用 lineType=cv2.LINE_AA(抗锯齿)绘制文字。
>>> import numpy as np
>>> from supervision.geometry.core import Point
>>> from supervision.draw.utils import draw_text
>>> scene = np.zeros((100, 100, 3), dtype=np.uint8)
>>> text_anchor = Point(x=50, y=50)
>>> scene = draw_text(scene=scene, text="Hello, world!", text_anchor=text_anchor)
>>> scene.shape
(100, 100, 3)

# 带背景色的典型标注写法
>>> from supervision.draw.color import Color
>>> scene = draw_text(
...     scene=scene, text="person 0.92", text_anchor=Point(x=50, y=20),
...     text_scale=1.0, text_color=Color.WHITE, background_color=Color.RED,
... )

测试验证了调用后场景被修改且 shape 保持不变(tests/draw/test_utils.py 第 250–272 行)。

draw_image:把图片(文件或数组)按透明度叠加进场景

draw_image(scene, image: str | npt.NDArray[np.uint8],
           opacity: float, rect: Rect) -> scene

用于把另一张图片作为水印/角标/信息卡片叠加到场景指定区域,例如在画面角落贴 logo 或统计信息小图。

输入与校验(utils.py 第 436–465 行)——这是函数中防御逻辑最密集的一处:

  1. image 是路径字符串:文件不存在抛 FileNotFoundError;存在但解码失败(cv2.imread 返回 None)抛 OSError;测试用例 test_draw_image_invalid_path_raises_oserror 用伪图片文件验证了这一分支(tests/draw/test_utils.py 第 18–31 行);
  2. image_np.ndim != 3 或通道数不在 (3, 4) 中抛 ValueError("Image must have 3 or 4 channels.")——灰度图(2 维)会被拒绝,测试同样覆盖了「文件路径」与「内存数组」两种灰度输入(第 53–83 行);
  3. opacity 不在 [0.0, 1.0]ValueError
  4. 目标 rect 越出场景边界(x<0y<0、右下角超出 scene.shape)抛 ValueError("Invalid rectangle dimensions.")

混合算法(utils.py 第 467–487 行):

  1. cv2.resize 把待叠加图缩放到 rect 的宽高;
  2. 若图片是 4 通道 RGBA/BGRA,则取第 4 通道作为天然 alpha 蒙版;3 通道图则生成全 255 的 alpha 平面;
  3. cv2.convertScaleAbs(alpha_channel * opacity) 把「图片自身 alpha × 用户 opacity」合并成最终透明度;
  4. 在场景对应 ROI 上逐像素做 alpha 混合后写回原图。

因此 draw_image 既支持直接传内存数组(测试中通过 np.full((40, 40, 3), 255) 模拟),也支持传文件路径,且对带透明通道的 PNG 能自动保留其透明信息

分辨率自适应辅助:两个 calculate_optimal_* 函数

当可视化画面分辨率变化时(如 720p/1080p 视频流),硬编码的字号和线宽会导致小图糊成一片、大图又看不清。supervision 为此提供了两个启发式函数,都只依赖分辨率较短边:

calculate_optimal_text_scale(resolution_wh: tuple[int, int]) -> float

实现为 min(resolution_wh) * 1e-3(utils.py 第 514 行),即字号与较短边成正比:

>>> import supervision as sv
>>> sv.calculate_optimal_text_scale((1920, 1080))
1.08
>>> sv.calculate_optimal_text_scale((640, 480))
0.48
calculate_optimal_line_thickness(resolution_wh: tuple[int, int]) -> int

实现为分段常量:较短边 < 1080 时返回 2,否则返回 4(utils.py 第 539–541 行):

>>> sv.calculate_optimal_line_thickness((1920, 1080))
4
>>> sv.calculate_optimal_line_thickness((640, 480))
2

典型用法是取帧后先算一次,再传给各绘制函数,实现「分辨率不变式」的标注体验:

h, w = frame.shape[:2]
text_scale = sv.calculate_optimal_text_scale((w, h))
thickness = sv.calculate_optimal_line_thickness((w, h))

源码级统一设计:这些原语如何支撑上层标注器

纵观 utils.py 的全部实现,可以总结出四条贯穿性的设计约定(均有源码依据):

  1. 就地绘制、返回同一数组:所有函数直接修改传入的 scenereturn scene(测试多次用 result is scene 断言),适合流式视频处理——逐帧就地叠加标注,无需反复拷贝大数组;
  2. 颜色统一走 Color/as_bgr():从不在业务代码里直接写裸 BGR 三元组,保证颜色语义(RGBA 世界观)与 OpenCV 落地(BGR)解耦;
  3. 透明统一走 opacity + addWeighted:四个「填充/叠加」类函数共享同一套透明度混合心智模型;
  4. 对外只暴露 NumPy 图像接口:不感知具体推理框架的 Detections 对象,因此既可在顶层直接使用,也可作为其他模块的积木。

关于最后一点,可在上层标注器代码中直接看到证据: annotators/core.py 第 38–40 行从 supervision.draw.utils 导入了 draw_polygondraw_rounded_rectangledraw_text,从 supervision.draw.color 导入 ColorColorPalette,并在内部(如第 720 行画多边形、第 3599 行画文字)调用它们实现各类 Box/多边形标注器。换言之,本文讲解的函数就是全套标注器可视化的最小公共原语——理解它们,等于理解了所有 Annotator 的绘制内核。

进阶组合示例:一个完整的自定义标注流程

综合全篇 API,拼一段接近实战的「检测框 + 类别标签 + 置信度 + 半透明 ROI」标注逻辑(BGR 场景帧来自任何检测流程):

import numpy as np
import supervision as sv
from supervision.geometry.core import Point, Rect

frame = np.zeros((720, 1280, 3), dtype=np.uint8)   # 模拟一帧 720p 场景

w, h = 1280, 720
text_scale = sv.calculate_optimal_text_scale((w, h))
thickness = sv.calculate_optimal_line_thickness((w, h))

# 1) 为多个目标从色板循环取色
palette = sv.ColorPalette.ROBOFLOW
for i, (xyxy, label) in enumerate(detections_for_frame):
    x1, y1, x2, y2 = xyxy
    color = palette.by_idx(i)
    # 2) 检测框 + 文字标签
    sv.draw_rectangle(frame, Rect(x=x1, y=y1, width=x2 - x1, height=y2 - y1),
                      color=color, thickness=thickness)
    sv.draw_text(frame, text=label, text_anchor=Point(x=(x1 + x2) // 2, y=y1),
                 text_scale=text_scale, text_color=sv.Color.WHITE,
                 background_color=color)
    # 3) 半透明禁入区域
    roi = np.array([[x1, y1], [x2, y1], [x2, y2], [x1, y2]])
    sv.draw_filled_polygon(frame, roi, color=sv.Color.ROBOFLOW, opacity=0.3)

detections_for_frame 在此仅为示意数据源,实际检测框坐标可从任意检测器得到。)这段代码演示了本文全部核心要素:Color/ColorPalette 取色、按索引回绕、Point/Rect 几何构造、空心与填充绘制的透明度控制、抗锯齿带背景文字,以及两个分辨率自适应函数的实际组合。

小结

本文基于 docs/utils/draw.md 参考页骨架,逐一展开 supervision 绘图工具箱中 7 个绘制函数、2 个自适应辅助函数与 Color/ColorPalette 两个颜色类。它们统一以 NumPy 数组为场景、以 Point/Rect 描述几何、以 RGBA 世界观管理颜色、就地修改并返回原数组,是项目全部高层标注器共享的底层绘制内核。想要继续深入,可对照 src/supervision/draw/utils.pysrc/supervision/draw/color.py 的完整实现,以及 tests/draw/test_utils.pytests/draw/test_color.py 中逐像素级的测试断言;想观察这些原语在「大图标注」场景下的成品效果,则可参考 docs/detection/annotators.md 等上层标注器文档。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388