想要标注自己的语义分割数据集,我们常借助的工具是labelme。
·下载Labelme
首先,我们下载labelme:打开网站https://github.com/wkentaro/labelme,进入labelme的github主页,然后下划,在右侧找到“Releases”并点击下方版本,之后选择对应自己系统的版本下载即可。
·运行Labelme并完成图片标注
下载并安装之后,打开labelme。首先点击“File”,取消勾选“Save With Image Data”选项,并勾选“Save Automatically”选项,这样可以减少文件大小,并且可以保证可以自动保存文件,避免丢失。
之后点击“Open Dir”,打开需要标注的图片所在的文件夹(注意:需要提前将图片保存在同一个文件夹中).在“Edit”中,你可以选择不同的标注类型,如:多段线,矩形,圆,点等等。选择对应的标注工具,将不同事物的轮廓“画”出来,要尽量保证轮廓的平滑。“画”完之后对其命名。如果在标注过程中有失误或者需要修改的地方,你可以点击左侧的“Edit Polygons”对其进行修正,确认无误后,按“Ctrl+S”进行保存。之后,你在本地就可以找到一个json格式的文件,这就是labelme格式的json标注文件。
如果你觉得人工标注过程中轮廓“画”的没有那么完美,你可以选择“Edit”中的“AI-Polygon”,它可以自动帮我们标注,比如你想标注一块草地,你只需要点击这块草地,系统可以自动帮我们把草地标注出来,在这个过程中,你也可以多点几个区域使标注更合理,之后按回车键或者双击即可确定。
在标注完成一张图片之后,你可以按d进入下一张图片。
注意,点开一个json文件之后,你可以看到每一个标注的名称,标注工具类型,标注点坐标等信息。下划到最下方,可以看到这张图片的路径,这里的路径只能是图片名字本身,不能存在其他路径或符号。
现在,我们就标注好了json文件,但是labelme的json文件并不能直接用于语义分割模型训练,我们需要把json文件转换未整数掩膜mask格式,在像素层面上作类别标注,并且划分出训练几何测试集,才能用于后续的训练。
·代码实现labelme的json格式转mask格式
准备工作:打开网站github.com/TommyZihao/Label2Everything ,找到“labelme2mask”并打开,里面包含了分别对单张图像,训练集以及划分训练测试集的代码,全部下载到本地。
我们以处理单张图像为例,介绍如何将labelme格式的json文件转换为mask格式。
我们首先导入工具包:
i
mport os
import json
import numpy as np
import cv2
import matplotlib.pyplot as plt
%matplotlib inline
之后载入我们刚刚标注的jpeg图片:
img_path=”***.jpeg”
# ***为你刚才自己设置的图片名称
之后创立一张与原图一样大小的空白图像:
img_mask = np.zeros(img_brg.shape[:21])
plt.imshow(img_mask)
plt.show()
之后载入labelme格式的json标注文件:
labelme_json_path = “***.json”
# ***同上
with open(labelme_json_path,”r”,encoding=”utf-8”) as f:
labelme = json.load(f)
labelme.keys()
dict_keys([“version” , ”flags” , “shapes” , “imagePath” , “imageData” , “imageHeight” , “imageWidth”])
每个类别的信息及画mask的顺序(按照由大到小,由粗到精的顺序)
# 0-背景,从 1 开始class_info = [
{'label':'sky', 'type':'polygon', 'color':1}, # polygon 多段线
{'label':'road', 'type':'polygon', 'color':2},
{'label':'building', 'type':'polygon', 'color':3},
{'label':'tower','type':'polygon','color':4},
{'label':'bus','type':'polygon','color':5},
{'label':'car','type':'polygon','color':6},
{'label':'tree','type':'polygon','color':7},
{'label':'fence','type':'polygon','color':8},
{'label':'wall','type':'polygon','color':9},
{'label':'person','type':'polygon','color':10},
{'label':'clock', 'type':'circle', 'color':11, 'thickness':-1}, # circle 圆形,-1表示填充
{'label':'lane', 'type':'line', 'color':12, 'thickness':5}, # line 两点线段,填充线宽
{'label':'sign', 'type':'linestrip', 'color':13, 'thickness':3} # linestrip 多段线,填充线宽]
之后按顺序将mask画在空白图上面:
for one_class in class_info: # 按顺序遍历每一个类别
for each in labelme['shapes']: # 遍历所有标注,找到属于当前类别的标注
if each['label'] == one_class['label']:
if one_class['type'] == 'polygon': # polygon 多段线标注
# 获取点的坐标
points = [np.array(each['points'], dtype=np.int32).reshape((-1, 1, 2))]
# 在空白图上画 mask(闭合区域)
img_mask = cv2.fillPoly(img_mask, points, color=one_class['color'])
elif one_class['type'] == 'line' or one_class['type'] == 'linestrip': # line 或者 linestrip 线段标注
# 获取点的坐标
points = [np.array(each['points'], dtype=np.int32).reshape((-1, 1, 2))]
# 在空白图上画 mask(非闭合区域)
img_mask = cv2.polylines(img_mask, points, isClosed=False, color=one_class['color'], thickness=one_class['thickness'])
elif one_class['type'] == 'circle': # circle 圆形标注
points = np.array(each['points'], dtype=np.int32)
center_x, center_y = points[0][0], points[0][1] # 圆心点坐标
edge_x, edge_y = points[1][0], points[1][1] # 圆周点坐标
radius = np.linalg.norm(np.array([center_x, center_y] - np.array([edge_x, edge_y]))).astype('uint32') # 半径
img_mask = cv2.circle(img_mask, (center_x, center_y), radius, one_class['color'], one_class['thickness'])
else:
print('未知标注类型', one_class['type'])
plt.imshow(img_mask)
plt.show()
然后保存mask标注图像(png格式):
img_mask.shape
mask_path = img_path.split('.')[0] + '.png'
cv2.imwrite(mask_path, img_mask)
最后,载入mask标注图像:
mask_img = cv2.imread('uk1.png')
mask_img.shape
np.unique(mask_img)
plt.imshow(mask_img[:,:,0])
plt.show()
现在我们就完成了abelme格式的json文件到mask格式的转换。其他类型的转换也大同小异,你可参考github.com/TommyZihao/Label2Everything里面的教程一步步的进行。
最后,本教程参考了同济自豪兄和OpenMMLab的相关教程,非常感谢OpenMMLab提供的平台。向所有开源工作者致敬。