反正跑数据也要等待,不如来写几篇博客,这也是我第一次参加这样类型的比赛,做一下相关经验的总结。在天池的2021广东工业智造创新大赛,智能算法赛:瓷砖表面瑕疵质检中,提供了基础的 json
数据和文件,这里介绍三种预处理方式:
- 普通数据制作
COCO
数据集 - 普通数据制作
mask
数据 - 因图片尺寸过大显存溢出,所以需要切割图片使得切割图片包含目标区域
- 自适应切割
- 类别平衡处理
题目中,提供的 json
格式文件如下,即使在文件名相同的情况下,也是一个目标对应一条 json
数据。
1 | { |
制作 COCO 数据集
COCO
数据集格式可以参考这里。在真实训练的过程中,info, version, licenses
等信息没啥卵用,可以直接设为 None
。
对于 images
字段,由『图片名、图片ID、图片宽高』组成,图片宽高和图片名可以直接获取。所以需要为每一个图片设置 ID
,图片名相同,则 ID
名相同且唯一,这个用 python 的字典不难实现。
对于 annotations
字段,由『segmentation, area, iscrowd, image_id, bbox, category_id, id』组成。其中,这里的 id
是盒子的 id
,全局唯一,遍历数据集的时候设置自增变量便可实现。
segmentation
表示分割的多边形,如果是矩形就写顺时针切割:x1, y1, x2, y1, x2, y2, x1, y2
;如果不是矩形,题中的数据会提供的,自己获取即可;area
表示面积,矩形的话就长乘宽;iscrowd
为 1 时,表示用起始像素加一段其它像素来框住mask
区域;为 0 时,表示segmentation
为多边形;image_id
表示这个字段对应哪个图片;bbox
表示盒子区域;category_id
表示这个目标区域的类别;
获取起来也不是很难的样子,一次 for 循环就可以搞定。代码。
制作 mask 数据集
mask
数据格式可以参考我的上篇文章。这里,因为一张图片里面有好几个目标。所以,先用字典按照 name
排序,再用 groupby
进行分组,使得一张图片下面能包括所有的目标区域。新json文件的格式:
1 | { |
遍历得到的新字典(注意:groupby 后得到的字典只能迭代一次)。这里为了节省内存,不打开原始图片文件,按照 json
文件提供生成背景为黑色、尺寸为原图像尺寸的图片,在对图片的每个目标区域生成掩码颜色,把掩码颜色贴到背景图片在保存即可。代码。
mask 颜色注意事项
这里需要注意的是,如果要用颜色来区分类别(取决于训练数据的代码如何写),一定不能写成这样。因为读取图片后,想要根据目标的数量来设置训练使用的 label,此时用np.unique()
统计类别数量时,所有类别的数值都一样,便无法获取准确类别。
1 | { |
可以写成这样,总之保证颜色不一致就可以了。1
2
3
4
5{
'1': '#110000',
'2': '#002200',
'3': '#000033',
}
切割图片
这个是最头疼的一步。假设我们要切割 512 X 512
的图片,使切割图片包含目标区域。FasrerRCNN
等主流目标检测工具有负样本的机制,所以不用切割无关区域。初试的裁剪想法是,随机生成一个点的坐标,这个点是左上角的顶点,这个顶点的坐标加上 512 后能覆盖目标区域。点的坐标由两部分组成,分别是 left
和 top
。在生成left
和top
时加入限制,保证裁剪区域会覆盖目标区域的像素点。但切割中仍然会有很多问题存在,溢出、损坏区域大于 512 等,如下图所示:
所以总结后的裁剪逻辑:
- 先处理损坏区域大于 512 的情况
- 处理顶点左侧溢出边界
- 若不满足以上情况,随机生成顶点的横坐标
- 处理顶点下侧溢出边界
- 若不满足以上情况,随机生成顶点的纵坐标
- 如果横向裁剪溢出右边界,那么缩小横坐标
- 如果竖向裁剪溢出下边界,那么缩小纵坐标
- 判断裁剪区域和目标区域是否有交集,没有交集的情况下,移动坐标点
裁剪完毕后,需要对裁剪区域进行检验,防止裁剪失败。这里就是用np.unique(array)
来获取裁剪区域中的所有像素点,判断返回值的长度是否大于1来判断是否含有目标区域。因为背景元素肯定占一个元素,所以不能用大于0来判断。代码。
自适应切割
图片的大小大约为 6000X8000
,是不小存在了。在处理图片的时候我们发现,有的坏点(目标区域)很小,只有 5X5
的规模,而有的目标区域很大,有 512X512
的规模。除了网络要就加入自适应机制外,我们在图像预处理部分也加入了相关处理。
首先定位到原始的盒子B1
,用随机数生成一个更大的B2
框,B2
框在允许的情况下是 B1
的 10 倍左右(这里用随机数实现)。最后裁剪B2
框,裁剪后将裁剪的图片设置为统一大小,也就是所有的目标区域在512尺寸的图片中大小都会一致。防止了特别大和特别小的极端对立,也省去了在 FasterRCNN
中 Ancthor
数量和规模的设置,训练和推理更加迅速。在切割的时候,把原始盒子B1
的位置重新定位下,保存到新的 json
,预处理就做完了。代码。
类别平衡处理
在训练集中,我们发现5号类有8886个样本,6号类只有331个样本。可能是由于制作工艺的原因,6号类对应的损坏很难出现,但容易出现5号类对应的损坏。为了平衡样本,直接采取了一种简单粗暴的解决方案:
- 每个类共需要 2000 张图片;
- 对于样本数量大于 2000 的类来说,就随机选择;
- 对于样本数量低于 2000 的类来说,就做数据增强,翻转或每个盒子多裁剪几张都是可以的
最后,每个类都有 2000 张图片,这样样本就均衡了许多。
代码
完整项目代码:
https://github.com/XDU-Bigbing/Simple-Key-Point-Detection/tree/main/code
经验总结
参加比赛的时候距离比赛开始过去半个月了,and实验室一直有破事,比赛打的断断续续,没有弃赛胜似弃赛,这次就当积累经验和踩坑了,准确率23%,排名460/4700。生命不息,踩坑无数,记录参赛经验毕竟纯做论文得不到更加贴近实际的经验:
- 写完代码后要检查,不要怕麻烦,不要等到最后执行完采取看结果,或者执行小规模的代码,发现错误后可能两个小时已经浪费了,这一点上我的确吃了不少亏;
- 数据与代码分离,不要将代码和数据放在一个文件夹。虽然读取数据方便,可万一认为当前的模型不好使想删除时,一不小心直接
rm -rf /*
了,连数据一起删没了。数据应该放在单独文件夹,不在任何代码的文件夹下。这一点在使用服务器进行炼丹的时候要尤为注意; - 写出通用性强的代码,做出一次结果后,很可能不满意需要再次调整数据和模型,比如额外增加功能或选择判断。需求可能会一直在变,所以代码不要一次性写死,要写的更加灵活和可维护。当然这需要一些程序设计经验,只可意会不可言传。这里只是推荐
argparse
这个库,不用一次次去程序里面改参数了; - 模型大体上都一样,学懂理论后,先找个别人实现好的直接套用观察结果就好
实力硬可以自己重头写;其他的印象不是很深刻了,只要python功底强大,还是能应付各种场景需求和读懂他人源代码的。
参考
- COCO 数据集格式:https://blog.yuxinzhao.top/coco-dataset-format/
- segmentation写法:http://www.xyu.ink/3612.html
- iscrowd解释:https://github.com/cocodataset/cocoapi/issues/184
- PIL缩放图像注释事项:https://jdhao.github.io/2020/11/18/pillow_image_resize_pitfall/