Az/yolo format support (#619)
* added yolo loader/dumper * changed format_spec * updated reamde, changelog * Used bold font for default dump formatmain
parent
f17847ff33
commit
1454ec7ecc
@ -0,0 +1,136 @@
|
||||
format_spec = {
|
||||
"name": "YOLO",
|
||||
"dumpers": [
|
||||
{
|
||||
"display_name": "{name} {format} {version}",
|
||||
"format": "ZIP",
|
||||
"version": "1.0",
|
||||
"handler": "dump"
|
||||
},
|
||||
],
|
||||
"loaders": [
|
||||
{
|
||||
"display_name": "{name} {format} {version}",
|
||||
"format": "ZIP",
|
||||
"version": "1.0",
|
||||
"handler": "load"
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
def get_filename(path):
|
||||
import os
|
||||
return os.path.splitext(os.path.basename(path))[0]
|
||||
|
||||
def load(file_object, annotations):
|
||||
from pyunpack import Archive
|
||||
import os
|
||||
from tempfile import TemporaryDirectory
|
||||
from glob import glob
|
||||
|
||||
def convert_from_yolo(img_size, box):
|
||||
# convertation formulas are based on https://github.com/pjreddie/darknet/blob/master/scripts/voc_label.py
|
||||
# <x> <y> <width> <height> - float values relative to width and height of image
|
||||
# <x> <y> - are center of rectangle
|
||||
def clamp(value, _min, _max):
|
||||
return max(min(_max, value), _min)
|
||||
xtl = clamp(img_size[0] * (box[0] - box[2] / 2), 0, img_size[0])
|
||||
ytl = clamp(img_size[1] * (box[1] - box[3] / 2), 0, img_size[1])
|
||||
xbr = clamp(img_size[0] * (box[0] + box[2] / 2), 0, img_size[0])
|
||||
ybr = clamp(img_size[1] * (box[1] + box[3] / 2), 0, img_size[1])
|
||||
|
||||
return [xtl, ytl, xbr, ybr]
|
||||
|
||||
def parse_yolo_obj(img_size, obj):
|
||||
label_id, x, y, w, h = obj.split(" ")
|
||||
return int(label_id), convert_from_yolo(img_size, (float(x), float(y), float(w), float(h)))
|
||||
|
||||
def match_frame(frame_info, filename):
|
||||
import re
|
||||
# try to match by filename
|
||||
yolo_filename = get_filename(filename)
|
||||
for frame_number, info in frame_info.items():
|
||||
cvat_filename = get_filename(info["path"])
|
||||
if cvat_filename == yolo_filename:
|
||||
return frame_number
|
||||
|
||||
# try to extract frame number from filename
|
||||
numbers = re.findall(r"\d+", filename)
|
||||
if numbers and len(numbers) == 1:
|
||||
return int(numbers[0])
|
||||
|
||||
raise Exception("Cannot match filename or determinate framenumber for {} filename".format(filename))
|
||||
|
||||
def parse_yolo_file(annotation_file, labels_mapping):
|
||||
frame_number = match_frame(annotations.frame_info, annotation_file)
|
||||
with open(annotation_file, "r") as fp:
|
||||
line = fp.readline()
|
||||
while line:
|
||||
frame_info = annotations.frame_info[frame_number]
|
||||
label_id, points = parse_yolo_obj((frame_info["width"], frame_info["height"]), line)
|
||||
annotations.add_shape(annotations.LabeledShape(
|
||||
type="rectangle",
|
||||
frame=frame_number,
|
||||
label=labels_mapping[label_id],
|
||||
points=points,
|
||||
occluded=False,
|
||||
attributes=[],
|
||||
))
|
||||
line = fp.readline()
|
||||
|
||||
def load_labels(labels_file):
|
||||
with open(labels_file, "r") as f:
|
||||
return {idx: label.strip() for idx, label in enumerate(f.readlines()) if label.strip()}
|
||||
|
||||
archive_file = file_object if isinstance(file_object, str) else getattr(file_object, "name")
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
Archive(archive_file).extractall(tmp_dir)
|
||||
|
||||
labels_file = glob(os.path.join(tmp_dir, "*.names"))
|
||||
if not labels_file:
|
||||
raise Exception("Could not find '*.names' file with labels in uploaded archive")
|
||||
elif len(labels_file) == 1:
|
||||
labels_mapping = load_labels(labels_file[0])
|
||||
else:
|
||||
raise Exception("Too many '*.names' files in uploaded archive: {}".format(labels_file))
|
||||
|
||||
for dirpath, _, filenames in os.walk(tmp_dir):
|
||||
for file in filenames:
|
||||
if ".txt" == os.path.splitext(file)[1]:
|
||||
parse_yolo_file(os.path.join(dirpath, file), labels_mapping)
|
||||
|
||||
def dump(file_object, annotations):
|
||||
from zipfile import ZipFile
|
||||
|
||||
# convertation formulas are based on https://github.com/pjreddie/darknet/blob/master/scripts/voc_label.py
|
||||
# <x> <y> <width> <height> - float values relative to width and height of image
|
||||
# <x> <y> - are center of rectangle
|
||||
def convert_to_yolo(img_size, box):
|
||||
x = (box[0] + box[2]) / 2 / img_size[0]
|
||||
y = (box[1] + box[3]) / 2 / img_size[1]
|
||||
w = (box[2] - box[0]) / img_size[0]
|
||||
h = (box[3] - box[1]) / img_size[1]
|
||||
|
||||
return x, y, w, h
|
||||
|
||||
labels_ids = {label[1]["name"]: idx for idx, label in enumerate(annotations.meta["task"]["labels"])}
|
||||
|
||||
with ZipFile(file_object, "w") as output_zip:
|
||||
for frame_annotation in annotations.group_by_frame():
|
||||
image_name = frame_annotation.name
|
||||
annotation_name = "{}.txt".format(get_filename(image_name))
|
||||
width = frame_annotation.width
|
||||
height = frame_annotation.height
|
||||
|
||||
yolo_annotation = ""
|
||||
for shape in frame_annotation.labeled_shapes:
|
||||
if shape.type != "rectangle":
|
||||
continue
|
||||
|
||||
label = shape.label
|
||||
yolo_bb = convert_to_yolo((width, height), shape.points)
|
||||
yolo_bb = " ".join("{:.6f}".format(p) for p in yolo_bb)
|
||||
yolo_annotation += "{} {}\n".format(labels_ids[label], yolo_bb)
|
||||
|
||||
output_zip.writestr(annotation_name, yolo_annotation)
|
||||
output_zip.writestr("obj.names", "\n".join(l[0] for l in sorted(labels_ids.items(), key=lambda x:x[1])))
|
||||
Loading…
Reference in New Issue