语义分割之 标签生成
在
语义分割之 json 文件分析
中分析了标注后生成的 json 文件, 接下来就可以生成标签图像了
假设你标注的图像放到了 D:\raccoon 中, 里面存放了原始图像和对应的 json 文件
Labelme 有提供生成标签图像的工具, 下面演示如何使用
-
打开 Anaconda Prompt 切换到安装 Labelme 的环境, 再来到 D:\raccoon 目录
-
输入 labelme_json_to_dataset 文件名.json 回车生成标签图像. 文件名就是 json 文件的名称
完成后会在 D:\raccoon 中生成一个文件夹, 这个文件夹的名称和 json 文件的名称一样, 只是把点换成了下划线
这样一张图像的标签数据就做好了. 打开生成的这个文件夹看一下
里面有 4 个文件
-
img.png: 原始图像, 训练时需要
-
label.png: 标签图像, 训练时需要
-
label_names.txt: 在这张图像中目标的类别名称. 没有什么卵用
-
label_viz.png: 标签可视化, 只是方便确认你标记对了没有. 没有什么卵用
标签的颜色是不是红色? 在
语义分割之 数据标注
中已经讲过了为什么是红的了
再来看一下多分类的例子
上面有两个类别, 一个是 raccoon, 一个是 dog. 第二个分类 dog 就是绿色, 如果还有其他类别的话, 颜色相信你也能推出来了
上面讲的方法只能一次生成一张, 太不友好了, 所以可以用下面的代码批量生成
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 文件和原始图像同名
先定义几个函数方便调用. 需要用到的库如下
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]
接下来定义生成标签函数
def get_label(json_file, categories, color_table = None):
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))
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 = color_table[cat] if color_table else [0, 0, 128]
color.reverse()
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])
label_viz /= 2
label_viz += (gray_3 / 2)
return img_src, label, label_viz
有了生成标签的函数, 下面是调用代码, 注意类别列表和颜色索引表
categories = ["back_ground", "raccoon", "dog"]
color_table = [[0, 0, 0], [128, 0, 0], [0, 128, 0], [128, 128, 0]]
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()
上面的调用会返回三张图像. 分别是原始图像, 标签图像和标签可视化图像, 如下图
上图中, 中间的标签图像可以看到是白的, 是因为 matplotlib 在显示图像时, 将其转换到了 0~255 的范围
现在将生成的标签图像保存到 json 文件目录下
label_dir = json_file.replace(".json", "")
if False == osp.exists(label_dir):
os.makedirs(label_dir)
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
多类别也是一样的调用, 只是 json 文件中有多个类别, 调用后如下图
可以看到, 标签图像中 dog 的像素值为 2
为什么不用索引图像来做标签图像呢? 因为读索引图像不心小会转换成 RGB 三通道的图像, 这样标签图像就不正确了. 以后会讲怎么处理这种情况
批量生成只需要在一个循环中就可以完成了
categories = ["back_ground", "raccoon", "dog"]
color_table = [[0, 0, 0], [128, 0, 0
], [0, 128, 0], [128, 128, 0]]
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)
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)
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)
按如下方式调用
categories = ["back_ground", "raccoon", "dog"]
color_table = [[0, 0, 0], [128, 0, 0], [0, 128, 0], [128, 128, 0]]
json_path = "D:\\raccoon"
create_labels(json_path, categories, color_table)
print ("转换完成.")
在生成的 label_viz 中没有图例, 追求完美的你也可以自己加上去, 相信难不倒你的
比如你标记了 4 个类别, 只想用其中的三个类, 又不想一个一个删除标记的图形, 因为以后可能会有用. 只需要在 categories 中将其删除就可以了
上图就是将 categories 设置为 categories = [“back_ground”, “raccoon”] 的效果. 因为就算在 json 中有这个类别, 但是 categories 中没有, 代码也不会将其标注出来
还有可能原始图像比较大, 目标又比较小, 你只想取一小部分图像进行训练. 亦或是只想取标注的一部分. 可以在标注时标注一个 Rectangle 范围, 假设类别名称为 rgn. 如下图
现在我只想将 rgn 范围内的图形取出来作训练图像和标签图像, 那可以修改 get_label 函数如下
def get_label(json_file, categories, color_table = None, rgn_text = None):
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))
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"]
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()
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])
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):
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)
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")
生成的训练图像和标签则只有限制区域矩形内部的图像
完整的代码可下载 Jupyter Notebook 代码示例
上一篇: 语义分割之 json 文件分析
下一篇: 语义分割之 加载训练数据
以voc数据集为例,语义分割问题的标签是一个灰度图像,与图像有相同的尺寸,像素点的值代表的是此像素点的类别,这限制了这种标注方法最多标注256类。voc数据集0-19为物体,20为背景,共21类,标注数据时为了清晰,物体类的边缘可能会用白框圈出,白框的像素值为255,一般在处理标签时把所有大于等于num_classes的像素值都修改为num_classes,这样整个灰度图像总共有num_classes+1中像素值。png (bn, 512, 512) (0-20为类别,21为白边,训练时忽略)
此篇博客为采坑后的知识点总结,记录语义分割中mask的保存与读取方式:分L、P两种方式,分别为灰度模式、调色板模式。灰度模式下,直接保存的结果没有颜色,调色板模式下保存的结果有颜色,但两种方式得到的mask的png图像中的值一致。读取时,PIL可以通用,而OpenCV则需要区别对待。
精、点击上方“码农的后花园”,选择“星标”公众号 精选文章,第一时间送达上期讲解了语义分割模型的基本架构和常用数据集,这期就讲解一下语义分割数据集的制作,追下去吧~制作总体步骤:1...
本人刚接触这方面不久,课题是关于语义分割的内容,在网上搜索了很多文章,发现很多文章都存在一些弊端,自己做了一些总结,方法比较基础,但亲测有效。
首先,我们得有一些原始图片,然后通过labelme标注后得到json文件(labelme使用大家可以去搜一下),接下来我们需要做的是将json文件转换为可用于训练的标签集。
网上很多说python中自带了一个脚本json_to_dataset.py可以转换...
(比如像素0对应的是(0,0,0)黑色,像素1对应的是(127,0,0)深红色,像素255对应的是(224,224,129))。Labelme是一款非常老的标注工具,使用起来非常简单,就是靠你人工一个个点去标,将我们的目标慢慢的框出来。这个标注工具,基于百度提供的预训练模型,这些模型在一些非常大的数据集上进行训练,它已经包含了我们日常生活中大部分常见的一些目标,使用起来非常简单。针对分割任务的标注工具有很多,网上搜一搜一大把,你一个个去试,总有一款是你喜欢的。语义分割,实例分割,全景分割这三个分割任务的。
在001.json所在文件夹内,会生成一个001_json的文件夹,里面有5个文件,其中的label.png为所要的分割掩膜,label_viz.png是通道融合图,新版本的labelme没有.yaml文件(因此新版本的labelme中仅会生成4个文件)。生成的label图片均在文件中,且图片名均是label.png,所以需要批量提取label.png,其他4个文件没有用,代码如下。新建一个txt文件,把这个复制进去,然后改名为test.bat,和要转换的文件放在一起。图片的转换随便搜索就有。