rooneysh/Labelme2YOLO

polygon shape cannot convert yolo

stphtan94117 opened this issue · 13 comments

i have a lot of polygon (labelme json)
use this model and convert it. it only show bbox, not polygon shape.
CAN you fix it?

yolo only has bbox format, polygon isn't included.

yolov5 and yolov7 support segmentation. it can read polygon shape.
but it still support txt.

yolov5 and yolov7 support segmentation. it can read polygon shape. but it still support txt.

Any documents? I will check and upgrade this tools as soon as I have time.

this is yolov7 custom traning sample, and provide the dataset to dowload.
https://medium.com/augmented-startups/yolov7-segmentation-on-crack-using-roboflow-dataset-f13ae81b9958
the dataset are txt format,record each class and polygon.

hope you guy can make labelme2yolo and yolo2labelme 2 tools, it will help a lot of people who use yolo segmentation.

i have a lot of polygon (labelme json) use this model and convert it. it only show bbox, not polygon shape. CAN you fix it?
You can get it in https://pypi.org/project/labelme2yolo

i have a lot of polygon (labelme json) use this model and convert it. it only show bbox, not polygon shape. CAN you fix it?
You can get it in https://pypi.org/project/labelme2yolo

but after I use this model to convert, the lable is wrong.

i have a lot of polygon (labelme json) use this model and convert it. it only show bbox, not polygon shape. CAN you fix it?
You can get it in https://pypi.org/project/labelme2yolo

but after I use this model to convert, the lable is wrong.

@liuhhx are you also having issues with normalized values?

@rooneysh was wondering if you had a chance to look into this issue? I'm assuming the code was designed for conversion of BBoxes only, not for segmentation polygon points?

I created a pull request #7 just now, and the new code works on my datasets. Have a look if you're interested in it 😉
@stphtan94117 @Arshadoid

LoeWHJ commented

