# 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。 这一步不是文本 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 化”的核心步骤。 投影后的特征图形状是: ```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 和特征图尺寸取整影响。