From 42700c54f480a3918a51a4fafdf0be91699aab40 Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 5 Feb 2019 11:30:21 -0600 Subject: [PATCH 1/8] Add ffmpeg command line to logs for easier debuggging of frame extraction issues in the future. --- cvat/apps/engine/task.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cvat/apps/engine/task.py b/cvat/apps/engine/task.py index e1b2b1ffd268..37b00ba393ab 100644 --- a/cvat/apps/engine/task.py +++ b/cvat/apps/engine/task.py @@ -283,6 +283,8 @@ def __init__(self, source_path, compress_quality, flip_flag=False): ff = FFmpeg( inputs = {source_path: None}, outputs = {target_path: output_opts}) + + slogger.glob.info("FFMpeg cmd: {} ".format(ff.cmd)) ff.run() def getframepath(self, k): From 1f859c05c6d0294160a3da35f648d5f83473627d Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 5 Feb 2019 11:51:43 -0600 Subject: [PATCH 2/8] Added support for converting interpolated video to Python VOC format. --- utils/voc/converter.py | 136 ++++++++++++++++++++++++++++++----------- 1 file changed, 101 insertions(+), 35 deletions(-) diff --git a/utils/voc/converter.py b/utils/voc/converter.py index a7ac348df24a..ad64431ba255 100644 --- a/utils/voc/converter.py +++ b/utils/voc/converter.py @@ -6,9 +6,10 @@ CVAT XML and writes the annotations in PASCAL VOC format into a given directory. -This implementation only supports bounding boxes in CVAT annotation format, and -warns if it encounter any tracks or annotations that are not bounding boxes, -ignoring them in both cases. +This implementation only supports bounding boxes in CVAT annotation format. +It supports both interpolation tracks from video and annotated images. +If it encounters any tracks or annotations that are not bounding boxes, +it ignores them. """ import os @@ -56,38 +57,103 @@ def process_cvat_xml(xml_file, image_dir, output_dir): os.makedirs(output_dir, exist_ok=True) cvat_xml = etree.parse(xml_file) - tracks = [(x.get('id'), x.get('label')) - for x in cvat_xml.findall('track')] - if tracks: - log.warn('Cannot parse interpolation tracks, ignoring {} tracks'.format(len(tracks))) - - for img_tag in cvat_xml.findall('image'): - image_name = img_tag.get('name') - width = img_tag.get('width') - height = img_tag.get('height') - image_path = os.path.join(image_dir, image_name) - if not os.path.exists(image_path): - log.warn('{} image cannot be found. Is `{}` image directory correct?'. - format(image_path, image_dir)) - writer = Writer(image_path, width, height) - - unknown_tags = {x.tag for x in img_tag.iter()}.difference(KNOWN_TAGS) - if unknown_tags: - log.warn('Ignoring tags for image {}: {}'.format(image_path, unknown_tags)) - - for box in img_tag.findall('box'): - label = box.get('label') - xmin = float(box.get('xtl')) - ymin = float(box.get('ytl')) - xmax = float(box.get('xbr')) - ymax = float(box.get('ybr')) - - writer.addObject(label, xmin, ymin, xmax, ymax) - - anno_name = os.path.basename(os.path.splitext(image_name)[0] + '.xml') - anno_dir = os.path.dirname(os.path.join(output_dir, image_name)) - os.makedirs(anno_dir, exist_ok=True) - writer.save(os.path.join(anno_dir, anno_name)) + basename = os.path.splitext( os.path.basename( xml_file ) )[0] + + tracks= cvat_xml.findall( './/track' ) + + if (tracks is not None) and (len(tracks) > 0): + frames = {} + + for track in tracks: + trackid = int(track.get("id")) + label = track.get("label") + boxes = track.findall( './box' ) + for box in boxes: + frameid = int(box.get('frame')) + outside = int(box.get('outside')) + occluded = int(box.get('occluded')) + keyframe = int(box.get('keyframe')) + xtl = float(box.get('xtl')) + ytl = float(box.get('ytl')) + xbr = float(box.get('xbr')) + ybr = float(box.get('ybr')) + + frame = frames.get( frameid, {} ) + + if outside == 0: + frame[ trackid ] = { 'xtl': xtl, 'ytl': ytl, 'xbr': xbr, 'ybr': ybr, 'label': label } + + frames[ frameid ] = frame + + # Spit out a list of each object for each frame + for frameid in sorted(frames.keys()): + print( frameid ) + + image_name = "%s_%08d.jpg" % (basename, frameid) + width = 1270 + height = 780 + image_path = os.path.join(image_dir, image_name) + if not os.path.exists(image_path): + log.warn('{} image cannot be found. Is `{}` image directory correct?'. + format(image_path, image_dir)) + writer = Writer(image_path, width, height) + + + frame = frames[frameid] + + objids = sorted(frame.keys()) + + for objid in objids: + + obj = frame[objid] + print( " %d: (%f,%f) (%f,%f) '%s'" % (objid, obj['xtl'],obj['ytl'],obj['xbr'],obj['ybr'],obj['label']) ) + + + for objid in objids: + + box = frame[objid] + + label = box.get('label') + xmin = float(box.get('xtl')) + ymin = float(box.get('ytl')) + xmax = float(box.get('xbr')) + ymax = float(box.get('ybr')) + + writer.addObject(label, xmin, ymin, xmax, ymax) + + anno_name = os.path.basename(os.path.splitext(image_name)[0] + '.xml') + anno_dir = os.path.dirname(os.path.join(output_dir, image_name)) + os.makedirs(anno_dir, exist_ok=True) + writer.save(os.path.join(anno_dir, anno_name)) + + else: + for img_tag in cvat_xml.findall('image'): + image_name = img_tag.get('name') + width = img_tag.get('width') + height = img_tag.get('height') + image_path = os.path.join(image_dir, image_name) + if not os.path.exists(image_path): + log.warn('{} image cannot be found. Is `{}` image directory correct?'. + format(image_path, image_dir)) + writer = Writer(image_path, width, height) + + unknown_tags = {x.tag for x in img_tag.iter()}.difference(KNOWN_TAGS) + if unknown_tags: + log.warn('Ignoring tags for image {}: {}'.format(image_path, unknown_tags)) + + for box in img_tag.findall('box'): + label = box.get('label') + xmin = float(box.get('xtl')) + ymin = float(box.get('ytl')) + xmax = float(box.get('xbr')) + ymax = float(box.get('ybr')) + + writer.addObject(label, xmin, ymin, xmax, ymax) + + anno_name = os.path.basename(os.path.splitext(image_name)[0] + '.xml') + anno_dir = os.path.dirname(os.path.join(output_dir, image_name)) + os.makedirs(anno_dir, exist_ok=True) + writer.save(os.path.join(anno_dir, anno_name)) def main(): From 5a9dc5dbaa4d093db715fa1d770044bb673d1ed8 Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 5 Feb 2019 12:17:23 -0600 Subject: [PATCH 3/8] Fixing issues with code quality checks --- utils/voc/converter.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/utils/voc/converter.py b/utils/voc/converter.py index ad64431ba255..44b2ec547e25 100644 --- a/utils/voc/converter.py +++ b/utils/voc/converter.py @@ -6,7 +6,7 @@ CVAT XML and writes the annotations in PASCAL VOC format into a given directory. -This implementation only supports bounding boxes in CVAT annotation format. +This implementation only supports bounding boxes in CVAT annotation format. It supports both interpolation tracks from video and annotated images. If it encounters any tracks or annotations that are not bounding boxes, it ignores them. @@ -71,8 +71,8 @@ def process_cvat_xml(xml_file, image_dir, output_dir): for box in boxes: frameid = int(box.get('frame')) outside = int(box.get('outside')) - occluded = int(box.get('occluded')) - keyframe = int(box.get('keyframe')) + #occluded = int(box.get('occluded')) #currently unused + #keyframe = int(box.get('keyframe')) #currently unused xtl = float(box.get('xtl')) ytl = float(box.get('ytl')) xbr = float(box.get('xbr')) @@ -82,7 +82,7 @@ def process_cvat_xml(xml_file, image_dir, output_dir): if outside == 0: frame[ trackid ] = { 'xtl': xtl, 'ytl': ytl, 'xbr': xbr, 'ybr': ybr, 'label': label } - + frames[ frameid ] = frame # Spit out a list of each object for each frame From 10fbc287bf39389db48e36a59112d6623c9c9181 Mon Sep 17 00:00:00 2001 From: Josh Date: Wed, 6 Feb 2019 13:06:56 -0600 Subject: [PATCH 4/8] code review updates --- utils/voc/converter.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/utils/voc/converter.py b/utils/voc/converter.py index 44b2ec547e25..ced565c9c633 100644 --- a/utils/voc/converter.py +++ b/utils/voc/converter.py @@ -6,10 +6,9 @@ CVAT XML and writes the annotations in PASCAL VOC format into a given directory. -This implementation only supports bounding boxes in CVAT annotation format. -It supports both interpolation tracks from video and annotated images. -If it encounters any tracks or annotations that are not bounding boxes, -it ignores them. +This implementation supports both interpolation tracks from video and +annotated images. If it encounters any tracks or annotations that are +not bounding boxes, it ignores them. """ import os @@ -98,7 +97,6 @@ def process_cvat_xml(xml_file, image_dir, output_dir): format(image_path, image_dir)) writer = Writer(image_path, width, height) - frame = frames[frameid] objids = sorted(frame.keys()) From 02df1f18b00552d3856a8bb223547b547c72b317 Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 8 Feb 2019 20:09:55 -0600 Subject: [PATCH 5/8] Addressing review comments. --- CHANGELOG.md | 1 + utils/voc/converter.py | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6097e3648355..5eda7d5c6d05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - OpenVINO auto annotation: it is possible to upload a custom model and annotate images automatically. - Ability to rotate images/video in the client part (Ctrl+R, Shift+Ctrl+R shortcuts) (#305) +- Converter for VOC now supports interpolation tracks ### Changed - Propagation setup has been moved from settings to bottom player panel diff --git a/utils/voc/converter.py b/utils/voc/converter.py index ced565c9c633..75c94cb531af 100644 --- a/utils/voc/converter.py +++ b/utils/voc/converter.py @@ -86,11 +86,12 @@ def process_cvat_xml(xml_file, image_dir, output_dir): # Spit out a list of each object for each frame for frameid in sorted(frames.keys()): - print( frameid ) + #print( frameid ) image_name = "%s_%08d.jpg" % (basename, frameid) - width = 1270 - height = 780 + orig = cvat_xml.find('.//meta/task/original_size') + width = orig.get( 'width' ) + height = orig.get( 'height' ) image_path = os.path.join(image_dir, image_name) if not os.path.exists(image_path): log.warn('{} image cannot be found. Is `{}` image directory correct?'. @@ -104,7 +105,7 @@ def process_cvat_xml(xml_file, image_dir, output_dir): for objid in objids: obj = frame[objid] - print( " %d: (%f,%f) (%f,%f) '%s'" % (objid, obj['xtl'],obj['ytl'],obj['xbr'],obj['ybr'],obj['label']) ) + #print( " %d: (%f,%f) (%f,%f) '%s'" % (objid, obj['xtl'],obj['ytl'],obj['xbr'],obj['ybr'],obj['label']) ) for objid in objids: From 077f98486d11976d815518979ae21b3249e30252 Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 8 Feb 2019 20:38:28 -0600 Subject: [PATCH 6/8] Fixed failing tests. --- utils/voc/tests/test_process_cvat_xml.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/utils/voc/tests/test_process_cvat_xml.py b/utils/voc/tests/test_process_cvat_xml.py index eb3456219f78..60a300bd4bb2 100644 --- a/utils/voc/tests/test_process_cvat_xml.py +++ b/utils/voc/tests/test_process_cvat_xml.py @@ -103,6 +103,10 @@ admin + + 1024 + 768 + 2018-06-06 15:52:11.138470+03:00 @@ -153,6 +157,7 @@ def test_parse_annotation_xml(self, mock_log): process_cvat_xml(xml_filename, 'img_dir', voc_dir) for exp in expected_xmls: self.assertTrue(os.path.exists(exp)) + # We should add in some code to parse the resulting xml files @mock.patch('utils.voc.converter.log') def test_parse_interpolation_xml(self, mock_log): @@ -161,10 +166,19 @@ def test_parse_interpolation_xml(self, mock_log): file.write(XML_INTERPOLATION_EXAMPLE) voc_dir = os.path.join(self.test_dir, 'voc_dir') - expected_warn = 'Cannot parse interpolation tracks, ignoring 2 tracks' + + + frames = [0, 1, 2, 110, 111, 112 ] + expected_xmls = [os.path.join(voc_dir, 'interpolations_%08d.xml' % x ) + for x in frames] process_cvat_xml(xml_filename, 'img_dir', voc_dir) self.assertTrue(os.path.exists(voc_dir)) - self.assertTrue(len(os.listdir(voc_dir)) == 0) - mock_log.warn.assert_called_once_with(expected_warn) + self.assertTrue(len(os.listdir(voc_dir)) == len(frames)) + for exp in expected_xmls: + self.assertTrue(os.path.exists(exp)) + # We should add in some code to parse the resulting xml files + + + From 61877415f28f0b1f813970024e1b7a433fa206a0 Mon Sep 17 00:00:00 2001 From: Josh Date: Mon, 11 Feb 2019 18:50:37 -0600 Subject: [PATCH 7/8] Fixed two additional findings from review. --- utils/voc/converter.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/utils/voc/converter.py b/utils/voc/converter.py index 75c94cb531af..66b8bd5c5284 100644 --- a/utils/voc/converter.py +++ b/utils/voc/converter.py @@ -89,9 +89,8 @@ def process_cvat_xml(xml_file, image_dir, output_dir): #print( frameid ) image_name = "%s_%08d.jpg" % (basename, frameid) - orig = cvat_xml.find('.//meta/task/original_size') - width = orig.get( 'width' ) - height = orig.get( 'height' ) + width = int(cvat_xml.find('.//original_size/width').text) + height = int(cvat_xml.find('.//original_size/height').text) image_path = os.path.join(image_dir, image_name) if not os.path.exists(image_path): log.warn('{} image cannot be found. Is `{}` image directory correct?'. @@ -102,12 +101,6 @@ def process_cvat_xml(xml_file, image_dir, output_dir): objids = sorted(frame.keys()) - for objid in objids: - - obj = frame[objid] - #print( " %d: (%f,%f) (%f,%f) '%s'" % (objid, obj['xtl'],obj['ytl'],obj['xbr'],obj['ybr'],obj['label']) ) - - for objid in objids: box = frame[objid] From 46bcd7a50d7567ab31b3023dfa45f1d5ef1f5a05 Mon Sep 17 00:00:00 2001 From: Josh Date: Mon, 11 Feb 2019 18:52:54 -0600 Subject: [PATCH 8/8] Moved width and height call out of for loop --- utils/voc/converter.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/utils/voc/converter.py b/utils/voc/converter.py index 66b8bd5c5284..1764e819313e 100644 --- a/utils/voc/converter.py +++ b/utils/voc/converter.py @@ -84,13 +84,14 @@ def process_cvat_xml(xml_file, image_dir, output_dir): frames[ frameid ] = frame + width = int(cvat_xml.find('.//original_size/width').text) + height = int(cvat_xml.find('.//original_size/height').text) + # Spit out a list of each object for each frame for frameid in sorted(frames.keys()): #print( frameid ) image_name = "%s_%08d.jpg" % (basename, frameid) - width = int(cvat_xml.find('.//original_size/width').text) - height = int(cvat_xml.find('.//original_size/height').text) image_path = os.path.join(image_dir, image_name) if not os.path.exists(image_path): log.warn('{} image cannot be found. Is `{}` image directory correct?'.