This post will show a few methods to get Labelbox box annotations to YOLO annotations with Ultralytics.
Feel free to modify these scripts to your needs, but use them at your own risk.
Review this article on how to get YOLO annotations onto Labelbox.
Setup
Inside Labelbox, you must create a matching ontology and project with the data rows you are trying to label to YOLO annotations. Setting that up is outside the scope of this post, but review Labelbox Developer Docs for more information. Once you have this setup, you need to label some data rows to use these functions.
For this tutorial, you must create a mapping with your Labelbox feature name to YOLO class names. This will allow you the flexibility to use an integer or full class name for your YOLO format. Below is a Python dictionary object demonstrating an example of what this would look like:
# {<labelbox_feature_name>: <yolo_class_name>}
class_mapping = {
"Person": "person",
"Vehicle": "bus",
}
Export Labelbox Data Rows
Now that you have a project set up, you can use the below scripts to export to bounding boxes, segment masks, or polygon annotations in YOLO format. You will need to either utilize Labelbox export_v2 or export streamable to loop through your data row list and run each data row on your desired functions. Refer to the setup examples later in the tutorial.
Bounding Box
def lb_json_data_row_bbox_to_yolo(data_row: dict[str:str], project_id: str, ontology_mapping: dict[str:str]):
"""Convert export V2 Labelbox data row to YOLOV8 bbox xywh format
Args:
data_row (dict[str:str]): Single Labelbox data row from export streamables
project_id (str): Labelbox project id were data rows where exported from
ontology_mapping (dict[<labelbox_feature_name>: <yolo_class_name>]): Bbox feature name must match class name given from Labelbox.
Returns:
dict["bbox":dict["xywh": list[tuple["<yolo_xywh_format>"]]]: "cls":["<yolo_class_names>"]]
"""
yolov8_format = {
"bbox": {"xywh": []},
"cls": []
}
class_list = ontology_mapping.keys()
labels = data_row["projects"][project_id]["labels"]
for label in labels:
for object in label["annotations"]["objects"]:
if object["annotation_kind"] == "ImageBoundingBox" and object["name"] in class_list:
x = (object["bounding_box"]["top"] + object["bounding_box"]["width"]) / 2
y = (object["bounding_box"]["left"] - object["bounding_box"]["height"]) / 2
height = object["bounding_box"]["height"]
width = object["bounding_box"]["width"]
yolov8_format["cls"].append(ontology_mapping[object["name"]])
yolov8_format["bbox"]["xywh"].append(x, y, width, height)
return yolov8_format
Segment Mask
import urllib.request
from PIL import Image
import labelbox as lb
import numpy as np
def lb_json_data_row_masks_to_yolo(data_row: dict[str:str], project_id: str, ontology_mapping: dict[str:str], labelbox_client: lb.Client):
"""Convert export V2 Labelbox data row to YOLOV8 binary numpy arrays.
Args:
data_row (dict[str:str]): Single Labelbox data row from export streamables or export v2
project_id (str): Labelbox project id were data rows where exported from
ontology_mapping (dict[<labelbox_feature_name>: <yolo_class_name>]): Bbox feature name must match class name given from Labelbox.
Returns:
dict["masks":dict["numpy":list["<binary_numpy_array>"]]: "cls":["<yolo_class_names>"]]
"""
yolov8_format = {
"masks": {"numpy": []},
"cls": []
}
class_list = ontology_mapping.keys()
labels = data_row["projects"][project_id]["labels"]
for label in labels:
for object in label["annotations"]["objects"]:
if object["annotation_kind"] == "ImageSegmentationMask" and object["name"] in class_list:
req = urllib.request.Request(object["mask"]["url"], headers=labelbox_client.headers)
image_np = np.asarray(Image.open(urllib.request.urlopen(req)))
binary_array = image_np/255
if np.max(binary_array) != 1:
print(object["name"], " is invalid mask")
continue
yolov8_format["cls"].append(ontology_mapping[object["name"]])
yolov8_format["masks"]["numpy"].append(image_np/255)
return yolov8_format
Polygon
import labelbox as lb
def lb_json_data_row_polygon_to_yolo(data_row: dict[str:str], project_id: str, ontology_mapping: dict[str:str], labelbox_client: lb.Client):
"""Convert export V2 Labelbox data row to YOLOV8 polygon in xy coordinate format
Args:
data_row (dict[str:str]): Single Labelbox data row from export streamables or export v2
project_id (str): Labelbox project id were data rows where exported from
ontology_mapping (dict[<labelbox_feature_name>: <yolo_class_name>]): Bbox feature name must match class name given from Labelbox.
Returns:
dict["masks":dict["numpy":list["<binary_numpy_array>"]]: "cls":["<yolo_class_names>"]]
"""
yolov8_format = {
"masks": {"xy": []},
"cls": []
}
class_list = ontology_mapping.keys()
labels = data_row["projects"][project_id]["labels"]
for label in labels:
for object in label["annotations"]["objects"]:
if object["annotation_kind"] == "ImagePolygon" and object["name"] in class_list:
coordinates = [{"x": lb_coordinates["x"], "y": lb_coordinates["y"]} for lb_coordinates in object["polygon"]]
yolov8_format["cls"].append(ontology_mapping[object["name"]])
yolov8_format["masks"] = {"xy": coordinates}
return yolov8_format
Example with Export_V2
import labelbox as lb
client = lb.Client("")
PROJECT_ID = ""
project = client.get_project(PROJECT_ID)
task = project.export_v2()
task.wait_till_done()
export_json = task.result
masks = []
polygons = []
bboxes = []
ontology_mapping_masks = {
"Person_mask": "person",
"Vehicle_mask": "truck"
}
ontology_mapping_polygon = {
"Person_polygon": "person",
"Vehicle_polygon": "truck"
}
ontology_mapping_bboxes = {
"Person_bbox": "person",
"Vehicle_bbox": "truck"
}
for data_row in export_json:
masks.append(lb_json_data_row_masks_to_yolo(data_row, PROJECT_ID, ontology_mapping_masks, client))
polygons.append(lb_json_data_row_polygon_to_yolo(data_row, PROJECT_ID, ontology_mapping_polygon))
bboxes.append(lb_json_data_row_bbox_to_yolo(data_row, PROJECT_ID, ontology_mapping_bboxes))
Example with Export Streamable
import labelbox as lb
import json
client = lb.Client("")
PROJECT_ID = ""
client.enable_experimental = True
project = client.get_project(PROJECT_ID)
export_task = project.export()
export_task.wait_till_done()
ontology_mapping_masks = {
"Person_mask": "person",
"Vehicle_mask": "truck"
}
ontology_mapping_polygon = {
"Person_polygon": "person",
"Vehicle_polygon": "truck"
}
ontology_mapping_bboxes = {
"Person_bbox": "person",
"Vehicle_bbox": "truck"
}
masks = []
polygons = []
bboxes = []
def json_stream_handler(output: lb.JsonConverterOutput):
data_row = json.loads(output.json_str)
masks.append(lb_json_data_row_masks_to_yolo(data_row, PROJECT_ID, ontology_mapping_masks, client))
polygons.append(lb_json_data_row_polygon_to_yolo(data_row, PROJECT_ID, ontology_mapping_polygon))
bboxes.append(lb_json_data_row_bbox_to_yolo(data_row, PROJECT_ID, ontology_mapping_bboxes))
if export_task.has_errors():
export_task.get_stream(
converter=lb.JsonConverter(),
stream_type=lb.StreamType.ERRORS
).start(stream_handler=lambda error: print(error))
if export_task.has_result():
export_json = export_task.get_stream(
converter=lb.JsonConverter(),
stream_type=lb.StreamType.RESULT
).start(stream_handler=json_stream_handler)
Conclusion
This guide only serves as a starting point for your YOLO annotations. Feel free to provide feedback on how to improve these scripts. Also, note that these functions only apply to image annotations, not video annotations.