半年前做比赛项目,其中涉及到了目标检测与姿态估计技术,于是采用了yolopose模型框架,当时训练模型时一度遇到难题(一不小心就熬到1,2点,真的累)。发表这篇文章是因为当时自己做项目也很迷,大概弄懂之后有人在b战和csdn里面私信问我如何解决,可做项目属实是比较忙,如今时间相对比较宽裕了,就给大家分享一下在标记过程中如何处理被遮挡的关键点以及如何将json文件转为txt文件(以下说明均以人体的17个关键点为例)。
1、标注工具的选择
标注工具推荐大家使用labelme,因为labelimg无法进行关键点的标记。labelme的下载与使用这里我不再赘述,有很多博客文章都详细介绍过,不了解的小伙伴可自行查阅一下。
顺便给大家提一嘴,大家在使用labelme标注的过程中最好打开save automatically(自动保存),因为这款软件不是很稳定,可能会闪退。
2、数据标注注意点
(1)标注的图片保存路径不要存在中文;
(2)所有图片的标注采用一个特定顺序,如先标记类别框,再标记关键点,且关键点的标记顺序也不能错乱,这样做是方便后续格式文件的转换;
(3)标注过程中如果一张图片有多个类别物体,则分别单独标记,如一张图片中有2个人,那就标完第一个人后再标记第二个人,不要混合标注(如先把框都标完再去标记关键点,这种做法会在后面转换格式文件时造成不必要的麻烦);
(4)类别可通过添加group_id值进行区分,如人类别的group_id = 1,猫类别的group_id = 2 等,如下图所示,当然如果你不想麻烦的话,可以在后续将json转换为txt格式文件时写几句代码自动添加上去。
3、关键点的处理
对于yolopose模型而言,关键点分为三种:(1)标记且正常显示的关键点;(2)标记但被遮挡的关键点;(3)未被标记的关键点。
三类关键点在yolo模型训练所需读取的txt格式文件中分别被标记为2,1,0。第一类和第三类都很好处理,那么如何处理被遮挡的关键点呢?
首先,第二类和第三类关键点对于我们标记的人而言其实是属于一类的,即都无法用人眼直接观察到,但区别在于被遮挡的关键点的位置我们是大概知道的,而不被标记的关键点我们是几乎不知道的,所以被遮挡的关键点要不要选择标记取决于2点:(1)它的位置是不是大概知道;(2)项目功能的精度与关键点的联系有多大,如果精度很大程度上取决于关键点,那么位置比较模糊的被遮挡关键点就可以不进行标记,反之则可以进行标记。对于两类关键点的区分大家可以参考下图(此处只是作为参考,具体要不要标注视自己的项目而定)
其次,在我们知道了被遮挡的关键点要不要标注之后,我们需要通过添加group_id值来区分三类关键点,不过我已经在后面的格式转换代码中对三类关键点做了区分处理,所以大家在标注时只需要对被遮挡的关键点添加group_id值即可。对于group_id值我填写的是1,但大家可任意选取,只需在后面的代码中进行更改就可以。
4、json格式文件信息简析
json格式文件如上图所示(很长,只截取了部分),这其中包含了很多信息,但不是所有信息都是我们需要的,我们所需要的是'shapes'这个信息数组以及'imageHeight'和'imageWidth'。'shapes'数组里面包含了标注的类别框和关键点信息,'imageHeight'与'imageWidth'则表示图片宽高。
5、json文件信息写入到txt文本文件
为什么要进行文件格式的转换是因为yolo模型在训练时只能识别读取到txt文本中的内容,转换代码我直接放在下面了,为了助于大家理解,我做了一定的注释,不懂的地方大家可留言讨论。
import json
import os
json_path = r'你的json文件存放路径' # json文件路径
txt_path = r'你的txt文本文件存放路径' # txt文本文件路径
# 关键点数组(这里我写的是人体的17个骨骼关键点,大家可以改成自己标注顺序的关键点数组)
pointsArr = ['nose', 'left_eye', 'right_eye', 'left_ear', 'right_ear', 'left_shoulder', 'right_shoulder',
'left_elbow', 'right_elbow', 'left_wrist', 'right_wrist', 'left_hip', 'right_hip', 'left_knee',
'right_knee', 'left_ankle', 'right_ankle']
# 坐标归一化,返回中心点坐标和宽高
def coordinates2yolo(xmin, ymin, xmax, ymax, img_w, img_h):
x = abs(xmin + xmax) / (2.0 * img_w)
y = abs(ymin + ymax) / (2.0 * img_h)
w = abs(xmax - xmin) / (1.0 * img_w)
h = abs(ymax - ymin) / (1.0 * img_h)
return x, y, w, h
def writeJson(rootpath, rootpath1, filename):
path = os.path.join(rootpath, filename + '.json')
count = 0 # 记录一张图片中人数的多少
index = 0 # data索引,用于区分json文件中的label值
with open(path) as f:
# 读取json格式文件并获取相应信息
data = json.load(f)
imageHeight = data['imageHeight']
imageWidth = data['imageWidth']
data = data['shapes']
length = len(data)
# print('length', length)
# 遍历json文件,用变量count记录 data[i]['label']=’类别名‘ 的次数,以此说明图片中有几个人
for i in range(0, length):
# 类别名换成自己的类别,当有多个类别时,用关键字or进行连接
# if data[i]['label'] == '类别名1' or data[i]['label'] == '类别名2'。。。
if data[i]['label'] == '类别名':
count += 1
# 将json文件信息写入txt文本文件中
file = open(os.path.join(rootpath1, filename + '.txt'), mode='w')
for j in range(0, count):
# 在txt文本文件中写入类别id、目标框中心坐标以及图片宽高
file.write(str(data[index]['group_id']))
file.write(" ")
points = data[index]['points']
xmin = points[0][0]
ymin = points[0][1]
xmax = points[1][0]
ymax = points[1][1]
x, y, w, h = coordinates2yolo(xmin, ymin, xmax, ymax, imageWidth, imageHeight)
file.write(str(round(x, 6)))
file.write(" ")
file.write(str(round(y, 6)))
file.write(" ")
file.write(str(round(w, 6)))
file.write(" ")
file.write(str(round(h, 6)))
file.write(" ")
index += 1
# 在txt文本文件中写入关键点坐标与对应id值
for point in pointsArr:
# print(index)
if index < length:
if data[index]['label'] == point:
point = data[index]['points'] # 获取关键点的坐标值
file.write(str(round(point[0][0] / imageWidth, 6)))
file.write(" ")
file.write(str(round(point[0][1] / imageHeight, 6)))
file.write(" ")
# data[index]['group_id'] == 1,表名为被遮挡的关键点,在txt文档中写入1
if data[index]['group_id'] == 1:
file.write('1.000000')
file.write(" ")
# data[index]['group_id'] != 1,表名为正常标记的关键点,在txt文档中写入2
else:
file.write('2.000000')
file.write(" ")
index += 1
# 若data[index]['label'] != point,则写入(0, 0, 0),前两个代表坐标,最后一个‘0’代表此关键点未被标记
else:
file.write('0.000000')
file.write(" ")
file.write('0.000000')
file.write(" ")
file.write('0.000000')
file.write(" ")
else:
file.write('0.000000')
file.write(" ")
file.write('0.000000')
file.write(" ")
file.write('0.000000')
file.write(" ")
file.write('\n')
# 读取path路径中的文件
filenames = os.listdir(json_path)
for item in filenames:
# 以'.'为标志分割获取文件名
filename = item.split('.')[0]
print(filename)
writeJson(json_path, txt_path, filename)
转换后的txt文本文件的内容如下所示
其中每一串数据的第一个值代表类别id,用于区分类别,后面紧跟的4个值代表类别目标框的四个角点坐标,之后的一连串数据则代表关键点信息,其中每三个为一组,前两个代表关键点坐标,后一个代表关键点类别。
OK,上述就是我对于如何处理被遮挡的关键点并将json格式文件转为txt文本文件操作的全过程,也是本次文章的全部内容,感谢大家阅读!