Labelme для набора данных COCO (обнаружение объектов)

искусственный интеллект

содержание

Резюме

Формат аннотации типа экземпляра объекта

1. Общий формат файла JSON

2. Поле аннотаций

3. Поле категорий

Код от Labelme к COCO:


Резюме

Полное название COCO — Common Objects in COntext, это набор данных, предоставленный командой Microsoft, который можно использовать для распознавания изображений. Изображения в наборе данных MS COCO разделены на обучающие, проверочные и тестовые наборы. COCO собрала изображения, выполнив поиск на Flickr по 80 категориям объектов и различным типам сцен, используя Amazon Mechanical Turk (AMT).

Набор данных COCO теперь имеет 3 типа аннотаций: экземпляры объектов (целевые экземпляры), ключевые точки объекта (ключевые точки на цели) и подписи к изображениям (см. изображения и обсуждение). В этой статье основное внимание уделяется экземплярам объектов.

Формат аннотации типа экземпляра объекта

1. Общий формат файла JSON

Файл в формате Object Instance разбит на следующие абзацы по порядку от начала до конца:

{

    "info": info,

    "licenses": [license],

    "images": [image],

    "annotations": [annotation],

    "categories": [category]

}

2. Поле аннотаций

Поле аннотации представляет собой массив, содержащий несколько экземпляров аннотации, а сам тип аннотации содержит ряд полей, таких как идентификатор категории и маска сегментации цели. Формат сегментации зависит от того, является ли экземпляр отдельным объектом (т.е. iscrowd=0, будет использоваться формат полигонов) или группой объектов (т.е. iscrowd=1, будет использоваться формат RLE). bbox представляет собой сохраненную информацию аннотаций объекта.В отличие от формата VOC, формат, хранящийся в COCO, представляет собой [координата x левого верхнего угла, координата y верхнего левого угла, ширина объекта, длина объекта], что необходимо внимание. Следующее:

annotation{

    "id": int,

    "image_id": int,

    "category_id": int,

    "segmentation": RLE or [polygon],

    "area": float,

    "bbox": [x,y,width,height],

    "iscrowd": 0 or 1,

}

Обратите внимание, что для представления одного объекта (iscrowd=0) может потребоваться несколько полигонов, например, объект заблокирован на изображении. Когда iscrowd=1 (будет отмечена группа объектов, например, группа людей), сегментация использует формат RLE.

Кроме того, каждый объект (будь то iscrowd=0 или iscrowd=1) будет иметь прямоугольный блок bbox, координаты верхнего левого угла прямоугольного блока, а также длина и ширина прямоугольного блока будут представлены в виде массив, первым элементом массива является верхний левый угол значения по оси абсцисс.

area — это область закодированных масок.

Наконец, поле категорий в структуре аннотации хранит идентификатор категории, к которой принадлежит текущий объект, и имя надкатегории, к которой он принадлежит.

Ниже приведен пример аннотации, извлеченной из файла instances_val2017.json:

{

         "segmentation": [[510.66,423.01,511.72,420.03,510.45,416.0,510.34,413.02,510.77,410.26,\

                            510.77,407.5,510.34,405.16,511.51,402.83,511.41,400.49,510.24,398.16,509.39,\

                            397.31,504.61,399.22,502.17,399.64,500.89,401.66,500.47,402.08,499.09,401.87,\

                            495.79,401.98,490.59,401.77,488.79,401.77,485.39,398.58,483.9,397.31,481.56,\

                            396.35,478.48,395.93,476.68,396.03,475.4,396.77,473.92,398.79,473.28,399.96,\

                            473.49,401.87,474.56,403.47,473.07,405.59,473.39,407.71,476.68,409.41,479.23,\

                            409.73,481.56,410.69,480.4,411.85,481.35,414.93,479.86,418.65,477.32,420.03,\

                            476.04,422.58,479.02,422.58,480.29,423.01,483.79,419.93,486.66,416.21,490.06,\

                            415.57,492.18,416.85,491.65,420.24,492.82,422.9,493.56,424.39,496.43,424.6,\

                            498.02,423.01,498.13,421.31,497.07,420.03,497.07,415.15,496.33,414.51,501.1,\

                            411.96,502.06,411.32,503.02,415.04,503.33,418.12,501.1,420.24,498.98,421.63,\

                            500.47,424.39,505.03,423.32,506.2,421.31,507.69,419.5,506.31,423.32,510.03,\

                            423.01,510.45,423.01]],

         "area": 702.1057499999998,

         "iscrowd": 0,

         "image_id": 289343,

         "bbox": [473.07,395.93,38.65,28.67],

         "category_id": 18,

         "id": 1768

},

