Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/ultralytics obb support #4230

Merged
merged 2 commits into from
Apr 5, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 82 additions & 1 deletion fiftyone/utils/ultralytics.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ def convert_ultralytics_model(model):
elif isinstance(model.model, ultralytics.nn.tasks.PoseModel):
return _convert_yolo_pose_model(model)
elif isinstance(model.model, ultralytics.nn.tasks.DetectionModel):
return _convert_yolo_detection_model(model)
if isinstance(model.model, ultralytics.nn.tasks.OBBModel):
return _convert_yolo_obb_model(model)
else:
return _convert_yolo_detection_model(model)
else:
raise ValueError(
"Unsupported model type; cannot convert %s to a FiftyOne model"
Expand Down Expand Up @@ -166,6 +169,59 @@ def _to_instances(result, confidence_thresh=None):
return fol.Detections(detections=detections)


def obb_to_polylines(results, confidence_thresh=None, filled=False):
"""Converts ``ultralytics.YOLO`` instance segmentations to FiftyOne format.

Args:
results: a single or list of ``ultralytics.engine.results.Results``
confidence_thresh (None): a confidence threshold to filter boxes
filled (False): whether the polyline should be filled

Returns:
a single or list of :class:`fiftyone.core.labels.PolyLines`
"""

single = not isinstance(results, list)
if single:
results = [results]

batch = [
_obb_to_polylines(r, filled, confidence_thresh=confidence_thresh)
for r in results
]

if single:
return batch[0]

return batch


def _obb_to_polylines(result, filled, confidence_thresh=None):
if result.obb is None:
return None
classes = np.rint(result.obb.cls.detach().cpu().numpy()).astype(int)
confs = result.obb.conf.detach().cpu().numpy().astype(float)
points = result.obb.xyxyxyxyn.detach().cpu().numpy()
polylines = []
for cls, _points, conf in zip(classes, points, confs):
if confidence_thresh is not None and conf < confidence_thresh:
continue

_points = [_points.astype(float)]

label = result.names[cls]

polyline = fol.Polyline(
label=label,
points=_points,
confidence=conf,
closed=True,
filled=filled,
)
polylines.append(polyline)
return fol.Polylines(polylines=polylines)


def to_polylines(results, confidence_thresh=None, tolerance=2, filled=True):
"""Converts ``ultralytics.YOLO`` instance segmentations to FiftyOne format.

Expand Down Expand Up @@ -381,6 +437,26 @@ def predict_all(self, args):
return self._format_predictions(predictions)


class FiftyOneYOLOOBBConfig(FiftyOneYOLOModelConfig):
pass


class FiftyOneYOLOOBBModel(FiftyOneYOLOModel):
"""FiftyOne wrapper around an Ultralytics YOLO OBB detection model.

Args:
config: a :class:`FiftyOneYOLOConfig`
"""

def _format_predictions(self, predictions):
return obb_to_polylines(predictions)

def predict_all(self, args):
images = [Image.fromarray(arg) for arg in args]
predictions = self.model(images, verbose=False)
return self._format_predictions(predictions)


class FiftyOneYOLOSegmentationModelConfig(FiftyOneYOLOModelConfig):
pass

Expand Down Expand Up @@ -426,6 +502,11 @@ def _convert_yolo_detection_model(model):
return FiftyOneYOLODetectionModel(config)


def _convert_yolo_obb_model(model):
config = FiftyOneYOLOOBBConfig({"model": model})
return FiftyOneYOLOOBBModel(config)


def _convert_yolo_segmentation_model(model):
config = FiftyOneYOLOSegmentationModelConfig({"model": model})
return FiftyOneYOLOSegmentationModel(config)
Expand Down
Loading