使用Lableme图像标注—盲道分割与目标检测
数据集格式
在介绍使用Lableme软件进行数据集的标注之前,首先先对计算机视觉领域最知名的两个数据集的格式来进行简单的复习或者说是重新的学习。
在读研之后自己最常用的几个数据集进行存在在磁盘中跑代码的时候在拿出来使用。VOC2012 COCO2017和MOT17等数据集,在标注数据的时候还是先介绍一下目前一些成熟的数据集结构。也就是voc和coco的结构
VOC数据集
我自己之前做目标检测的时候使用的是Pascal VOC2012的数据集。
官网地址:http://host.robots.ox.ac.uk/pascal/VOC/voc2012/
PASCAL VOC挑战赛 (The PASCAL Visual Object Classes )是一个世界级的计算机视觉挑战赛,PASCAL全称:Pattern Analysis, Statical Modeling and Computational Learning,是一个由欧盟资助的网络组织。PASCAL VOC挑战赛主要包括以下几类:图像分类
(Object Classification),目标检测
(Object Detection),目标分割
(Object Segmentation),行为识别
(Action Classification) 等。
因为标注的盲道分割的任务属于是计算机视觉中的分割任务。我只对这个任务进行一定的说明
- 分割任务,注意,图像分割一般包括语义分割、实例分割和全景分割,实例分割是要把每个单独的目标用一种颜色表示(下图中间的图像),而语义分割只是把同一类别的所有目标用同一颜色表示(下图右侧的图片)
在Pascal VOC数据集中主要包含20个目标类别(目标检测的时候对这20个目标进行检测
)
{"background": 0,"aeroplane": 1,"bicycle": 2,"bird": 3,"boat": 4,"bottle": 5,"bus": 6,"car": 7,"cat": 8,"chair": 9,"cow": 10,"diningtable": 11,"dog": 12,"horse": 13,"motorbike": 14,"person": 15,"pottedplant": 16,"sheep": 17,"sofa": 18,"train": 19,"tvmonitor": 20
}
将VOC2011解压之后就可以看VOC数据集的完整结构了。 自己使用的VOC2011数据集是经过后期一部分删减的数据集
VOCdevkit└── VOC2012├── Annotations 所有的图像标注信息(XML文件)├── ImageSets │ ├── Action 人的行为动作图像信息│ ├── Layout 人的各个部位图像信息│ ││ ├── Main 目标检测分类图像信息│ │ ├── train.txt 训练集(5717)│ │ ├── val.txt 验证集(5823)│ │ └── trainval.txt 训练集+验证集(11540)│ ││ └── Segmentation 目标分割图像信息│ ├── train.txt 训练集(1464)│ ├── val.txt 验证集(1449)│ └── trainval.txt 训练集+验证集(2913)│ ├── JPEGImages 所有图像文件├── SegmentationClass 语义分割png图(基于类别)└── SegmentationObject 实例分割png图(基于目标)
注意,train.txt、val.txt和trainval.txt文件里记录的是对应标注文件的索引,每一行对应一个索引信息。如下图所示。
目标检测任务使用实例
- 首先在Main文件中,读取对应的txt文件(注意,在Main文件夹里除了train.txt、val.txt和trainval.txt文件外,还有针对每个类别的文件,例如bus_train.txt、bus_val.txt和bus_trainval.txt)。比如使用train.txt中的数据进行训练,那么读取该txt文件,解析每一行。上面说了每一行对应一个标签文件的索引。
├── Main 目标检测分类图像信息│ ├── train.txt 训练集(5717)│ ├── val.txt 验证集(5823)│ └── trainval.txt 训练集+验证集(11540)
接着通过索引在Annotations文件夹下找到对应的标注文件(.xml)。比如索引为2007_000032,那么在Annotations 文件夹中能够找到2007_000032.xml文件。如下图所示,在标注文件中包含了所有需要的信息。
接着通过在标注文件中的filename字段在JPEGImages 文件夹中找到对应的图片。比如在2007_000323.xml文件中的filename字段为2007_000323.jpg,那么在JPEGImages 文件夹中能够找到2007_000323.jpg文件。
语义分割任务使用实例
接下来简单介绍下如何使用该数据集中做语义分割的数据。
└── Segmentation 目标分割图像信息├── train.txt 训练集(1464)├── val.txt 验证集(1449)└── trainval.txt 训练集+验证集(2913)
-
首先在Segmentarion文件中,读取对应的txt文件。比如使用train.txt中的数据进行训练,那么读取该txt文件,解析每一行,每一行对应一个图像的索引。
-
根据索引在JPEGImages文件夹中找到相应的图像(.png)。以2007_000032为例,可以找到2007_000032.png文件。
- 根据索引在SegmentationClass文件夹中找到相应的标注图像(.png)。还是以2007_000323为例,可以找到2007_000323.png文件。
注意,在语义分割中对应的标注图像(.png)用PIL的Image.open()函数读取时,默认是P模式,即一个单通道的图像。在背景处的像素值为0,目标边缘处用的像素值为255(训练时一般会忽略像素值为255的区域),目标区域内根据目标的类别索引信息进行填充,例如人对应的目标索引是15,所以目标区域的像素值用15填充。
MS COCO数据集
官网地址:https://cocodataset.org/
简介: MS COCO是一个非常大型且常用的数据集,其中包括了目标检测,分割,图像描述等。其主要特性如下:
- Object segmentation: 目标级分割
- Recognition in context: 图像情景识别
- Superpixel stuff segmentation: 超像素分割
- 330K images (>200K labeled): 超过33万张图像,标注过的图像超过20万张
- 1.5 million object instances: 150万个对象实例
- 80 object categories: 80个目标类别
- 91 stuff categories: 91个材料类别
- 5 captions per image: 每张图像有5段情景描述
- 250,000 people with keypoints: 对25万个人进行了关键点标注.
我自己在做检测的时候使用过coco2017数据集
数据集结构
这里以下载coco2017数据集为例,主要下载三个文件:
- 2017 Train images [118K/18GB]:训练过程中使用到的所有图像文件
- 2017 Val images [5K/1GB]:验证过程中使用到的所有图像文件
- 2017 Train/Val annotations [241MB]:对应训练集和验证集的标注json文件
下载后都解压到coco2017目录下,可以得到如下目录结构:
├── coco2017: 数据集根目录├── train2017: 所有训练图像文件夹(118287张)├── val2017: 所有验证图像文件夹(5000张)└── annotations: 对应标注文件夹├── instances_train2017.json: 对应目标检测、分割任务的训练集标注文件├── instances_val2017.json: 对应目标检测、分割任务的验证集标注文件├── captions_train2017.json: 对应图像描述的训练集标注文件├── captions_val2017.json: 对应图像描述的验证集标注文件├── person_keypoints_train2017.json: 对应人体关键点检测的训练集标注文件└── person_keypoints_val2017.json: 对应人体关键点检测的验证集标注文件夹
通过代码读取数据集格式
因为官方的json文件定义为一行,看起来是比较麻烦的。所以使用断点调试结合代码来读取一下数据集的信息。
https://cocodataset.org/#format-data
import jsonjson_path = "datasets/coco/annotations/instances_val2017.json"
json_labels = json.load(open(json_path, "r"))
print(json_labels["info"])
- images是一个列表(元素个数对应图像的张数),列表中每个元素都是一个dict,对应一张图片的相关信息。包括对应图像名称、图像宽度、高度等信息。
- annotations是一个列表(元素个数对应数据集中所有标注的目标个数,注意不是图像的张数),列表中每个元素都是一个dict对应一个目标的标注信息。包括目标的分割信息(polygons多边形)、目标边界框信息[x,y,width,height](左上角x,y坐标,以及宽高)、目标面积、对应图像id以及类别id等。iscrowd参数只有0或1两种情况,一般0代表单个对象,1代表对象集合。
对于coco的操作官方提供了一个api来进行实现。
- Linux系统安装pycocotools:
pip install pycocotools
Windows系统安装pycocotools:
pip install pycocotools-windows
Labelme使用conda版
这里首先提供了Labelme的官网
官方的代码提供了一些标注数据转为coco格式或者voc格式的代码片段。最后如何有需要可以在改进。
官网中提供了python3如何使用label这个项目。
# python3
conda create --name=labelme python=3.6
conda activate labelme
pip install labelme
下载与使用
conda create -n labelme python=3.8
进入到对应的虚拟环境后输入下面命令安装即可。注意:安装的版本,建议安装3.16.7版本,其它版本的容易出错:
pip install labelme==3.16.7
打开labelme
在当前安装的虚拟环境下面直接使用命令:打开labelme
labelme
shortcuts:
close: Ctrl+W #关闭
open: Ctrl+O #打开
open_dir: Ctrl+U #打开文件夹
quit: Ctrl+Q #退出
save: Ctrl+S #保存
save_as: Ctrl+Shift+S #另存为
save_to: null
delete_file: Ctrl+Delete #删除文件open_next: [D, Ctrl+Shift+D] #打开下一张图
open_prev: [A, Ctrl+Shift+A] #打开上一张图zoom_in: [Ctrl++, Ctrl+=] #放大
zoom_out: Ctrl+- #缩小
zoom_to_original: Ctrl+0 #回到原尺寸
fit_window: Ctrl+F #图片适应窗口
fit_width: Ctrl+Shift+F #图片适应宽度create_polygon: Ctrl+N #创建多边形(这个用的多,建议改了)
create_rectangle: Ctrl+R #创建圆
create_circle: null
create_line: null
create_point: null
create_linestrip: null
edit_polygon: Ctrl+J #编辑多边形(这个用的多,也是建议改了)
delete_polygon: Delete #删除
duplicate_polygon: Ctrl+D #等边行复制
copy_polygon: Ctrl+C #复制
paste_polygon: Ctrl+V #粘贴
undo: Ctrl+Z #重做
undo_last_point: Ctrl+Z #撤销上一个点
add_point_to_edge: Ctrl+Shift+P #增加一个点(用不到,直接在边界上点鼠标左键就能加点)
edit_label: Ctrl+E #编辑标签
toggle_keep_prev_mode: Ctrl+P
remove_selected_point: [Meta+H, Backspace] #删除选定的点
标注的过程比较简单准备直接在组会上演示一下不在写了
Json To Dataset
得到json文件之后,我们要将其转化成数据集使用,这里涉及到labelme源码的更改首先,找到labelme的json_to_dataset.py
D:\Enviroment\Anaconda\envs\label\Lib\site-packages\labelme\cli
import argparse
import json
import os
import os.path as osp
import warningsimport PIL.Image
import yamlfrom labelme import utils
import base64def main():warnings.warn("This script is aimed to demonstrate how to convert the\n""JSON file to a single image dataset, and not to handle\n""multiple JSON files to generate a real-use dataset.")parser = argparse.ArgumentParser()parser.add_argument('json_file')parser.add_argument('-o', '--out', default=None)args = parser.parse_args()json_file = args.json_fileif args.out is None:out_dir = osp.basename(json_file).replace('.', '_')out_dir = osp.join(osp.dirname(json_file), out_dir)else:out_dir = args.outif not osp.exists(out_dir):os.mkdir(out_dir)count = os.listdir(json_file) for i in range(0, len(count)):path = os.path.join(json_file, count[i])if os.path.isfile(path):data = json.load(open(path))if data['imageData']:imageData = data['imageData']else:imagePath = os.path.join(os.path.dirname(path), data['imagePath'])with open(imagePath, 'rb') as f:imageData = f.read()imageData = base64.b64encode(imageData).decode('utf-8')img = utils.img_b64_to_arr(imageData)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 denselabel_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)assert label_values == list(range(len(label_values)))lbl = utils.shapes_to_label(img.shape, data['shapes'], label_name_to_value)captions = ['{}: {}'.format(lv, ln)for ln, lv in label_name_to_value.items()]lbl_viz = utils.draw_label(lbl, img, captions)out_dir = osp.basename(count[i]).replace('.', '_')out_dir = osp.join(osp.dirname(count[i]), out_dir)if not osp.exists(out_dir):os.mkdir(out_dir)PIL.Image.fromarray(img).save(osp.join(out_dir, 'img.png'))#PIL.Image.fromarray(lbl).save(osp.join(out_dir, 'label.png'))utils.lblsave(osp.join(out_dir, 'label.png'), lbl)PIL.Image.fromarray(lbl_viz).save(osp.join(out_dir, 'label_viz.png'))with open(osp.join(out_dir, 'label_names.txt'), 'w') as f:for lbl_name in label_names:f.write(lbl_name + '\n')warnings.warn('info.yaml is being replaced by label_names.txt')info = dict(label_names=label_names)with open(osp.join(out_dir, 'info.yaml'), 'w') as f:yaml.safe_dump(info, f, default_flow_style=False)print('Saved to: %s' % out_dir)
if __name__ == '__main__':main()
D:\Enviroment\Anaconda\envs\label\Scripts 在这个文件下面执行匹配的根据json生成图片的代码。
下面就可以进行json文件批量处理了,exe程序后边是之前的json单独建立的目录
labelme_json_to_dataset.exe E:\Paper\voc\Annotations
成功执行后结果如下图,其保存的地址为相对地址,就是在