3. Поле категорий

Categories — это массив, содержащий несколько экземпляров категории, а структура категории описывается следующим образом:

{

    "id": int,

    "name": str,

    "supercategory": str,

}

Экземпляры 2 категорий, извлеченные из файла instances_val2017.json, выглядят следующим образом:

{

         "supercategory": "person",

         "id": 1,

         "name": "person"

},

{

         "supercategory": "vehicle",

         "id": 2,

         "name": "bicycle"

},

Код от Labelme к COCO:

# -*- coding:utf-8 -*-
# !/usr/bin/env python

import json
from labelme import utils
import numpy as np
import glob
import PIL.Image
labels={'одноразовая коробка для фаст-фуда':1,'книжная бумага':2,'зарядка сокровищ':3,'остатки':4,'сумка':5,
«Мусорное ведро»: 6, «Пластиковая посуда»: 7, «Пластиковые игрушки»: 8, «Пластиковые вешалки»: 9, «Большие кости»: 10, «Сухие батареи»: 11,
«Бумажный пакет экспресс»: 12, «Вилка и провод»: 13, «Старая одежда»: 14, «Банка из-под газировки»: 15, «Подушка»: 16, «Кожура и мякоть»: 17, «Плюшевые игрушки»: 18 ,
«Грязный пластик»: 19, «Грязная бумага»: 20, «Туалетные принадлежности»: 21, «Окурок»: 22, «Зубочистка»: 23, «Стеклянная посуда»: 24, «Разделочная доска»: 25,
«Палочки для еды»: 26, «Картонная коробка»: 27, «Цветочный горшок»: 28, «Остатки чая»: 29, «Овощные листья кайбанга»: 30, «Яичная скорлупа»: 31, «Бутылка для приправ»: 32,
«Мазь»: 33, «Просроченное лекарство»: 34, «Бутылка вина»: 35, «Металлическая кухонная утварь»: 36, «Металлическая посуда»: 37, «Металлические консервные банки»: 38, «Горшок»: 39,
«Керамическая посуда»: 40, «Обувь»: 41, «Бочки из-под пищевого масла»: 42, «Бутылки из-под напитков»: 43, «Рыбные кости»: 44}
class MyEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, np.integer):
return int(obj)
elif isinstance(obj, np.floating):
return float(obj)
elif isinstance(obj, np.ndarray):
return obj.tolist()
else:
return super(MyEncoder, self).default(obj)


class labelme2coco(object):
def __init__(self, labelme_json=[], save_json_path='./tran.json'):
'''
:param labelme_json: список всех путей к файлам labelme json.
:param save_json_path: место сохранения json
'''
self.labelme_json = labelme_json
self.save_json_path = save_json_path
self.images = []
self.categories = []
self.annotations = []
# self.data_coco = {}
self.label = []
self.annID = 1
self.height = 0
self.width = 0

        self.save_json()

    def data_transfer(self):

        for num, json_file in enumerate(self.labelme_json):
