ITADN

PostProcess does not clamp predicted boxes to target image dimensions

#1167OpenArmaggheddon 创建于 2026-06-26
bug
A
Armaggheddoncommented
### Search before asking - [x] I have searched the RF-DETR issues and found no similar bug report. ### Bug RF-DETR `predict()` outputs bounding boxes with coordinates outside image bounds (negative x1/y1, or x2/y2 exceeding image dimensions) when objects are near the image edges. Using this image crop with the below code, results in the following bounding box predictions: ```bash [[ 9.0999508e+00 -4.7557354e-02 2.1185352e+02 6.8473389e+01]] ``` note the negative `y1` value. | crop | model input | |:-:|:-:| | <img width="222" height="178" alt="Image" src="https://github.com/user-attachments/assets/3197ebc3-271c-4091-83df-4ec369bc0b0d" /> | <img width="640" height="480" alt="Image" src="https://github.com/user-attachments/assets/a952ac90-0078-4cb8-a28a-29d124072ae6" /> | The coordinate overflow occurs consistently when the object falls inside the coloured regions of this map (specific values may vary with configuration): <img width="1013" height="787" alt="Image" src="https://github.com/user-attachments/assets/0e1f8260-cd86-4dc8-aa64-2008999f1a39" /> ### Environment - rfdetr==1.8.2 - WSL2 Ubuntu 24-04 LTS, Windows 11 - Python 3.12.3 - Pytorch 2.12.1 - CUDA 13.3 - Nvidia RTX 3070 Ti ### Minimal Reproducible Example Use the above crop in the table as input. ```python3 from rfdetr import RFDETRNano import numpy as np, cv2 model = RFDETRNano() vehicle = cv2.imread("image.png") vh, vw = vehicle.shape[:2] bg_h, bg_w = 480, 640 # Place vehicle at top edge (partially offscreen) canvas = np.zeros((bg_h, bg_w, 3), dtype=np.uint8) py = -100 # vehicle overflows top edge canvas[0:vh+py, 0:vw] = vehicle[-py:vh, 0:vw] out = model.predict(canvas) print(out.xyxy) # y1 is negative ``` ### Additional The issue can be resolved by clamping the absolute pixel coordinates to the valid image area at the end of `_gather_and_scale_boxes`. This prevents negative `x1`, `y1` values and ensures `x2`, `y2` never exceed the image width/height. Proposed change (in `PostProcess._gather_and_scale_boxes`): ```python3 @staticmethod def _gather_and_scale_boxes( out_bbox: torch.Tensor, topk_boxes: torch.Tensor, target_sizes: torch.Tensor, ) -> torch.Tensor: """Gather selected boxes and scale normalized coordinates to pixels. Args: out_bbox: Normalized ``cxcywh`` boxes with shape ``(B, Q, 4)``. topk_boxes: Query indices selected by :meth:`_select_topk`. target_sizes: Per-image ``(height, width)`` tensor. Returns: Absolute ``xyxy`` boxes with shape ``(B, K, 4)`` in pixel units. """ boxes = box_ops.box_cxcywh_to_xyxy(out_bbox) boxes = torch.gather(boxes, 1, topk_boxes.unsqueeze(-1).repeat(1, 1, 4)) img_h, img_w = target_sizes.unbind(1) scale_fct = torch.stack([img_w, img_h, img_w, img_h], dim=1) boxes = boxes * scale_fct[:, None, :] # clamp boxes to image size to avoid negative coordinates or coordinates larger than image size boxes = boxes.clamp(min=torch.zeros_like(scale_fct[:, None, :]), max=scale_fct[:, None, :]) return boxes ``` Why it works: - `scale_fct` already contains `[img_w, img_h, img_w, img_h]` per image, which matches `x1, y1, x2, y2` order. - Broadcasting `(B, 1, 4)` tensors applies the per-image limits efficiently without any loop. - Both `min` and `max` are tensors, satisfying `Tensor.clamp`'s signature. After the fix, the output bounding box becomes correctly clamped (from the repro code): ```bash [[ 9.099951 0. 211.85352 68.47339 ]] ``` (The previous negative `y1` is now `0`, and all coordinates remain within the image boundaries.) ### Are you willing to submit a PR? - [x] Yes, I'd like to help by submitting a PR!
0 条评论