Skip to content

Commit 3ce1a6f

Browse files
authored
Replace YOLO format support in CVAT with Datumaro (#1151)
* Employ transforms and item wrapper * Add image class and tests * Add image info support to formats * Fix cli * Fix merge and voc converte * Update remote images extractor * Codacy * Remove item name, require path in Image * Merge images of dataset items * Update tests * Add image dir converter * Update Datumaro format * Update COCO format with image info * Update CVAT format with image info * Update TFrecord format with image info * Update VOC formar with image info * Update YOLO format with image info * Update dataset manager bindings with image info * Add image name to id transform * Replace YOLO export and import in CVAT with Datumaro
1 parent b36f402 commit 3ce1a6f

File tree

1 file changed

+34
-86
lines changed

1 file changed

+34
-86
lines changed

cvat/apps/annotation/yolo.py

+34-86
Original file line numberDiff line numberDiff line change
@@ -24,98 +24,46 @@
2424

2525
def load(file_object, annotations):
2626
from pyunpack import Archive
27-
import os
27+
import os.path as osp
2828
from tempfile import TemporaryDirectory
2929
from glob import glob
30-
31-
def convert_from_yolo(img_size, box):
32-
# convertation formulas are based on https://github.com/pjreddie/darknet/blob/master/scripts/voc_label.py
33-
# <x> <y> <width> <height> - float values relative to width and height of image
34-
# <x> <y> - are center of rectangle
35-
def clamp(value, _min, _max):
36-
return max(min(_max, value), _min)
37-
xtl = clamp(img_size[0] * (box[0] - box[2] / 2), 0, img_size[0])
38-
ytl = clamp(img_size[1] * (box[1] - box[3] / 2), 0, img_size[1])
39-
xbr = clamp(img_size[0] * (box[0] + box[2] / 2), 0, img_size[0])
40-
ybr = clamp(img_size[1] * (box[1] + box[3] / 2), 0, img_size[1])
41-
42-
return [xtl, ytl, xbr, ybr]
43-
44-
def parse_yolo_obj(img_size, obj):
45-
label_id, x, y, w, h = obj.split(" ")
46-
return int(label_id), convert_from_yolo(img_size, (float(x), float(y), float(w), float(h)))
47-
48-
def parse_yolo_file(annotation_file, labels_mapping):
49-
frame_number = annotations.match_frame(annotation_file)
50-
with open(annotation_file, "r") as fp:
51-
line = fp.readline()
52-
while line:
53-
frame_info = annotations.frame_info[frame_number]
54-
label_id, points = parse_yolo_obj((frame_info["width"], frame_info["height"]), line)
55-
annotations.add_shape(annotations.LabeledShape(
56-
type="rectangle",
57-
frame=frame_number,
58-
label=labels_mapping[label_id],
59-
points=points,
60-
occluded=False,
61-
attributes=[],
62-
))
63-
line = fp.readline()
64-
65-
def load_labels(labels_file):
66-
with open(labels_file, "r") as f:
67-
return {idx: label.strip() for idx, label in enumerate(f.readlines()) if label.strip()}
30+
from datumaro.plugins.yolo_format.importer import YoloImporter
31+
from cvat.apps.dataset_manager.bindings import import_dm_annotations
6832

6933
archive_file = file_object if isinstance(file_object, str) else getattr(file_object, "name")
7034
with TemporaryDirectory() as tmp_dir:
7135
Archive(archive_file).extractall(tmp_dir)
7236

73-
labels_file = glob(os.path.join(tmp_dir, "*.names"))
74-
if not labels_file:
75-
raise Exception("Could not find '*.names' file with labels in uploaded archive")
76-
elif len(labels_file) == 1:
77-
labels_mapping = load_labels(labels_file[0])
78-
else:
79-
raise Exception("Too many '*.names' files in uploaded archive: {}".format(labels_file))
80-
81-
for dirpath, _, filenames in os.walk(tmp_dir):
82-
for file in filenames:
83-
if ".txt" == os.path.splitext(file)[1]:
84-
parse_yolo_file(os.path.join(dirpath, file), labels_mapping)
37+
image_info = {}
38+
anno_files = glob(osp.join(tmp_dir, '**', '*.txt'), recursive=True)
39+
for filename in anno_files:
40+
filename = osp.basename(filename)
41+
frame_info = None
42+
try:
43+
frame_info = annotations.frame_info[
44+
int(osp.splitext(filename)[0])]
45+
except Exception:
46+
pass
47+
try:
48+
frame_info = annotations.match_frame(filename)
49+
frame_info = annotations.frame_info[frame_info]
50+
except Exception:
51+
pass
52+
if frame_info is not None:
53+
image_info[osp.splitext(filename)[0]] = \
54+
(frame_info['height'], frame_info['width'])
55+
56+
dm_project = YoloImporter()(tmp_dir, image_info=image_info)
57+
dm_dataset = dm_project.make_dataset()
58+
import_dm_annotations(dm_dataset, annotations)
8559

8660
def dump(file_object, annotations):
87-
from zipfile import ZipFile
88-
import os
89-
90-
# convertation formulas are based on https://github.com/pjreddie/darknet/blob/master/scripts/voc_label.py
91-
# <x> <y> <width> <height> - float values relative to width and height of image
92-
# <x> <y> - are center of rectangle
93-
def convert_to_yolo(img_size, box):
94-
x = (box[0] + box[2]) / 2 / img_size[0]
95-
y = (box[1] + box[3]) / 2 / img_size[1]
96-
w = (box[2] - box[0]) / img_size[0]
97-
h = (box[3] - box[1]) / img_size[1]
98-
99-
return x, y, w, h
100-
101-
labels_ids = {label[1]["name"]: idx for idx, label in enumerate(annotations.meta["task"]["labels"])}
102-
103-
with ZipFile(file_object, "w") as output_zip:
104-
for frame_annotation in annotations.group_by_frame():
105-
image_name = frame_annotation.name
106-
annotation_name = "{}.txt".format(os.path.splitext(os.path.basename(image_name))[0])
107-
width = frame_annotation.width
108-
height = frame_annotation.height
109-
110-
yolo_annotation = ""
111-
for shape in frame_annotation.labeled_shapes:
112-
if shape.type != "rectangle":
113-
continue
114-
115-
label = shape.label
116-
yolo_bb = convert_to_yolo((width, height), shape.points)
117-
yolo_bb = " ".join("{:.6f}".format(p) for p in yolo_bb)
118-
yolo_annotation += "{} {}\n".format(labels_ids[label], yolo_bb)
119-
120-
output_zip.writestr(annotation_name, yolo_annotation)
121-
output_zip.writestr("obj.names", "\n".join(l[0] for l in sorted(labels_ids.items(), key=lambda x:x[1])))
61+
from cvat.apps.dataset_manager.bindings import CvatAnnotationsExtractor
62+
from cvat.apps.dataset_manager.util import make_zip_archive
63+
from datumaro.components.project import Environment
64+
from tempfile import TemporaryDirectory
65+
extractor = CvatAnnotationsExtractor('', annotations)
66+
converter = Environment().make_converter('yolo')
67+
with TemporaryDirectory() as temp_dir:
68+
converter(extractor, save_dir=temp_dir)
69+
make_zip_archive(temp_dir, file_object)

0 commit comments

Comments
 (0)