python实现labelme样本自动标注


 

前言

公司前段时间做一个项目,需要用到语义分割,样本很多,但是都没有标注,也没有让标注公司弄,只好自己标注,平均两分半一张,一天标200多张人都要疯了,而且项目进度也比较着急。所以自己实现了一个基于labelme的自动标注模块,在了解正文之前,请先看下一段的说明,选择性绕道,以免耽误个人宝贵的时间。

说明

一、模块适用场景应满足以下条件:
1、 样本的标签数量、标签类别不变的场景(但也可以基于图像处理做标签检测,有标签出现时,可以实现自动标注,这就要看具体场景了)
2、 标签形态,大小,无明显变化,只存在相对位置的平移和旋转
3、 可利用图像处理技术,匹配到样本中一个或多个固定的位置(且该位置相对于样本的像素位置不变)
二、实现模块需要具备的相应技能:
1、 了解json文件的结构;
2、 了解图片的I/O操作及相应的类型转换;
3、 了解基础的图像处理技术,能实现图像突出特征点或区域的检测;
4、 python基础
三、模块效果:
1、模块标注准确率在90%以上,只需要调整小部分样本即可;
2、效果图如下(第一张为未标注状态)
第一张为未标注状态

正文

一、 json文件简介及相关API:

json结构简介:

