基于语义分割Ground Truth(GT)转换yolov5图像分割标签(路面积水检测例子)
概述
随着开发者在issues中对 用yolov5做分割任务的呼声高涨,yolov5团队真的在帮开发者解决问题,v6.0版本之后推出了最新的解决方案并配指导教程。
之前就有使用改进yolo添加分割头的方式实现目标检测和分割的方法,最新的v7.0版本有了很好的效果,yolov8在分割方面也是重拳出击
因此使用yolo进行完成目标检测也是落地项目的一个选择,而且yolo的生态更适合落地,并且实现试试检测。但是目前的公开数据集大部分使用的是其他分割领域模型,当然标签也是适配其他模型。我在做极市平台的比赛时想到了这一点,路面积水感觉用目标检测更省力,但是他却给了分割数据,
我尝试转换GT图像标签到yolo的格式,查了好久也没有找到好的解决办法,因此根据之前的转目标检测经验,我尝试修改。
流程
由于没有对应分割区域的json格式或者其他格式的标签,因此需要根据GT找到对应坐标,可以理解为Polygon标签格式,每个拐点除标记,常规使用lableimg标注的来,所以需要通过轮廓检测获取大致的坐标点,在转换为yolo需要的格式
1、查找分割区域,
2、获取分割区域的轮廓坐标
3、精简坐标点
4、转存txt
上面的所有操作都基于OpenCV进行
读取并处理
转换为单通道灰度图并对二值化图像进行处理,让图像自动转换阈值,
cv2.threshold (src, thresh, maxval, type)
src:源图片,必须是单通道
thresh:阈值,取值范围0~255
maxval:填充色,取值范围0~255
type:阈值类型,具体见下表
阈值类型:
阈值 | 参数类型 | 小于阈值的像素点 | 大于阈值的像素点 |
---|---|---|---|
0 | cv2.THRESH_BINARY | 置0 | 置填充色 |
1 | cv2.THRESH_BINARY_INV | 置填充色 | 置0 |
2 | cv2.THRESH_TRUNC | 保持原色 | 置灰色 |
3 | cv2.THRESH_TOZERO | 置0 | 保持原色 |
4 | cv2.THRESH_TOZERO_INV | 保持原色 | 置0 |
这里我使用了自动阈值调整,因此只需要给定范围0-255即可。
gray_img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,bin_img = cv2.threshold(gray_img,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
查询轮廓并获取坐标点
单通道图会送到边缘检测算法中进行轮廓点的查询,因为GT标签的标注是很精细的,所有边缘点会很多,在检测过程中需要使用一个点估计量较少的方法,或者是筛选方法
cv2.findContours(image, mode, method[, offset])
method:轮廓近似方法有以下几种方法
cv2.CHAIN_APPROX_NONE:存储所有的轮廓点
cv2.CHAIN_APPROX_SIMPLE:压缩水平,垂直和对角线段,只留下端点。 例如矩形轮廓可以用4个点编码。
cv2.CHAIN_APPROX_TC89_L1,cv2.CHAIN_APPROX_TC89_KCOS:使用Teh-Chini chain近似算法
经过测试cv2.CHAIN_APPROX_TC89_KCOS方法比较符合我们的需求,下面是几种方法的对比图:
原图
cv2.CHAIN_APPROX_NONE
cv2.CHAIN_APPROX_SIMPLE
cv2.CHAIN_APPROX_TC89_L1
cv2.CHAIN_APPROX_TC89_KCOS
yolo的标签格式是转折或者较长的边缘添加标注点,因此不需要太多相邻点,给出大概的轮廓即可,对比上面最合适的是cv2.CHAIN_APPROX_TC89_KCOS近似方法,
但是从最后的结果图看,依然存在一些不需要的点,因此我们选择一个简单原则,相邻的点在x或者y上如果变化超过一个阈值才保留,否则不标注和不作为分割点。阈值不固定,我设置为30的效果如下
上面部分的代码
cnt,hit = cv2.findContours(bin_img,cv2.RETR_TREE,cv2.CHAIN_APPROX_TC89_KCOS)
cv2.drawContours(img1,cnt,-1,(0,255,0),5)
cnt = list(cnt)
for j in cnt:
result = []
pre = j[0]
for i in j:
if abs(i[0][0] - pre[0][0]) > 30 or abs(i[0][1] - pre[0][1]) > 30:
pre = i
temp = list(i[0])
#根据yolo的归一化方式,x,y分别除以原图的宽和高
temp[0] /= W
temp[1] /= H
result.append(temp)
cv2.circle(img1,i[0],1,(0,0,255),2)
计算并转存txt
按每个类别的坐标存入,有的坐标会很多,所以要一个数组写一次。先写入的“0”是当前的类别,如果多分类的需要单独处理
f.write("0 ")
for line in result:
line = str(line)[1:-2].replace(",","")
# print(line)
f.write(line+" ")
f.write("\n")
效果演示:https://live.csdn.net/v/271857
完整代码:https://github.com/magau123/CSDN/blob/master/GT2yolo-seg.py