制作语义分割数据集(VOC风格)
本案例使用labelme作为工具,标注地物分类中的建筑物,用于遥感影像地物分类的数据集。
下图是语义分割的数据集目录。其中village是待标注的数据集,village_VOC是标注完成的数据集。
可以看下village_VOC标注完成的数据集以及数据的情况。JPEGImages保存的是原图。labels保存的是全黑图像。其他文件夹如VOC数据集一样。class_names.txt是设置的类别。
-
将标注类别写到class_name.txt中
-
路径切换到要打标签的图像文件夹所在的目录下,执行命令:labelme building --labels labels.txt。将labelme设置成’Saved Automatically’(点击labelme软件左上角的file就有此选项),然后开始标记工作。
-
标记完成后保存的是json数据,路径在标记图像的目录下,接着需要将json文件转换成VOC格式。假设将转换的图像保存在buildings_VOC下。执行命令:python label2VOC.py building building_VOC --labels labels.txt。labels.txt就是你一开始编写的txt文件。最终生成VOC格式的训练数据。
label2voc.py 的具体代码如下:
#!/usr/bin/env python
# labelme data_annotated --labels labels.txt --nodata --validatelabel exact
# ./labelme2voc.py data_annotated data_dataset_voc --labels labels.txt
from __future__ import print_function
import argparse
import glob
import os
import os.path as osp
import sys
import imgviz
import numpy as np
import labelme
def main():
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
parser.add_argument("input_dir", help="input annotated directory")
parser.add_argument("output_dir", help="output dataset directory")
parser.add_argument("--labels", help="labels file", required=True)
parser.add_argument(
"--noviz", help="no visualization", action="store_true"
)
args = parser.parse_args()
if osp.exists(args.output_dir):
print("Output directory already exists:", args.output_dir)
sys.exit(1)
os.makedirs(args.output_dir)
os.makedirs(osp.join(args.output_dir, "JPEGImages"))
os.makedirs(osp.join(args.output_dir, "SegmentationClass"))
os.makedirs(osp.join(args.output_dir, "SegmentationClassPNG"))
if not args.noviz:
os.makedirs(
osp.join(args.output_dir, "SegmentationClassVisualization")
)
print("Creating dataset:", args.output_dir)
class_names = []
class_name_to_id = {}
for i, line in enumerate(open(args.labels).readlines()):
class_id = i - 1 # starts with -1
class_name = line.strip()
class_name_to_id[class_name] = class_id
if class_id == -1:
assert class_name == "__ignore__"
continue
elif class_id == 0:
assert class_name == "_background_"
class_names.append(class_name)
class_names = tuple(class_names)
print("class_names:", class_names)
out_class_names_file = osp.join(args.output_dir, "class_names.txt")
with open(out_class_names_file, "w") as f:
f.writelines("\n".join(class_names))
print("Saved class_names:", out_class_names_file)
for filename in glob.glob(osp.join(args.input_dir, "*.json")):
print("Generating dataset from:", filename)
label_file = labelme.LabelFile(filename=filename)
base = osp.splitext(osp.basename(filename))[0]
out_img_file = osp.join(args.output_dir, "JPEGImages", base + ".jpg")
out_lbl_file = osp.join(
args.output_dir, "SegmentationClass", base + ".npy"
)
out_png_file = osp.join(
args.output_dir, "SegmentationClassPNG", base + ".png"
)
if not args.noviz:
out_viz_file = osp.join(
args.output_dir,
"SegmentationClassVisualization",
base + ".jpg",
)
with open(out_img_file, "wb") as f:
f.write(label_file.imageData)
img = labelme.utils.img_data_to_arr(label_file.imageData)
lbl, _ = labelme.utils.shapes_to_label(
img_shape=img.shape,
shapes=label_file.shapes,
label_name_to_value=class_name_to_id,
)
labelme.utils.lblsave(out_png_file, lbl)
np.save(out_lbl_file, lbl)
if not args.noviz:
viz = imgviz.label2rgb(
label=lbl,
img=imgviz.rgb2gray(img),
font_size=15,
label_names=class_names,
loc="rb",
)
imgviz.io.imsave(out_viz_file, viz)
if __name__ == "__main__":
main()
到此,可以说数据标签已经完毕了。可以使用JPEGImages下的图像作为原图,SegmentationClassPNG作为标签送入网络进行训练。但是我们一般用单通道,0,1,2,3表示类别,因此我们也可以再做一次转换,将SegmentationClassPNG转换成那种全黑的图。(这里我不太会表达。)修改一下ConvertVOC2Gray.py里面的图像的路径,然后执行文件即可。ConvertVOC2Gray.py主要修改以下代码(路径以及保存路径):
tf.flags.DEFINE_string('original_gt_folder',#读取voc格式的png图片路径
r'.\buildings_VOC\SegmentationClassPNG',#default
'Original ground truth annotations.')#help
tf.flags.DEFINE_string('segmentation_format', 'png', 'Segmentation format.')
tf.flags.DEFINE_string('output_dir',#保存路径
r'.\buildings_VOC\labels',
'folder to save modified ground truth annotations.')
"""Removes the color map from segmentation annotations.
Removes the color map from the ground truth segmentation annotations and save
the results to output_dir.
"""
#源程序链接:https://github.com/tensorflow/models/tree/master/research/deeplab/datasets
#该程序用于将voc格式的索引图片转换为可用于语义分割的灰度图片,因为TensorFlow版本的缘故,在原程序上做了少许改动
#tf.__version__==1.12
import glob
import os.path
import numpy as np
from PIL import Image
import tensorflow as tf
#FLAGS = tf.compat.v1.flags.FLAGS
FLAGS = tf.flags.FLAGS
tf.flags.DEFINE_string('original_gt_folder',#读取voc格式的png图片路径
r'.\cityBuilding_VOC\SegmentationClassPNG',#default
'Original ground truth annotations.')#help
tf.flags.DEFINE_string('segmentation_format', 'png', 'Segmentation format.')
tf.flags.DEFINE_string('output_dir',#保存路径
r'.\cityBuilding_VOC\labels',
'folder to save modified ground truth annotations.')
def _remove_colormap(filename):
"""Removes the color map from the annotation.
Args:
filename: Ground truth annotation filename.
Returns:
Annotation without color map.
"""
return np.array(Image.open(filename))
def _save_annotation(annotation, filename):
"""Saves the annotation as png file.
Args:
annotation: Segmentation annotation.
filename: Output filename.
"""
pil_image = Image.fromarray(annotation.astype(dtype=np.uint8))
'''
with tf.io.gfile.GFile(filename, mode='w') as f:
#with open(filename, mode='w') as f:
print(f)
pil_image.save(f, 'PNG')
'''
pil_image.save(filename)
def main(unused_argv):
# Create the output directory if not exists.
if not os.path.exists(FLAGS.output_dir):
os.makedirs(FLAGS.output_dir)
#if not tf.io.gfile.isdir(FLAGS.output_dir):
#tf.io.gfile.makedirs(FLAGS.output_dir)
annotations = glob.glob(os.path.join(FLAGS.original_gt_folder,
'*.' + FLAGS.segmentation_format))
for annotation in annotations:
raw_annotation = _remove_colormap(annotation)
filename = os.path.basename(annotation)[:-4]
_save_annotation(raw_annotation,
os.path.join(
FLAGS.output_dir,
filename + '.' + FLAGS.segmentation_format))
if __name__ == '__main__':
#tf.compat.v1.app.run()
tf.app.run()
然后可以看到village_VOC下的labels文件夹有的标签图。虽然是全黑,但是通过np.unique可以看到有0,1两种类别的像素。到此我们的数据集已经制作完成了。
制作实例分割数据集
- 类似语义分割数据集一样,对图像进行标注,生成json文件。要注意一点的是:不同的实例之间的标签尽可能不一样,如person的实例分为person.1,person.2,当然你也可以按照你的风格用不同的分割符,如“,”,“。”等。这样方便后续生成yaml文件。为什么要这样做?看一下第二步json_to_dataset就知道了。
"shapes": [
{
"label": "sign",
"points": [
[
1065.8518518518517,
535.5925925925926
],
[
954.7407407407406,
744.8518518518518
],
[
791.7777777777776,
1133.7407407407406
],
[
791.7777777777776,
1174.4814814814813
],
[
1049.185185185185,
1170.7777777777776
],
[
1389.9259259259259,
1135.5925925925926
],
[
1389.9259259259259,
1102.2592592592591
],
[
1119.5555555555554,
561.5185185185185
],
[
1099.185185185185,
531.8888888888888
]
],
"group_id": 1,
"shape_type": "polygon",
"flags": {}
},
{
"label": "sign.1",
"points": [
[
443.62962962962956,
1259.6666666666665
],
[
778.8148148148148,
1567.074074074074
],
[
754.7407407407406,
1352.2592592592591
],
[
423.25925925925924,
1074.4814814814815
]
],
"group_id": null,
"shape_type": "polygon",
"flags": {}
},
{
"label": "sign",
"points": [
[
2136.222222222222,
559.6666666666666
],
[
2264.0,
807.8148148148148
],
[
2591.7777777777774,
793.0
],
[
2539.9259259259256,
596.7037037037037
],
[
2338.074074074074,
441.1481481481481
],
[
2260.296296296296,
437.4444444444444
]
],
"group_id": 3,
"shape_type": "polygon",
"flags": {}
}
]
- 生成训练集,运行json_to_dataset文件,注意自己的路径以及实例标签的分隔符。
import json
import os
import os.path as osp
import warnings
import PIL.Image
import yaml
from labelme import utils
import base64
from glob import glob
import shutil
'''
labelme:4.5.7
args:
dataset: 使用labelme标注完成的数据集,其中包括标注图以及json文件存放的位置
train_dir:保存的待训练的数据集,imgs--原图 mask--掩膜 yaml--yaml存放
注意:标注的时候后面以.区分实例,如person.1, person.2,以此类推
'''
def main(dataset = "./dataset/JPEGImages", train_dir = './dataset', env_command = 'conda activate labelme', split_flag = '.'):
# 批量处理json文件
if env_command is not None:
os.system(env_command)
json_file = os.path.join(dataset, '*.json')
for item in glob(json_file):
os.system(f"labelme_json_to_dataset.exe {item}")
save_path = os.path.join(dataset, '*.json')
for json_path in glob(save_path):
# 保存yaml文件
json_basename = os.path.basename(json_path).split('.')[0]
data = json.load(open(json_path))
if data['imageData']:
imageData = data['imageData']
else:
imagePath = os.path.join(os.path.dirname(json_path), data['imagePath'])
with open(imagePath, 'rb') as f:
imageData = f.read()
imageData = base64.b64encode(imageData).decode('utf-8')
label_name_to_value = {'_background_': 0}
for shape in data['shapes']:
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
# label_values must be dense
label_values, label_names = [], []
for ln, lv in sorted(label_name_to_value.items(), key=lambda x: x[1]):
label_values.append(lv)
label_names.append(ln.split(split_flag)[0])
assert label_values == list(range(len(label_values)))
yaml_path = os.path.join(train_dir, "yaml")
if not os.path.exists(yaml_path):
os.mkdir(yaml_path)
warnings.warn('info.yaml is being replaced by label_names.txt')
info = dict(label_names=label_names)
with open(osp.join(yaml_path, str(json_basename)+'.yaml'), 'w') as f:
yaml.safe_dump(info, f, default_flow_style=False)
print('Saved : %s' % str(json_basename+'.yaml'))
# copy ori image to imgs and rename
src_path = os.path.join(dataset, json_basename+'_json', 'img.png')
dst_path = os.path.join(train_dir, 'images')
if not os.path.exists(dst_path):
os.mkdir(dst_path)
shutil.copy(src_path, os.path.join(dst_path, json_basename+'.png'))
# copy mask img to mask
src_path = os.path.join(dataset, json_basename+'_json', 'label.png')
dst_path = os.path.join(train_dir, 'mask')
if not os.path.exists(dst_path):
os.mkdir(dst_path)
shutil.copy(src_path, os.path.join(dst_path, json_basename+'.png'))
if __name__ == '__main__':
main()
- 最终设定的目录下生成images、mask、yaml文件夹,分别存放原图,掩膜图,yaml文件。具体的Demo可以参照Instance_dataset_demo
参考
https://github.com/wkentaro/labelme/tree/master/examples/semantic_segmentation
https://blog.csdn.net/Winters____/article/details/105659632
代码地址
目前该项目放在我的github上Semantic-Segmentation-Datasets,如果对您有用,请给我一个star,非常感谢!