I changed the polygon data conversion that handles a single json file. Here is the modified code.
`'''
Created on Aug 18, 2021

@author: xiaosonh
'''
import os
import sys
import argparse
import shutil
import math
from collections import OrderedDict

import json
import cv2
import PIL.Image

from sklearn.model_selection import train_test_split
from labelme import utils

class Labelme2YOLO(object):

def __init__(self, json_dir):
    self._json_dir = json_dir

    self._label_id_map = self._get_label_id_map(self._json_dir)

def _make_train_val_dir(self):
    self._label_dir_path = os.path.join(self._json_dir,
                                        'YOLODataset/labels/')
    self._image_dir_path = os.path.join(self._json_dir,
                                        'YOLODataset/images/')

    for yolo_path in (os.path.join(self._label_dir_path + 'train/'),
                      os.path.join(self._label_dir_path + 'val/'),
                      os.path.join(self._image_dir_path + 'train/'),
                      os.path.join(self._image_dir_path + 'val/')):
        if os.path.exists(yolo_path):
            shutil.rmtree(yolo_path)

        os.makedirs(yolo_path)

def _get_label_id_map(self, json_dir):
    label_set = set()

    for file_name in os.listdir(json_dir):
        if file_name.endswith('json'):
            json_path = os.path.join(json_dir, file_name)
            data = json.load(open(json_path))
            for shape in data['shapes']:
                label_set.add(shape['label'])

    return OrderedDict([(label, label_id) \
                        for label_id, label in enumerate(label_set)])

def _train_test_split(self, folders, json_names, val_size):
    if len(folders) > 0 and 'train' in folders and 'val' in folders:
        train_folder = os.path.join(self._json_dir, 'train/')
        train_json_names = [train_sample_name + '.json' \
                            for train_sample_name in os.listdir(train_folder) \
                            if os.path.isdir(os.path.join(train_folder, train_sample_name))]

        val_folder = os.path.join(self._json_dir, 'val/')
        val_json_names = [val_sample_name + '.json' \
                          for val_sample_name in os.listdir(val_folder) \
                          if os.path.isdir(os.path.join(val_folder, val_sample_name))]

        return train_json_names, val_json_names

    train_idxs, val_idxs = train_test_split(range(len(json_names)),
                                            test_size=val_size)
    train_json_names = [json_names[train_idx] for train_idx in train_idxs]
    val_json_names = [json_names[val_idx] for val_idx in val_idxs]

    return train_json_names, val_json_names

def convert(self, val_size):
    json_names = [file_name for file_name in os.listdir(self._json_dir) \
                  if os.path.isfile(os.path.join(self._json_dir, file_name)) and \
                  file_name.endswith('.json')]
    folders = [file_name for file_name in os.listdir(self._json_dir) \
               if os.path.isdir(os.path.join(self._json_dir, file_name))]
    train_json_names, val_json_names = self._train_test_split(folders, json_names, val_size)

    self._make_train_val_dir()

    # convert labelme object to yolo format object, and save them to files
    # also get image from labelme json file and save them under images folder
    for target_dir, json_names in zip(('train/', 'val/'),
                                      (train_json_names, val_json_names)):
        for json_name in json_names:
            json_path = os.path.join(self._json_dir, json_name)
            json_data = json.load(open(json_path))

            print('Converting %s for %s ...' % (json_name, target_dir.replace('/', '')))

            img_path = self._save_yolo_image(json_data,
                                             json_name,
                                             self._image_dir_path,
                                             target_dir)

            yolo_obj_list = self._get_yolo_object_list(json_data, img_path)
            self._save_yolo_label(json_name,
                                  self._label_dir_path,
                                  target_dir,
                                  yolo_obj_list)

    print('Generating dataset.yaml file ...')
    self._save_dataset_yaml()

def convert_one(self, json_name):
    json_path = os.path.join(self._json_dir, json_name)
    json_data = json.load(open(json_path))

    print('Converting %s ...' % json_name)

    img_path = self._save_yolo_image(json_data, json_name,
                                     self._json_dir, '')

    yolo_obj_list = self._get_yolo_object_list(json_data, img_path)
    self._save_yolo_label(json_name, self._json_dir,
                          '', yolo_obj_list)

def _get_yolo_object_list(self, json_data, img_path):
    yolo_obj_list = []

    img_h, img_w, _ = cv2.imread(img_path).shape
    for shape in json_data['shapes']:
        one_yolo_obj_list = []
        # labelme circle shape is different from others
        # it only has 2 points, 1st is circle center, 2nd is drag end point
        if shape['shape_type'] == 'circle':
            yolo_obj = self._get_circle_shape_yolo_object(shape, img_h, img_w)
        else:
            yolo_obj = self._get_other_shape_yolo_object(shape, img_h, img_w)
        one_yolo_obj_list.append(yolo_obj[0])
        for i in range(len(yolo_obj[1])):
            one_yolo_obj_list.append(yolo_obj[1][i])
            one_yolo_obj_list.append(yolo_obj[2][i])

        yolo_obj_list.append(one_yolo_obj_list)

    return yolo_obj_list

def _get_circle_shape_yolo_object(self, shape, img_h, img_w):
    obj_center_x, obj_center_y = shape['points'][0]

    radius = math.sqrt((obj_center_x - shape['points'][1][0]) ** 2 +
                       (obj_center_y - shape['points'][1][1]) ** 2)
    obj_w = 2 * radius
    obj_h = 2 * radius

    yolo_center_x = round(float(obj_center_x / img_w), 6)
    yolo_center_y = round(float(obj_center_y / img_h), 6)
    yolo_w = round(float(obj_w / img_w), 6)
    yolo_h = round(float(obj_h / img_h), 6)

    label_id = self._label_id_map[shape['label']]

    return label_id, yolo_center_x, yolo_center_y, yolo_w, yolo_h

def _get_other_shape_yolo_object(self, shape, img_h, img_w):
    def __get_object_desc(obj_port_list):
        __get_dist = lambda int_list: max(int_list) - min(int_list)

        x_lists = [port[0] for port in obj_port_list]
        y_lists = [port[1] for port in obj_port_list]
        numPoints = len(x_lists)

        return x_lists, y_lists, numPoints

    x_lists, y_lists, numPoints = __get_object_desc(shape['points'])
    for i in range(numPoints):
        x_lists[i] = round(float(x_lists[i] / img_w), 6)
        y_lists[i] = round(float(y_lists[i] / img_h), 6)

    label_id = self._label_id_map[shape['label']]

    return label_id, x_lists, y_lists

def _save_yolo_label(self, json_name, label_dir_path, target_dir, yolo_obj_list):
    txt_path = os.path.join(label_dir_path,
                            target_dir,
                            json_name.replace('.json', '.txt'))

    with open(txt_path, 'w+') as f:
        for yolo_obj_idx, yolo_obj in enumerate(yolo_obj_list):
            print("dsdsdsd:",yolo_obj)
            str_list = [str(num) for num in yolo_obj]
            my_string = ' '.join(str_list)
            my_string = my_string + '\n'
            f.write(my_string)

def _save_yolo_image(self, json_data, json_name, image_dir_path, target_dir):
    img_name = json_name.replace('.json', '.png')
    img_path = os.path.join(image_dir_path, target_dir, img_name)

    if not os.path.exists(img_path):
        img = utils.img_b64_to_arr(json_data['imageData'])
        PIL.Image.fromarray(img).save(img_path)

    return img_path

def _save_dataset_yaml(self):
    yaml_path = os.path.join(self._json_dir, 'YOLODataset/', 'dataset.yaml')

    with open(yaml_path, 'w+') as yaml_file:
        yaml_file.write('train: %s\n' % \
                        os.path.join(self._image_dir_path, 'train/'))
        yaml_file.write('val: %s\n\n' % \
                        os.path.join(self._image_dir_path, 'val/'))
        yaml_file.write('nc: %i\n\n' % len(self._label_id_map))

        names_str = ''
        for label, _ in self._label_id_map.items():
            names_str += "'%s', " % label
        names_str = names_str.rstrip(', ')
        yaml_file.write('names: [%s]' % names_str)

if name == 'main':
parser = argparse.ArgumentParser()
parser.add_argument('--json_dir', type=str,
help='Please input the path of the labelme json files.')
parser.add_argument('--val_size', type=float, nargs='?', default=None,
help='Please input the validation dataset size, for example 0.1 ')
parser.add_argument('--json_name', type=str, nargs='?', default=None,
help='If you put json name, it would convert only one json file to YOLO.')
args = parser.parse_args(sys.argv[1:])

convertor = Labelme2YOLO(args.json_dir)
if args.json_name is None:
    convertor.convert(val_size=args.val_size)
else:
    convertor.convert_one(args.json_name)

`

The following transformation can be implemented:
0 0.18505 0.501039 0.212223 0.485455 0.243512 0.498961 0.249275 0.517661 0.250922 0.542595 0.232807 0.562334 0.207282 0.569606 0.18505 0.544673 0.18505 0.511428
0 0.808362 0.341049 0.832241 0.332738 0.856943 0.333777 0.86106 0.352477 0.86847 0.375332 0.862707 0.395071 0.843769 0.404422 0.820713 0.406499 0.805069 0.392994 0.805069 0.370138
0 0.799305 0.680768 0.81495 0.671418 0.837181 0.658951 0.854473 0.676612 0.86353 0.69739 0.86353 0.727518 0.850356 0.737907 0.82236 0.748296 0.798482 0.729596 0.793541 0.703624
0 0.6091 0.363905 0.6091 0.37741 0.618158 0.388838 0.634626 0.387799 0.646976 0.36806 0.649447 0.34936 0.632155 0.347282 0.617334 0.34936
0 0.397487 0.542595 0.413955 0.549867 0.431246 0.550906 0.435363 0.522856 0.428776 0.503117 0.415602 0.505194 0.406544 0.524934

Already update feature to support this.