语义分割之 标签生成

语义分割之 json 文件分析 中分析了标注后生成的 json 文件, 接下来就可以生成标签图像了

假设你标注的图像放到了 D:\raccoon 中, 里面存放了原始图像和对应的 json 文件

一. Labelme 生成工具单张生成

Labelme 有提供生成标签图像的工具, 下面演示如何使用

  1. 打开 Anaconda Prompt 切换到安装 Labelme 的环境, 再来到 D:\raccoon 目录
    raccoon dir
  2. 输入 labelme_json_to_dataset 文件名.json 回车生成标签图像. 文件名就是 json 文件的名称
    create label images
    完成后会在 D:\raccoon 中生成一个文件夹, 这个文件夹的名称和 json 文件的名称一样, 只是把点换成了下划线
    labels folder
    这样一张图像的标签数据就做好了. 打开生成的这个文件夹看一下
    label images
    里面有 4 个文件
  • img.png: 原始图像, 训练时需要
  • label.png: 标签图像, 训练时需要
  • label_names.txt: 在这张图像中目标的类别名称. 没有什么卵用
  • label_viz.png: 标签可视化, 只是方便确认你标记对了没有. 没有什么卵用

标签的颜色是不是红色? 在 语义分割之 数据标注 中已经讲过了为什么是红的了

再来看一下多分类的例子
multi-class
上面有两个类别, 一个是 raccoon, 一个是 dog. 第二个分类 dog 就是绿色, 如果还有其他类别的话, 颜色相信你也能推出来了

二. Labelme 生成工具批量生成

上面讲的方法只能一次生成一张, 太不友好了, 所以可以用下面的代码批量生成

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import os.path as osp
path = "你存放 json 文件的路径"
files = os.listdir(path)

for i in files:
    full_name = osp.join(path, i)
    if full_name.endswith(".json"):
        os.system("labelme_json_to_dataset.exe %s" % full_name)

print ("转换完成.")

怎么使用这个代码? 把它复制到 Jupyter Notebook 中运行就可以了. 只是要注意 Kernel 选择安装了 Labelme 的那一个. 怎么选择 Kernel 还不知道? 看一下 Windows 下无痛安装 Jupyter Notebook 吧. 至于 Jupyter Notebook 怎么使用不是本文的重点, 自己想办法哈

也可以把上面的代码做成一个函数方便调用

# 批量转换
def json_2_data(path, show_detail = False):
    files = os.listdir(path)

    for i in files:
        full_name = osp.join(path, i)
        
        if full_name.endswith(".json"):
            if show_detail:
                print (i)
            os.system("labelme_json_to_dataset %s" % full_name)
        
    print ("转换完成.")

一般的话, 上面生成标签的方式已经够用了. 但是你也可以自己写代码, 实现一些你想要的功能

三. 自己写代码生成标签图像

自己写代码就是把 json 文件中的图形提取出来, 再把这些图形画到一张和原始图像等大, 像素值全是 0 的图像上, 以下的代码都在 Jupyter Notebook 中运行

这里假设 json 文件和原始图像同名

1. 提取图形类型与坐标并画图

先定义几个函数方便调用. 需要用到的库如下

# 导入各种库
# !/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import os.path as osp
import cv2 as cv
import numpy as np
import json
import matplotlib.pyplot as plt

读矩形标注坐标函数

# 取得矩形标注坐标
def get_rectangle_pts(pts):
    x1 = round(pts[0][0])
    x2 = round(pts[1][0])
    y1 = round(pts[0][1])
    y2 = round(pts[1][1])

    # 这里需要判断是防止有的左撇子在标注时喜欢从右上角画到左下角
    if x1 > x2:
        x1, x2 = x2, x1
    if y1 > y2:
        y1, y2 = y2, y1
        
    return [x1, y1, x2, y2]

读圆形标注坐标函数

# 取得圆形标注坐标
def get_circle_pts(pts):
    x1 = int(pts[0][0])
    y1 = int(pts[0][1])
    x2 = int(pts[1][0])
    y2 = int(pts[1][1])

    r = pow((x2 - x1) ** 2 + (y2 - y1) ** 2, 0.5)
    
    return [x1, y1, r]

接下来定义生成标签函数

