Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]
### Added
-
- `WiderFace` dataset format (<https://github.com/openvinotoolkit/datumaro/pull/65>)

### Changed
-
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ CVAT annotations ---> Publication, statistics etc.
- [PASCAL VOC](http://host.robots.ox.ac.uk/pascal/VOC/voc2012/htmldoc/index.html) (`classification`, `detection`, `segmentation`, `action_classification`, `person_layout`)
- [YOLO](https://github.com/AlexeyAB/darknet#how-to-train-pascal-voc-data) (`bboxes`)
- [TF Detection API](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/using_your_own_dataset.md) (`bboxes`, `masks`)
- [WIDER Face](http://shuoyang1213.me/WIDERFACE/) (`bboxes`)
- [MOT sequences](https://arxiv.org/pdf/1906.04567.pdf)
- [MOTS PNG](https://www.vision.rwth-aachen.de/page/mots)
- [ImageNet](http://image-net.org/)
Expand Down
116 changes: 116 additions & 0 deletions datumaro/plugins/widerface_format.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@

# Copyright (C) 2020 Intel Corporation
#
# SPDX-License-Identifier: MIT

import os
import os.path as osp
import re

from datumaro.components.converter import Converter
from datumaro.components.extractor import (AnnotationType, Bbox, DatasetItem,
Importer, SourceExtractor)


class WiderFacePath:
IMAGE_EXT = '.jpg'
ANNOTATIONS_DIR = 'wider_face_split'
IMAGES_DIR = 'images'
SUBSET_DIR = 'WIDER_'
BBOX_ATTRIBUTES = ['blur', 'expression', 'illumination',
'occlusion', 'pose', 'invalid']

class WiderFaceExtractor(SourceExtractor):
def __init__(self, path):
if not osp.isfile(path):
raise Exception("Can't read annotation file '%s'" % path)
self._path = path
self._dataset_dir = osp.dirname(osp.dirname(path))

subset = osp.splitext(osp.basename(path))[0]
match = re.fullmatch(r'wider_face_\S+_bbx_gt', subset)
if match:
subset = subset.split('_')[2]
super().__init__(subset=subset)

self._items = list(self._load_items(path).values())

def _load_items(self, path):
items = {}
with open(path, 'r') as f:
lines = f.readlines()

image_ids = [image_id for image_id, line in enumerate(lines)
if WiderFacePath.IMAGE_EXT in line]

for image_id in image_ids:
image = lines[image_id]
image_path = osp.join(self._dataset_dir, WiderFacePath.SUBSET_DIR
+ self._subset, WiderFacePath.IMAGES_DIR, image[:-1])
item_id = image[:-(len(WiderFacePath.IMAGE_EXT) + 1)]

bbox_count = lines[image_id + 1]
bbox_lines = lines[image_id + 2 : image_id + int(bbox_count) + 2]
annotations = []
for bbox in bbox_lines:
bbox_list = bbox.split()
if len(bbox_list) >= 4:
attributes = {}
if len(bbox_list) == 10:
i = 4
for attr in WiderFacePath.BBOX_ATTRIBUTES:
attributes[attr] = int(bbox_list[i])
i += 1
annotations.append(Bbox(
int(bbox_list[0]), int(bbox_list[1]),
int(bbox_list[2]), int(bbox_list[3]),
attributes = attributes
))

items[item_id] = DatasetItem(id=item_id, subset=self._subset,
image=image_path, annotations=annotations)
return items

class WiderFaceImporter(Importer):
@classmethod
def find_sources(cls, path):
return cls._find_sources_recursive(osp.join(path,
WiderFacePath.ANNOTATIONS_DIR), '.txt', 'wider_face')

class WiderFaceConverter(Converter):
DEFAULT_IMAGE_EXT = '.jpg'

def apply(self):
save_dir = self._save_dir

os.makedirs(save_dir, exist_ok=True)

for subset_name, subset in self._extractor.subsets().items():
subset_dir = osp.join(save_dir, WiderFacePath.SUBSET_DIR + subset_name)

wider_annotation = ''
for item in subset:
wider_annotation += '%s\n' % (item.id + WiderFacePath.IMAGE_EXT)
if item.has_image and self._save_images:
self._save_image(item, osp.join(save_dir, subset_dir,
WiderFacePath.IMAGES_DIR, item.id + WiderFacePath.IMAGE_EXT))

bboxes = [a for a in item.annotations
if a.type == AnnotationType.bbox]

wider_annotation += '%s\n' % len(bboxes)
for bbox in bboxes:
wider_bb = ' '.join('%d' % p for p in bbox.get_bbox())
wider_annotation += '%s ' % wider_bb
if bbox.attributes:
for attr in WiderFacePath.BBOX_ATTRIBUTES:
if bbox.attributes[attr]:
wider_annotation += '%s ' % bbox.attributes[attr]
else:
wider_annotation += '0 '
wider_annotation += '\n'
annotation_path = osp.join(save_dir, WiderFacePath.ANNOTATIONS_DIR,
'wider_face_' + subset_name + '_bbx_gt.txt')
os.makedirs(osp.dirname(annotation_path), exist_ok=True)
with open(annotation_path, 'w') as f:
f.write(wider_annotation)
3 changes: 3 additions & 0 deletions docs/user_manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ List of supported formats:
- TF Detection API (`bboxes`, `masks`)
- Format specifications: [bboxes](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/using_your_own_dataset.md), [masks](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/instance_segmentation.md)
- [Dataset example](../tests/assets/tf_detection_api_dataset)
- WIDER Face (`bboxes`)
- [Format specification](http://shuoyang1213.me/WIDERFACE/)
- [Dataset example](../tests/assets/wider_dataset)
- MOT sequences
- [Format specification](https://arxiv.org/pdf/1906.04567.pdf)
- [Dataset example](../tests/assets/mot_dataset)
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
0--Parade/0_Parade_image_01.jpg
1
1 2 2 2 0 0 0 0 0 0
1--Handshaking/1_Handshaking_image_02.jpg
2
1 1 2 2 0 0 1 0 0 0
5 1 2 2 0 0 1 0 0 0
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
0--Parade/0_Parade_image_03.jpg
3
0 0 1 1 2 0 0 0 2 0
3 2 1 2 0 0 0 1 0 0
5 6 1 1 2 0 0 0 2 0
122 changes: 122 additions & 0 deletions tests/test_widerface_format.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import os.path as osp
from unittest import TestCase

import numpy as np
from datumaro.components.extractor import Bbox, DatasetItem
from datumaro.components.project import Dataset, Project
from datumaro.plugins.widerface_format import WiderFaceConverter, WiderFaceImporter
from datumaro.util.test_utils import TestDir, compare_datasets


class WiderFaceFormatTest(TestCase):
def test_can_save_and_load(self):
source_dataset = Dataset.from_iterable([
DatasetItem(id='1', subset='train', image=np.ones((8, 8, 3)),
annotations=[
Bbox(0, 2, 4, 2),
Bbox(0, 1, 2, 3, attributes = {
'blur': 2, 'expression': 0, 'illumination': 0,
'occlusion': 0, 'pose': 2, 'invalid': 0}),
]
),
DatasetItem(id='2', subset='train', image=np.ones((10, 10, 3)),
annotations=[
Bbox(0, 2, 4, 2, attributes = {
'blur': 2, 'expression': 0, 'illumination': 1,
'occlusion': 0, 'pose': 1, 'invalid': 0}),
Bbox(3, 3, 2, 3, attributes = {
'blur': 0, 'expression': 1, 'illumination': 0,
'occlusion': 0, 'pose': 2, 'invalid': 0}),
Bbox(2, 1, 2, 3, attributes = {
'blur': 2, 'expression': 0, 'illumination': 0,
'occlusion': 0, 'pose': 0, 'invalid': 1}),
]
),

DatasetItem(id='3', subset='val', image=np.ones((8, 8, 3)),
annotations=[
Bbox(0, 1, 5, 2, attributes = {
'blur': 2, 'expression': 1, 'illumination': 0,
'occlusion': 0, 'pose': 1, 'invalid': 0}),
Bbox(0, 2, 3, 2),
Bbox(0, 2, 4, 2),
Bbox(0, 7, 3, 2, attributes = {
'blur': 2, 'expression': 1, 'illumination': 0,
'occlusion': 0, 'pose': 1, 'invalid': 0}),
]
),

DatasetItem(id='4', subset='val', image=np.ones((8, 8, 3))),
])

with TestDir() as test_dir:
WiderFaceConverter.convert(source_dataset, test_dir, save_images=True)
parsed_dataset = WiderFaceImporter()(test_dir).make_dataset()

compare_datasets(self, source_dataset, parsed_dataset)

def test_can_save_dataset_with_no_subsets(self):
source_dataset = Dataset.from_iterable([
DatasetItem(id='a/b/1', image=np.ones((8, 8, 3)),
annotations=[
Bbox(0, 2, 4, 2),
Bbox(0, 1, 2, 3, attributes = {
'blur': 2, 'expression': 0, 'illumination': 0,
'occlusion': 0, 'pose': 2, 'invalid': 0}),
]
),
])

with TestDir() as test_dir:
WiderFaceConverter.convert(source_dataset, test_dir, save_images=True)
parsed_dataset = WiderFaceImporter()(test_dir).make_dataset()

compare_datasets(self, source_dataset, parsed_dataset)

DUMMY_DATASET_DIR = osp.join(osp.dirname(__file__), 'assets', 'widerface_dataset')

class WiderFaceImporterTest(TestCase):
def test_can_detect(self):
self.assertTrue(WiderFaceImporter.detect(DUMMY_DATASET_DIR))

def test_can_import(self):
expected_dataset = Dataset.from_iterable([
DatasetItem(id='0--Parade/0_Parade_image_01', subset='train',
image=np.ones((10, 15, 3)),
annotations=[
Bbox(1, 2, 2, 2, attributes = {
'blur': 0, 'expression': 0, 'illumination': 0,
'occlusion': 0, 'pose': 0, 'invalid': 0}),
]
),
DatasetItem(id='1--Handshaking/1_Handshaking_image_02', subset='train',
image=np.ones((10, 15, 3)),
annotations=[
Bbox(1, 1, 2, 2, attributes = {
'blur': 0, 'expression': 0, 'illumination': 1,
'occlusion': 0, 'pose': 0, 'invalid': 0}),
Bbox(5, 1, 2, 2, attributes = {
'blur': 0, 'expression': 0, 'illumination': 1,
'occlusion': 0, 'pose': 0, 'invalid': 0}),
]
),
DatasetItem(id='0--Parade/0_Parade_image_03', subset='val',
image=np.ones((10, 15, 3)),
annotations=[
Bbox(0, 0, 1, 1, attributes = {
'blur': 2, 'expression': 0, 'illumination': 0,
'occlusion': 0, 'pose': 2, 'invalid': 0}),
Bbox(3, 2, 1, 2, attributes = {
'blur': 0, 'expression': 0, 'illumination': 0,
'occlusion': 1, 'pose': 0, 'invalid': 0}),
Bbox(5, 6, 1, 1, attributes = {
'blur': 2, 'expression': 0, 'illumination': 0,
'occlusion': 0, 'pose': 2, 'invalid': 0}),
]
),
])

dataset = Project.import_from(DUMMY_DATASET_DIR, 'wider_face') \
.make_dataset()

compare_datasets(self, expected_dataset, dataset)