x2coco.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. #!/usr/bin/env python
  2. # coding: utf-8
  3. # Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved.
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. import argparse
  17. import glob
  18. import json
  19. import os
  20. import os.path as osp
  21. import shutil
  22. import xml.etree.ElementTree as ET
  23. from tqdm import tqdm
  24. import numpy as np
  25. import PIL.ImageDraw
  26. label_to_num = {}
  27. categories_list = []
  28. labels_list = []
  29. class MyEncoder(json.JSONEncoder):
  30. def default(self, obj):
  31. if isinstance(obj, np.integer):
  32. return int(obj)
  33. elif isinstance(obj, np.floating):
  34. return float(obj)
  35. elif isinstance(obj, np.ndarray):
  36. return obj.tolist()
  37. else:
  38. return super(MyEncoder, self).default(obj)
  39. def images_labelme(data, num):
  40. image = {}
  41. image['height'] = data['imageHeight']
  42. image['width'] = data['imageWidth']
  43. image['id'] = num + 1
  44. if '\\' in data['imagePath']:
  45. image['file_name'] = data['imagePath'].split('\\')[-1]
  46. else:
  47. image['file_name'] = data['imagePath'].split('/')[-1]
  48. return image
  49. def images_cityscape(data, num, img_file):
  50. image = {}
  51. image['height'] = data['imgHeight']
  52. image['width'] = data['imgWidth']
  53. image['id'] = num + 1
  54. image['file_name'] = img_file
  55. return image
  56. def categories(label, labels_list):
  57. category = {}
  58. category['supercategory'] = 'component'
  59. category['id'] = len(labels_list) + 1
  60. category['name'] = label
  61. return category
  62. def annotations_rectangle(points, label, image_num, object_num, label_to_num):
  63. annotation = {}
  64. seg_points = np.asarray(points).copy()
  65. seg_points[1, :] = np.asarray(points)[2, :]
  66. seg_points[2, :] = np.asarray(points)[1, :]
  67. annotation['segmentation'] = [list(seg_points.flatten())]
  68. annotation['iscrowd'] = 0
  69. annotation['image_id'] = image_num + 1
  70. annotation['bbox'] = list(
  71. map(float, [
  72. points[0][0], points[0][1], points[1][0] - points[0][0], points[1][
  73. 1] - points[0][1]
  74. ]))
  75. annotation['area'] = annotation['bbox'][2] * annotation['bbox'][3]
  76. annotation['category_id'] = label_to_num[label]
  77. annotation['id'] = object_num + 1
  78. return annotation
  79. def annotations_polygon(height, width, points, label, image_num, object_num,
  80. label_to_num):
  81. annotation = {}
  82. annotation['segmentation'] = [list(np.asarray(points).flatten())]
  83. annotation['iscrowd'] = 0
  84. annotation['image_id'] = image_num + 1
  85. annotation['bbox'] = list(map(float, get_bbox(height, width, points)))
  86. annotation['area'] = annotation['bbox'][2] * annotation['bbox'][3]
  87. annotation['category_id'] = label_to_num[label]
  88. annotation['id'] = object_num + 1
  89. return annotation
  90. def get_bbox(height, width, points):
  91. polygons = points
  92. mask = np.zeros([height, width], dtype=np.uint8)
  93. mask = PIL.Image.fromarray(mask)
  94. xy = list(map(tuple, polygons))
  95. PIL.ImageDraw.Draw(mask).polygon(xy=xy, outline=1, fill=1)
  96. mask = np.array(mask, dtype=bool)
  97. index = np.argwhere(mask == 1)
  98. rows = index[:, 0]
  99. clos = index[:, 1]
  100. left_top_r = np.min(rows)
  101. left_top_c = np.min(clos)
  102. right_bottom_r = np.max(rows)
  103. right_bottom_c = np.max(clos)
  104. return [
  105. left_top_c, left_top_r, right_bottom_c - left_top_c,
  106. right_bottom_r - left_top_r
  107. ]
  108. def deal_json(ds_type, img_path, json_path):
  109. data_coco = {}
  110. images_list = []
  111. annotations_list = []
  112. image_num = -1
  113. object_num = -1
  114. for img_file in os.listdir(img_path):
  115. img_label = os.path.splitext(img_file)[0]
  116. if img_file.split('.')[
  117. -1] not in ['bmp', 'jpg', 'jpeg', 'png', 'JPEG', 'JPG', 'PNG']:
  118. continue
  119. label_file = osp.join(json_path, img_label + '.json')
  120. print('Generating dataset from:', label_file)
  121. image_num = image_num + 1
  122. with open(label_file) as f:
  123. data = json.load(f)
  124. if ds_type == 'labelme':
  125. images_list.append(images_labelme(data, image_num))
  126. elif ds_type == 'cityscape':
  127. images_list.append(images_cityscape(data, image_num, img_file))
  128. if ds_type == 'labelme':
  129. for shapes in data['shapes']:
  130. object_num = object_num + 1
  131. label = shapes['label']
  132. if label not in labels_list:
  133. categories_list.append(categories(label, labels_list))
  134. labels_list.append(label)
  135. label_to_num[label] = len(labels_list)
  136. p_type = shapes['shape_type']
  137. if p_type == 'polygon':
  138. points = shapes['points']
  139. annotations_list.append(
  140. annotations_polygon(data['imageHeight'], data[
  141. 'imageWidth'], points, label, image_num,
  142. object_num, label_to_num))
  143. if p_type == 'rectangle':
  144. (x1, y1), (x2, y2) = shapes['points']
  145. x1, x2 = sorted([x1, x2])
  146. y1, y2 = sorted([y1, y2])
  147. points = [[x1, y1], [x2, y2], [x1, y2], [x2, y1]]
  148. annotations_list.append(
  149. annotations_rectangle(points, label, image_num,
  150. object_num, label_to_num))
  151. elif ds_type == 'cityscape':
  152. for shapes in data['objects']:
  153. object_num = object_num + 1
  154. label = shapes['label']
  155. if label not in labels_list:
  156. categories_list.append(categories(label, labels_list))
  157. labels_list.append(label)
  158. label_to_num[label] = len(labels_list)
  159. points = shapes['polygon']
  160. annotations_list.append(
  161. annotations_polygon(data['imgHeight'], data[
  162. 'imgWidth'], points, label, image_num, object_num,
  163. label_to_num))
  164. data_coco['images'] = images_list
  165. data_coco['categories'] = categories_list
  166. data_coco['annotations'] = annotations_list
  167. return data_coco
  168. def voc_get_label_anno(ann_dir_path, ann_ids_path, labels_path):
  169. with open(labels_path, 'r') as f:
  170. labels_str = f.read().split()
  171. labels_ids = list(range(1, len(labels_str) + 1))
  172. with open(ann_ids_path, 'r') as f:
  173. ann_ids = f.read().split()
  174. ann_paths = []
  175. for aid in ann_ids:
  176. if aid.endswith('xml'):
  177. ann_path = os.path.join(ann_dir_path, aid)
  178. else:
  179. ann_path = os.path.join(ann_dir_path, aid + '.xml')
  180. ann_paths.append(ann_path)
  181. return dict(zip(labels_str, labels_ids)), ann_paths
  182. def voc_get_image_info(annotation_root, im_id):
  183. filename = annotation_root.findtext('filename')
  184. assert filename is not None
  185. img_name = os.path.basename(filename)
  186. size = annotation_root.find('size')
  187. width = float(size.findtext('width'))
  188. height = float(size.findtext('height'))
  189. image_info = {
  190. 'file_name': filename,
  191. 'height': height,
  192. 'width': width,
  193. 'id': im_id
  194. }
  195. return image_info
  196. def voc_get_coco_annotation(obj, label2id):
  197. label = obj.findtext('name')
  198. assert label in label2id, "label is not in label2id."
  199. category_id = label2id[label]
  200. bndbox = obj.find('bndbox')
  201. xmin = float(bndbox.findtext('xmin'))
  202. ymin = float(bndbox.findtext('ymin'))
  203. xmax = float(bndbox.findtext('xmax'))
  204. ymax = float(bndbox.findtext('ymax'))
  205. assert xmax > xmin and ymax > ymin, "Box size error."
  206. o_width = xmax - xmin
  207. o_height = ymax - ymin
  208. anno = {
  209. 'area': o_width * o_height,
  210. 'iscrowd': 0,
  211. 'bbox': [xmin, ymin, o_width, o_height],
  212. 'category_id': category_id,
  213. 'ignore': 0,
  214. }
  215. return anno
  216. def voc_xmls_to_cocojson(annotation_paths, label2id, output_dir, output_file):
  217. output_json_dict = {
  218. "images": [],
  219. "type": "instances",
  220. "annotations": [],
  221. "categories": []
  222. }
  223. bnd_id = 1 # bounding box start id
  224. im_id = 0
  225. print('Start converting !')
  226. for a_path in tqdm(annotation_paths):
  227. # Read annotation xml
  228. ann_tree = ET.parse(a_path)
  229. ann_root = ann_tree.getroot()
  230. img_info = voc_get_image_info(ann_root, im_id)
  231. output_json_dict['images'].append(img_info)
  232. for obj in ann_root.findall('object'):
  233. ann = voc_get_coco_annotation(obj=obj, label2id=label2id)
  234. ann.update({'image_id': im_id, 'id': bnd_id})
  235. output_json_dict['annotations'].append(ann)
  236. bnd_id = bnd_id + 1
  237. im_id += 1
  238. for label, label_id in label2id.items():
  239. category_info = {'supercategory': 'none', 'id': label_id, 'name': label}
  240. output_json_dict['categories'].append(category_info)
  241. output_file = os.path.join(output_dir, output_file)
  242. with open(output_file, 'w') as f:
  243. output_json = json.dumps(output_json_dict)
  244. f.write(output_json)
  245. def main():
  246. parser = argparse.ArgumentParser(
  247. formatter_class=argparse.ArgumentDefaultsHelpFormatter)
  248. parser.add_argument(
  249. '--dataset_type',
  250. help='the type of dataset, can be `voc`, `labelme` or `cityscape`')
  251. parser.add_argument('--json_input_dir', help='input annotated directory')
  252. parser.add_argument('--image_input_dir', help='image directory')
  253. parser.add_argument(
  254. '--output_dir', help='output dataset directory', default='./')
  255. parser.add_argument(
  256. '--train_proportion',
  257. help='the proportion of train dataset',
  258. type=float,
  259. default=1.0)
  260. parser.add_argument(
  261. '--val_proportion',
  262. help='the proportion of validation dataset',
  263. type=float,
  264. default=0.0)
  265. parser.add_argument(
  266. '--test_proportion',
  267. help='the proportion of test dataset',
  268. type=float,
  269. default=0.0)
  270. parser.add_argument(
  271. '--voc_anno_dir',
  272. help='In Voc format dataset, path to annotation files directory.',
  273. type=str,
  274. default=None)
  275. parser.add_argument(
  276. '--voc_anno_list',
  277. help='In Voc format dataset, path to annotation files ids list.',
  278. type=str,
  279. default=None)
  280. parser.add_argument(
  281. '--voc_label_list',
  282. help='In Voc format dataset, path to label list. The content of each line is a category.',
  283. type=str,
  284. default=None)
  285. parser.add_argument(
  286. '--voc_out_name',
  287. type=str,
  288. default='voc.json',
  289. help='In Voc format dataset, path to output json file')
  290. args = parser.parse_args()
  291. try:
  292. assert args.dataset_type in ['voc', 'labelme', 'cityscape']
  293. except AssertionError as e:
  294. print(
  295. 'Now only support the voc, cityscape dataset and labelme dataset!!')
  296. os._exit(0)
  297. if args.dataset_type == 'voc':
  298. assert args.voc_anno_dir and args.voc_anno_list and args.voc_label_list
  299. label2id, ann_paths = voc_get_label_anno(
  300. args.voc_anno_dir, args.voc_anno_list, args.voc_label_list)
  301. voc_xmls_to_cocojson(
  302. annotation_paths=ann_paths,
  303. label2id=label2id,
  304. output_dir=args.output_dir,
  305. output_file=args.voc_out_name)
  306. else:
  307. try:
  308. assert os.path.exists(args.json_input_dir)
  309. except AssertionError as e:
  310. print('The json folder does not exist!')
  311. os._exit(0)
  312. try:
  313. assert os.path.exists(args.image_input_dir)
  314. except AssertionError as e:
  315. print('The image folder does not exist!')
  316. os._exit(0)
  317. try:
  318. assert abs(args.train_proportion + args.val_proportion \
  319. + args.test_proportion - 1.0) < 1e-5
  320. except AssertionError as e:
  321. print(
  322. 'The sum of pqoportion of training, validation and test datase must be 1!'
  323. )
  324. os._exit(0)
  325. # Allocate the dataset.
  326. total_num = len(glob.glob(osp.join(args.json_input_dir, '*.json')))
  327. if args.train_proportion != 0:
  328. train_num = int(total_num * args.train_proportion)
  329. out_dir = args.output_dir + '/train'
  330. if not os.path.exists(out_dir):
  331. os.makedirs(out_dir)
  332. else:
  333. train_num = 0
  334. if args.val_proportion == 0.0:
  335. val_num = 0
  336. test_num = total_num - train_num
  337. out_dir = args.output_dir + '/test'
  338. if args.test_proportion != 0.0 and not os.path.exists(out_dir):
  339. os.makedirs(out_dir)
  340. else:
  341. val_num = int(total_num * args.val_proportion)
  342. test_num = total_num - train_num - val_num
  343. val_out_dir = args.output_dir + '/val'
  344. if not os.path.exists(val_out_dir):
  345. os.makedirs(val_out_dir)
  346. test_out_dir = args.output_dir + '/test'
  347. if args.test_proportion != 0.0 and not os.path.exists(test_out_dir):
  348. os.makedirs(test_out_dir)
  349. count = 1
  350. for img_name in os.listdir(args.image_input_dir):
  351. if count <= train_num:
  352. if osp.exists(args.output_dir + '/train/'):
  353. shutil.copyfile(
  354. osp.join(args.image_input_dir, img_name),
  355. osp.join(args.output_dir + '/train/', img_name))
  356. else:
  357. if count <= train_num + val_num:
  358. if osp.exists(args.output_dir + '/val/'):
  359. shutil.copyfile(
  360. osp.join(args.image_input_dir, img_name),
  361. osp.join(args.output_dir + '/val/', img_name))
  362. else:
  363. if osp.exists(args.output_dir + '/test/'):
  364. shutil.copyfile(
  365. osp.join(args.image_input_dir, img_name),
  366. osp.join(args.output_dir + '/test/', img_name))
  367. count = count + 1
  368. # Deal with the json files.
  369. if not os.path.exists(args.output_dir + '/annotations'):
  370. os.makedirs(args.output_dir + '/annotations')
  371. if args.train_proportion != 0:
  372. train_data_coco = deal_json(args.dataset_type,
  373. args.output_dir + '/train',
  374. args.json_input_dir)
  375. train_json_path = osp.join(args.output_dir + '/annotations',
  376. 'instance_train.json')
  377. json.dump(
  378. train_data_coco,
  379. open(train_json_path, 'w'),
  380. indent=4,
  381. cls=MyEncoder)
  382. if args.val_proportion != 0:
  383. val_data_coco = deal_json(args.dataset_type,
  384. args.output_dir + '/val',
  385. args.json_input_dir)
  386. val_json_path = osp.join(args.output_dir + '/annotations',
  387. 'instance_val.json')
  388. json.dump(
  389. val_data_coco,
  390. open(val_json_path, 'w'),
  391. indent=4,
  392. cls=MyEncoder)
  393. if args.test_proportion != 0:
  394. test_data_coco = deal_json(args.dataset_type,
  395. args.output_dir + '/test',
  396. args.json_input_dir)
  397. test_json_path = osp.join(args.output_dir + '/annotations',
  398. 'instance_test.json')
  399. json.dump(
  400. test_data_coco,
  401. open(test_json_path, 'w'),
  402. indent=4,
  403. cls=MyEncoder)
  404. if __name__ == '__main__':
  405. main()