# 生成标签函数
# json_file: json 文件
# categories: 类别列表
# color_table: label_viz 颜色索引表
# 返回三张图像, 分别是 原始图, label 和 label_viz, 这样主要是为了和 Labelme 对应起来方便以后处理
def get_label(json_file, categories, color_table = None):
    # 打开 json 文件
    with open(json_file, 'r', encoding = "utf-8") as f:
        jsn = f.read()
        js_dict = json.loads(jsn)
        
        img_path = js_dict["imagePath"] # 图像名
        ext_name = osp.splitext(img_path)[1] # 扩展名
        
        # 这一步假设 json 和原始图像同名
        img_src = cv.imread(json_file.replace(".json", ext_name))
        src_shape = img_src.shape
        
        # 准备两张全 0 的图像, 一张用于标签, 一张用于可视化图像
        label = np.zeros((src_shape[0], src_shape[1]), np.uint8)
        label_viz = np.zeros((src_shape[0], src_shape[1], src_shape[2]), np.float32)
        
        shapes = js_dict["shapes"] # 取出所有图形
        
        for shape in shapes:
            if shape["label"] in categories:
                # 类别序号
                cat = categories.index(shape["label"])
                # 从颜色表中取出对应颜色
                color = color_table[cat] if color_table else [0, 0, 128]
                color.reverse() # 因为 opencv 的数据是 BGR 排列, 所以 color 要反一下通道顺序
                
                # 这里只写了 polygon, rectangle, circle 其他你自己写
                if "polygon" == shape["shape_type"]:
                    pts = []
                    for pt in shape["points"]:
                        pts.append((round(pt[0]), round(pt[1])))
                    # 用类别为像素值, 填充多边形
                    cv.fillPoly(label, [np.array(pts)], [cat])
                    # 用类别对应的可视化颜色, 填充多边形
                    cv.fillPoly(label_viz, [np.array(pts)], color)
                    
                elif "rectangle" == shape["shape_type"]:
                    x1, y1, x2, y2 = get_rectangle_pts(shape["points"])
                    top_left = (x1, y1)
                    bottom_right = (x2, y2)
                    cv.rectangle(label, top_left, bottom_right, [cat], cv.FILLED)
                    cv.rectangle(label_viz, top_left, bottom_right, color, cv.FILLED)

                elif "circle" == shape["shape_type"]:
                    x, y, r = get_circle_pts(shape["points"])
                    cv.circle(label, (x, y), round(r), [cat], cv.FILLED)
                    cv.circle(label_viz, (x, y), round(r), color, cv.FILLED)
                    
                color.reverse() # 还原颜色, 如果不这么做, 会破坏颜色表, 后面的生成会有问题
        
        gray = cv.cvtColor(img_src, cv.COLOR_BGR2GRAY)
        gray_3 = cv.merge([gray, gray, gray])
        
        # 除以 2 是为了防止溢出和突出显示标注图形
        label_viz /= 2
        label_viz += (gray_3 / 2)
        
        return img_src, label, label_viz

2. 生成标签图像和可视化图像

有了生成标签的函数, 下面是调用代码, 注意类别列表和颜色索引表

# 类别列表, 这个表 back_ground 一定要排在最开始, 表示背景
categories = ["back_ground", "raccoon", "dog"]
# label_viz 颜色索引表, 这里我只写了 4 个类别, 其他你自己添加, 颜色顺序是 RGB
color_table = [[0, 0, 0], [128, 0, 0], [0, 128, 0], [128, 128, 0]]

# json 文件路径, 上面有讲到我的放到了 D 盘, 所以改成你自己的路径
json_file = "D:\\raccoon\\raccoon-1.json"
img, label, label_viz = get_label(json_file, categories, color_table)

# 显示生成的图像
plt.figure("show_image", figsize = (12, 4))
plt.subplot(1, 3, 1)
plt.imshow(img[..., : : -1])

plt.subplot(1, 3, 2)
plt.imshow(label, cmap = "gray")

plt.subplot(1, 3, 3)
plt.imshow(label_viz[..., : : -1] / 255)
plt.show()

上面的调用会返回三张图像. 分别是原始图像, 标签图像和标签可视化图像, 如下图

raccoon

上图中, 中间的标签图像可以看到是白的, 是因为 matplotlib 在显示图像时, 将其转换到了 0~255 的范围

现在将生成的标签图像保存到 json 文件目录下

label_dir = json_file.replace(".json", "")
if False == osp.exists(label_dir):
    os.makedirs(label_dir)
    
