在 语义分割之 json 文件分析 中分析了标注后生成的 json 文件, 接下为就可以生成标签图像了
假设你标注的图像放到了 D:\raccoon 中, 里面存放了原始图像和生成的 json 文件. 如果你在标注时有勾选 Save With Image Data, 那就可以不使用原始图像. 有也没有关系, 方便以后修改什么的
Labelme 有提供生成标签图像的工具, 下面演示如果使用
标签的颜色是不是红色? 在 语义分割之 数据标注 中已经讲过了为什么是红的了
再来看一下多分类的例子
上面有两个类别, 一个是 raccoon, 一个是 dog. 第二个分类 dog 就是绿色, 如果还有其他类别的话, 颜色相信你也能推出来了
上面讲的方法只能一次生成一张, 太不友好了, 所以可以用下面的代码批量生成
#!/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 中运行
这里假设你没有取消 Save With Image Data, 还假设 json 文件和原始图像同名. 先定义一个函数方便调用. 需要用到的库如下
# 导入各种库
# !/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import os.path as osp
import cv2 as cv
import numpy as np
import json
定义函数
# 生成标签图像
# 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]
img_src = cv.imread(json_file.replace(".json", ext_name)) # 这一步假设 json 和原始图像同名
src_shape = img_src.shape
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 = [0, 0, 0]
if color_table:
color = color_table[cat]
color.reverse() # 因为 opencv 的数据是 BGR 排列, 所以 color 要反一下通道顺序
# 这里只写了 rectangle 和 polygon 其他你自己写
if shape["shape_type"] == "rectangle":
pts = shape["points"]
top_left = (round(pts[0][0]), round(pts[0][1]))
bottom_right = (round(pts[1][0]), round(pts[1][1]))
cv.rectangle(label, top_left, bottom_right, (cat, cat, cat), cv.FILLED)
cv.rectangle(label_viz, top_left, bottom_right, color, cv.FILLED)
elif shape["shape_type"] == "polygon":
pts = []
pts_float = shape["points"]
for pt in pts_float:
pts.append((round(pt[0]), round(pt[1])))
cv.fillPoly(label, [np.array(pts)], (cat, cat, cat))
cv.fillPoly(label_viz, [np.array(pts)], color)
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
有了生成标签的函数, 下面是调用代码, 注意类别列表和颜色索引表
# 类别列表, 这个表 back_ground 一定要排在最开始, 表示背景
categories = ["back_ground", "raccoon", "dog"]
# label_viz 颜色索引表, 这里我只写了 4 个类别, 其他你自己添加
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)
上面的调用会返回三张图像. 分别是原始图像, 标签图像和标签可视化图像. 下面代码将三张图像保存在 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 文件一样
打开生成的文件夹, 里面有三张图像, 分别是原始图像, 标签图像和标签可视化图像
标签图像看起来是黑的, 因为像素值是类别序号. 这里是 1, 值很小, 所以看起来是黑的. 我们用截图工具可以看到有标注的地方像素值是 1
在 Jupyter Notebook 中用 matplotlib 显示(cmap = “gray”) 出来是下面这个样子, matplotlib 显示时会转换到 0~255 的范围, 所以看起来就正常了
多类别也是一样的调用, 只是 json 文件中有多个类别, 调用后如下图
可以看到, 标签图像中 dog 的像素值为 2
为什么不用索引图像来做标签图像呢? 因为读索引图像不心小会转换成 RGB 三通道的图像, 这样标签图像就不正确了. 以后会讲怎么处理这种情况
批量生成只需要在一个循环中就可以完成了
# 类别列表, 这个表 back_ground 一定要排在最开始, 表示背景
categories = ["back_ground", "raccoon", "dog"]
# label_viz 颜色索引表, 这里我只写了 4 个类别, 其他你自己添加
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 个类别, 其他你自己添加
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 ("转换完成.")
在生成的 label_viz 中没有图例, 追求完美的你也可以自己加上去, 相信难不倒你的
如果还要加一入些其他的功能, 比如你标记了 4 个类别, 只想用其中的三个类别, 又不想一个一个删除标记的图形, 因为以后可能会有用. 只需要在 categories 中将其删除就可以了
上图就是将 categories 设置为 categories = [“back_ground”, “raccoon”] 的效果. 因为就算在 json 中有这个类别, 但是 categories 中没有, 代码也不会将其标注出来
还有可能原始图像比较大, 目标又比较小, 你只想取一小部分图像进行训练. 可以在标注时标注一个 Rectangle 范围, 类别名称为 range. 在生成标签图像的时候只使用 Rectangle 范围内的部分. 这个需要修改 get_label 函数. 当图形类别为 range 时, 跳过此类别. 在最后返回生成图像之前取子图像就可以了
以上就是生成标签图像的全部过程
上一篇: 语义分割之 json 文件分析
下一篇: 语义分割之 加载训练数据