Files
tokenresearch/tokenizer.md
2026-06-05 15:55:39 +08:00

511 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# DETR 的视觉 token 化过程说明
本文基于当前项目代码 `app/detector.py` 中的实现说明 DETR 的“token 化”过程。
当前代码使用的是 Hugging Face Transformers
```python
self.processor = DetrImageProcessor.from_pretrained(model_name)
self.model = DetrForObjectDetection.from_pretrained(model_name)
```
默认模型为:
```text
facebook/detr-resnet-50
```
需要注意DETR 这里没有文本 tokenizer。它处理的是图像因此所谓“token 化”指的是把图像经过 CNN backbone 后得到的二维视觉特征图,展开成 Transformer 可以处理的一维视觉 token 序列。
## 当前代码中的入口
`app/detector.py` 中,检测入口是:
```python
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)
```
这里分成两部分:
1. `DetrImageProcessor`:做图像预处理。
2. `DetrForObjectDetection`在模型内部完成视觉特征提取、flatten、位置编码、Transformer 编码解码和目标检测。
## 总体流程
完整流程可以理解为:
```text
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 哪个环节”,最核心的是:
```text
第 6 步:产生视觉 token embedding
第 7 步:加入位置 embedding
第 9 步object query embedding 进入 Decoder
```
第 6 步是图像内容 embedding第 7 步是空间位置 embedding第 9 步是检测目标查询 embedding。
## 第 1 步:图像预处理
代码:
```python
inputs = self.processor(images=image, return_tensors="pt")
```
`DetrImageProcessor` 主要做这些事情:
- 调整图像尺寸。
- 转换为 PyTorch tensor。
- 归一化像素值。
- 生成 `pixel_values`
- 必要时生成 `pixel_mask`,用于标记 padding 区域。
输出通常包含:
```python
{
"pixel_values": Tensor[batch, 3, H, W],
"pixel_mask": Tensor[batch, H, W]
}
```
其中:
- `pixel_values` 是送入模型的图像张量。
- `pixel_mask` 用于告诉模型哪些区域是真实图像,哪些区域是 padding。
这一步还没有生成 DETR 的视觉 token。它只是把图像整理成模型可以接收的输入张量。
它也不是文本 token 化,不会产生 `input_ids``attention_mask` 这类 NLP tokenizer 输出。
## 第 2 步ResNet-50 提取特征图
模型内部首先使用 ResNet-50 backbone 处理图像:
```text
pixel_values: [batch, 3, H, W]
↓ ResNet-50
feature map: [batch, 2048, H', W']
```
`H'``W'` 是下采样后的空间尺寸。ResNet 通常会把图像下采样约 32 倍。
例如输入图像尺寸为:
```text
[batch, 3, 800, 1280]
```
经过 ResNet 后,特征图可能接近:
```text
[batch, 2048, 25, 40]
```
这里的每个空间位置 `(y, x)` 都是一个高层视觉特征向量。
## 第 3 步1×1 卷积投影通道
ResNet 输出通道数通常是 `2048`,而 DETR Transformer 默认隐藏维度通常是 `256`
因此模型会使用一个 `1×1 convolution` 做通道投影:
```text
[batch, 2048, H', W']
↓ 1×1 conv
[batch, 256, H', W']
```
这一步不会改变空间大小,只改变每个空间位置的特征维度。
可以理解为:
```text
每个网格点的 2048 维向量 → 256 维向量
```
## 第 4 步flatten 成视觉 token 序列
这是“视觉 token 化”的核心步骤。
当前项目代码中对应的是:
```python
tokens = projected_feature_map.flatten(2).permute(0, 2, 1)
```
投影后的特征图形状是:
```text
[batch, 256, H', W']
```
模型会把二维空间维度 `H' × W'` 展开成一维序列:
```text
[batch, 256, H', W']
↓ flatten H' 和 W'
[batch, 256, H'×W']
↓ 调整维度顺序
[batch, H'×W', 256]
```
如果特征图是:
```text
[batch, 256, 25, 40]
```
那么 token 数是:
```text
25 × 40 = 1000
```
最终得到:
```text
[batch, 1000, 256]
```
也就是说:
```text
每个特征图网格位置 = 1 个视觉 token
每个视觉 token = 1 个 256 维向量
```
伪代码可以写成:
```python
# 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 格式:
```python
# 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
```text
token_0, token_1, token_2, ..., token_N
```
如果不加入位置编码,模型不知道某个 token 原来位于图像左上角、中心还是右下角。
因此 DETR 会为特征图每个 `(y, x)` 位置生成二维位置编码:
```text
position encoding: [batch, 256, H', W']
```
然后同样 flatten
```text
[batch, 256, H', W']
[batch, H'×W', 256]
```
Transformer Encoder 接收的是:
```text
visual token + positional encoding
```
位置编码让模型知道 token 的空间布局。
## 第 6 步Transformer Encoder 处理视觉 token
经过 flatten 和位置编码后,视觉 token 序列进入 Transformer Encoder
```text
[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。常见数量是
```text
100 个 object queries
```
这些 queries 进入 Transformer Decoder并关注 Encoder 输出的视觉 token
```text
object queries: [batch, 100, 256]
encoder tokens: [batch, H'×W', 256]
↓ Transformer Decoder
object features: [batch, 100, 256]
```
每个 query 最终预测一个候选目标:
```text
类别 + 边界框
```
因此 DETR 输出通常可以理解为:
```text
最多 100 个候选目标
```
每个候选目标会包含:
- 类别 logits。
- 归一化边界框。
## 第 8 步:后处理成车辆检测结果
当前代码中的后处理是:
```python
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`
然后代码过滤车辆类别:
```python
label_name = self.model.config.id2label[label.item()]
if label_name not in self.vehicle_labels:
continue
```
默认车辆类别为:
```text
car, motorcycle, bus, truck, bicycle
```
最终输出格式:
```python
{
"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 通常是:
```text
原图 -> 切成 16×16 patch -> 线性投影 -> token
```
DETR-ResNet 是:
```text
原图 -> ResNet 特征图 -> 每个特征图网格点 -> token
```
因此 DETR 的每个 token
- 对应特征图上的一个空间位置。
- 感受野来自 ResNet 深层网络。
- 通常覆盖原图中一片较大的区域。
- 相邻 token 的感受野会重叠。
## 一个具体尺寸例子
假设预处理后图像大小为:
```text
800 × 1280
```
ResNet-50 下采样约 32 倍:
```text
H' = 800 / 32 ≈ 25
W' = 1280 / 32 ≈ 40
```
Backbone 输出:
```text
[batch, 2048, 25, 40]
```
1×1 卷积投影:
```text
[batch, 256, 25, 40]
```
flatten
```text
[batch, 1000, 256]
```
所以该图像大约会生成:
```text
1000 个视觉 token
```
每个 token 是:
```text
256 维连续向量
```
## 当前项目中的实际检测路径
当前项目的整体路径是:
```text
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 化具体是:
```text
图像经过 ResNet-50 得到二维特征图
1×1 卷积把通道投影到 Transformer hidden size
把 H' × W' 个空间位置 flatten 成 H'×W' 个视觉 token
给每个 token 加二维位置编码
送入 Transformer Encoder
```
因此“token 数”主要由输入分辨率和 backbone 下采样比例决定:
```text
视觉 token 数 ≈ (输入高度 / 32) × (输入宽度 / 32)
```
实际数量会受到图像预处理 resize、padding 和特征图尺寸取整影响。