Transformers 零样本目标检测实战:基于 OWL-ViT 的开放词表物体检测指南
【免费下载链接】transformers🤗 Transformers: the model-definition framework for state-of-the-art machine learning models in text, vision, audio, and multimodal models, for both inference and training.项目地址: https://gitcode.com/GitHub_Trending/tra/transformers
零样本目标检测(Zero-shot object detection)是计算机视觉中无需针对目标类别进行任何标注训练,即可在图像中定位并识别物体的任务。本文以 Transformers 仓库中的 docs/source/ja/tasks/zero_shot_object_detection.md 任务指南为骨架,系统讲解如何基于 OWL-ViT 模型完成文本提示检测、批量检测与图像引导检测三类实战场景,并结合仓库内 OWL-ViT 的模型、处理器与 pipeline 源码,深入说明其内部工作原理与后处理细节。读完本文,你将掌握从一行代码调用 pipeline 到手工组装推理全流程的完整技能。
从传统检测到零样本检测:为什么需要开放词表
传统目标检测模型通常依赖带标注的图像数据集进行训练,因此只能检测训练数据中出现过的类别集合;一旦目标类别不在训练集中,模型便无能为力。
零样本目标检测采用完全不同的思路:模型接收一张图像和一组候选类别(以自由文本形式给出),直接输出物体所在位置的边界框(bounding box)与类别标签,整个过程不需要针对这些类别做任何微调。实现这一能力的关键模型就是OWL-ViT(Vision Transformer for Open-World Localization),一个开放词表(open-vocabulary)目标检测器。
从仓库源码可以看出 OWL-ViT 的开放词表能力来源:它的配置类由OwlViTTextConfig与OwlViTVisionConfig两个子配置组成,视觉侧采用 ViT 风格的 Transformer 提取图像特征,文本侧采用类 CLIP 的文本编码器;模型将多模态表征与轻量级物体分类头(OwlViTClassPredictionHead)、定位头(OwlViTBoxPredictionHead)组合,实现开放词表检测(见 modeling_owlvit.py)。检测时先用 CLIP 文本编码器把自由文本查询嵌入成向量,再将其作为分类与定位头的输入,与图像 patch 特征做相似度匹配。
作者先从头训练 CLIP,再使用标准检测数据集通过二部匹配损失(bipartite matching loss)对 OWL-ViT 进行端到端微调。借助这一方案,模型无需在标注数据集上预先训练,即可根据文本描述检测任意物体。
开始动手前,请先确认已安装必要依赖:
pip install -q transformers使用 pipeline 完成零样本目标检测
用 OWL-ViT 做推理最简单的方式是通过 Transformers 的pipeline。在transformers.pipelines中,任务标识"zero-shot-object-detection"对应ZeroShotObjectDetectionPipeline(见 zero_shot_object_detection.py),其模型白名单由 modeling_auto.py 中的MODEL_FOR_ZERO_SHOT_OBJECT_DETECTION_MAPPING_NAMES定义,当前包括grounding-dino、owlv2、owlvit等架构,本文以经典的google/owlvit-base-patch32检查点为例。
>>> from transformers import pipeline >>> checkpoint = "google/owlvit-base-patch32" >>> detector = pipeline(model=checkpoint, task="zero-shot-object-detection")接着选择一张要检测物体的图像。这里以 NASA Great Images 数据集中的宇航员 Eileen Collins 照片为例,使用skimage加载内置示例图:
>>> import skimage >>> import numpy as np >>> from PIL import Image >>> image = skimage.data.astronaut() >>> image = Image.fromarray(np.uint8(image)).convert("RGB") >>> image把图像和希望查找的候选物体标签一起传给 pipeline。图像可以直接传 PIL 对象,也支持本地路径或图片 URL;同时传入所有需要查询的文本描述:
>>> predictions = detector( ... image, ... candidate_labels=["human face", "rocket", "nasa badge", "star-spangled banner"], ... ) >>> predictions [{'score': 0.3571370542049408, 'label': 'human face', 'box': {'xmin': 180, 'ymin': 71, 'xmax': 271, 'ymax': 178}}, {'score': 0.28099656105041504, 'label': 'nasa badge', 'box': {'xmin': 129, 'ymin': 348, 'xmax': 206, 'ymax': 427}}, {'score': 0.2110239565372467, 'label': 'rocket', 'box': {'xmin': 350, 'ymin': -1, 'xmax': 468, 'ymax': 288}}, {'score': 0.13790413737297058, 'label': 'star-spangled banner', 'box': {'xmin': 1, 'ymin': 1, 'xmax': 105, 'ymax': 509}}, {'score': 0.11950037628412247, 'label': 'nasa badge', 'box': {'xmin': 277, 'ymin': 338, 'xmax': 327, 'ymax': 380}}, {'score': 0.10649408400058746, 'label': 'rocket', 'box': {'xmin': 358, 'ymin': 64, 'xmax': 424, 'ymax': 280}}]用 PIL 的ImageDraw把预测结果可视化:
>>> from PIL import ImageDraw >>> draw = ImageDraw.Draw(image) >>> for prediction in predictions: ... box = prediction["box"] ... label = prediction["label"] ... score = prediction["score"] ... xmin, ymin, xmax, ymax = box.values() ... draw.rectangle((xmin, ymin, xmax, ymax), outline="red", width=1) ... draw.text((xmin, ymin), f"{label}: {round(score,2)}", fill="white") >>> image从源码看 pipeline 内部的工作方式:ZeroShotObjectDetectionPipeline继承ChunkPipeline,在preprocess阶段对每个候选标签分别调用 tokenizer 与 image processor,在_forward阶段逐标签前向推理,在postprocess阶段调用image_processor.post_process_object_detection过滤低分框、转换为(xmin, ymin, xmax, ymax)格式的字典,最后按分数降序排序(支持threshold、top_k、timeout等参数,见 zero_shot_object_detection.py)。
手工实现文本提示的零样本目标检测
了解 pipeline 的用法后,下面手动复现同样的结果,以便理解每个环节。
首先从 Hub 加载模型和对应的处理器(processor)。OwlViTProcessor把图像处理器与 CLIP tokenizer 封装为单一实例(见 processing_owlvit.py),图像处理器负责缩放、归一化图像,tokenizer 负责编码文本输入:
>>> from transformers import AutoProcessor, AutoModelForZeroShotObjectDetection >>> model = AutoModelForZeroShotObjectDetection.from_pretrained(checkpoint) >>> processor = AutoProcessor.from_pretrained(checkpoint)换个场景,取一张海滩照片:
>>> import requests >>> url = "https://unsplash.com/photos/oj0zeY2Ltk4/download?ixid=MnwxMjA3fDB8MXxzZWFyY2h8MTR8fHBpY25pY3xlbnwwfHx8fDE2Nzc0OTE1NDk&force=true&w=640" >>> im = Image.open(requests.get(url, stream=True).raw) >>> im用处理器准备模型输入。processor内部通过CLIPTokenizer处理文本、通过图像处理器对图像做 resize 与 normalize:
>>> text_queries = ["hat", "book", "sunglasses", "camera"] >>> inputs = processor(text=text_queries, images=im, return_tensors="pt")将输入送入模型、做后处理并可视化结果。由于图像处理器在喂给模型前已对图像做了缩放,必须调用post_process_object_detection方法把预测的归一化边界框映射回原图坐标系:
>>> import torch >>> with torch.no_grad(): ... outputs = model(**inputs) ... target_sizes = torch.tensor([im.size[::-1]]) ... results = processor.post_process_object_detection(outputs, threshold=0.1, target_sizes=target_sizes)[0] >>> draw = ImageDraw.Draw(im) >>> scores = results["scores"].tolist() >>> labels = results["labels"].tolist() >>> boxes = results["boxes"].tolist() >>> for box, score, label in zip(boxes, scores, labels): ... xmin, ymin, xmax, ymax = box ... draw.rectangle((xmin, ymin, xmax, ymax), outline="red", width=1) ... draw.text((xmin, ymin), f"{text_queries[label]}: {round(score,2)}", fill="white") >>> im这段代码的背后逻辑值得展开。OwlViTProcessor在__call__中把文本编码为input_ids与attention_mask,把图像编码为pixel_values(processing_owlvit.py)。模型的forward流程(modeling_owlvit.py)依次为:
image_text_embedder同时计算文本与图像嵌入;- 将图像特征重排为
(batch_size, num_patches_height, num_patches_width, hidden_dim)的二维特征图,再展平为 patch 序列; class_predictor用文本查询嵌入与图像特征做点积相似度,得到每个 patch 相对每个查询的 logits(分类头还会应用可学习的 logit shift/scale);box_predictor输出每个 patch 中心化的边界框,并加上基于特征网格位置计算的 box bias 后经 sigmoid 归一化。
后处理时,post_process_object_detection(image_processing_owlvit.py)对每个 patch 取 logits 最大值、经 sigmoid 得到置信度,把中心格式(cxcywh)转为角点格式(x0y0x1y1),再按target_sizes缩放为原图绝对坐标,最后用threshold(默认 0.1)过滤低分预测。注意target_sizes的元素顺序是(height, width),因此用im.size[::-1]把 PIL 的(width, height)反转。
另外,仓库的OwlViTProcessor还提供post_process_grounded_object_detection便捷方法(processing_owlvit.py),它内部调用图像处理器的post_process_object_detection,并额外根据text_labels把预测的类别索引映射回可读的文本标签,输出键包含"scores"、"labels"、"boxes"与"text_labels"。
批量处理:一次检测多张图像
可以同时传入多组图像与文本查询,在多张图像中搜索不同(或相同)的物体。把宇航员图像与海滩图像组合起来:批量处理时,文本查询要以嵌套列表形式传给处理器,图像则以 PIL 图像、PyTorch 张量或 NumPy 数组的列表形式传入。
>>> images = [image, im] >>> text_queries = [ ... ["human face", "rocket", "nasa badge", "star-spangled banner"], ... ["hat", "book", "sunglasses", "camera"], ... ] >>> inputs = processor(text=text_queries, images=images, return_tensors="pt")处理器对嵌套文本列表的处理逻辑在 processing_owlvit.py 中:先计算批次内最大的查询数量,不足的样本用空白字符串补齐到相同长度,再统一编码后沿 batch 维拼接,保证input_ids形状对齐。
后处理时,此前单张图像用张量传尺寸,这里可以传元组;多张图像则传元组列表。为两个样本生成预测,并可视化第二个样本(image_idx = 1):
>>> with torch.no_grad(): ... outputs = model(**inputs) ... target_sizes = [x.size[::-1] for x in images] ... results = processor.post_process_object_detection(outputs, threshold=0.1, target_sizes=target_sizes) >>> image_idx = 1 >>> draw = ImageDraw.Draw(images[image_idx]) >>> scores = results[image_idx]["scores"].tolist() >>> labels = results[image_idx]["labels"].tolist() >>> boxes = results[image_idx]["boxes"].tolist() >>> for box, score, label in zip(boxes, scores, labels): ... xmin, ymin, xmax, ymax = box ... draw.rectangle((xmin, ymin, xmax, ymax), outline="red", width=1) ... draw.text((xmin, ymin), f"{text_queries[image_idx][label]}: {round(score,2)}", fill="white") >>> images[image_idx]post_process_object_detection的返回值是列表,每个元素对应该批次中的一张图像,包含scores、labels、boxes三个键。若target_sizes的数量与 batch 不一致,实现会直接抛出ValueError提示(image_processing_owlvit.py),因此批量场景下务必为每张图像都提供目标尺寸。
图像引导的目标检测
除了文本查询,OWL-ViT 还支持图像引导(image-guided)检测:用一个示例图像作为查询,在目标图像中寻找与之相似的物体。与文本查询不同,图像引导场景只允许一个示例图像作为查询。
取一张沙发上两只猫的图像作为目标图像,另取一张单只猫的图像作为查询:
>>> url = "http://images.cocodataset.org/val2017/000000039769.jpg" >>> image_target = Image.open(requests.get(url, stream=True).raw) >>> query_url = "http://images.cocodataset.org/val2017/000000524280.jpg" >>> query_image = Image.open(requests.get(query_url, stream=True).raw)先快速查看这两张图像:
>>> import matplotlib.pyplot as plt >>> fig, ax = plt.subplots(1, 2) >>> ax[0].imshow(image_target) >>> ax[1].imshow(query_image)预处理阶段,不再传文本查询,而是改用query_images参数:
>>> inputs = processor(images=image_target, query_images=query_image, return_tensors="pt")此时处理器内部会把查询图像编码为query_pixel_values(见 processing_owlvit.py),并且查询图像会覆盖文本提示,模型执行的是图像到图像的匹配而非文本到图像匹配。
预测阶段,不再把输入直接传给模型,而是传给image_guided_detection方法(modeling_owlvit.py),绘制预测框的方式与之前相同,只是没有标签:
>>> with torch.no_grad(): ... outputs = model.image_guided_detection(**inputs) ... target_sizes = torch.tensor([image_target.size[::-1]]) ... results = processor.post_process_image_guided_detection(outputs=outputs, target_sizes=target_sizes)[0] >>> draw = ImageDraw.Draw(image_target) >>> scores = results["scores"].tolist() >>> boxes = results["boxes"].tolist() >>> for box, score, label in zip(boxes, scores, labels): ... xmin, ymin, xmax, ymax = box ... draw.rectangle((xmin, ymin, xmax, ymax), outline="white", width=4) >>> image_target图像引导检测的底层原理可以从源码中看到全貌。image_guided_detection依次执行:
- 分别对查询图像和目标图像调用
image_embedder提取特征图; embed_image_query(modeling_owlvit.py)对查询图像先做一次分类与定位预测,选出与整张查询图 IoU 最高的候选框区域,聚合出"最能代表查询对象"的 class embedding 作为视觉查询向量;- 用该视觉查询向量对目标图像执行与文本查询相同的分类与定位预测。
对应的后处理方法是post_process_image_guided_detection(image_processing_owlvit.py),它比文本版本多了NMS(非极大值抑制)步骤:按分数从高到低遍历预测框,抑制与高置信度框 IoU 超过nms_threshold(默认 0.3)的重复框,再按threshold(默认 0.0)过滤。返回值中labels一律为None,因为该场景是单次(one-shot)检测,没有类别标签。
交互式体验与延伸阅读
如果想交互式地体验 OWL-ViT 推理,可以运行huggingface_hub上的 OWL-ViT Space 演示应用。此外,仓库中还提供了更深入的资料可供继续探索:
- OWL-ViT 模型总览与使用技巧:docs/source/en/model_doc/owlvit.md,其中包含
OwlViTProcessor+OwlViTForObjectDetection的完整示例; - 模型配置(文本/视觉子配置与默认超参):configuration_owlvit.py;
- 模型前向、图像引导检测与各类预测头实现:modeling_owlvit.py;
- 处理器与后处理方法:processing_owlvit.py、image_processing_owlvit.py;
- pipeline 封装与参数说明:zero_shot_object_detection.py;
- 模型测试用例(验证前向、后处理与批处理行为):tests/models/owlvit;
- CLIP 多模态骨干网络文档:docs/source/ja/model_doc/clip.md。
实际部署时需要注意几点:其一,pipeline 支持传入图片 URL、本地路径与 PIL 对象,批量检测可直接传图像列表与对应标签列表(zero_shot_object_detection.py);其二,手工推理务必传入正确的target_sizes((height, width)顺序),否则边界框坐标会错位;其三,文本查询数量在批次内会被自动补齐,最终预测分数可通过调整threshold与 NMS 相关参数平衡召回率与精确率。
【免费下载链接】transformers🤗 Transformers: the model-definition framework for state-of-the-art machine learning models in text, vision, audio, and multimodal models, for both inference and training.项目地址: https://gitcode.com/GitHub_Trending/tra/transformers
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考