# 要保存为 png 或者 bmp 格式, jpg 是有损压缩
cv.imwrite(osp.join(label_dir, "img.png"), img)
cv.imwrite(osp.join(label_dir, "label.png"), label)
cv.imwrite(osp.join(label_dir, "label_viz.png"), label_viz)

可以看到在 json 文件目录下生成了一个文件夹, 这个文件夹的名称和 json 文件一样

label folder

打开生成的文件夹, 里面有三张图像, 分别是原始图像, 标签图像和标签可视化图像

image label label_viz

标签图像看起来是黑的, 因为像素值是类别序号. 这里是 1, 值很小, 所以看起来是黑的. 我们用截图工具可以看到有标注的地方像素值是 1

label value

多类别也是一样的调用, 只是 json 文件中有多个类别, 调用后如下图

multi label_viz

可以看到, 标签图像中 dog 的像素值为 2

dog value

为什么不用索引图像来做标签图像呢? 因为读索引图像不心小会转换成 RGB 三通道的图像, 这样标签图像就不正确了. 以后会讲怎么处理这种情况

3. 批量生成

批量生成只需要在一个循环中就可以完成了

# 类别列表, 这个表 back_ground 一定要排在最开始, 表示背景
categories = ["back_ground", "raccoon", "dog"]
# label_viz 颜色索引表, 这里我只写了 4 个类别, 其他你自己添加, 颜色顺序是 RGB
color_table = [[0, 0, 0], [128, 0, 0], [0, 128, 0], [128, 128, 0]]

# json 文件路径, 上面有讲到我的放到了 D 盘, 所以改成你自己的路径
json_path = "D:\\raccoon"
files = os.listdir(json_path)

for i in files:
    full_name = osp.join(json_path, i)
    if full_name.endswith(".json"):
        img, label, label_viz = get_label(full_name, categories, color_table)

    label_dir = full_name.replace(".json", "")
    if False == osp.exists(label_dir):
        os.makedirs(label_dir)
        
    # 要保存为 png 或者 bmp 格式, jpg 是有损压缩
    cv.imwrite(osp.join(label_dir, "img.png"), img)
    cv.imwrite(osp.join(label_dir, "label.png"), label)
    cv.imwrite(osp.join(label_dir, "label_viz.png"), label_viz)

也可以写成一个函数方便调用

# 批量生成标签图像
def create_labels(json_path, categories, color_table = None, show_detail = False):
    files = os.listdir(json_path)

    for i in files:
        full_name = osp.join(json_path, i)
        if full_name.endswith(".json"):
            if show_detail:
                print(i)
            img, label, label_viz = get_label(full_name, categories, color_table)

            label_dir = full_name.replace(".json", "")
            if False == osp.exists(label_dir):
                os.makedirs(label_dir)
                
            # 要保存为 png 或者 bmp 格式, jpg 是有损压缩
            cv.imwrite(osp.join(label_dir, "img.png"), img)
            cv.imwrite(osp.join(label_dir, "label.png"), label)
            cv.imwrite(osp.join(label_dir, "label_viz.png"), label_viz)

按如下方式调用

# 类别列表, 这个表 back_ground 一定要排在最开始, 表示背景
categories = ["back_ground", "raccoon", "dog"]
# label_viz 颜色索引表, 这里我只写了 4 个类别, 其他你自己添加, 颜色顺序是 RGB
color_table = [[0, 0, 0], [128, 0, 0], [0, 128, 0], [128, 128, 0]]

# json 文件路径, 上面有讲到我的放到了 D 盘, 所以改成你自己的路径
json_path = "D:\\raccoon"
create_labels(json_path, categories, color_table)

print ("转换完成.")

四. 其他功能

1. 图例

在生成的 label_viz 中没有图例, 追求完美的你也可以自己加上去, 相信难不倒你的

2. 删除类别

比如你标记了 4 个类别, 只想用其中的三个类, 又不想一个一个删除标记的图形, 因为以后可能会有用. 只需要在 categories 中将其删除就可以了

delete dog

上图就是将 categories 设置为 categories = [“back_ground”, “raccoon”] 的效果. 因为就算在 json 中有这个类别, 但是 categories 中没有, 代码也不会将其标注出来

3. 限制区域

还有可能原始图像比较大, 目标又比较小, 你只想取一小部分图像进行训练. 亦或是只想取标注的一部分. 可以在标注时标注一个 Rectangle 范围, 假设类别名称为 rgn. 如下图

