查看 Markdown

使用 Results 对象

每次预测都会为每张图像返回一个 Results 对象。它为每一种载荷准备了一个具名槽位(slot),除了模型实际产出的那些之外全都是空的;导出的产物上也有同样这些槽位。

一个对象,每种载荷一个槽位

对一张图像做一次预测返回一个 Results。它带着十八个载荷槽位,模型只填充自己 这个任务会产出的那些。其余每个槽位都是 None,所以在检测器上读 result.masks 得到的是 None,而不是报错。

槽位形状由什么产出
boxesBoxes(N, 4),外加分数和类别检测,以及任何先做定位的任务
masksMasks(N, H, W)实例分割
keypointsKeypoints(N, K, 2)(N, K, 3)姿态
probsProbs(C,)分类
obbOBB(N, 7)(N, 8)旋转框
gazeGaze(N, 2),弧度制的俯仰角和偏航角视线估计
pointsPoints(N, 4),依次是 x、y、类别、置信度点定位
semantic_maskSemanticMask(H, W) 类别 id语义分割
panopticPanopticSegmentation(H, W) 分段 id,外加 segments_info全景分割
depth_mapDepthMap(H, W) 浮点数深度估计
normal_mapNormalMap(H, W, 3) 单位向量表面法线
edgesEdgeMap[0, 1] 区间的 (H, W) 浮点数边缘检测
restoredRestoredImage(H, W, 3) uint8 RGB图像恢复与超分辨率
matteMatte[0, 1] 区间的 (H, W) 浮点数alpha 抠图与背景移除
ocrOCRRegions(N, 4, 2) 多边形,外加转写文本文本检测与识别
embeddingsEmbeddings(N, D),行已做 L2 归一化embed 任务
identitiesIdentitiesN 个名字和分数带图库的 embed 任务
meshesMeshes人体参数,以及可选的顶点人体网格恢复

和它们并列的,是每个结果都有的那些字段:orig_shape(height, width)path 是源路径(内存输入时为 None),names 把类别 id 映射到类别名, frame_idx 用于视频和实时帧,track_id 在跟踪时出现,还有 restore_scale, 即恢复类结果的整数放大倍数。

result.normalsresult.normal_map 的别名。

result.speed 每个结果上都有,但只有集成会填充它, 这时它的键是 member_0member_1fusion,单位是毫秒。单个模型时它一直 是一个空字典。

Boxes

Boxes
from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreYOLO9s.pt")result = model(SAMPLE_IMAGE) print(result.orig_shape)   # 源图像的 (height, width)print(result.path)         # 源路径,内存输入时为 None for xyxy, conf, cls in zip(    result.boxes.xyxy.tolist(),    result.boxes.conf.tolist(),    result.boxes.cls.tolist(),):    print(result.names[int(cls)], round(float(conf), 3), xyxy)
归一化坐标
from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreYOLO9s.pt")result = model(SAMPLE_IMAGE) print(result.boxes.xyxy[:1])    # 像素,x1 y1 x2 y2print(result.boxes.xywh[:1])    # 像素,中心 x、中心 y、w、hprint(result.boxes.xyxyn[:1])   # 同一个框除以宽和高print(result.boxes.xywhn[:1])
NumPy 与设备
from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreYOLO9s.pt")result = model(SAMPLE_IMAGE) # 下面每一个都返回新的 Results,原对象不变as_numpy = result.numpy()on_cpu = result.cpu() print(type(as_numpy.boxes.xyxy).__name__)print(type(on_cpu.boxes.xyxy).__name__)

Boxes 把坐标和分数分开存成几个数组,而不是打包成一个张量。

属性内容
xyxy(N, 4) 绝对像素,x1 y1 x2 y2
xywh(N, 4) 绝对像素,中心 x、中心 y、宽、高
xyxynxywhn同样的值除以图像宽和高
conf(N,) 置信度
cls(N,) 类别 id,浮点数形式
id(N,) 跟踪 id,或者 None
is_trackid 是否已设置
data全部拼接在一起:检测框、可选的 id、conf、cls

cls 是浮点数组,所以要这样用它:result.names[int(cls)]

xyxynxywhn 需要 orig_shape,这个字段由 Results 替你填好。

稠密载荷

覆盖整张图像的载荷,行为和逐实例的载荷不同,切片的时候这一点很重要。

SemanticMask 存的是原始画布上的 (H, W) 类别 id,其中 255 被保留为忽略值, 永远不算作一个类别。classes 列出出现过的 id,并把它排除在外;class_mask(id) 返回一个布尔的 (H, W)

PanopticSegmentation 存的是 (H, W) 分段 id,0 是 void id,另外还有一个 segments_info 字典列表,每项至少带 idcategory_idsegment_ids 列出 出现过的 id,segment_mask(id) 选出其中一个。

DepthMap 存的是 (H, W) 相对逆深度:值越大表示越近,而且这些值不是以米为 单位的度量值。它提供在有限值上算出的 minmaxmean,以及把范围重新缩放 到 [0, 1]normalized()

NormalMap 存的是 OpenCV 相机坐标系下的 (H, W, 3) 单位向量,+x 向右, +y 向下,+z 指向场景内部,所以正对相机的表面是 (0, 0, -1)assert_normalized() 检查每个像素都是有限值、且长度为 1。

EdgeMap 存的是 [0, 1] 区间的 (H, W) float32。这里保留的是连续图,而不是 阈值化之后的结果,所以选择截断点的地方是 binary(threshold=0.5)

Matte 存的是 [0, 1] 区间的 (H, W) float32,1 表示完全是前景。array 返回裁剪后的 float32。

RestoredImage 存的是 (H, W, 3) uint8 RGB,array 给出原始 ndarray, save(path) 把它写出去。