imagePath=json_file.split('.')[0]+'.jpg'
imageName=imagePath.split('\')[-1]
print(imageName)
with open(json_file, 'r') as fp:
data = json.load(fp) # загрузить json-файл
self.images.append(self.image(data, num,imageName))
for shapes in data['shapes']:
label = shapes['label']
if label not in self.label:
self.categories.append(self.categorie(label))
self.label.append(label)
points = shape['points'] # Здесь точка отмечена прямоугольником, точек всего две, и их нужно преобразовать в четыре точки
# points.append([points[0][0],points[1][1]])
# points.append([points[1][0],points[0][1]])
self.annotations.append(self.annotation(points, label, num))
self.annID += 1

    def image(self, data, num,imagePath):
image = {}
img = utils.img_b64_to_arr(data['imageData']) # Анализ исходных данных изображения
# img=io.imread(data['imagePath']) # Открыть изображение по пути к изображению
# img = cv2.imread(data['imagePath'], 0)
height, width = img.shape[:2]
img = None
image['height'] = height
image['width'] = width
image['id'] = num + 1
# image['file_name'] = data['imagePath'].split('/')[-1]
image['file_name'] = imagePath
self.height = height
self.width = width

        return image

    def categorie(self, label):
categorie = {}
categorie['supercategory'] = 'Cancer'
Categories['id'] = labels[label] # 0 по умолчанию фон
categorie['name'] = label
return categorie

    def annotation(self, points, label, num):
annotation = {}
annotation['segmentation'] = [list(np.asarray(points).flatten())]
annotation['iscrowd'] = 0
annotation['image_id'] = num + 1
# annotation['bbox'] = str(self.getbbox(points)) # Ошибка при использовании списка для сохранения файла json (не знаю почему)
# list(map(int,a[1:-1].split(','))) a=annotation['bbox'] Используйте этот метод для преобразования в список
annotation['bbox'] = list(map(float, self.getbbox(points)))
annotation['area'] = annotation['bbox'][2] * annotation['bbox'][3]
# annotation['category_id'] = self.getcatid(label)
annotation['category_id'] = self.getcatid(label) # Обратите внимание, исходный код по умолчанию равен 1
print(label,annotation['category_id'])
annotation['id'] = self.annID
return annotation

    def getcatid(self, label):
for categorie in self.categories:
if label == categorie['name']:
return categorie['id']
return 1

    def getbbox(self, points):
# img = np.zeros([self.height,self.width],np.uint8)
# cv2.polylines(img, [np.asarray(points)], True, 1, lineType=cv2.LINE_AA) # рисуем граничные линии
# cv2.fillPoly(img, [np.asarray(points)], 1) # нарисовать многоугольник со значением внутреннего пикселя 1
polygons = points

        mask = self.polygons_to_mask([self.height, self.width], polygons)
return self.mask2box(mask)

    def mask2box(self, mask):
'''Обратно вычислить его границу от маски
маска: [h,w] изображение, состоящее из 0 и 1
1 соответствует объекту, нужно только рассчитать номер строки и столбца, соответствующий 1 (номер строки и столбца в верхнем левом углу и номер строки и столбца в правом нижнем углу, можно рассчитать границу)
'''
# np.where(mask==1)
index = np.argwhere(mask == 1)
rows = index[:, 0]
clos = index[:, 1]
# Разобрать номер строки и столбца в верхнем левом углу
left_top_r = np.min(rows)  # y
left_top_c = np.min(clos)  # x

# Разобрать номера строк и столбцов в правом нижнем углу
right_bottom_r = np.max(rows)
right_bottom_c = np.max(clos)

        # return [(left_top_r,left_top_c),(right_bottom_r,right_bottom_c)]
# return [(left_top_c, left_top_r), (right_bottom_c, right_bottom_r)]
# return [left_top_c, left_top_r, right_bottom_c, right_bottom_r]  # [x1,y1,x2,y2]
return [left_top_c, left_top_r, right_bottom_c - left_top_c,
right_bottom_r - left_top_r] # [x1,y1,w,h] соответствует формату COCO bbox

    def polygons_to_mask(self, img_shape, polygons):
mask = np.zeros(img_shape, dtype=np.uint8)
mask = PIL.Image.fromarray(mask)
xy = list(map(tuple, polygons))
PIL.ImageDraw.Draw(mask).polygon(xy=xy, outline=1, fill=1)
mask = np.array(mask, dtype=bool)
return mask

    def data2coco(self):
data_coco = {}
data_coco['images'] = self.images
data_coco['categories'] = self.categories
data_coco['annotations'] = self.annotations
return data_coco

    def save_json(self):
self.data_transfer()
self.data_coco = self.data2coco()
# сохранить json-файл
json.dump(self.data_coco, open(self.save_json_path, 'w'), indent=4, cls=MyEncoder) # indent=4 красивее


labelme_json = glob.glob('D:/HWLabelme/*.json')
from sklearn.model_selection import train_test_split
trainval_files, test_files = train_test_split(labelme_json, test_size=0.2, random_state=55)

labelme2coco(trainval_files, 'instances_train2017.json')
labelme2coco(test_files, 'instances_val2017.json')