碎点篇—— labelme json 数据制作 mask 实例分割 数据

返回主目录

一. labelme 标记数据

            

 生成的数据如下: 

               

将数据放入到对应文件夹中:

                

              

二. 数据转换代码:

   config.py  和 json_2_dataset.py 文件。

#!/usr/bin/env python
# _*_ coding:utf-8 _*_
# ============================================
# @Time     : 2020/04/15 22:53
# @Author   : WanDaoYi
# @FileName : config.py
# ============================================

import os
from easydict import EasyDict as edict


__C = edict()
# Consumers can get config by: from config import cfg
cfg = __C

# common options 公共配置文件
__C.COMMON = edict()

# 相对路径 当前路径
__C.COMMON.RELATIVE_PATH = "./"

# mask 默认背景 类别, 背景为第一个类别
__C.COMMON.DEFAULT_CLASS_INFO = [{"source": "", "id": 0, "name": "BG"}]

__C.COMMON.DATA_SET_PATH = os.path.join(__C.COMMON.RELATIVE_PATH, "dataset")

# 原始图像 文件 路径
__C.COMMON.IMAGE_PATH = os.path.join(__C.COMMON.RELATIVE_PATH, "dataset/images")
# labelme 生成的 json 注释文件 路径
__C.COMMON.JSON_PATH = os.path.join(__C.COMMON.RELATIVE_PATH, "dataset/ann_json")

# 生成的 info.yaml 文件 路径
__C.COMMON.INFO_YAML_PATH = os.path.join(__C.COMMON.RELATIVE_PATH, "dataset/info_yaml")
# 生成的 label.png 图像 文件 路径
__C.COMMON.LABEL_IMAGE_PATH = os.path.join(__C.COMMON.RELATIVE_PATH, "dataset/label_png")
# 生成的 label_name.txt 文件 路径
__C.COMMON.LABEL_NAME_PATH = os.path.join(__C.COMMON.RELATIVE_PATH, "dataset/label_name")
# 生成的 label_viz.png 图像 文件 路径
__C.COMMON.LABEL_VIZ_IMAGE_PATH = os.path.join(__C.COMMON.RELATIVE_PATH, "dataset/label_viz_png")

# 是否删除已有文件,True 为删除,False 为不删除
__C.COMMON.FILE_EXISTS_FLAG = True

# 数据划分比例
__C.COMMON.TEST_PERCENT = 0.7
__C.COMMON.VAL_PERCENT = 0.2
__C.COMMON.TEST_PERCENT = 0.1

# 文件后缀名
__C.COMMON.JSON_SUFFIX = ".json"
__C.COMMON.PNG_SUFFIX = ".png"
__C.COMMON.JPG_SUFFIX = ".jpg"
__C.COMMON.YAML_SUFFIX = ".yaml"
__C.COMMON.TXT_SUFFIX = ".txt"


# 划分数据的保存路径
__C.COMMON.TRAIN_DATA_PATH = os.path.join(__C.COMMON.RELATIVE_PATH, "infos/train_data.txt")
__C.COMMON.VAL_DATA_PATH = os.path.join(__C.COMMON.RELATIVE_PATH, "infos/val_data.txt")
__C.COMMON.TEST_DATA_PATH = os.path.join(__C.COMMON.RELATIVE_PATH, "infos/test_data.txt")



#!/usr/bin/env python
# _*_ coding:utf-8 _*_
# ============================================
# @Time     : 2020/04/25 14:40
# @Author   : WanDaoYi
# @FileName : json_2_dataset.py
# ============================================

import math
import json
import os
import io
import random
import shutil
import base64
from datetime import datetime
import matplotlib.pyplot as plt
from PIL import Image
from PIL import ImageDraw
import numpy as np
import yaml

from config import cfg


