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