- 前一篇博客我们完成了样本增强操作,但是此时所有样本对应的Json文件是不能直接放到mask r-cnn模型中进行训练的,有两种方法可以完成对数据的处理。第一种是基于labelme_json_to_datase工具,为每一张样本生成单独掩膜和标签文件,即info.yaml以及label.png,但是在样本数量较大的时候,处理起来比较困难,
Mask_RCNN-master源码中的train_shapes.ipynb里面就是用的labelme标注生成的文件,这款笔记本引入了一个玩具数据集(Shapes)来演示新数据集的训练。有兴趣的可以参考一下。 - 由于本例中,设计到的样本数量较大,总共有1080张图片,懒得再去折腾代码了,所以选择采用coco数据集格式对标签和掩膜信息进行存储,即所有图片对应的掩膜和标签信息全在annotations.json中,并利用coco模块来进行文件解析。
- 下面是labelme对应的json文件格式。
{
"version": "4.5.6",
"flags": {},
"shapes": [
{
"label": "CL",
"points": [
[
135.11933174224342,
56.32458233890214
],
[
146.99751861042182,
77.91563275434243
],
[
177.02233250620347,
71.21588089330024
],
[
169.4868735083532,
56.08591885441527
]
],
"group_id": null,
"shape_type": "polygon",
"flags": {}
},
{
"label": "CL",
"points": [
[
140.54590570719603,
45.90570719602977
],
[
140.04962779156327,
50.37220843672456
],
[
166.848635235732,
51.86104218362283
],
[
167.09677419354838,
46.89826302729528
]
],
"group_id": null,
"shape_type": "polygon",
"flags": {}
},
{
"label": "CL",
"points": [
....省略一万个点
{
"images": [
{
"height": 200,
"width": 200,
"id": 1,
"file_name": "sample_1004_1.jpg"
},
{
"height": 200,
"width": 200,
"id": 2,
"file_name": "sample_1004_2.jpg"
},
{
"height": 200,
"width": 200,
"id": 3,
"file_name": "sample_1004_3.jpg"
},
{
"height": 200,
"width": 200,
"id": 4,
"file_name": "sample_1004_4.jpg"
},
{
"height": 200,
"width": 200,
"id": 5,
"file_name": "sample_1004_5.jpg"
}
....省略一万张图片
],
"categories": [
{
"supercategory": "CL",
"id": 1,
"name": [
"CL"
]
}
],
"annotations": [
{
"segmentation": [
[
67.46491369842275,
137.16382525014652,
49.12680481590127,
153.49620347364223,
41.10388217979812,
148.33861035043304,
37.09242086174655,
142.03488542206628,
42.53654693624512,
137.4503582014359,
45.974942351717885,
138.3099570553041,
51.419068426216455,
126.84863900372818
]
],
"iscrowd": 0,
"image_id": 1,
"bbox": [
37.0,
126.0,
30.0,
27.0
],
"category_id": 1,
"id": 1
},
{
"segmentation": [
[
- 可以看出相对于初始的json文件,coco数据集包含了bbox,并且把所有的坐标信集成到了annotations下的segmentation下,一个segmentation就是一个实例,在相应的实例下再补充image_id、bbox、category_id、id等信息即可。其中bbox通过最小横纵坐标值产生,为了提高训练效率,bbox一般保留为整数格式(本例是保留一位小数,但其实就是保留为整数)。因此我们就产生了如下代码。
import argparse
import json
import matplotlib.pyplot as plt
import skimage.io as io
from labelme import utils
import cv2
import numpy as np
import glob
import PIL.Image
class labelme2coco(object):
def __init__(self,labelme_json=[],save_json_path=''):
'''
: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.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):
with open(json_file,'r') as fp:
data = json.load(fp)
self.images.append(self.image(data,num))
for shapes in data['shapes']:
label=shapes['label'].split('_')
if label not in self.label:
self.categories.append(self.categorie(label))
self.label.append(label)
points=shapes['points']
self.annotations.append(self.annotation(points,label,num))
self.annID+=1
def image(self,data,num):
image={}
img = utils.img_b64_to_arr(data['imageData'])
height, width = img.shape[:2]
img = None
image['height']=height
image['width'] = width
image['id']=num+1
image['file_name'] = data['imagePath'].split('/')[-1]
self.height=height
self.width=width
return image
def categorie(self,label):
categorie={}
categorie['supercategory'] = label[0]
categorie['id']=len(self.label)+1
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'] = list(map(float,self.getbbox(points)))
annotation['category_id'] = self.getcatid(label)
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):
polygons = points
mask = self.polygons_to_mask([self.height,self.width], polygons)
return self.mask2box(mask)
def mask2box(self, mask):
'''从mask反算出其边框
mask:[h,w] 0、1组成的图片
1对应对象,只需计算1对应的行列号(左上角行列号,右下角行列号,就可以算出其边框)
'''
index = np.argwhere(mask == 1)
if len(index) == 0:
return [0, 0, 0, 0]
rows = index[:, 0]
clos = index[:, 1]
left_top_r = np.min(rows)
left_top_c = np.min(clos)
right_bottom_r = np.max(rows)
right_bottom_c = np.max(clos)
return [left_top_c, left_top_r, right_bottom_c-left_top_c, right_bottom_r-left_top_r]
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.dump(self.data_coco, open(self.save_json_path, 'w'), indent=4)
labelme_json=glob.glob(r'.\train\*.json')
labelme2coco(labelme_json,r'.\annotations.json')
print("complete!")
- 在相应的环境下,设置好输入和输出路径,通过下面的命令行语言进行调用lable2coco.py
python lable2coco.py