不同的标注工具,产生和保存的标签文件也是不同的。比如:
- labelImg保存的是xml文件
- labelme保存的是json文件
不同的任务,对标签的需求也不同。比如:
- 分类任务,就是one hot形式即可
- 目标检测任务,给出矩形框即可,x,y,w,h等等形式
- 对于分割任务,则需要给出polygon,由一个个坐标点构成
与此同时,在公开数据集中,不同的数据集的存储形式也不近相同。比如:
- voc 的数据格式
- coco 的数据格式
所以,对于标注数据进行处理,格式之间的转换,就成了工程师常常需要操作的事情。这里记录下来,重复的工作快速完成。
一、polygon 2 bbox
任务描述:
采用labelme标注的图像是多边形Polygon,转为矩形标注BBox xmin,ymin,xmax,ymax
读取数据是json文件
代码如下:
import os
import json
import numpy as np
import PIL.Image
import PIL.ImageDraw
import base64
from PIL import Image
import io
import cv2
import matplotlib.pyplot as plt
def polygons_to_mask(img_shape, polygons):
mask = np.zeros(img_shape, dtype=np.uint8)
mask = PIL.Image.fromarray(mask)
xy = list(map(tuple, polygons))
PIL.ImageDraw.Draw(mask).polygon(xy=xy, outline=1, fill=1)
mask = np.array(mask, dtype=bool)
return mask
def mask2box(mask): # [x1,y1,w,h]
'''从mask反算出其边框
mask:[h,w] 0、1组成的图片
1对应对象,只需计算1对应的行列号(左上角行列号,右下角行列号,就可以算出其边框)
'''
index = np.argwhere(mask == 1)
rows = index[:, 0]
clos = index[:, 1]
# 解析左上角行列号
left_top_r = np.min(rows) # y
left_top_c = np.min(clos) # x
# 解析右下角行列号
right_bottom_r = np.max(rows)
right_bottom_c = np.max(clos)
# return [(left_top_r,left_top_c),(right_bottom_r,right_bottom_c)]
# return [(left_top_c, left_top_r), (right_bottom_c, right_bottom_r)]
return [left_top_c, left_top_r, right_bottom_c, right_bottom_r] # [x1,y1,x2,y2]
def getbbox(points):
# 多边形变矩形框bbox
# img = np.zeros([self.height,self.width],np.uint8)
# cv2.polylines(img, [np.asarray(points)], True, 1, lineType=cv2.LINE_AA) # 画边界线
# cv2.fillPoly(img, [np.asarray(points)], 1) # 画多边形 内部像素值为1
polygons = points
mask = polygons_to_mask([1024, 1024], polygons)
return mask2box(mask) # [x1,y1,w,h]
# 构建COCO的annotation字段
def annotation(points):
bbox = list(map(float, getbbox(points)))
return bbox
# 法一
def data_transfer(json_path):
bbox_list = []
with open(json_path, 'r') as fp:
#print(json_path)
data = json.load(fp) # 加载json文件
for shapes in data['shapes']:
points = shapes['points']
points.append([points[0][0], points[1][1]])
points.append([points[1][0], points[0][1]])
bbox = annotation(points)
bbox_list.append(bbox)
return bbox_list
def contour_label(json_path, image):
with open(json_path, 'r') as fp:
#print(json_path)
data = json.load(fp) # 加载json文件
for shapes in data['shapes']:
points = shapes['points']
contour = np.array([points])
contour = np.trunc(contour).astype(int)
#print(contour, type(contour))
#print("***")
cv2.drawContours(image, [contour], 0, (0, 0, 255), 2) # cv2.FILLED填充
return image
# 法二:
def simple_label(json_path, image):
with open(json_path, 'r') as fp:
#print(json_path)
data = json.load(fp) # 加载json文件
for shapes in data['shapes']:
points = np.array(shapes['points'])
y, x, y2, x2 = np.min(points[:, 1]), np.min(points[:, 0]), np.max(points[:, 1]), np.max(points[:, 0])
print(y, x, y2, x2)
cv2.rectangle(image, (int(x), int(y)), (int(x2), int(y2)), (0, 0, 255), thickness=2)
return image
if __name__=="__main__":
labelme_json = r"./label"
png_json = r"./image"
for (path, dirs, files) in os.walk(labelme_json):
for filename in files:
zhong_json_path = os.path.join(path, filename)
img = cv2.imread(os.path.join(png_json, filename.replace(".json", ".jpg")))
img_bbox = img.copy()
img_contour = img.copy()
img_simple_bbox = img.copy()
zhong_contour_image = contour_label(zhong_json_path, img_contour)
zhong_bbox_list = data_transfer(zhong_json_path)
plt.figure(figsize=[15, 5])
plt.subplot(131)
plt.title("contour")
plt.imshow(zhong_contour_image)
for box in zhong_bbox_list:
x, y, x2, y2 = box
cv2.rectangle(img_bbox, (int(x), int(y)), (int(x2), int(y2)), (0, 0, 255), thickness=2)
plt.subplot(132)
plt.title("Method 1:mask2box")
plt.imshow(img_bbox)
simple_image = simple_label(zhong_json_path, img_simple_bbox)
plt.subplot(133)
plt.title("Method 2:np.min[:, 1], np.max(points[:, 0])")
plt.imshow(img_bbox)
plt.show()
显示的图像如下所示:
方法1,和方法2都在上面了。这里把简单点的方法单独抽出来做展示:
def data_transfer(json_path):
bbox_list = []
with open(json_path, 'r') as fp:
#print(json_path)
data = json.load(fp) # 加载json文件
for shapes in data['shapes']:
points = np.array(shapes['points'])
bbox = [np.min(points[:, 1]), np.min(points[:, 0]), np.max(points[:, 1]), np.max(points[:, 0])]
#print(bbox)
bbox_list.append(bbox)
return bbox_list
二、mask 2 json
有时候没法直接获取标注的json文件,只有黑白色的mask图像。 这里需要将mask,转为labelme标注的json文件,也是在对数据处理时候常常遇到的问题。mask图如下所示:
完整的代码,如下:
import os
import cv2
import json
def mask2json():
mask_path = r"./results/mask_pd"
save_json_path = r"./results/label/"
label = "TB"
for (path, dirs, files) in os.walk(mask_path):
for filename in files:
A = dict()
listbigoption=[]
prefile_path = os.path.join(path, filename)
print(prefile_path)
pred_seged = cv2.imread(prefile_path, 0)
# opencv版本的不同
try:
_, contours, _ = cv2.findContours(pred_seged, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
except:
contours, _ = cv2.findContours(pred_seged, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
print(len(contours))
if len(contours) != 0:
for contour in contours:
if len(contour)>=3:
listobject = dict()
listxy = []
for e in contour:
listxy.append(e[0].tolist())
#print(e,e[0][0],e[0][1])
#x, y = e[0][0], e[0][1]
print(listxy)
listobject['points'] = listxy
listobject['line_color'] = 'null'
listobject['label'] = label
listobject['fill_color'] = 'null'
listbigoption.append(listobject)
A['lineColor'] = [0, 255, 0, 128]
A['imageData'] = 'imageData'
A['fillColor'] = [255, 0, 0, 128]
A['imagePath'] = filename
A['shapes'] = listbigoption
A['flags'] = {}
with open(save_json_path + filename.replace(".png", ".json"), 'w') as f:
json.dump(A, f)
if __name__=="__main__":
mask2json()
此时,就可以去存储json文件的路径进行查看了。但是,这个阶段的json还不能够直接用labelme打开,因为还没有加入base64位的图像信息。
若想用labelme打开和修改,可在上述生成的json文件内增加base64的图像信息即可。下文的base64encode_img,就是将图像信息转成base64的函数,供参考。
三、保存四边形矩形框bbox标注信息
任意四边形坐标标记格式:tl_x, tl_y, tr_x, tr_y, br_x, br_y, bl_x, bl_y。从左上点开始顺时针旋转的四个顶点的坐标,如下图所示:
将TXT文档中标注信息,存储到 labelme 标注的json文件内,代码如下:
import json
import base64
from PIL import Image
import io
import os
import cv2
import numpy as np
def generate_json(file_dir, file_name):
str_json = {}
shapes = []
# 读取坐标
fr = open(os.path.join(file_dir, file_name))
for line in fr.readlines(): # 逐行读取,滤除空格等
print(line.strip())
print(type(line.strip()))
lineArr = line.strip().split(',')
print("lineArr:", lineArr)
points = []
points.append([float(lineArr[0]), float(lineArr[1])])
points.append([float(lineArr[2]), float(lineArr[3])])
points.append([float(lineArr[4]), float(lineArr[5])])
points.append([float(lineArr[6]), float(lineArr[7])])
print(points) # 从左上点开始顺时针旋转的四个顶点的坐标
shape = {}
shape["label"] = "plate_1"
shape["points"] = points
shape["line_color"] = []
shape["fill_color"] = []
shape["flags"] = {}
shapes.append(shape)
str_json["version"] = "3.14.1"
str_json["flags"] = {}
str_json["shapes"] = shapes
str_json["lineColor"] = [0, 255, 0, 128]
str_json["fillColor"] = [255, 0, 0, 128]
picture_basename = file_name.replace('.txt', '.png')
str_json["imagePath"] = picture_basename
img = cv2.imread(os.path.join(file_dir, picture_basename))
str_json["imageHeight"] = img.shape[0]
str_json["imageWidth"] = img.shape[1]
str_json["imageData"] = base64encode_img(os.path.join(file_dir, picture_basename))
return str_json
def base64encode_img(image_path):
src_image = Image.open(image_path)
output_buffer = io.BytesIO()
src_image.save(output_buffer, format='JPEG')
byte_data = output_buffer.getvalue()
base64_str = base64.b64encode(byte_data).decode('utf-8')
return base64_str
if __name__=="__main__":
"""
txt文件和图像(png)文件,均存储在下面这个文件夹下
生成的json文件,也保存到这个文件夹下
"""
file_dir = r"E:\temp"
file_name_list = [file_name for file_name in os.listdir(file_dir) \
if file_name.lower().endswith('txt')]
for file_name in file_name_list:
str_json = generate_json(file_dir, file_name)
json_data = json.dumps(str_json, indent=2, ensure_ascii=False)
jsonfile_name = file_name.replace(".txt", ".json")
f = open(os.path.join(file_dir, jsonfile_name), 'w')
f.write(json_data)
f.close()
检验下生成的json,是否成功。用labelme打开,查看标注结果,进行检验:
四、保存不规则边框 polygon 标注信息
这里与bbox的区别就在于:
- bbox只有2个坐标点,分别是左上角,和右下角的坐标,但是在json文件内会记录此标记为矩形框;
- polygon是大于2个点都可以的,坐标的排序也是按照顺时针进行排布的,记录为polygon。
所以,在poly中坐标点的数量是不固定的。在读取时候,依次将坐标点存储进来就行
import json
import base64
from PIL import Image
import io
import os
import cv2
import numpy as np
def generate_json(file_dir, file_name):
str_json = {}
shapes = []
# 读取坐标
fr = open(os.path.join(file_dir, file_name))
for line in fr.readlines(): # 逐行读取,滤除空格等
print(line.strip())
print(type(line.strip()))
lineArr = line.strip().split(',')
print("lineArr:", lineArr)
points = []
for i in range(0, len(lineArr), 2):
points.append([float(lineArr[i]), float(lineArr[i+1])])
print(points) # 从左上点开始顺时针旋转的四个顶点的坐标
shape = {}
shape["label"] = "plate_1"
shape["points"] = points
shape["line_color"] = []
shape["fill_color"] = []
shape["flags"] = {}
shapes.append(shape)
str_json["version"] = "3.14.1"
str_json["flags"] = {}
str_json["shapes"] = shapes
str_json["lineColor"] = [0, 255, 0, 128]
str_json["fillColor"] = [255, 0, 0, 128]
picture_basename = file_name.replace('.txt', '.png')
str_json["imagePath"] = picture_basename
img = cv2.imread(os.path.join(file_dir, picture_basename))
str_json["imageHeight"] = img.shape[0]
str_json["imageWidth"] = img.shape[1]
str_json["imageData"] = base64encode_img(os.path.join(file_dir, picture_basename))
return str_json
def base64encode_img(image_path):
src_image = Image.open(image_path)
output_buffer = io.BytesIO()
src_image.save(output_buffer, format='JPEG')
byte_data = output_buffer.getvalue()
base64_str = base64.b64encode(byte_data).decode('utf-8')
return base64_str
if __name__=="__main__":
"""
txt文件和图像(png)文件,均存储在下面这个文件夹下
生成的json文件,也保存到这个文件夹下
"""
file_dir = r"./polygon"
file_name_list = [file_name for file_name in os.listdir(file_dir) \
if file_name.lower().endswith('txt')]
for file_name in file_name_list:
str_json = generate_json(file_dir, file_name)
json_data = json.dumps(str_json, indent=2, ensure_ascii=False)
jsonfile_name = file_name.replace(".txt", ".json")
with open(os.path.join(file_dir, jsonfile_name), 'w') as f:
f.write(json_data)
同样,labelme打开,查看标注结果:
五、将 labelImg 标注的 xml BBox 文件转成 json 文件
对于初学者,总是分不清什么时候使用labeiImg标注软件,什么时候使用labelme标注软件,这里就对这两个常常使用的开源标注软件做个简述:
- labelImg 只能标注矩形框标签,比较的单一。
- 但是labelme就比较的丰富,能够标注不规则图形。
将 labelImg 标注的BBox标签文件xml,转为labelme格式的json文件。常常也是需要转换的事情。
from xml.etree import ElementTree as et
import json
import glob
import os
def readxml_et(xml_file, save_json_path):
tree = et.ElementTree(file=xml_path)
root = tree.getroot()
A = dict()
listbigoption = []
for child_root in root:
if child_root.tag == 'filename':
imagePath = child_root.text
if child_root.tag == 'object':
listobject = dict()
for xylabel in child_root:
if xylabel.tag == 'name':
label = xylabel.text
if xylabel.tag == 'bndbox':
xmin = int(xylabel.find('xmin').text)
ymin = int(xylabel.find('ymin').text)
xmax = int(xylabel.find('xmax').text)
ymax = int(xylabel.find('ymax').text)
listxy=[[xmin,ymin],[xmax,ymin],[xmax,ymax],[xmin,ymax]]
listobject['points'] = listxy
listobject['line_color'] = 'null'
listobject['label'] = label
listobject['fill_color'] = 'null'
listbigoption.append(listobject)
# print(listbigoption)
A['lineColor'] = [0, 255, 0, 128]
A['imageData'] = 'imageData'
A['fillColor'] = [255, 0, 0, 128]
A['imagePath'] = imagePath
A['shapes'] = listbigoption
A['flags'] = {}
with open(save_json_path + imagePath.replace(".png", ".json"), 'w') as f:
json.dump(A, f)
if __name__=='__main__':
labelme_xml_path = r'./xml_labels'
save_json_path = r"./labels/"
for root, dirs, files in os.walk(labelme_xml_path):
for filename in files: # 遍历所有文件
xml_path = os.path.join(root, filename)
print(xml_path)
readxml_et(xml_path, save_json_path)
六、voc 中 txt 格式的,转为 json 格式
存储标注好的数据方式多种多样,有直接存储到txt文件的,还有XML文件、json文件、Excel文件等等。各种格式数据间的转换,也是常会用到的。
from xml.etree import ElementTree as et
import json
import glob
import os
def readxml_et(file_path, save_json_path):
A = dict()
listbigoption = []
with open(file_path, "r") as f:
for line in f.readlines():
listobject = dict()
line = line.strip('\n') # 去掉列表中每一个元素的换行符
x_center = int(float(line.split(" ")[1]) * 512)
y_center = int(float(line.split(" ")[2]) * 512)
x_shift = int(float(line.split(" ")[3]) * 512)
y_shift = int(float(line.split(" ")[4]) * 512)
xmin = x_center - x_shift
xmax = x_center + x_shift
ymin = y_center - y_shift
ymax = y_center + y_shift
listxy=[[xmin,ymin],[xmax,ymin],[xmax,ymax],[xmin,ymax]]
listobject['points'] = listxy
listobject['line_color'] = 'null'
listobject['label'] = "jie_jie"
listobject['fill_color'] = 'null'
listbigoption.append(listobject)
A['lineColor'] = [0, 255, 0, 128]
A['imageData'] = 'imageData'
A['fillColor'] = [255, 0, 0, 128]
A['imagePath'] = os.path.basename(file_path).replace(".txt", ".png")
A['shapes'] = listbigoption
A['flags'] = {}
with open(save_json_path + os.path.basename(file_path).replace(".txt", ".json"), 'w') as f:
json.dump(A, f)
if __name__=='__main__':
labelme_xml_path = r'./txt_label'
save_json_path = r"./labels/"
for root, dirs, files in os.walk(labelme_xml_path):
for filename in files: # 遍历所有文件
xml_path = os.path.join(root, filename)
print(xml_path)
readxml_et(xml_path, save_json_path)
七、labelme 标注的 json 文件,打印到原图 image 上
每次看 labelme 标注好的信息,都需要用这个标注软件再次打开,就显得很麻烦。能不能把标注的信息,打印到原始图像上,这样发给别人对照或者展示的时候,就可以直接查看标注的位置和类别了。
import numpy as np
import cv2
import os
import json
def loadFont(path):
with open(path, 'r', encoding='utf8')as fp:
json_data = json.load(fp)
return json_data
def genarete_mask_label(label_info, filename):
shapes = label_info['shapes']
mask_temp = np.zeros((512, 512))
for i in range(len(shapes)):
points = shapes[i]["points"]
contour = np.array([points])
contour = np.trunc(contour).astype(int)
print(contour, type(contour))
print("***")
cv2.drawContours(mask_temp, [contour], 0, (255, 255, 255), cv2.FILLED) # 填充
cv2.imwrite(r"./mask/" + filename.replace(".json",".png"), mask_temp)
def draw_mask_image(label_info, filepath):
shapes = label_info['shapes']
raw_PATH = filepath.replace(".json", ".png").replace("labels", "images")
print(raw_PATH)
raw_temp = cv2.imread(raw_PATH)
for i in range(len(shapes)):
points = shapes[i]["points"]
contour = np.array([points])
contour = np.trunc(contour).astype(int)
print(contour, type(contour))
print("***")
cv2.drawContours(raw_temp, [contour], 0, (0, 0, 255), 1) # 勾勒边界
print(filepath.replace(".json", ".png").replace("labels", "label_png"))
cv2.imwrite(filepath.replace(".json", ".png").replace("labels", "label_png"), raw_temp)
if __name__ == '__main__':
# 路径中不要有中文字符
raw_path = r"./labels"
for root, dirs, files in os.walk(raw_path):
for filename in files: # 遍历所有文件
filepath=os.path.join(root,filename)
if filename[len(filename) - 4:len(filename)] == 'json':
label_info = loadFont(filepath)
print(label_info)
draw_mask_image(label_info, filepath)
八、labelme的 json数据转换成 coco 数据读取所需的json文件
普通的由labelme标注生成的文件,一个json对应一个标注图。单coco数据集要求将训练集和验证集的所有标签汇集在一个json文件内。
这个在很多开源项目中,是会直接采用coco数据集的格式,毕竟这是参加比赛的代码。
import argparse
import json
import matplotlib.pyplot as plt
import skimage.io as io
import cv2
from labelme import utils
import numpy as np
import glob
import PIL.Image
import os
class MyEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, np.integer):
return int(obj)
elif isinstance(obj, np.floating):
return float(obj)
elif isinstance(obj, np.ndarray):
return obj.tolist()
else:
return super(MyEncoder, self).default(obj)
class labelme2coco(object):
def __init__(self, labelme_json=[], save_json_path='./tran.json'):
'''
:param labelme_json: 所有labelme的json文件路径组成的列表
:param save_json_path: json保存位置
'''
self.labelme_json = labelme_json
self.save_json_path = save_json_path
self.images = []
self.categories = []
self.annotations = []
# self.data_coco = {}
self.label = []
self.annID = 1
self.height = 0
self.width = 0
self.save_json()
# 由json文件构建COCO
def data_transfer(self):
for num, json_file in enumerate(self.labelme_json):
print(json_file)
with open(json_file, 'r') as fp:
data = json.load(fp) # 加载json文件
self.images.append(self.image(json_file, data, num))
for shapes in data['shapes']:
label = shapes['label']
if label not in self.label:
self.categories.append(self.categorie(label))
self.label.append(label)
points = shapes['points']
points.append([points[0][0],points[1][1]])
points.append([points[1][0],points[0][1]])
self.annotations.append(self.annotation(points, label, num))
self.annID += 1
# 构建COCO的image字段
def image(self, json_file, data, num):
"""
{
"height": 1024,
"width": 1024,
"id": 1,
"file_name": "1.2.156.112536.2.560.7050106199066.1346626203150.42_2519_3027_0.140_0.140.png"
},
"""
image = {}
#img = utils.img_b64_to_arr(data['imageData']) # 解析原图片数据
#print(data['imagePath'])
#img=io.imread(os.path.join(r"./database/images",data['imagePath'])) # 通过图片路径打开图片
img = io.imread(os.path.join(r"./database/images", os.path.basename(json_file).replace(".json", ".png")))
#img = cv2.imread(data['imagePath'], 0)
height, width = img.shape[:2]
#print(height,width)
#height, width = data['imageHeight'], data['imageWidth']
img = None
image['height'] = height
image['width'] = width
image['id'] = num + 1
#image['file_name'] = data['imagePath'].split('/')[-1]
image['file_name'] = os.path.basename(json_file).replace(".json", ".png")
self.height = height
self.width = width
return image
# 构建类别
def categorie(self, label):
"""
"categories": [
{
"supercategory": "component",
"id": 1,
"name": "TB"
}
],
"""
categorie = {}
categorie['supercategory'] = 'component'
categorie['id'] = len(self.label) + 1 # 0 默认为背景
categorie['name'] = label
return categorie
# 构建COCO的annotation字段
def annotation(self, points, label, num):
"""
{
"segmentation": [
],
"iscrowd": 0,
"image_id": 5,
"bbox": [
],
"area": 37101.0,
"category_id": 1,
"id": 1
},
"""
annotation = {}
annotation['segmentation'] = [list(np.asarray(points).flatten())]
annotation['iscrowd'] = 0
annotation['image_id'] = num + 1
# annotation['bbox'] = str(self.getbbox(points)) # 使用list保存json文件时报错(不知道为什么)
# list(map(int,a[1:-1].split(','))) a=annotation['bbox'] 使用该方式转成list
annotation['bbox'] = list(map(float, self.getbbox(points)))
annotation['area'] = annotation['bbox'][2] * annotation['bbox'][3]
annotation['category_id'] = self.getcatid(label)
#annotation['category_id'] = 1
annotation['id'] = self.annID
return annotation
def getcatid(self, label):
for categorie in self.categories:
if label == categorie['name']:
return categorie['id']
return 1
def getbbox(self, points):
# img = np.zeros([self.height,self.width],np.uint8)
# cv2.polylines(img, [np.asarray(points)], True, 1, lineType=cv2.LINE_AA) # 画边界线
# cv2.fillPoly(img, [np.asarray(points)], 1) # 画多边形 内部像素值为1
polygons = points
mask = self.polygons_to_mask([self.height, self.width], polygons)
return self.mask2box(mask)
def mask2box(self, mask): # [x1,y1,w,h]
'''从mask反算出其边框
mask:[h,w] 0、1组成的图片
1对应对象,只需计算1对应的行列号(左上角行列号,右下角行列号,就可以算出其边框)
'''
# np.where(mask==1)
index = np.argwhere(mask == 1)
rows = index[:, 0]
clos = index[:, 1]
# 解析左上角行列号
left_top_r = np.min(rows) # y
left_top_c = np.min(clos) # x
# 解析右下角行列号
right_bottom_r = np.max(rows)
right_bottom_c = np.max(clos)
# return [(left_top_r,left_top_c),(right_bottom_r,right_bottom_c)]
# return [(left_top_c, left_top_r), (right_bottom_c, right_bottom_r)]
# return [left_top_c, left_top_r, right_bottom_c, right_bottom_r] # [x1,y1,x2,y2]
return [left_top_c, left_top_r, right_bottom_c - left_top_c,
right_bottom_r - left_top_r] # [x1,y1,w,h] 对应COCO的bbox格式
def polygons_to_mask(self, img_shape, polygons):
mask = np.zeros(img_shape, dtype=np.uint8)
mask = PIL.Image.fromarray(mask)
xy = list(map(tuple, polygons))
PIL.ImageDraw.Draw(mask).polygon(xy=xy, outline=1, fill=1)
mask = np.array(mask, dtype=bool)
return mask
def data2coco(self):
data_coco = {}
data_coco['images'] = self.images # 构建COCO的image字段
data_coco['categories'] = self.categories # 构建COCO的categories字段(标注类别数量和类别name信息)
data_coco['annotations'] = self.annotations # 构建COCO的annotations字段
return data_coco
def save_json(self):
self.data_transfer()
self.data_coco = self.data2coco()
# 保存json文件
json.dump(self.data_coco, open(self.save_json_path, 'w'), indent=4, cls=MyEncoder) # indent=4 更加美观显示
labelme_json = glob.glob(r'./database/label/*.json')
labelme2coco(labelme_json, r'./database\annotations/train_label.json')
至此,coco数据线形式训练json文件和验证json文件都生成了。配合上images图像信息,就可以租组成符合条件的训练数据了。
九、python opencv minAreaRect 生成最小外接矩形
画一个任意四边形(任意多边形都可以)的最小外接矩形,那么点集 cnt 存放的就是该四边形的4个顶点坐标(点集里面有4个点)
cnt = np.array([[x1,y1],[x2,y2],[x3,y3],[x4,y4]]) # 必须是array数组的形式
rect = cv2.minAreaRect(cnt) # 得到最小外接矩形的(中心(x,y), (宽,高), 旋转角度)
box = cv2.cv.BoxPoints(rect) # 获取最小外接矩形的4个顶点坐标(ps: cv2.boxPoints(rect) for OpenCV 3.x)
box = np.int0(box)
# 画出来
cv2.drawContours(img, [box], 0, (255, 0, 0), 1)
cv2.imwrite('contours.png', img)
函数 cv2.minAreaRect() 返回一个Box2D结构rect:(最小外接矩形的中心(x,y),(宽度,高度),旋转角度),但是要绘制这个矩形,我们需要矩形的4个顶点坐标box, 通过函数 cv2.cv.BoxPoints() 获得,返回形式[ [x0,y0], [x1,y1], [x2,y2], [x3,y3] ]。
得到的最小外接矩形的4个顶点顺序:中心坐标、宽度、高度、旋转角度(是度数形式,不是弧度数)的对应关系如下:
注意:
旋转角度θ是水平轴(x轴)逆时针旋转,直到碰到矩形的第一条边停住,此时该边与水平轴的夹角。并且这个边的边长是width,另一条边边长是height。也就是说,在这里,width与height不是按照长短来定义的。
在opencv中,坐标系原点在左上角,相对于x轴,逆时针旋转角度为负,顺时针旋转角度为正。所以,θ∈(-90度,0]。
十、自动化修改 labelme 标注保存好的json文件
labelme标注好后,会自动的生成一个json文件。但是如果需要对标记好的文件内的字段进行批量修改,那么一个个点开再保存,那效率太低了。这里利用python,先获取json存储的信息,修改后,再保存下来。
这里以改变json文件内的label为例。如果你想要修改其他的参数,比如说坐标值、甚至是base64的图像,都可以。
# -*- coding=utf-8 -*-
import json
import numpy as np
import glob
import json
def get_new_json(filepath):
flag = False
move_flag=False
listbigoption = []
A = dict()
with open(filepath, 'r', encoding='utf-8') as f:
json_data = json.load(f)
# if json_data['shapes'] == []:
# print(filepath)
# print(json_data['shapes'])
# print(len(json_data['shapes']))
for shapes in json_data['shapes']:
label = shapes['label']
print(label)
noduleMalignant = label.split('_')[-1]
shapes['label'] = 'nodule_'+noduleMalignant
print(shapes['label'])
flag = True
f.close()
return json_data, flag, move_flag
class MyEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, np.integer):
return int(obj)
elif isinstance(obj, np.floating):
return float(obj)
elif isinstance(obj, np.ndarray):
return obj.tolist()
else:
return super(MyEncoder, self).default(obj)
def rewrite_json_file(filepath, json_data):
with open(filepath, 'w') as f:
json.dump(json_data, f, indent=4, cls=MyEncoder)
f.close()
import matplotlib.pyplot as plt
def oneDocument():
labelme_json = glob.glob(r'./json/*.json')
n=0
label_list = []
for num, json_path in enumerate(labelme_json):
# print(json_path)
m_json_data, flag, move_flag = get_new_json(json_path)
rewrite_json_file(json_path, m_json_data)
if __name__ == '__main__':
oneDocument()
十一、由yolov5学习矩形坐标变换
11.1、xywh2xyxy
def xywh2xyxy(x):
print('x:', x)
# Convert nx4 boxes from [x, y, w, h] to [x1, y1, x2, y2] where xy1=top-left, xy2=bottom-right
y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)
y[:, 0] = x[:, 0] - x[:, 2] / 2 # top left x
y[:, 1] = x[:, 1] - x[:, 3] / 2 # top left y
y[:, 2] = x[:, 0] + x[:, 2] / 2 # bottom right x
y[:, 3] = x[:, 1] + x[:, 3] / 2 # bottom right y
print('y:', y)
return y
输入和输出的坐标信息如下所示:
x: tensor([[188.29141, 55.49617, 56.29586, 95.01012],
[188.17989, 55.49852, 55.87830, 95.67655],
[183.24368, 54.52598, 66.56559, 99.71941],
[188.91458, 55.26056, 58.75016, 93.95803],
[368.82562, 50.15098, 74.95942, 97.38110],
])
y: tensor([[160.14348, 7.99111, 216.43935, 103.00124],
[160.24074, 7.66025, 216.11903, 103.33680],
[149.96089, 4.66628, 216.52647, 104.38568],
[159.53951, 8.28154, 218.28966, 102.23957],
[331.34592, 1.46043, 406.30533, 98.84154]])
11.2、xyxy2xywh
def xyxy2xywh(x):
# Convert nx4 boxes from [x1, y1, x2, y2] to [x, y, w, h] where xy1=top-left, xy2=bottom-right
y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)
y[:, 0] = (x[:, 0] + x[:, 2]) / 2 # x center
y[:, 1] = (x[:, 1] + x[:, 3]) / 2 # y center
y[:, 2] = x[:, 2] - x[:, 0] # width
y[:, 3] = x[:, 3] - x[:, 1] # height
return y
11.3、xywhn2xyxy
从归一化坐标 (x = 0,1) (y = 0,1) 转换到像素坐标 (x = 0, w) (x = 0, h)
def xywhn2xyxy(x, w=640, h=640, padw=0, padh=0):
# Convert nx4 boxes from [x, y, w, h] normalized to [x1, y1, x2, y2] where xy1=top-left, xy2=bottom-right
y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)
y[:, 0] = w * (x[:, 0] - x[:, 2] / 2) + padw # top left x
y[:, 1] = h * (x[:, 1] - x[:, 3] / 2) + padh # top left y
y[:, 2] = w * (x[:, 0] + x[:, 2] / 2) + padw # bottom right x
y[:, 3] = h * (x[:, 1] + x[:, 3] / 2) + padh # bottom right y
return y
11.4、xyn2xy
从归一化坐标 (x = 0,1) (y = 0,1) 转换到像素分割坐标 (x = 0, w) (x = 0, h)
def xyn2xy(x, w=640, h=640, padw=0, padh=0):
# Convert normalized segments into pixel segments, shape (n,2)
y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)
y[:, 0] = w * x[:, 0] + padw # top left x
y[:, 1] = h * x[:, 1] + padh # top left y
return y
十二、总结
上述搞了那么多,其实总结起来也是比较的简单,但是对于初学者,希望有一些帮助。
- 文件的操作,包括读取和存储;
- 数据的转换,遵从一个规则,需要什么,就补充什么;规则怎么定,就按照规则来;
最后,希望这里能对你有所帮助吧,哪怕微小的帮助都行,节省时间是最最重要的。如果你也常常需要用到,收藏mark住,以便随时查看。