让车跑起来(三):逐像素分车道线,以及三条路怎么选
这一章讲三件事: 第三条路怎么走 —— 给图上每一个像素判一个类别; 逐像素分类为什么那么贵,以及一个轻量模型怎么把它变便宜; 以及三条路到底怎么选 —— 这张对比表书没做过,是我 们做的。 它在全书链条里的位置是第 11 级台阶的第三段,也是这本书的落点。 这一章的主走查还是那张车前道路图,只是这一次它的输出不是一个数,而是一张同尺寸的标注图。 遇到的生词都在当场解释。
1. 顶层全景
同一张车前道路图,三条路吐出来的东西:
路线一(12 章) ─▶ 一张只有 0 和 255 的二值图,再由人写的规则算出偏差
路线二(13 章) ─▶ **一个数**:+0.558
路线三(本章) ─▶ **一张和原图同尺寸的图**,每个像素上写着 0 / 1 / 2 / 3
(0 背景、1 实车道线、2 虚车道线、3 斑马线)
⇒ ** 同一张输入图,三种输出形状 —— 这就是三条路线的根本区别。**
图说:输出形状不同,后面接什么、要多少标注、要多少算力,全都跟着不同。
第 8 节那张对比表就是照这条线索排出来的。
这一章的全程:
① 先分清三种分割任务(第 2 节)—— 语义 / 实例 / 全景
▼
② 数据要人一个多边形一个多边形地画(第 3 节)
│ ⚠ 几个会让人白干一天的坑
▼
③ 逐像素分类为什么贵,双分支怎么治(第 4 节)
▼
④ 走一遍:那张图的输出长什么样(第 5 节)
▼
⑤ 部署换了硬件,而且装框架要对版本(第 6 节)
▼
⑥ 这条链在书里断在哪儿(第 7 节)—— 要划准,不能含糊
▼
⑦ 三条路怎么选(第 8 节)—— 这张表是我们做的
2. 「分割」是三件事,先分清
现象先行:第三种问法
第 12 章问的是「哪些像素是亮的」,第 13 章问的是「方向盘该打多少」。 这一章问的是第三种:「这个像素属于什么」。
书给的定义是:图像分割旨在把图像分成不同的区域,每个区域表示不同的对象; 按任务和输入数据类型的不同,细分成三种1:
| 任务 | 干什么 | 举个 例子 |
|---|---|---|
| 语义分割 | 对图像中每一个像素点做类别预测 | 图上所有车道线像素都标成「车道线」,不区分是第几条 |
| 实例分割 | 目标检测 + 语义分割:在每个预测框里勾出对应的「实例」 | 左边那条是 1 号车道线,右边那条是 2 号,分开 |
| 全景分割 | 两者都要:既检出全部目标,又区分同类别中的不同实例 | 背景也要判类别,同类的不同个体也要编号 |
书还给了三者的关系:语义分割是图像分割方法的基础; 实例分割、全景分割是语义分割与目标检测算法融合的应用2 (书在后半句上带了个保留,原话是「在某种程度上其实是」)。
这本书做的是第一种 —— 语义分割。
这正是线上赛那 16000 张数据的形状
第 01 章讲过线上赛的数据集:16000 张车载影像, 标注是与原图尺寸相同的单通道灰度图,背景 0、实车道线 1、虚车道线 2、斑马线 3。
「单通道灰度图」在这里的意思要说清楚: 它不是给人看的图, 它是一张和原图一样大的表格,每个格子里写着一个类别编号。 0 到 3 这四个数在灰度图上看起来都是接近全黑的,肉眼几乎分不出来 —— 它是给程序看的,不是给人看的。(这一句是我们补的;书只说了存储方式。)
所以第 01 章那句「这是一个典型的图像分割任务」,到这一章才真正落地。
3. 数据:要人一个多边形一个多边形地画
这是这条路最大的成本,而书用了整整一节讲操作细节。
采集这一半和第 13 章一样
书说得很干脆: 用手柄控制小车运动,编写脚本以一定帧率读取摄像头画面保存下来; 数据增强的方法与 §10.3.2 相同,「这里不再赘述」3。 —— 也就是第 13 章那套:只做颜色变换,不做几何变换。
标注这一半是全新的、也是最贵的
第 13 章那条路的标签是开车开出来的,一分钟人工都不用。 这条路不行 —— 得有人对着每一张图,沿着车道线的边缘画闭合多边形。
一件容易混掉的事,先钉住: 上一节那 16000 张是线上赛主办方已经标好的, 拿到手就能训。这一节讲的是线下自己采数据的情形 —— 那一步得自己做。 书没有给这个自采项目的数据集规模(见第 10 节边界),下面出现的张数都是量级估计。
书用的工具是 LabelMe。操作流程4:
① 打开图片文件夹
② 点 Create Polygons,**沿着目标的边缘画一个闭合的多边形**
③ 输入或选择这个目标的类别
④ 一张图标完,下一张
⇒ 几千到上万张图,一张一张来。
(张数是量级估计,书没给这个自采项目的数据集规模。)
四个会让人白干一天的坑
书把这几条写得很实,值得一条条抄下来。
| # | 坑 | 后果 |
|---|---|---|
| ① | Save Automatically 要勾上 | 不勾就得每张手动保存,漏一张就白标 |
| ② | Save With Image Data 要取消 | 不取消的话,标注文件里会把整张图片的数据也塞进去,文件体积暴涨 |
| ③ | 手动标背景时,类别名必须写成 _background_ | 书明说:否则格式转换会有问题 |
| ④ | 有孔洞的目标:先标外轮廓,再沿孔洞边缘画多边形,并把孔洞指定为特定类别(是背景就写 _background_) | 不这么标,孔洞会被算进目标里 |
书还给了一条默认规则:通常只需要标注前景目标并设置类别,其他像素默认作为背景5。 只有需要手动标背景时,③ 那条才用得上。
标完之后还有两步机械活
格式 转换: 用配套工具把 LabelMe 产出的格式转成训练框架认的格式;
脚本会自动在输入目录下创建一个 annotations 文件夹放转换后的数据6。
切分: 再跑一个脚本,生成 train.txt、val.txt、test.txt;
每一行是「一张原始图片的路径 + 空格 + 对应标注文件的路径」7。
注意这里出现了第三个集合(测试集) —— 第 09 章讲过它和验证集的分工: 验证集用来调超参数、决定什么时候停;测试集只在最后用一次。 (书全书没有统一过这三个词的口径,这一句是我们补的。)
书还给了一个现成的参考数据集链接,读者可以直接下载8 —— 换句话说:书自己也知道这一步的人工成本高到大多数读者不会自己做一遍。
4. 逐像素分类为什么贵,以及怎么变便宜
这是本章的第一个承重点。