zoukankan      html  css  js  c++  java
  • 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

    感谢耐心查阅!

  • 相关阅读:
    关于flask-sesson
    自己动手写出静态网站与动态网站
    支付宝支付
    Scanner
    JAVA编程
    《剑指offer》经典面试50题
    java程序员常见面试题
    Java面试题(一) String相关
    Java集合总览
    写好Java代码的30条经验总结
  • 原文地址:https://www.cnblogs.com/shuimuqingyang/p/14252444.html
Copyright © 2011-2022 走看看