class Json2Dataset(object):

    def __init__(self):
        # 输入文件路径
        self.json_file = cfg.COMMON.JSON_PATH
        self.image_file = cfg.COMMON.IMAGE_PATH

        # 数据的百分比
        self.test_percent = cfg.COMMON.TEST_PERCENT
        self.val_percent = cfg.COMMON.VAL_PERCENT

        # 各成分数据保存路径
        self.train_data_path = cfg.COMMON.TRAIN_DATA_PATH
        self.val_data_path = cfg.COMMON.VAL_DATA_PATH
        self.test_data_path = cfg.COMMON.TEST_DATA_PATH

        # 输出文件路径
        self.info_yaml_file = cfg.COMMON.INFO_YAML_PATH
        self.label_image_file = cfg.COMMON.LABEL_IMAGE_PATH
        self.label_name_file = cfg.COMMON.LABEL_NAME_PATH
        self.label_viz_image_file = cfg.COMMON.LABEL_VIZ_IMAGE_PATH

        # 生成文件的文件夹处理方式
        self.deal_data_file(file_exists_flag=cfg.COMMON.FILE_EXISTS_FLAG)

        # 文件后缀名
        self.json_suffix = cfg.COMMON.JSON_SUFFIX
        self.png_suffix = cfg.COMMON.PNG_SUFFIX
        self.yaml_suffix = cfg.COMMON.YAML_SUFFIX
        self.txt_suffix = cfg.COMMON.TXT_SUFFIX
        pass

    def divide_data(self):
        """
            train, val, test 数据划分
        :return:
        """
        # 图像名字的 list
        image_name_list = os.listdir(self.image_file)
        # 统计有多少张图像
        image_number = len(image_name_list)

        # 根据百分比得到各成分 数据量
        n_test = int(image_number * self.test_percent)
        n_val = int(image_number * self.val_percent)
        n_train = image_number - n_test - n_val

        if os.path.exists(self.train_data_path):
            os.remove(self.train_data_path)
            pass

        if os.path.exists(self.val_data_path):
            os.remove(self.val_data_path)
            pass

        if os.path.exists(self.test_data_path):
            os.remove(self.test_data_path)
            pass

        # 随机划分数据
        n_train_val = n_train + n_val
        train_val_list = random.sample(image_name_list, n_train_val)
        train_list = random.sample(train_val_list, n_train)

        train_file = open(self.train_data_path, "w")
        val_file = open(self.val_data_path, "w")
        test_file = open(self.test_data_path, "w")

        for image_name in image_name_list:
            if image_name in train_val_list:
                if image_name in train_list:
                    train_file.write(image_name + "\n")
                    pass
                else:
                    val_file.write(image_name + "\n")
                    pass
                pass
            else:
                test_file.write(image_name + "\n")
                pass
        pass

    # 生成文件的文件夹处理方式
    def deal_data_file(self, file_exists_flag=True):
        # 删除存在的文件夹
        if file_exists_flag:
            if os.path.exists(self.info_yaml_file):
                shutil.rmtree(self.info_yaml_file)
                pass
            if os.path.exists(self.label_image_file):
                shutil.rmtree(self.label_image_file)
                pass
            if os.path.exists(self.label_name_file):
                shutil.rmtree(self.label_name_file)
                pass
            if os.path.exists(self.label_viz_image_file):
                shutil.rmtree(self.label_viz_image_file)
                pass

            os.mkdir(self.info_yaml_file)
            os.mkdir(self.label_image_file)
            os.mkdir(self.label_name_file)
            os.mkdir(self.label_viz_image_file)
            pass
        # 不删除存在的
        else:
            if not os.path.exists(self.info_yaml_file):
                os.mkdir(self.info_yaml_file)
            if not os.path.exists(self.label_image_file):
                os.mkdir(self.label_image_file)
            if not os.path.exists(self.label_name_file):
                os.mkdir(self.label_name_file)
            if not os.path.exists(self.label_viz_image_file):
                os.mkdir(self.label_viz_image_file)
            pass
        pass

    def do_data(self):

        json_name_list = os.listdir(self.json_file)

        for json_name in json_name_list:
            json_data_path = os.path.join(self.json_file, json_name)
            if os.path.isfile(json_data_path):

                # 获取 .json 文件名: 如 000001.json -> 000001
                name_info = json_name.split(self.json_suffix)[0]

                # 文件的保存路径
                info_yaml_path = os.path.join(self.info_yaml_file, name_info + self.yaml_suffix)
                label_png_path = os.path.join(self.label_image_file, name_info + self.png_suffix)
                label_name_path = os.path.join(self.label_name_file, name_info + self.txt_suffix)
                label_viz_png_path = os.path.join(self.label_viz_image_file, name_info + self.png_suffix)

                # 加载 .json 文件
                data_info = json.load(open(json_data_path))
                # print(data_info)

                # 获取文件内 图像信息
                if data_info["imageData"]:
                    image_data = data_info["imageData"]
                    pass
                else:
                    image_path = os.path.join(os.path.dirname(json_data_path), data_info['imagePath'])
                    with open(image_path, 'rb') as f:
                        image_data = f.read()
                        image_data = base64.b64encode(image_data).decode('utf-8')
                    pass

                # 将 base64 str 类型的图像 转为 np.array() 类型
                image_arr = self.img_b64_2_arr(image_data)
                # 设置 背景 label
                label_name_to_value = {'_background_': 0}

                for shape in data_info["shapes"]:
                    label_name = shape["label"]

                    # 如果 label_name 不在 label_name_to_value 这个 list 里面,则添加进去
                    if label_name not in label_name_to_value:
                        label_value = len(label_name_to_value)
                        label_name_to_value[label_name] = label_value
                        pass

                label_values = []
                label_names = []

                # ln 为 label_name, lv 为 label_value
                for ln, lv in sorted(label_name_to_value.items(), key=lambda x: x[1]):
                    label_names.append(ln)
                    label_values.append(lv)
                    pass

                assert label_values == list(range(len(label_values)))

                lbl = self.shapes_2_label(image_arr.shape, data_info["shapes"], label_name_to_value)
                # 保存 label.png 图像
                self.lbl_save(label_png_path, lbl)

                captions = ["{}: {}".format(lv, ln) for ln, lv in label_name_to_value.items()]
                lbl_viz = self.draw_label(lbl, image_arr, captions)
                # 保存 label_viz.png 图像
                Image.fromarray(lbl_viz).save(label_viz_png_path)

                # 保存 label_name.txt 文件
                with open(label_name_path, "w") as file:
                    for lbl_name in label_names:
                        file.write(lbl_name + "\n")
                    pass

                info = dict(label_names=label_names)
                # print("info: {}".format(info))

                # 保存 info.yaml 文件
                with open(info_yaml_path, 'w') as f:
                    yaml.safe_dump(info, f, default_flow_style=False)

            pass
        print("saved to {}".format(cfg.COMMON.DATA_SET_PATH))

        pass

    # 将 base64 图像 转为 np.array() list 图像
    def img_b64_2_arr(self, img_b64):
        file = io.BytesIO()
        file.write(base64.b64decode(img_b64))
        img_arr = np.array(Image.open(file))

        return img_arr
        pass

    def shapes_2_label(self, image_shape, shapes, label_name_to_value, type='class'):
        assert type in ['class', 'instance']

        cls = np.zeros(image_shape[: 2], dtype=np.int32)

        if type.__eq__("instance"):
            ins = np.zeros(image_shape[: 2], dtype=np.int32)
            instance_names = ["_background_"]
            pass
        else:
            ins = None
            instance_names = None

        for shape in shapes:
            points = shape["points"]
            label = shape["label"]
            shape_type = shape.get("shape_type", None)

            # 分类处理
            if type.__eq__("class"):
                cls_name = label
                ins_id = None
            # 实例分割处理
            else:
                cls_name = label.split("-")[0]

                if label not in instance_names:
                    instance_names.append(label)
                    pass

                ins_id = instance_names.index(label)

            cls_id = label_name_to_value[cls_name]
            mask = self.shape_2_mask(image_shape[: 2], points, shape_type)
            cls[mask] = cls_id
            if type.__eq__("instance"):
                ins[mask] = ins_id
                pass
            pass

        if type.__eq__("instance"):
            return cls, ins
        else:
            return cls
        pass

    def shape_2_mask(self, image_shape, points, shape_type=None,
                     line_width=10, point_size=5):

        mask = np.zeros(image_shape[: 2], dtype=np.uint8)
        mask = Image.fromarray(mask)
        draw = ImageDraw.Draw(mask)
        xy = [tuple(point) for point in points]

        if "circle".__eq__(shape_type):
            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)
            pass

        elif "rectangle".__eq__(shape_type):
            assert len(xy) == 2, 'Shape of shape_type=rectangle must have 2 points'
            draw.rectangle(xy, outline=1, fill=1)
            pass

        elif "line".__eq__(shape_type):
            assert len(xy) == 2, 'Shape of shape_type=line must have 2 points'
            draw.line(xy=xy, fill=1, width=line_width)
            pass

        elif "linestrip".__eq__(shape_type):
            draw.line(xy=xy, fill=1, width=line_width)
            pass

        elif "point".__eq__(shape_type):
            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)
            pass

        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 draw_label(self, label, image=None, label_names=None,
                   color_map=None, **kwargs):
        """
        :param label: ndarray, (H, W), Pixel-wise labels to colorize.
        :param image: ndarray, (H, W, 3), optional, Image on which the colorized label will be drawn.
        :param label_names: iterable, List of label names.
        :param color_map:
        :param kwargs:
        :return:
        """

        backend_org = plt.rcParams["backend"]
        plt.switch_backend("agg")
        plt.subplots_adjust(left=0, right=1, top=1, bottom=0, wspace=0, hspace=0)
        plt.margins(0, 0)
        plt.gca().xaxis.set_major_locator(plt.NullLocator())
        plt.gca().yaxis.set_major_locator(plt.NullLocator())

        if label_names is None:
            label_names = [str(l) for l in range(label.max() + 1)]
            pass

        color_map = self.validate_color_map(color_map, len(label_names))
        label_viz = self.label_2_rgb(label, image, n_labels=len(label_names), color_map=color_map)

        plt.imshow(label_viz)
        plt.axis("off")

        plt_handlers = []
        plt_titles = []

        for label_value, label_name in enumerate(label_names):
            if label_value not in label:
                continue
                pass
            fc = color_map[label_value]
            p = plt.Rectangle((0, 0), 1, 1, fc=fc)
            plt_handlers.append(p)
            plt_titles.append("{value}: {name}".format(value=label_value, name=label_name))

            file = io.BytesIO()
            plt.savefig(file, bbox_inches="tight", pad_inches=0)
            plt.cla()
            plt.close()

            plt.switch_backend(backend_org)

            out_size = (label_viz.shape[1], label_viz.shape[0])
            out = Image.open(file).resize(out_size, Image.BILINEAR).convert('RGB')
            out = np.asarray(out)

            return out
            pass
        pass

    def validate_color_map(self, color_map, n_labels):
        if color_map is None:
            color_map = self.label_color_map(n_labels)
            pass
        else:
            assert color_map.shape == (color_map.shape[0], 3), \
                'color_map must be sequence of RGB values'
            assert 0 <= color_map.min() and color_map.max() <= 1, \
                'color_map must ranges 0 to 1'
            pass

        return color_map
        pass

    def label_color_map(self, num=256):

        color_map = np.zeros((num, 3))
        for i in range(num):
            id = i
            r, g, b = 0, 0, 0
            for j in range(8):
                r = np.bitwise_or(r, (self.bit_get(id, 0) << 7 - j))
                g = np.bitwise_or(g, (self.bit_get(id, 1) << 7 - j))
                b = np.bitwise_or(b, (self.bit_get(id, 2) << 7 - j))
                id = (id >> 3)
                pass
            color_map[i, 0] = r
            color_map[i, 1] = g
            color_map[i, 2] = b
            pass

        color_map = color_map.astype(np.float32) / 255
        return color_map
        pass

    def bit_get(self, byte_val, inx):
        return ((byte_val & (1 << inx)) != 0)
        pass

    def label_2_rgb(self, lbl, image=None, n_labels=None, alpha=0.5,
                    color_map=None):

        if n_labels is None:
            n_labels = len(np.unique(lbl))
            pass

        color_map = self.validate_color_map(color_map, n_labels)
        color_map = (color_map * 255).astype(np.uint8)

        lbl_viz = color_map[lbl]
        # 未标记的
        lbl_viz[lbl == -1] = (0, 0, 0)

        if image is not None:
            image_gray = Image.fromarray(image).convert('LA')
            image_gray = np.asarray(image_gray.convert("RGB"))
            # image_gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
            # image_gray = cv2.cvtColor(image_gray, cv2.COLOR_GRAY2RGB)
            lbl_viz = alpha * lbl_viz + (1 - alpha) * image_gray
            lbl_viz = lbl_viz.astype(np.uint8)
            pass

        return lbl_viz
        pass

    def lbl_save(self, file_name, lbl):
        if os.path.splitext(file_name)[1] != ".png":
            file_name += ".png"

        if lbl.min() >= -1 and lbl.max() < 255:
            lbl_pil = Image.fromarray(lbl.astype(np.uint8), mode='P')
            color_map = self.label_color_map(255)
            lbl_pil.putpalette((color_map * 255).astype(np.uint8).flatten())
            lbl_pil.save(file_name)
            pass
        else:
            raise ValueError('[%s] Cannot save the pixel-wise class label as PNG. '
                             'Please consider using the .npy format.' % file_name)
            pass
        pass


if __name__ == "__main__":
    # 代码开始时间
    start_time = datetime.now()
    print("开始时间: {}".format(start_time))

    demo = Json2Dataset()
    demo.divide_data()
    demo.do_data()

    # 代码结束时间
    end_time = datetime.now()
    print("结束时间: {}, 训练模型耗时: {}".format(end_time, end_time - start_time))
    pass

 

运行代码后,得到

               

                  

 

在 实例分割中,有时候会用到这样的数据。

 

 

            

返回主目录

猜你喜欢

转载自blog.csdn.net/qq_38299170/article/details/106261634