最近正在学习深度学习中的图像分割,需要把使用labelme标记的多边形,转换成掩膜图片,经过参考多篇文章,发现需要使用labelme提供的json_to_dataset.py命令行工具,但操作比较麻烦而且不能支持批量转换,所以把代码整合了一下,做成了一个可执行的独立文件。
转换单张json标记命令行:
python labelme_gen_seg.py --json_file <json文件路径>
批量转换json命令行:
python labelme_gen_seg.py --json_dir <json目录路径>
labelme_gen_seg.py 源码如下,参考整合了labelme的相关源码:
import argparse
import base64
import json
import sys
import os
import os.path as osp
import io
import numpy as np
import math
import uuid
import glob
import imgviz
import PIL.Image
import logging
import base64
def lbl_save(filename, lbl):
if osp.splitext(filename)[1] != ".png":
filename += ".png"
# Assume label ranses [-1, 254] for int32,
# and [0, 255] for uint8 as VOC.
if lbl.min() >= -1 and lbl.max() < 255:
lbl_pil = PIL.Image.fromarray(lbl.astype(np.uint8), mode="P")
colormap = imgviz.label_colormap()
lbl_pil.putpalette(colormap.flatten())
lbl_pil.save(filename)
else:
raise ValueError(
"[%s] Cannot save the pixel-wise class label as PNG. "
"Please consider using the .npy format." % filename
)
def img_data_to_pil(img_data):
f = io.BytesIO()
f.write(img_data)
img_pil = PIL.Image.open(f)
return img_pil
def img_data_to_arr(img_data):
img_pil = img_data_to_pil(img_data)
img_arr = np.array(img_pil)
return img_arr
def img_b64_to_arr(img_b64):
img_data = base64.b64decode(img_b64)
img_arr = img_data_to_arr(img_data)
return img_arr
def shape_to_mask(
img_shape, points, shape_type=None, line_width=10, point_size=5
):
mask = np.zeros(img_shape[:2], dtype=np.uint8)
mask = PIL.Image.fromarray(mask)
draw = PIL.ImageDraw.Draw(mask)
xy = [tuple(point) for point in points]
if shape_type == "circle":
assert len(xy) == 2, "Shape of shape_type=circle must have 2 points"
(cx, cy), (px, py) = xy
d = math.sqrt((cx - px) ** 2 + (cy - py) ** 2)
draw.ellipse([cx - d, cy - d, cx + d, cy + d], outline=1, fill=1)
elif shape_type == "rectangle":
assert len(xy) == 2, "Shape of shape_type=rectangle must have 2 points"
draw.rectangle(xy, outline=1, fill=1)
elif shape_type == "line":
assert len(xy) == 2, "Shape of shape_type=line must have 2 points"
draw.line(xy=xy, fill=1, width=line_width)
elif shape_type == "linestrip":
draw.line(xy=xy, fill=1, width=line_width)
elif shape_type == "point":
assert len(xy) == 1, "Shape of shape_type=point must have 1 points"
cx, cy = xy[0]
r = point_size
draw.ellipse([cx - r, cy - r, cx + r, cy + r], outline=1, fill=1)
else:
assert len(xy) > 2, "Polygon must have points more than 2"
draw.polygon(xy=xy, outline=1, fill=1)
mask = np.array(mask, dtype=bool)
return mask
def shapes_to_label(img_shape, shapes, label_name_to_value):
cls = np.zeros(img_shape[:2], dtype=np.int32)
ins = np.zeros_like(cls)
instances = []
for shape in shapes:
points = shape["points"]
label = shape["label"]
group_id = shape.get("group_id")
if group_id is None:
group_id = uuid.uuid1()
shape_type = shape.get("shape_type", None)
cls_name = label
instance = (cls_name, group_id)
if instance not in instances:
instances.append(instance)
ins_id = instances.index(instance) + 1
cls_id = label_name_to_value[cls_name]
mask = shape_to_mask(img_shape[:2], points, shape_type)
cls[mask] = cls_id
ins[mask] = ins_id
return cls, ins
def parse_opt(known=False):
parser = argparse.ArgumentParser()
parser.add_argument('--json_file', default='', required=False,help='json file')
parser.add_argument('--json_dir', default='', required=False,help='json directory')
parser.add_argument('--out_dir', default='output', required=False,help='output directory')
opt = parser.parse_known_args()[0] if known else parser.parse_args()
return opt
def seg_json_file(json_file,out_dir):
json_name = osp.basename(json_file)
json_name = json_name[:-5]
out_dir = osp.join(out_dir, json_name)
if not osp.exists(out_dir):
os.makedirs(out_dir)
data = json.load(open(json_file))
imageData = data.get("imageData")
if not imageData:
imagePath = os.path.join(os.path.dirname(json_file), data["imagePath"])
with open(imagePath, "rb") as f:
imageData = f.read()
imageData = base64.b64encode(imageData).decode("utf-8")
img = img_b64_to_arr(imageData)
label_name_to_value = {"_background_": 0}
for shape in sorted(data["shapes"], key=lambda x: x["label"]):
label_name = shape["label"]
if label_name in label_name_to_value:
label_value = label_name_to_value[label_name]
else:
label_value = len(label_name_to_value)
label_name_to_value[label_name] = label_value
lbl, _ = shapes_to_label(
img.shape, data["shapes"], label_name_to_value
)
label_names = [None] * (max(label_name_to_value.values()) + 1)
for name, value in label_name_to_value.items():
label_names[value] = name
lbl_viz = imgviz.label2rgb(
lbl, imgviz.asgray(img), label_names=label_names, loc="rb"
)
PIL.Image.fromarray(img).save(osp.join(out_dir, "img.png"))
lbl_save(osp.join(out_dir, "label.png"), lbl)
PIL.Image.fromarray(lbl_viz).save(osp.join(out_dir, "label_viz.png"))
with open(osp.join(out_dir, "label_names.txt"), "w") as f:
for lbl_name in label_names:
f.write(lbl_name + "\n")
logging.info("Saved to: {}".format(out_dir))
def seg_json_dir(json_dir,out_dir):
total_json = glob.glob(os.path.join(json_dir,'*.json'))
for json_file in total_json:
seg_json_file(json_file,out_dir)
def main(opt):
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
datefmt='%a, %d %b %Y %H:%M:%S',
filename='logs/labelme_gen_seg.log',
filemode='w')
console = logging.StreamHandler()
console.setLevel(logging.INFO)
logging.getLogger('').addHandler(console)
if opt.json_file != '':
seg_json_file(opt.json_file,opt.out_dir)
elif opt.json_dir != '':
seg_json_dir(opt.json_dir,opt.out_dir)
else:
logging.warning('NO ACTION DEFINED')
if __name__ == "__main__":
opt = parse_opt()
main(opt)