PostProcess does not clamp predicted boxes to target image dimensions
bug
### 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 条评论