图像分割数据集制作

制作语义分割数据集(VOC风格)

本案例使用labelme作为工具,标注地物分类中的建筑物,用于遥感影像地物分类的数据集。
下图是语义分割的数据集目录。其中village是待标注的数据集,village_VOC是标注完成的数据集。
图像分割数据集制作_第1张图片
可以看下village_VOC标注完成的数据集以及数据的情况。JPEGImages保存的是原图。labels保存的是全黑图像。其他文件夹如VOC数据集一样。class_names.txt是设置的类别。
图像分割数据集制作_第2张图片

  1. 将标注类别写到class_name.txt中

  2. 路径切换到要打标签的图像文件夹所在的目录下,执行命令:labelme building --labels labels.txt。将labelme设置成’Saved Automatically’(点击labelme软件左上角的file就有此选项),然后开始标记工作。

  3. 标记完成后保存的是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()
   

图像分割数据集制作_第3张图片


到此,可以说数据标签已经完毕了。可以使用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两种类别的像素。到此我们的数据集已经制作完成了。

图像分割数据集制作_第4张图片

制作实例分割数据集

  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": {}
    }
  ]
  1. 生成训练集,运行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()

  1. 最终设定的目录下生成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,非常感谢!

你可能感兴趣的:(#,图像分割,深度学习)