limit_rgn

现在我只想将 rgn 范围内的图形取出来作训练图像和标签图像, 那可以修改 get_label 函数如下

# 生成标签函数
# json_file: json 文件
# categories: 类别列表
# color_table: label_viz 颜色索引表
# rgn_text: 限制区域矩形名称
# 返回三张图像, 分别是 原始图, label 和 label_viz, 这样主要是为了和 Labelme 对应起来方便以后处理
def get_label(json_file, categories, color_table = None, rgn_text = None):
    # 打开 json 文件
    with open(json_file, 'r', encoding = "utf-8") as f:
        jsn = f.read()
        js_dict = json.loads(jsn)
        
        img_path = js_dict["imagePath"] # 图像名
        ext_name = osp.splitext(img_path)[1] # 扩展名
        
        # 这一步假设 json 和原始图像同名
        img_src = cv.imread(json_file.replace(".json", ext_name))
        src_shape = img_src.shape
        
        # 准备两张全 0 的图像, 一张用于标签, 一张用于可视化图像
        label = np.zeros((src_shape[0], src_shape[1]), np.uint8)
        label_viz = np.zeros((src_shape[0], src_shape[1], src_shape[2]), np.float32)
        
        shapes = js_dict["shapes"] # 取出所有图形
        
        # 限制区矩形
        rgns = []
        for shape in shapes:
            # 如果是限制矩形
            if rgn_text == shape["label"] and "rectangle" == shape["shape_type"]:
                x1, y1, x2, y2 = get_rectangle_pts(shape["points"])
                rgns.append((x1, y1, x2, y2))
                continue
            
            if shape["label"] in categories:
                # 类别序号
                cat = categories.index(shape["label"])
                # 从颜色表中取出对应颜色
                color = color_table[cat] if color_table else [0, 0, 128]
                color.reverse() # 因为 opencv 的数据是 BGR 排列, 所以 color 要反一下通道顺序
                
                # 这里只写了 polygon, rectangle, circle 其他你自己写
                if "polygon" == shape["shape_type"]:
                    pts = []
                    for pt in shape["points"]:
                        pts.append((round(pt[0]), round(pt[1])))
                    # 用类别为像素值, 填充多边形
                    cv.fillPoly(label, [np.array(pts)], [cat])
                    # 用类别对应的可视化颜色, 填充多边形
                    cv.fillPoly(label_viz, [np.array(pts)], color)
                    
                elif "rectangle" == shape["shape_type"]:
                    x1, y1, x2, y2 = get_rectangle_pts(shape["points"])
                    top_left = (x1, y1)
                    bottom_right = (x2, y2)
                    cv.rectangle(label, top_left, bottom_right, [cat], cv.FILLED)
                    cv.rectangle(label_viz, top_left, bottom_right, color, cv.FILLED)

                elif "circle" == shape["shape_type"]:
                    x, y, r = get_circle_pts(shape["points"])
                    cv.circle(label, (x, y), round(r), [cat], cv.FILLED)
                    cv.circle(label_viz, (x, y), round(r), color, cv.FILLED)
                    
                color.reverse() # 还原颜色, 如果不这么做, 会破坏颜色表, 后面的生成会有问题
        
        gray = cv.cvtColor(img_src, cv.COLOR_BGR2GRAY)
        gray_3 = cv.merge([gray, gray, gray])
        
        # 除以 2 是为了防止溢出和突出显示标注图形
        label_viz /= 2
        label_viz += (gray_3 / 2)
        
        # 以下代码是提取各子区域代码
        img_train = []
        img_label = []
        img_viz = []
        rois = [] # 这个是用来记录各区域对应到原图的矩形
            
        for each in rgns:
            x1, y1, x2, y2 = each
            x = img_src[y1: y2, x1: x2]   # 训练图像
            y = label[y1: y2, x1: x2]     # 标签图像
            z = label_viz[y1: y2, x1: x2] # 可视化图像

            # 抠出来的图像添加到列表中
            img_train.append(x)
            img_label.append(y)
            img_viz.append(z)
            rois.append((x1, y1, x2, y2))
            
        # 如果没有找到限制区域, 则用整张图
        if 0 == len(rgns):
            img_train.append(img_src)
            img_label.append(label)
            img_viz.append(label_viz)
            rois.append((0, 0, src_shape[1], src_shape[0]))
        
        if rgn_text:
            return img_train, img_label, img_viz, rois
        
        # 没有限制区域时返回整张图
        return img_src, label, label_viz

