11 KiB
DETR 的视觉 token 化过程说明
本文基于当前项目代码 app/detector.py 中的实现说明 DETR 的“token 化”过程。
当前代码使用的是 Hugging Face Transformers:
self.processor = DetrImageProcessor.from_pretrained(model_name)
self.model = DetrForObjectDetection.from_pretrained(model_name)
默认模型为:
facebook/detr-resnet-50
需要注意:DETR 这里没有文本 tokenizer。它处理的是图像,因此所谓“token 化”指的是把图像经过 CNN backbone 后得到的二维视觉特征图,展开成 Transformer 可以处理的一维视觉 token 序列。
当前代码中的入口
在 app/detector.py 中,检测入口是:
image = Image.fromarray(frame_rgb)
inputs = self.processor(images=image, return_tensors="pt")
inputs = {key: value.to(self.device) for key, value in inputs.items()}
outputs = self.model(**inputs)
这里分成两部分:
DetrImageProcessor:做图像预处理。DetrForObjectDetection:在模型内部完成视觉特征提取、flatten、位置编码、Transformer 编码解码和目标检测。
总体流程
完整流程可以理解为:
OpenCV RGB 帧
↓
PIL Image
↓
DetrImageProcessor 图像预处理
↓
pixel_values: [batch, 3, H, W]
pixel_mask: [batch, H, W]
↓
ResNet-50 backbone 提取视觉特征
↓
feature map: [batch, 2048, H', W']
↓
1×1 convolution 投影通道
↓
projected feature map: [batch, 256, H', W']
↓
flatten 空间维度 H' × W'
↓
visual token embeddings: [batch, H'×W', 256]
↓
加入二维位置 embedding
↓
Transformer Encoder
↓
Object query embeddings + Transformer Decoder
↓
类别 logits + 边界框 boxes
↓
post_process_object_detection 还原到原图坐标
Embedding 在 1-11 个环节中的位置
在这个 DETR 流程里,embedding 不是单独只有一步,而是出现在 3 个关键环节:
| 页面步骤 | 名称 | embedding 含义 |
|---|---|---|
| 第 6 步 | visual token embedding | projected feature map 经过 flatten 后,每个空间网格点变成一个 256 维视觉 token embedding。 |
| 第 7 步 | position embedding | 给每个视觉 token 加入二维位置 embedding,让 Transformer 知道 token 原本在图像中的位置。 |
| 第 9 步 | object query embedding | DETR 使用一组可学习的 object query embeddings 进入 Decoder,每个 query 最终预测一个候选目标。 |
所以如果问“embedding 在 1-11 哪个环节”,最核心的是:
第 6 步:产生视觉 token embedding
第 7 步:加入位置 embedding
第 9 步:object query embedding 进入 Decoder
第 6 步是图像内容 embedding,第 7 步是空间位置 embedding,第 9 步是检测目标查询 embedding。
第 1 步:图像预处理
代码:
inputs = self.processor(images=image, return_tensors="pt")
DetrImageProcessor 主要做这些事情:
- 调整图像尺寸。
- 转换为 PyTorch tensor。
- 归一化像素值。
- 生成
pixel_values。 - 必要时生成
pixel_mask,用于标记 padding 区域。
输出通常包含:
{
"pixel_values": Tensor[batch, 3, H, W],
"pixel_mask": Tensor[batch, H, W]
}
其中:
pixel_values是送入模型的图像张量。pixel_mask用于告诉模型哪些区域是真实图像,哪些区域是 padding。
这一步不是文本 token 化,不会产生 input_ids、attention_mask 这类 NLP tokenizer 输出。
第 2 步:ResNet-50 提取特征图
模型内部首先使用 ResNet-50 backbone 处理图像:
pixel_values: [batch, 3, H, W]
↓ ResNet-50
feature map: [batch, 2048, H', W']
H' 和 W' 是下采样后的空间尺寸。ResNet 通常会把图像下采样约 32 倍。
例如输入图像尺寸为:
[batch, 3, 800, 1280]
经过 ResNet 后,特征图可能接近:
[batch, 2048, 25, 40]
这里的每个空间位置 (y, x) 都是一个高层视觉特征向量。
第 3 步:1×1 卷积投影通道
ResNet 输出通道数通常是 2048,而 DETR Transformer 默认隐藏维度通常是 256。
因此模型会使用一个 1×1 convolution 做通道投影:
[batch, 2048, H', W']
↓ 1×1 conv
[batch, 256, H', W']
这一步不会改变空间大小,只改变每个空间位置的特征维度。
可以理解为:
每个网格点的 2048 维向量 → 256 维向量
第 4 步:flatten 成视觉 token 序列
这是“视觉 token 化”的核心步骤。
投影后的特征图形状是:
[batch, 256, H', W']
模型会把二维空间维度 H' × W' 展开成一维序列:
[batch, 256, H', W']
↓ flatten H' 和 W'
[batch, 256, H'×W']
↓ 调整维度顺序
[batch, H'×W', 256]
如果特征图是:
[batch, 256, 25, 40]
那么 token 数是:
25 × 40 = 1000
最终得到:
[batch, 1000, 256]
也就是说:
每个特征图网格位置 = 1 个视觉 token
每个视觉 token = 1 个 256 维向量
伪代码可以写成:
# x: [batch, 256, h, w]
x = x.flatten(2) # [batch, 256, h*w]
x = x.transpose(1, 2) # [batch, h*w, 256]
原始 DETR 论文和部分 PyTorch 实现中也常见 sequence-first 格式:
# x: [batch, 256, h, w]
x = x.flatten(2) # [batch, 256, h*w]
x = x.permute(2, 0, 1) # [h*w, batch, 256]
两种写法本质相同,只是 Transformer 接口期望的维度顺序不同。
第 5 步:加入二维位置 embedding
Transformer 本身不理解图像中的二维空间位置。
flatten 后,模型只看到一串 token:
token_0, token_1, token_2, ..., token_N
如果不加入位置编码,模型不知道某个 token 原来位于图像左上角、中心还是右下角。
因此 DETR 会为特征图每个 (y, x) 位置生成二维位置编码:
position encoding: [batch, 256, H', W']
然后同样 flatten:
[batch, 256, H', W']
↓
[batch, H'×W', 256]
Transformer Encoder 接收的是:
visual token + positional encoding
位置编码让模型知道 token 的空间布局。
第 6 步:Transformer Encoder 处理视觉 token
经过 flatten 和位置编码后,视觉 token 序列进入 Transformer Encoder:
[batch, H'×W', 256]
↓ Transformer Encoder
[batch, H'×W', 256]
Encoder 会通过 self-attention 建模图像中不同区域之间的关系。
例如:
- 车头区域可以关注车身区域。
- 道路区域可以影响车辆判断。
- 远处小目标可以和周围上下文一起被理解。
第 7 步:Object query embedding 和 Transformer Decoder
DETR 与传统检测器不同,它不是先生成大量 anchor box。
它使用一组可学习的 object query embeddings。常见数量是:
100 个 object queries
这些 queries 进入 Transformer Decoder,并关注 Encoder 输出的视觉 token:
object queries: [batch, 100, 256]
encoder tokens: [batch, H'×W', 256]
↓ Transformer Decoder
object features: [batch, 100, 256]
每个 query 最终预测一个候选目标:
类别 + 边界框
因此 DETR 输出通常可以理解为:
最多 100 个候选目标
每个候选目标会包含:
- 类别 logits。
- 归一化边界框。
第 8 步:后处理成车辆检测结果
当前代码中的后处理是:
target_sizes = torch.tensor([image.size[::-1]], device=self.device)
results = self.processor.post_process_object_detection(
outputs,
target_sizes=target_sizes,
threshold=self.confidence,
)[0]
这一步会:
- 把模型输出的归一化框还原为原图坐标。
- 根据置信度阈值过滤低分检测。
- 返回
scores、labels、boxes。
然后代码过滤车辆类别:
label_name = self.model.config.id2label[label.item()]
if label_name not in self.vehicle_labels:
continue
默认车辆类别为:
car, motorcycle, bus, truck, bicycle
最终输出格式:
{
"label": "car",
"score": 0.9132,
"box": [x1, y1, x2, y2]
}
与文本 tokenizer 的区别
| 对比项 | 文本 tokenizer | DETR 视觉 token 化 |
|---|---|---|
| 输入 | 文本字符串 | 图像张量 |
| 输出 | token id 序列 | 视觉特征向量序列 |
| token 来源 | 词、子词、字符片段 | CNN 特征图空间网格 |
| token 内容 | 离散整数 id | 连续浮点向量 |
| 位置编码 | 一维位置编码 | 二维图像位置编码 |
| 当前代码中的类 | 无 | DetrImageProcessor + DetrForObjectDetection |
为什么说它是 patch-like features
它们可以叫做 patch-like features,但不是 ViT 那种直接切原图 patch。
ViT 通常是:
原图 -> 切成 16×16 patch -> 线性投影 -> token
DETR-ResNet 是:
原图 -> ResNet 特征图 -> 每个特征图网格点 -> token
因此 DETR 的每个 token:
- 对应特征图上的一个空间位置。
- 感受野来自 ResNet 深层网络。
- 通常覆盖原图中一片较大的区域。
- 相邻 token 的感受野会重叠。
一个具体尺寸例子
假设预处理后图像大小为:
800 × 1280
ResNet-50 下采样约 32 倍:
H' = 800 / 32 ≈ 25
W' = 1280 / 32 ≈ 40
Backbone 输出:
[batch, 2048, 25, 40]
1×1 卷积投影:
[batch, 256, 25, 40]
flatten:
[batch, 1000, 256]
所以该图像大约会生成:
1000 个视觉 token
每个 token 是:
256 维连续向量
当前项目中的实际检测路径
当前项目的整体路径是:
RTSP/HLS 视频帧
↓
OpenCV 读取 BGR frame
↓
cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
↓
Image.fromarray(frame_rgb)
↓
DetrImageProcessor 图像预处理
↓
DetrForObjectDetection 内部完成视觉 token 化和目标检测
↓
post_process_object_detection
↓
过滤车辆类别
↓
OpenCV OSD 画框
↓
FastAPI /video 输出动态打标画面
对应代码位置:
app/detector.py:DETR 图像预处理、模型推理、后处理。app/stream_worker.py:视频帧读取、推理调用、OSD 画框。app/main.py:MJPEG 视频流和检测结果接口。
小结
当前项目中的 DETR 不包含文本 tokenizer。
它的视觉 token 化具体是:
图像经过 ResNet-50 得到二维特征图
↓
1×1 卷积把通道投影到 Transformer hidden size
↓
把 H' × W' 个空间位置 flatten 成 H'×W' 个视觉 token
↓
给每个 token 加二维位置编码
↓
送入 Transformer Encoder
因此,“token 数”主要由输入分辨率和 backbone 下采样比例决定:
视觉 token 数 ≈ (输入高度 / 32) × (输入宽度 / 32)
实际数量会受到图像预处理 resize、padding 和特征图尺寸取整影响。