From 2a20805ad655e03c617cc134f330dbf671ba7991 Mon Sep 17 00:00:00 2001 From: telenachos Date: Fri, 8 Nov 2019 14:44:45 -0600 Subject: [PATCH 1/4] Adding dump and load support for MOT CSV format. --- CHANGELOG.md | 1 + README.md | 1 + cvat/apps/annotation/mot.py | 111 +++++++++++++++++++++++++++++++ cvat/apps/annotation/settings.py | 1 + 4 files changed, 114 insertions(+) create mode 100644 cvat/apps/annotation/mot.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 293b0e2900f..3ee7edf9e61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Ability to [get basic information about users without admin permissions]( https://github.com/opencv/cvat/issues/750). - Changed REST API: removed PUT and added DELETE methods for /api/v1/users/ID. +- Added MOT CSV format support ### Changed - diff --git a/README.md b/README.md index 998c288b198..7c20f257c68 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ Format selection is possible after clicking on the Upload annotation / Dump anno | [MS COCO Object Detection](http://cocodataset.org/#format-data) | X | X | | PNG mask | X | | | [TFrecord](https://www.tensorflow.org/tutorials/load_data/tf_records) | X | X | +| [MOT](https://motchallenge.net/) | X | X | ## Links - [Intel AI blog: New Computer Vision Tool Accelerates Annotation of Digital Images and Video](https://www.intel.ai/introducing-cvat) diff --git a/cvat/apps/annotation/mot.py b/cvat/apps/annotation/mot.py new file mode 100644 index 00000000000..dceb5623e5e --- /dev/null +++ b/cvat/apps/annotation/mot.py @@ -0,0 +1,111 @@ +# SPDX-License-Identifier: MIT +format_spec = { + "name": "MOT", + "dumpers": [ + { + "display_name": "{name} {format} {version}", + "format": "CSV", + "version": "1.0", + "handler": "dump" + }, + ], + "loaders": [ + { + "display_name": "{name} {format} {version}", + "format": "CSV", + "version": "1.0", + "handler": "load", + } + ], +} + + +MOT = [ + "frame_id", + "track_id", + "xtl", + "ytl", + "width", + "height", + "confidence", + "class_id", + "visibility" +] + + +def dump(file_object, annotations): + """ Export track shapes in MOT CSV format. Due to limitations of the MOT + format, this process only supports rectangular interpolation mode + annotations. + """ + import csv + import io + + # csv requires a text buffer + with io.TextIOWrapper(file_object, encoding="utf-8") as csv_file: + writer = csv.DictWriter(csv_file, fieldnames=MOT) + for i, track in enumerate(annotations.tracks): + for shape in track.shapes: + # MOT doesn't support polygons or 'outside' property + if shape.type != 'rectangle' or shape.outside: + continue + writer.writerow({ + "frame_id": shape.frame, + "track_id": i, + "xtl": shape.points[0], + "ytl": shape.points[1], + "width": shape.points[2] - shape.points[0], + "height": shape.points[3] - shape.points[1], + "confidence": 1, + "class_id": track.label, + "visibility": 1 - int(shape.occluded) + }) + + +def load(file_object, annotations): + """ Read MOT CSV format and convert objects to annotated tracks. + """ + import csv + import io + tracks = {} + # csv requires a text buffer + with io.TextIOWrapper(file_object, encoding="utf-8") as csv_file: + reader = csv.DictReader(csv_file, fieldnames=MOT) + for row in reader: + # create one shape per row + xtl = float(row["xtl"]) + ytl = float(row["ytl"]) + xbr = xtl + float(row["width"]) + ybr = ytl + float(row["height"]) + shape = annotations.TrackedShape( + type="rectangle", + points=[xtl, ytl, xbr, ybr], + occluded=float(row["visibility"]) == 0, + outside=False, + keyframe=False, + z_order=0, + frame=int(row["frame_id"]), + attributes=[], + ) + # build trajectories as lists of shapes in track dict + track_id = int(row["track_id"]) + if track_id not in tracks: + tracks[track_id] = annotations.Track(row["class_id"], track_id, []) + tracks[track_id].shapes.append(shape) + for track in tracks.values(): + # MOT doesn't support the outside flag so we duplicate each + # track's last shape, increment frame_id and set outside=True + # to end the track + last = list(track.shapes[-1]) + last = annotations.TrackedShape( + type=track.shapes[-1].type, + points=track.shapes[-1].points, + occluded=track.shapes[-1].occluded, + outside=True, + keyframe=track.shapes[-1].keyframe, + z_order=track.shapes[-1].z_order, + frame=track.shapes[-1].frame + 1, + attributes=track.shapes[-1].attributes, + ) + track.shapes.append(last) + annotations.add_track(track) diff --git a/cvat/apps/annotation/settings.py b/cvat/apps/annotation/settings.py index 0ac2a38c8ad..06d82b09077 100644 --- a/cvat/apps/annotation/settings.py +++ b/cvat/apps/annotation/settings.py @@ -12,4 +12,5 @@ os.path.join(path_prefix, 'coco.py'), os.path.join(path_prefix, 'mask.py'), os.path.join(path_prefix, 'tfrecord.py'), + os.path.join(path_prefix, 'mot.py'), ) From f605384abbaf14d2f03792565961b09e21cbcfe5 Mon Sep 17 00:00:00 2001 From: telenachos <54951461+telenachos@users.noreply.github.com> Date: Fri, 8 Nov 2019 15:22:05 -0600 Subject: [PATCH 2/4] Remove conflict --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ee7edf9e61..911ed9aa044 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Ability to [get basic information about users without admin permissions]( https://github.com/opencv/cvat/issues/750). - Changed REST API: removed PUT and added DELETE methods for /api/v1/users/ID. +- Added Mask-RCNN Auto Annotation Script in OpenVINO format +- Added Yolo Auto Annotation Script +- Auto segmentation using Mask_RCNN component (Keras+Tensorflow Mask R-CNN Segmentation) - Added MOT CSV format support ### Changed From d0156b3604596196d40746b857de925a9e16271b Mon Sep 17 00:00:00 2001 From: telenachos <54951461+telenachos@users.noreply.github.com> Date: Fri, 8 Nov 2019 15:40:07 -0600 Subject: [PATCH 3/4] Removed typo --- cvat/apps/annotation/mot.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cvat/apps/annotation/mot.py b/cvat/apps/annotation/mot.py index dceb5623e5e..6fe5bb453e7 100644 --- a/cvat/apps/annotation/mot.py +++ b/cvat/apps/annotation/mot.py @@ -96,7 +96,6 @@ def load(file_object, annotations): # MOT doesn't support the outside flag so we duplicate each # track's last shape, increment frame_id and set outside=True # to end the track - last = list(track.shapes[-1]) last = annotations.TrackedShape( type=track.shapes[-1].type, points=track.shapes[-1].points, From 5e873178305d191db84d93130d422a307cc78740 Mon Sep 17 00:00:00 2001 From: telenachos Date: Thu, 21 Nov 2019 23:11:17 -0600 Subject: [PATCH 4/4] Fixing test failures for MOT loader. * Updated test cases to use correct track annotations for MOT format. * Removed behaviour of MOT loader which would duplicate the last track shape prior to setting outside=True. --- cvat/apps/annotation/mot.py | 11 +++++------ cvat/apps/engine/tests/test_rest_api.py | 3 +++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/cvat/apps/annotation/mot.py b/cvat/apps/annotation/mot.py index 6fe5bb453e7..b7f63d1e79b 100644 --- a/cvat/apps/annotation/mot.py +++ b/cvat/apps/annotation/mot.py @@ -47,7 +47,7 @@ def dump(file_object, annotations): for i, track in enumerate(annotations.tracks): for shape in track.shapes: # MOT doesn't support polygons or 'outside' property - if shape.type != 'rectangle' or shape.outside: + if shape.type != 'rectangle': continue writer.writerow({ "frame_id": shape.frame, @@ -93,9 +93,8 @@ def load(file_object, annotations): tracks[track_id] = annotations.Track(row["class_id"], track_id, []) tracks[track_id].shapes.append(shape) for track in tracks.values(): - # MOT doesn't support the outside flag so we duplicate each - # track's last shape, increment frame_id and set outside=True - # to end the track + # Set outside=True for the last shape since MOT has no support + # for this flag last = annotations.TrackedShape( type=track.shapes[-1].type, points=track.shapes[-1].points, @@ -103,8 +102,8 @@ def load(file_object, annotations): outside=True, keyframe=track.shapes[-1].keyframe, z_order=track.shapes[-1].z_order, - frame=track.shapes[-1].frame + 1, + frame=track.shapes[-1].frame, attributes=track.shapes[-1].attributes, ) - track.shapes.append(last) + track.shapes[-1] = last annotations.add_track(track) diff --git a/cvat/apps/engine/tests/test_rest_api.py b/cvat/apps/engine/tests/test_rest_api.py index ae109bc7272..15bfea7abad 100644 --- a/cvat/apps/engine/tests/test_rest_api.py +++ b/cvat/apps/engine/tests/test_rest_api.py @@ -2662,6 +2662,9 @@ def _get_initial_annotation(annotation_format): annotations["shapes"] = rectangle_shapes_with_attrs + rectangle_shapes_wo_attrs + polygon_shapes_wo_attrs annotations["tracks"] = rectangle_tracks_with_attrs + rectangle_tracks_wo_attrs + elif annotation_format == "MOT CSV 1.0": + annotations["tracks"] = rectangle_tracks_wo_attrs + return annotations response = self._get_annotation_formats(annotator)