修改后增加了一个限制区域矩形的名称参数, 如果在 json 文件中发现了这个名称, 则将矩形内部的图抠出来作为训练图像和标签, 如果没有, 则如之前正常操作

这样的话, 批量生成函数也要增加相应的参数, 现修改如下

# 批量生成标签图像
def create_labels(json_path, categories, color_table = None,
                  rgn_text = None, show_detail = False):
    files = os.listdir(json_path)

    for i in files:
        full_name = osp.join(json_path, i)
        if full_name.endswith(".json"):
            if show_detail:
                print(i)
                
            label_dir = full_name.replace(".json", "")
            if False == osp.exists(label_dir):
                os.makedirs(label_dir)
                
            if rgn_text:
                img, label, label_viz, rois = get_label(full_name,
                                                        categories, color_table, rgn_text)
                
                num_rgns = len(rois) # 限制区域矩形数量
                for j in range(num_rgns):
                    # 要保存为 png 或者 bmp 格式, jpg 是有损压缩
                    img_names = [
                        "img.png" if 1 == num_rgns else "img-%d.png" % (j + 1),
                        "label.png" if 1 == num_rgns else "label-%d.png" % (j + 1),
                        "label_viz.png" if 1 == num_rgns else "label_viz-%d.png" % (j + 1)]
                                 
                    cv.imwrite(osp.join(label_dir, img_names[0]), img[j])
                    cv.imwrite(osp.join(label_dir, img_names[1]), label[j])
                    cv.imwrite(osp.join(label_dir, img_names[2]), label_viz[j])
            else:
                img, label, label_viz = get_label(full_name, categories, color_table)
                
                # 要保存为 png 或者 bmp 格式, jpg 是有损压缩
                cv.imwrite(osp.join(label_dir, "img.png"), img)
                cv.imwrite(osp.join(label_dir, "label.png"), label)
                cv.imwrite(osp.join(label_dir, "label_viz.png"), label_viz)

调用时只需要增加相应的参数

create_labels(json_path, categories, color_table, rgn_text = "rgn")

生成的训练图像和标签则只有限制区域矩形内部的图像

limited_label

五. 代码下载

完整的代码可下载 Jupyter Notebook 代码示例

上一篇: 语义分割之 json 文件分析
下一篇: 语义分割之 加载训练数据

  • 36
    点赞
  • 207
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 20
    评论
语义分割的结果从 JSON 格式转换为标签格式的方法如下: 1. 读取 JSON 文件,解析其中的像素点信息和类别标签。 2. 将每个像素点的类别标签转换为相应的颜色值,比如将类别标签为人的像素点表示为红色,草地的像素点表示为绿色等等。可以事先定义好每个类别的颜色值。 3. 将颜色值填充到对应的像素点上,生成标签图像。 4. 将标签图像保存为可视化的图像文件,比如 PNG 格式的图片。 下面是一个 Python 代码示例,可以将 COCO 数据集的语义分割结果从 JSON 格式转换为标签图像: ```python import json import numpy as np from PIL import Image # 类别标签和颜色值的对应关系 LABEL_COLORS = { 1: [128, 0, 0], # 人 2: [0, 128, 0], # 草地 3: [0, 0, 128], # 水 4: [128, 128, 0], # 道路 # 其他类别... } # 读取 JSON 文件 with open('annotations.json', 'r') as f: annotations = json.load(f) # 解析 JSON 文件,生成标签图像 image_data = annotations['imageData'] image = Image.frombytes('RGB', (annotations['width'], annotations['height']), bytes.fromhex(image_data)) segmentation = annotations['segmentation'] mask = np.zeros((annotations['height'], annotations['width'], 3), dtype=np.uint8) for seg in segmentation: color = LABEL_COLORS[seg['category_id']] for pixel in seg['segmentation']: x, y = pixel[::2], pixel[1::2] mask[y, x] = color # 保存标签图像 Image.fromarray(mask).save('labels.png') ``` 需要注意的是,上述代码中的 `annotations.json` 文件应该是 COCO 数据集的语义分割结果文件,包含了图像的像素点信息和类别标签。如果需要将其他数据集的语义分割结果转换为标签图像,需要根据具体数据集的格式进行解析。
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr-MegRob

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值