Probs 为整张图像存一个概率向量。top1top5 是类别索引,top1conftop5conf 是对应的分数。

Embeddings 存的是已经做过 L2 归一化的 (N, D) 行,所以余弦相似度就是一次 点积。similarity(other) 对图库返回 (N, M),对单个向量返回 (N,)verify(i, j, threshold=0.4) 比较两行。

OCRRegions 按阅读顺序存 (N, 4, 2) 多边形,四个角点依次是左上、右上、右下、 左下。转写文本在 texts 里,识别分数在 conf 里,检测分数在 det_conf 里。 因为这些是真正的旋转多边形,它们不会填充 boxes;需要矩形时,ocr.xyxy 给出 轴对齐的外接框。

切片与搬移

result[i] 返回一个只装着一个实例的新 Results。逐实例的载荷会被切片,整图的 载荷则原样带过去,所以对一个分类结果切片不会把它的概率向量截断成单个类别,对一 个深度结果切片也不会破坏 (H, W) 的布局。

len(result) 数的是实例:检测框、点、嵌入向量、OCR 区域或网格。任何稠密的整图 载荷都算作 1。什么都没装的结果是 0

to()cpu()cuda()numpy() 各自返回一个新的 Results,其中每个已 填充的槽位都转换过。它们不会修改原对象。

update() 是唯一一个原地修改的方法,它替换指定名字的槽位,并返回同一个对象。

JSON

summary 与 to_json
import json from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreYOLO9s.pt")result = model(SAMPLE_IMAGE) rows = result.summary()print(json.dumps(rows[:2], indent=2)) # 同样的内容,只是以字符串返回,关键字参数也一样print(result.to_json(normalize=True, decimals=3)[:200])
CLI
libreyolo predict model=LibreYOLO9s.pt --json \  source=https://raw.githubusercontent.com/LibreYOLO/libreyolo/release/libreyolo/assets/parkour.jpg

summary() 返回一个由普通字典组成的列表,to_json() 就是把这个列表交给 json.dumps。两者接受同样的三个参数:normalize=False 把坐标切换成 [0, 1]decimals=5 设置四舍五入位数,embeddings=False 控制要不要包含嵌入向量。

每一行的形状跟着载荷走。检测行带 nameclassconfidence 和一个 box 字典,有掩码时会加上 segments,旋转框会加上 obbcorners,视线会加上以 弧度和角度两种单位给出的 gaze 角度,跟踪时加上 track_id,有网格时加上 mesh 参数。

没有检测框时,由某一个载荷来决定这些行:OCR 每个区域输出一行,带上它的 text,点每个点一行,全景每个分段一行、带 pixel_countpixel_fraction, 语义每个出现过的类别一行,分类输出前五个类别。深度、法线、边缘、恢复和抠图各自 只输出一行汇总,描述的是这张图本身,而不是它的像素。

有两种载荷是有意精简过的。嵌入向量只报告 embedding_dim,因为一行 512 个浮点数 意味着每张脸大约 2 KB;传 embeddings=True 才会把数值也包含进来。网格顶点则 完全不包含,因为那是每个人几万个坐标。要拿几何数据,读 result.meshes.vertices 或者调用 result.meshes.save_obj(path)

绘制与保存

标注后的图像
from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreYOLO9s.pt") # save=True 会画出载荷,并写到 runs/detect/predict* 下result = model(SAMPLE_IMAGE, save=True)print(result.saved_path)

predict(save=True) 才是那条既标注又写盘的路径。它根据哪个槽位被填充来挑选绘制 例程,所以语义结果写出来是一张上色(colorize)后的掩码,深度结果写成深度可视化 图,全景结果带上它的分段,matte 写成透明背景的 RGBA PNG,检测器写成检测框、掩码 画在框下面。写出的路径会作为 result.saved_path 挂在结果上。

Results.plot() 比它的名字听上去要窄。它只为法线图和边缘图定义,其他任何情况都 抛 NotImplementedError。别的任务请用 save=True

Results.save(path) 同样窄:它把 matte 结果写成透明背景的 RGBA PNG 抠图,其他 情况一律抛 NotImplementedErrorResults.cutout() 返回同一个 RGBA 数组,但 不写盘。两者都需要源图像,从 result.path 取,或者用 image= 传进来。

有两种载荷自带写盘方法:恢复后的图像用 result.restored.save(path),网格用 result.meshes.save_obj(path, index=0)

文件最终落在哪里,以及 output_pathoutput_file_format 的行为,见 预测数据源

导出产物返回同样的对象

安装导出用的 extra
pip install "libreyolo[onnx]"
从导出产物拿到同样的 Results
from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreYOLO9s.pt")path = model.export(format="onnx")   # 返回写入的路径 # LibreYOLO() 按文件后缀分派exported = LibreYOLO(path)result = exported(SAMPLE_IMAGE) print(type(result).__name__, len(result.boxes))

LibreYOLO() 按文件后缀分派,所以一个导出产物和一个 .pt 检查点(checkpoint) 走同一个调用加载,返回同样的 Results.onnx.engine.pte.mnn 文件按后缀识别,OpenVINO、Paddle 和 ncnn 目录以及 Triton 模型 URL 也一样。把一个 模型换成它的导出版本时,读 result.boxes.xyxy 的代码不用改。完整的格式清单见 导出

反过来,直接去用运行时自己的 API,就意味着预处理、后处理和类别名都得你自己管。

载荷类、槽位、搬移语义、summary()、to_json()、plot()、save() 和 cutout() 读自 libreyolo/utils/results.py。标注和写盘行为读自 libreyolo/models/base/inference.py 里的 InferenceRunner._save_annotated_image 和 libreyolo/utils/general.py 里的 resolve_save_path。后缀分派读自 libreyolo/models/__init__.py 里的 LibreYOLO()。