{ "imageHeight": 178,#图片的高(rows) "imageData": "/9j/4AAQSkZJRgABAQA.............gAooooA//2Q==",#图片编码成的str类型数据,可以再次解码成图片 "flags": {},#分类样本标注时用到,是样本的类别(整个图片属于什么类别) "version": "4.2.10", "imageWidth": 1236,#图片宽(cols) "imagePath": "001194.jpg",#图片的名称 "shapes": [#shepe里面以字典的形式存放标注的标签个数(类别个数) { "shape_type": "polygon",#标注形式,默认为多边形,还可以有矩形等其他形状 "flags": {},#分类标签 "label": "2",#这个框所属的类别 "points": [#围成框的所有点,标注时第一个点存放在这里index为0的位置。 [ 172.89719626168224,#第一个点的宽(cols) 39.77881619937695#第一个点的高(rows) ], [ 141.1214953271028, 53.17445482866043 ], ...... [ 144.23676012461058, 86.81931464174455 ] ], "group_id": null#组别 }, { "shape_type": "polygon", "flags": {}, "label": "0", "points": [ [ 170.09345794392522, 47.255451713395644 ], ...... [ 186.91588785046727, 74.3582554517134 ] ], "group_id": null }, { "shape_type": "polygon", "flags": {}, "label": "1", "points": [ [ 184.11214953271028, 36.35202492211838 ], ...... [ 185.0467289719626, 55.97819314641744 ] ], "group_id": null }, { "shape_type": "polygon", "flags": {}, "label": "0", "points": [ [ 1063.2398753894083, 37.90965732087227 ], ...... [ 1080.9968847352025, 64.0778816199377 ] ], "group_id": null }, { "shape_type": "polygon", "flags": {}, "label": "3", "points": [ [ 1061.0591900311526, 30.121495327102807 ], ...... [ 1092.2118380062304, 79.96573208722741 ] ], "group_id": null } ] } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99

综上,实现自动标注,第一步便是能让json文件和需要标注的样本一一对应起来,直接修改"imageData"的值便可。
代码如下:

with open(json_path, "r", encoding='utf-8') as jsonFile:#读取json文件 json_data = json.load(jsonFile)#json.load()用于读取json文件函数 with open(image_path,'rb') as image_file:#读取图片文件 image_text_data = image_file.read() image_text_bytes = base64.b64encode(image_text_data)#用base64API编码 image_text_tring = image_text_bytes.decode('utf-8')#再解码成str类型,注意,如果直接将图片以二进制读入然后用str()函数转成str类型则不行 json_data_tra['imageData'] = image_text_tring#json是一个字典,所以,以字典的形式索引,修改对应key的值。 with open(json_data_path, "w") as jsonFile: json.dump(json_data_tra, jsonFile,ensure_ascii=False)#将修改好的json文件写入本地,json.dump()函数,用于json文件的output。 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

二、 特征区域检测及相应API

有如上修改后,只需要加上标注框微调便可以了。实现这个功能,需要基于一个或多个始终能检测到的特征点,利用特征点的移动距离来调整标注框的形状和相对图片的距离。
特征点检测代码如下(我选取了5个点,因为相机标定问题,导致图片有些部位存在形变,每一个框对应一个点减小了图片形变的影响):

def tamplate_match(image_data,template_path):#传入待标注图片,以及制作的特征区域模板图片 point = [] image = cv2.cvtColor(image_data, cv2.COLOR_BGR2GRAY)#转换成单通道 image[int(image.shape[0] / 2.3):-1, int(image.shape[1] / 6):int(image.shape[1] - image.shape[1] / 6)] = 0#将多余特征覆盖,虑去杂质背景,增大检测准确率,此处可视情况改成图像相关的处理技术。 for _,_,tamplate_name in os.walk(template_path):#读取模板路径下的模板图片名称 break for name in tamplate_name: template_data_path = template_path + name template_data = cv2.imread(template_data_path,0) high,wide = template_data.shape template_image_result = cv2.matchTemplate(image, template_data, cv2.TM_SQDIFF_NORMED) # 归一化相关系数匹配法 cv2.normalize(template_image_result, template_image_result, 0, 1, cv2.NORM_MINMAX, -1)#归一化至0到1 # 寻找矩阵(一维数组当做向量,用Mat定义)中的最大值和最小值的匹配结果及其索引位置(像素坐标) min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(template_image_result)#用最小值 point.append([min_loc,high,wide]) return point#该列表下面每一个元素里面又都包含了一个2维元祖 def center_point_coordinate(point):#将返回的点,转换成区域中心点 point_coor = [] for info in point: min_loc = info[0] high = info[1] wide = info[2] point_coor.append((int(min_loc[0] + wide / 2), int(min_loc[1] + high / 2))) return point_coor#以元祖的形式返回结果 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

三、 计算偏移量以及标注框的新的点集

有了基础点,就可以基于这个点,先计算每个框的所有点基于其对应基础点的基础距离,然后再利用每次检测到的每一张图片上的特征点减去这个基础距离,就是每一张图片上不同框的所有的新的点集了。
代码如下:

def basic_json_point_offset(json_data,basic_point):#每个标签下面的所有点相对于基础检测点的偏移量,传入json数据,基本点的信息 json_point_offset = [] json_data_initial = copy.deepcopy(json_data)#深拷贝json文件,以防误修改 info = json_data_initial["shapes"]#找到json文件的所有标注框 if len(info) == len(basic_point):#假如使用的模板和标签数量一致 for reference_point,info_point in zip(basic_point,info):#返回一个基础点,一个标注框的所有点 point_set = [] for point in info_point["points"]: offset_x = reference_point[0] - point[0]#基础点col-标注框的每一个点的col offset_y = reference_point[1] - point[1] point_set.append([offset_x,offset_y])#将这对偏移量添加进列表 json_point_offset.append(point_set)#将这个框的所有点的偏移量添加到列表 else: basic_point = np.array(basic_point) hight,wide = float(np.mean(basic_point[:,0])),float(np.mean(basic_point[:,1])) for info_point in info: point_set = [] for point in info_point["points"]: offset_x = hight - point[0]#后面使用offset需要减 -= offset_y = wide - point[1] point_set.append([offset_x,offset_y]) json_point_offset.append(point_set) return json_point_offset#返回偏移量列表 def json_offset_translational(json_data,json_point_offset,coor_point):#利用偏移量计算新的标注框的点集,传入json数据,偏移量,特征点的像素坐标 json_data_initial = copy.deepcopy(json_data) info = json_data_initial["shapes"] if len(coor_point) == len(info): for num, info_point in enumerate(info): for offset_point,point in zip(json_point_offset[num],info_point["points"]): point[0] = coor_point[num][0] - offset_point[0] point[1] = coor_point[num][1] - offset_point[1] else: basic_point = np.array(coor_point) hight, wide = float(np.mean(basic_point[:, 0])), float(np.mean(basic_point[:, 1])) for e,info_point in enumerate(info): for offset_point,point in zip(json_point_offset[e],info_point["points"]): point[0] = hight - offset_point[0] point[1] = wide - offset_point[1] return json_data_initial#返回的是修改了标注框的json文件
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

四、 json文件数据其他修改

如上,便基本实现了如题功能,只需要在完善下便可。
代码如下:

def image_json_adjustment(path,template_path,json_path):#传入待标注图片的路径,模板路径,模板json文件的路径(需要手动标注一张json文件做为模板) with open(json_path, "r", encoding='utf-8') as jsonFile: json_data = json.load(jsonFile) file_name,json_point_offset = [],[] for _,_,file_name in os.walk(path): break for i,name in enumerate(file_name): image_path = path + name json_data_path = path + name[:-4] + '.json' image = cv2.imread(image_path, 1) point = tamplate_match(image,template_path) coor_point = center_point_coordinate(point) if i == 0: json_point_offset = basic_json_point_offset(json_data,coor_point) else: json_data_tra = json_offset_translational(json_data, json_point_offset,coor_point) with open(image_path,'rb') as image_file: image_text_data = image_file.read() image_text_bytes = base64.b64encode(image_text_data) image_text_tring = image_text_bytes.decode('utf-8') json_data_tra['imageData'] = image_text_tring#修改json文件对应的图片 json_data_tra["imagePath"] = name#修改名称 json_data_tra["imageHeight"] = image.shape[0]#修改高宽 json_data_tra["imageWidth"] = image.shape[1] with open(json_data_path, "w") as jsonFile: json.dump(json_data_tra, jsonFile,ensure_ascii=False) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

感谢耐心查阅!


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM