# RT-DETRv4 **Repository Path**: get-resource/RT-DETRv4 ## Basic Information - **Project Name**: RT-DETRv4 - **Description**: No description available - **Primary Language**: Python - **License**: Apache-2.0 - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-08-18 - **Last Updated**: 2026-08-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README 既然你的需求明确为**四个关键点预测(4-Point Keypoint Detection)**,而非旋转框(OBB),那么改造思路需要完全摒弃 OBB 的角度/宽高参数化方式。RT-DETRv4 本身是基于 Anchor-Free 的 DETR 架构,将其改为四点关键点检测在逻辑上比改 OBB 更直接,但需要解决**点的语义顺序**和**几何约束**问题。 以下是针对 RT-DETRv4 改造为任意四点关键点预测的具体技术方案: # 1. 待办事项 1. 关键点数据加载和预处理增强。 2. 模型结构改造。已改待验证。 3. 损失函数设计。没有完全改好 4. 后处理策略。... ### 1. 核心改造点概览 | 模块 | RT-DETRv4 原版 | 四点关键点改造版 | 关键注意事项 | | :--- | :--- | :--- | :--- | | **输出头 (Head)** | `(cx, cy, w, h)` + cls | `(cx,cy) + (Δx1..Δy4)` + cls | 输出维度从 4 → 12, 中心点辅助 Query Selection,偏移量提升平移不变性 | | **Query Selection** | 基于中心点 + IoU | 基于中心点 + 关键点置信度/Heatmap | 不能用 Box IoU 选 Query | | **标签匹配 (Matcher)** | Hungarian + Box Cost | Hungarian + Point L2/KPT Cost | 需加入点序一致性约束 | | **Loss Function** | Focal + L1 + GIoU | Focal + L1/L2 + Wing/SmoothL1 | 放弃 GIoU,改用关键点损失 | | **后处理** | Box NMS | Point-based NMS / 无NMS | 依据四点外接矩形做 NMS | | 组件 | 推荐配置 | 理由 | | :--- | :--- | :--- | | 回归 Loss | Wing Loss (w=5, ε=0.5) | 对像素级小误差敏感,适合卡片边缘精定位 | | 几何约束 Loss | Convex Hull Area Loss | 惩罚预测点交叉/凹陷,权重 λ=0.5 | | 可见性 Loss | BCE(可选) | 若存在截断/遮挡则加,否则可省略 | | Matcher Cost | `L2(kpt) + L1(center)` | 无需角度/IoU cost,纯点距离匹配 | KPT Loss:新增 Wing Loss 或 Smooth L1 Loss 作用于 dec_out_kpt_offsets,权重设高(如 1.0 或 5.0),引导模型学习精确的四点。 Convex Hull Loss:作用于 dec_out_kpts(绝对坐标),防止四个点交叉。 Pixel Loss :DLT + Warp 几何矫正,L1 Loss 或 Perceptual Loss 确保矫正后的图像与真实图像相似。 ``` # 要实现将任意四边形(4个角点)透视变换为标准 224x224 的正方形, # 并保证该过程在 PyTorch 中完全可导(Differentiable),以便后续进行梯度回传,我们需要分两步走: # 可微透视变换矩阵计算:使用直接线性变换(DLT)算法,通过 SVD 分解求解 3×3 矩阵。 # 可微图像采样:利用矩阵的逆矩阵生成采样网格,通过 grid_sample 进行双线性插值。 import torch import torch.nn.functional as F def get_homography_matrix(src_points, dst_size=(224, 224)): """ 计算从 src_points 到标准矩形的可微透视变换矩阵 (Homography) Args: src_points: 原图中的4个角点, shape: [B, 4, 2] (顺序必须严格为: 左上, 右上, 右下, 左下) dst_size: 目标图像的宽高 (W, H) Returns: H: 透视变换矩阵, shape: [B, 3, 3] """ B = src_points.shape[0] W, H = dst_size # 目标点 (固定为 224x224 的四个角) dst_points = torch.tensor([ [0, 0], [W, 0], [W, H], [0, H] ], dtype=src_points.dtype, device=src_points.device).unsqueeze(0).expand(B, -1, -1) # [B, 4, 2] # 提取坐标 x, y = src_points[..., 0], src_points[..., 1] u, v = dst_points[..., 0], dst_points[..., 1] # 构建 DLT 方程组 A * h = 0, 其中 A 的 shape 为 [B, 8, 9] # 每一对点产生两行方程 A = torch.stack([ -x, -y, -torch.ones_like(x), torch.zeros_like(x), torch.zeros_like(x), torch.zeros_like(x), x*u, y*u, u, torch.zeros_like(x), torch.zeros_like(x), torch.zeros_like(x), -x, -y, -torch.ones_like(x), x*v, y*v, v ], dim=-1).view(B, 8, 9) # 对 A 进行 SVD 分解: A = U * S * V^T # 最小奇异值对应的右奇异向量即为 h _, _, V = torch.linalg.svd(A) H = V[:, -1, :].view(B, 3, 3) # 归一化 H,使得 H[2, 2] = 1 H = H / H[:, 2:3, 2:3] return H def apply_perspective_warp(image, H, target_size=(224, 224)): """ 使用透视变换矩阵对图像进行可微 Warp Args: image: 原始输入图像, shape: [B, C, H_in, W_in] H: 透视变换矩阵, shape: [B, 3, 3] target_size: 目标图像尺寸 (H_out, W_out) Returns: warped_image: 矫正后的图像, shape: [B, C, H_out, W_out] """ B, C, H_in, W_in = image.shape h_out, w_out = target_size # 1. 生成目标图像的归一化坐标网格 [-1, 1] # 注意:grid_sample 期望的坐标范围是 [-1, 1] grid_y, grid_x = torch.meshgrid( torch.linspace(-1, 1, h_out, device=image.device), torch.linspace(-1, 1, w_out, device=image.device), indexing='ij' ) # 齐次坐标: [h_out, w_out, 3] -> [B, 3, h_out*w_out] ones = torch.ones_like(grid_x) grid_homo = torch.stack([grid_x, grid_y, ones], dim=-1) grid_homo = grid_homo.unsqueeze(0).expand(B, -1, -1, -1).view(B, -1, 3).transpose(1, 2) # 2. 计算 H 的逆矩阵 # grid_sample 的映射方向是 "目标 -> 源",所以我们需要 H_inv H_inv = torch.linalg.inv(H) # 3. 将目标坐标映射回原图坐标系 # src_coords = H_inv * dst_coords src_coords = torch.bmm(H_inv, grid_homo) # [B, 3, N] # 齐次坐标转 2D 坐标 (除以 z) src_coords = src_coords[:, :2, :] / (src_coords[:, 2:, :] + 1e-8) # 4. 还原为 grid_sample 期望的格式: [B, h_out, w_out, 2] sample_grid = src_coords.transpose(1, 2).view(B, h_out, w_out, 2) # 5. 执行可微双线性采样 warped_image = F.grid_sample( image, sample_grid, mode='bilinear', padding_mode='zeros', # 超出边界的部分填充为 0 align_corners=True ) return warped_image ``` ### 2. 详细修改步骤 #### A. 数据表示与标签预处理 * **坐标归一化**:将四个关键点坐标归一化到 `[0, 1]` 或相对于中心点的偏移量 `(Δx, Δy)`。**强烈推荐使用相对于中心点的偏移量**,即模型预测 `(cx, cy, Δx1, Δy1, ..., Δx4, Δy4)`,这样对平移具有不变性,收敛更快。 * **点序定义**:必须严格定义四个点的语义顺序(如:左上→右上→右下→左下,或顺时针)。如果标注数据点序不一致,需在预处理阶段通过**最小外接矩形**或**角度排序**进行统一,否则 Loss 会震荡不收敛。 ```python # 统一点序:确保所有标注为顺时针 TL→TR→BR→BL def normalize_quad(points): # 1. 按 y 排序取上下两组 # 2. 每组内按 x 排序确定左右 # 3. 验证凸性,非凸样本剔除或修正 return ordered_points # shape (4, 2) # 归一化为相对中心偏移量 cx, cy = points.mean(axis=0) offsets = (points - [cx, cy]) / max(w, h) # 除以最大边长保持比例 ``` * **可见性标记**:增加一个 visibility flag `(v1, v2, v3, v4)`,处理遮挡或截断的关键点。不可见点的 Loss 权重应置零或降低。 - **预训练**:加载 RT-DETRv4 COCO 预训练权重,**仅重置 Head**。Backbone+Encoder 迁移学习。 - **增强策略**: - ✅ 随机透视变换(模拟拍摄角度) - ✅ 运动模糊 + 低光照(屏幕/卡片常见场景) - ✅ 随机擦除单个关键点区域(提升遮挡鲁棒性) - ❌ 禁止水平翻转(除非同步调整点序标签) - **Loss 配比**:`λ_cls=1.0, λ_wing=5.0, λ_convex=0.5, λ_center=1.0` - **监控指标**:除 mAP 外,增加 **PCK@5px**(5像素内关键点准确率)和 **Quad IoU**(预测四点与GT四点的多边形IoU)。 #### B. IoU-Aware Query Selection 改造(最关键) RT-DETRv4 的核心优势是 Hybrid Encoder 后的 Top-K Query Selection,原版依赖 Box IoU。改为关键点后: * **方案一(推荐):Center + KPT Score** 保留中心点分支用于粗筛选,新增一个**关键点置信度分支**(类似 HRNet 的 Heatmap)。Query Selection 的综合分数 = `cls_score × center_score × mean(kpt_scores)`。 * **方案二:伪框 IoU** 在训练时,根据 GT 四点生成最小外接水平框(Bounding Box),用该伪框的 IoU 进行 Query Selection。**仅用于初始化选择,不参与最终回归 Loss**。这种方式改动最小,能复用 RT-DETRv4 原有的 Selection 逻辑。 ```python # 训练时动态生成伪框用于 Selection pseudo_boxes = cv2.minAreaRect(gt_quads) # 或 torch 实现 selection_scores = cls_score * iou_aware_score(pseudo_boxes) ``` #### C. Decoder Head 修改 ```python # 伪代码示意 class RTDETRv4KPTHead(nn.Module): def __init__(self, num_classes, num_points=4): self.cls_head = nn.Linear(hidden_dim, num_classes) # 中心点分支(可选,辅助定位) self.center_head = nn.Linear(hidden_dim, 2) # 关键点偏移分支 self.kpt_head = nn.Sequential( nn.Linear(hidden_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, num_points * 2) # 8个值 ) # 可见性分支 self.vis_head = nn.Linear(hidden_dim, num_points) ``` #### D. Loss Function 设计 放弃所有 Box-related Loss,替换为: * **分类 Loss**:Varifocal Loss / Focal Loss(保持不变) * **关键点回归 Loss**: * **Wing Loss** 或 **Smooth L1 Loss**:对关键点坐标回归,Wing Loss 对小误差更敏感,适合精确定位。 * **OKS-based Loss**(可选):如果有关键点尺度信息,可引入 OKS(Object Keypoint Similarity)作为辅助 Loss。 * **几何约束 Loss(重要)**: 四点检测容易出现"交叉"或"非凸"的异常预测。建议增加: * **凸包约束**:惩罚四点构成的多边形面积为负的情况。 * **边长比例约束**:如果目标有固定几何先验(如车牌、文档),加入边长比正则项。 * **Pairwise Distance Loss**:保持相邻点之间的距离合理性。 #### E. Hungarian Matcher 修改 ```python # 关键点匹配的 Cost Matrix cost_kpt = torch.cdist(pred_kpts, gt_kpts, p=2) # L2距离 cost_vis = binary_cross_entropy(pred_vis, gt_vis) # 可见性匹配 # 综合 Cost C = λ_cls * cost_cls + λ_kpt * cost_kpt + λ_vis * cost_vis ``` > ✅ 此方案改动最小,且对卡片类目标效果极佳,因为外接框 IoU ≈ 真实对齐度。 ⚠️ **注意**:如果 GT 的点序已统一,直接用索引匹配即可;如果点序未统一,需在 Matcher 中加入**排列枚举**(4! = 24种组合),取最小 Cost 的排列作为匹配结果。这会显著增加匹配耗时,**务必在数据预处理阶段统一点序**。 ### 3. 训练策略建议 1. **分阶段训练**: * Stage 1: 冻结 Backbone + Encoder,只训练 KPT Head,用较大学习率快速收敛关键点分支。 * Stage 2: 全量微调,小学习率联合优化。 * cost_giou降低,前 5-10 个 epoch 将 self.kpt_cost 设为 0,让模型先学会找卡片,再开启 KPT Cost 进行像素级对齐。 2. **数据增强**: * ✅ 随机旋转、仿射变换、透视变换(四点检测对透视敏感) * ✅ 随机遮挡单个关键点(提升鲁棒性) * ❌ 避免水平/垂直翻转(除非同时调整点序标签) 3. **预训练权重迁移**: RT-DETRv4 的 Backbone + Hybrid Encoder 权重可直接加载。只需重新初始化 Head 部分。中心点分支可从原 Box Head 的 `(cx, cy)` 权重初始化。 ### 4. 是否需要自己改?现有替代方案评估 | 方案 | 优点 | 缺点 | 推荐度 | | :--- | :--- | :--- | :--- | | **自行改造 RT-DETRv4** | 保留 RT-DETR 的速度优势,端到端无 NMS | 工程量大,Query Selection 需反复调优 | ⭐⭐⭐ | | **RTMPose / TopDown** | 关键点检测 SOTA,精度极高 | 两阶段,依赖外部检测器,非端到端 | ⭐⭐⭐⭐⭐ | | **DEKR / PETR** | 端到端多点检测,专为关键点设计 | 速度不如 RT-DETR,生态较小 | ⭐⭐⭐⭐ | | **YOLOv8-Pose** | 成熟稳定,部署方便 | 非 Transformer 架构,全局建模弱 | ⭐⭐⭐⭐ | ### 3. CPU < 1s 部署优化路线 RT-DETRv4 原版在 CPU 上推理约 300-800ms(取决于尺寸),改为关键点后计算量基本不变。确保 <1s 的关键: | 优化手段 | 预期收益 | 实施要点 | | :--- | :--- | :--- | | **输入分辨率** | 640×640 → 480×480 | 卡片检测通常不需要高分辨率,480 足够,速度提升 ~40% | | **ONNX Runtime** | 比 PyTorch 快 2-3x | 导出 ONNX opset=17,启用 `--optimize` | | **INT8 量化** | 再提速 2-4x | 使用 ONNX Runtime INT8 或 OpenVINO INT8,2W 数据足够校准 | | **Decoder 层数裁剪** | 减少 30% 延迟 | RT-DETRv4 默认 6 层 Decoder,卡片任务 3-4 层足够 | | **Hybrid Encoder 简化** | 减少 FLOPs | 将 CCFM 中的 RepBlock 替换为轻量 Conv,或减少重复次数 | | **OpenVINO (Intel CPU)** | 比 ORT 快 20-50% | 若目标硬件为 Intel,首选 OpenVINO IR 格式 | #### ⚠️ CPU 部署避坑 - **避免动态 Shape**:导出 ONNX 时固定输入尺寸 `(1,3,480,480)`,动态 Shape 在 CPU 上开销巨大。 - **Fuse Operations**:确保 Conv+BN+ReLU 融合,ORT/OpenVINO 自动处理,但需检查导出日志。 - **后处理极简**:四点检测无需 NMS!只需按 `cls_score > thresh` 过滤,然后解码偏移量即可。这是相比 OBB/Box 检测在 CPU 上的巨大优势。 ### 5. 备选方案对比(决策参考) > 💡 **最终建议**: > 若你的场景是**工业级卡片/屏幕定位**(如OCR预处理、AR锚点),**RT-DETRv4-KPT + INT8 + OpenVINO** 是最优解,兼顾精度与CPU实时性。 > 若对精度要求不高或开发周期紧张,先用 **YOLOv8-Pose** 快速验证 baseline,再决定是否迁移到 RT-DETR。