| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import torch |
| from ultralytics import YOLO |
| from ultralytics.nn.modules.head import OBB26 |
| import os |
| import shutil |
|
|
|
|
| def npu_obb26_forward(self, x): |
| """ |
| YOLO26 OBB26 Head Modified for NPU. |
| |
| OBB26 structure: |
| - cv2 / one2one_cv2: box regression layers (output 4*reg_max channels, reg_max=1 -> 4) |
| - cv3 / one2one_cv3: classification layers (output nc channels, nc=15 for DOTAv1) |
| - cv4 / one2one_cv4: angle regression layers (output ne channels, ne=1) |
| |
| Note: |
| OBB26 outputs raw angle predictions (in radians) without sigmoid transformation, |
| which is different from the original OBB head where the angle is wrapped via |
| ``(angle.sigmoid() - 0.25) * pi`` in ``forward_head``. The post-process therefore |
| consumes the raw angle directly via ``cos(angle)`` / ``sin(angle)``. |
| |
| Output: |
| List of Tensors (9 items: 3 scales * 3 outputs), Layout: NHWC for all branches. |
| For each scale: |
| - Box_Raw (B, H, W, 4*reg_max), <-- bbox regression (ltrb distance, reg_max=1) |
| - Cls_Raw (B, H, W, nc), <-- class scores (raw logits) |
| - Angle_Raw (B, H, W, ne), <-- rotation angle (raw radians, ne=1) |
| """ |
| if not isinstance(x, (list, tuple)): |
| x = [x] |
|
|
| res = [] |
|
|
| |
| if hasattr(self, 'one2one_cv2') and hasattr(self, 'one2one_cv3'): |
| box_layers = self.one2one_cv2 |
| cls_layers = self.one2one_cv3 |
| else: |
| box_layers = self.cv2 |
| cls_layers = self.cv3 |
|
|
| |
| if hasattr(self, 'one2one_cv4'): |
| angle_layers = self.one2one_cv4 |
| else: |
| angle_layers = self.cv4 |
|
|
| for i in range(self.nl): |
| |
| bboxes = box_layers[i](x[i]).permute(0, 2, 3, 1) |
|
|
| |
| scores = cls_layers[i](x[i]).permute(0, 2, 3, 1) |
|
|
| |
| angle = angle_layers[i](x[i]).permute(0, 2, 3, 1) |
|
|
| res.append(bboxes) |
| res.append(scores) |
| res.append(angle) |
|
|
| return res |
|
|
|
|
| def batch_export_yolo26_obb(): |
| variants = ['n', 's', 'm', 'l', 'x'] |
| imgsz = 1024 |
|
|
| |
| OBB26.forward = npu_obb26_forward |
| print("Monkey patch applied for OBB26: Output Layout forced to NHWC (Box, Cls, Angle for each scale).") |
|
|
| for v in variants: |
| model_name = f"yolo26{v}-obb" |
| pt_path = f"{model_name}.pt" |
| onnx_final_name = f"{model_name}_{imgsz}x{imgsz}.onnx" |
| print(f"\n--- Processing {model_name} ---") |
| try: |
| |
| model = YOLO(pt_path) |
|
|
| |
| OBB26.forward = npu_obb26_forward |
|
|
| |
| if hasattr(model.model, 'model') and len(model.model.model) > 0: |
| head = model.model.model[-1] |
| if isinstance(head, OBB26): |
| head.forward = lambda x: npu_obb26_forward(head, x) |
|
|
| |
| exported_path = model.export( |
| format="onnx", |
| imgsz=imgsz, |
| dynamic=False, |
| opset=11, |
| simplify=True, |
| nms=False |
| ) |
|
|
| |
| if exported_path: |
| shutil.move(exported_path, onnx_final_name) |
| print(f"Success: {onnx_final_name}") |
| except Exception as e: |
| print(f"Failed to export {model_name}: {e}") |
| import traceback |
| traceback.print_exc() |
|
|
|
|
| if __name__ == "__main__": |
| batch_export_yolo26_obb() |
|
|