循线 — 视觉管线、Jupyter 调参与完整 PID
循线 — 视觉管线、Jupyter 调参与完整 PID
这一章讲什么: 让机器人沿着地上的黑线自动行驶——机器人竞赛的经典项目, 也是全书控制密度的收口。流程四步: ① 打印拼贴一条带弯道的测试轨道;② 采集一批「各种条件」的测试图像; ③ 在 Jupyter Notebook 里逐格调出图像管线,把「线在哪」变成数字; ④ PID(至此 P、I、D 三项齐全)把数字变成转向。读完你会有一套「先离线调图、 再上机调参」的完整方法论。
1. 先看现象:为什么循线是「整链考试」
循线(line following)= 自主沿标记路径行驶:地上的视觉标记(蓝胶带、白线)、 或非视觉标记(埋线、磁条)。它比「规划整张地图」简单,所以成了竞赛和工业的常客—— 「顶级算法几秒就能跑完复杂线道」1。
它对系统的考验是全方位的:相机要看清线(感知)、管线要输出线的位置(算法)、 行为要把位置变成方向(控制)、电机要跟住方向(执行)——任何一环掉链子, 车就出线。所以本章顺序刻意反着来:先把图像部分在没有机器人的情况下调对, 再上机调控制。
2. 顶层全景:两阶段方法论
离线阶段(电脑,无机器人) 在线阶段(机器人)
┌──────────────────────────┐ ┌──────────────────────────┐
│ 打印轨道 → 拍照采样 │ │ line_detector 服务跑管线 │
│ → 建测试图像集 │ → │ → 发布 line_detector/ │
│ Jupyter Notebook: │ │ position(误差,0 = 中线)│
│ 灰度 → 掩码 → 轮廓 → │ │ line_follower 行为: │
│ 矩形 → 中线+角度 │ │ PID(0.001, 0.0001, │
│ 每步看图、改参数、再跑 │ │ 0.0005) → 差速 │
└──────────────────────────┘ └──────────────────────────┘
图说:视觉算法的 bug 在 Jupyter 里是免费的;上车才发现,每 bug 一次耗一轮电池。3. 核心原理之一:测试轨道与测试图像集
轨道用打印的瓷砖拼贴(本章 Technical requirements:一台打印机 + 胶带), 特点是曲率丰富、接缝处故意留缝——后面会看到,缝线正好是「脱线策略」的考点。
比轨道更重要的是测试图像集:本章反复强调,拿着一沓条件各异的照片调算法—— 线偏左偏右、大弯小弯、不同光照、地毯强噪、甚至没线(验证「无线时行为讲理」)2。 这是视觉工程的铁纪律:算法不在你记得的场景上调,在一沓刻意收集的「考试卷」上调。
4. 核心原理之二:Jupyter Notebook——图像管线的实验室
Jupyter Notebook 是本章的新工具:代码写在「单元格(cell)」里, 跑一格立刻在格子下方看到结果(图、数)——代码与图像并排,改参数→重跑→看图, 循环快得让调图像算法成为乐事3。
管线五格,照原书顺序接住:
格1 读图 + 缩到 320×240 + 转灰度
Tip:用「能干活的分辨率」——320×240 = 76,800 像素;
而 Pi 相机原片 4608×2592 ≈ 1,200 万像素,是它的 155 倍!
每个像素都是内存和算力,视觉算法一律低分辨率起步[^4]。
格2 掩码:cv2.inRange(灰度, 0, 50) —— 暗于 50 的像素为白(线),其余黑
阈值 50 是试出来的;调高了连影子都当线[^5]
格3 轮廓:findContours → 按面积取最大(max(contours, key=cv2.contourArea))
格4 最小面积矩形:cv2.minAreaRect(最大轮廓) → 带角度的包围盒
格5 取中线与角度:center_x/center_y、(宽,高,θ°);
高比宽大则 θ += 90°(矩形哪条边长不确定,线要沿长边);
θ 取负(mod 180)——图像的 Y 轴向下,与 pose 坐标相反,必须翻过来[^6]格 5 的示例输出是 (91, 239):角度 91°、长 239 像素(画面高 240,差的那 1 像素
来自线没贴满底边)——用具体数字锚定「拟合得对不对」4。
从矩形再推线的方向端点:中心沿 θ 走半条长度(endy 取负号翻 Y 轴),
画出来一根红线压在黑线上——拟合合格5。
管线出口两个数:线的中心 x(相对画面中心,即误差)和 θ(线朝哪)。
检测服务把误差发上 line_detector/position——0 就是目标,天然是 PID 的误差输入6。
5. 核心原理之三:循线行为——误差进 PID,脱线有策略
行为本体是第 14 章跟随行为的「线版」:
line_pid = PIDController(0.001, 0.0001, smart_reset=True)
# Think
turn = -line_pid.control(self.line_error, time_difference)
# Act:差速合成
left = speed - turn # speed = 0.4,低速好调
right = speed + turn两个语义点:误差为负 = 线在左,而 pose 坐标系取向相反,所以输出取负7; 每 0.05 秒一帧,空消息也发(保持最后指令)。
脱线策略是行为层真正的设计题,书里列了四个候选并选了第一个:
| 策略 | 评注 |
|---|---|
| 保持当前转向继续找(选中) | 大概率找回;顺带容忍轨道的打印接缝缝隙8 |
| 直行 | 简单,但可能越开越远 |
| 原地自旋找线 | 能找回,姿态狼狈 |
| 停车 | 工业安全环境的正解;调试期很烦 |
配套动作:脱线时 line_pid.reset() 清积分,但保留最后的 turn——
车会沿「最后看到线时的转向」继续扫,直到线重新入画7。
6. 核心原理之四:补上微分项 D——压振荡的最后一块
P+I 能跑,但书里观察到「过冲(overturn)」——这正是第 9 章就预告过的振荡, 现在治它:微分(derivative)项,误差的变化率,做阻尼。
实现照原书:导数 = (本次误差 − 上次误差) ÷ 时间差—— 误差在收窄,D 项给负的输出,把猛烈的纠偏「揉」软;首次调用没有上次误差,记 09。
line_pid = PIDController(0.001, 0.0001, 0.0005, smart_reset=True)
# ↑ D = P 的一半,偏猛,但作者实测更顺滑至此 PIDController 三项俱全(P 纠当下、I 消残差、D 压超调),外加两件护甲 (smart_reset、windup_limit)——全书控制线在本章收口。
调参的实战层话书里说得直白:
- 「方向误差不再频繁穿越 0 线 = 线被稳在车身一侧;弯道段误差本来就不该是 0」 ——看曲线读行为,别看车猜10;
- 「不同速度下,合适的 PID 组合不同;每有改进先记下来再细化」;
- 「我测到把瓷砖上的墨都磨穿了,只好重印」——调参的真实成本,作者没藏11。
7. 作者的判断与证据
- 判断: 先离线(Jupyter)后在线(机器人)。图像管线的 bug 在笔记本里秒级暴露, 上车才暴露就是分钟级 + 电池级;这是方法论的主论点3。
- 判断: 脱线选「保持转向」。不是最优,是「测试里足够好,还顺便容错接缝」—— 工程里的「足够好」被明写出来8。
- 事实层: 76,800 vs 1,200 万像素、阈值 50、(91, 239)、PID 三值、 D = P/2——全部可复现1249。
- 对 AI 的态度: 神经网络循线模型「当时没有比别家算法更划算,还容易被骗」—— 第 14 章用 AI,本章不用,理由都写在明处13。
8. 边界与局限
- 亮度对比假设。 掩码吃「暗线亮地」;反色线、彩色线得换掩码策略 (书承认「色相对比检测不到」)14。
- 单线、单帧。 没有多线选择、没有历史平滑;弯急时会整段丢线。
- 调参仍靠手。 挑战题才指向 config store;传感器阵列(更稳的硬件方案)只在练习里提一句15。
- D 项的噪声敏感性没展开。 真实信号含噪时微分项会放大抖动, 低通滤波(derivative filtering)是标准解法,本书未提。
9. 可带走的
- 循线 = 视觉(线在哪)+ 控制(怎么转)的整链考试;任何一环掉链子都出线1。
- 先建测试图像集(含「没线」的负例(反例)),再在 Jupyter 里逐格调管线23。
- 视觉算法用「能干活的分辨率」:320×240 与 1,200 万像素差 155 倍12。
- 管线五格:灰度 → 阈值掩码(50 起调)→ 最大轮廓 → 最小面积矩形 → 中线+角度164。
- 图像 Y 轴向下、pose Y 轴向上——跨坐标系必翻一个轴4。
- 脱线策略四选一,选「保持转向」:能找回、容忍接缝;清积分、留转向78。
- D 项 = 误差变化率 ÷ 时间;压振荡,不是加速收敛9。
- 看曲线读行为:误差不过零 = 稳;弯道不为零 = 正常10。
补遗:从传感器权衡到离线调参的完整流程
为什么用摄像头循线(而不是专用传感器)。 原书把三条路都摆出来权衡过: 专用循线传感器阵列(5–7 个探头)结果最稳,但要多花钱买硬件, 「不利用我们已有的东西」;VL53L5CX 距离阵列理论上能看反射率当地下线用, 但要在「看距离」和「看线」之间反复拆装,「未经证实、用途怪异」; 树莓派摄像头已经在机器人上,只需改装一次让相机能低头看过车头, 云台还能在「看路」与「看脸」之间切换——代价是要写视觉代码、吃算力、依赖光照17。 选择摄像头,是「复用已有硬件 + 练视觉处理」对「最省事方案」的取舍。
测试轨道的四条军规。 不管用什么材料(黑胶带、记号笔、白漆、打印卡纸), 轨道必须:① 线与背景亮度对比鲜明(机器人对比不出就看不出); ② 线宽一致,15–30 mm 为宜,忽粗忽细会迷惑循线系统; ③ 暗色部分不反光(高光会把黑线照成白的); ④ 压得住、蹭不坏——机器人会来回打方向,纸跑了全场白搭18。 作者用 A4 卡纸打印 RobotSquare 的免费轨道块,胶带拼接。
采图:给算法准备「考试卷」。 摄像头改装到位(云台低头 80°,让过传感器),
rpicam-still --immediate --output line_1.jpg 拍一张,
pyinfra ... files.get 拉回电脑——这一步本身就是第 4 章部署工具的活用19。
为什么要离线照片而不是对着摄像头调?书里给了两条硬理由:
同一张图跑一百次才能做有意义的性能对比,摄像头每次给的都是新数据;
而且在算法还不可靠时,不想让机器人真的动起来20。
脱线处理的完整语义。 正文 §5 说过「保持转向」,代码层的配套是:
检测超时后 line_pid.reset() 清掉积分,但 turn 变量保留最后的值——
于是车沿「最后看到线时的转向」继续扫,线一回画面立即接上 PID21。
相机前移:让镜头低头能看过车头。 改装的动机:云台保持原位低头时, 镜头会先看到自己的距离传感器支架,挡住要循的线。改法:云台底座整体前移, 让镜头越过车头前缘;距离传感器支架下移并翻转——从顶板下面向上装, 与云台底座共用螺丝;传感器随之退进车架里,低头时不再入画。 装配两条军规:两颗长螺栓从上往下穿,先用手拧紧、水平居中后再彻底上紧; 传感器装回支架的铜柱上。书顺带留了句行业实话:机器人能力增长带来的 重新布局是常事,偶尔推倒重装属于流程的一部分,不算失败22。
管线的两道负例闸。 Jupyter 里把管线包成 test_image 函数时,
书加了两道闸:一个轮廓都找不到(没线的图)→ 返回 None 并打印
"No contours found";最大轮廓的最短边不足 10 像素
("Detected line is too small")→ 同样返回 None。
第二道闸的理由:轮廓不等于线——高光、花纹也能在掩码里围出小轮廓块,
不设最小尺寸,噪声块会被当线把端点端出去23。
10. 原文地图
| 主题 | 原书位置 |
|---|---|
| 循线定义 | text/129-fm-technical-requirements.txt:26(搜「line following is the ability」) |
| 相机前移改装 | text/130-fm-making-a-line-follower-test-track.txt:132(搜「obscure」) · text/130-fm-making-a-line-follower-test-track.txt:146(搜「recessed」) |
| 负例两道闸 | text/132-fm-line-following-computer-vision-pipeline.txt:310(搜「No contours found」) · text/132-fm-line-following-computer-vision-pipeline.txt:320(搜「Detected line is too small」) |
| 测试图像集 | text/132-fm-line-following-computer-vision-pipeline.txt:4(搜「collection of test images」) |
| Jupyter 工作流 | text/132-fm-line-following-computer-vision-pipeline.txt:60(搜「Jupyter Notebook」) |
| 低分辨率原则 | text/132-fm-line-following-computer-vision-pipeline.txt:125(搜「lowest resolution you can」) · text/132-fm-line-following-computer-vision-pipeline.txt:127(搜「76,800」) |
| 掩码与轮廓 | text/132-fm-line-following-computer-vision-pipeline.txt:148(搜「cv2.inRange(gray, 0, 50)」) · text/132-fm-line-following-computer-vision-pipeline.txt:164(搜「max(contours」) |
| 矩形与角度 | text/132-fm-line-following-computer-vision-pipeline.txt:178(搜「minAreaRect」) · text/132-fm-line-following-computer-vision-pipeline.txt:215(搜「reflect the Y-axis」) |
| 方向线端点 | text/132-fm-line-following-computer-vision-pipeline.txt:243(搜「endy = centery」) |
| 脱线四策略 | text/133-fm-line-following-with-the-pid-algorithm.txt:53(搜「Keep turning」) · text/133-fm-line-following-with-the-pid-algorithm.txt:64(搜「turn correction until we find a line again」) |
| 循线 PID | text/133-fm-line-following-with-the-pid-algorithm.txt:101(搜「smart_reset=True」) |
| 微分项实现 | text/134-fm-adding-a-derivative-to-the-pid.txt:10(搜「change divided by time」) · text/134-fm-adding-a-derivative-to-the-pid.txt:76(搜「0.001, 0.0001, 0.0005」) |
| 调参成本实话 | text/135-fm-summary.txt:16(搜「different at different speeds」) · text/135-fm-summary.txt:19(搜「worn through the ink」) |
| 章节总结 | text/135-fm-summary.txt:23(搜「In this chapter, you saw」) |
Footnotes
-
出处:「Technical requirements」第 26 段(
text/129-fm-technical-requirements.txt:26,搜「line following is the ability」)。原文:循线 = 自主沿标记路径行驶;标记可视觉(蓝胶带、白线)或非视觉(埋线、磁铁);机器人边走边找线、修正航向;竞赛中速度在准确之后是关键。 ↩ ↩2 -
出处:「Line-following computer vision pipeline」第 4 段(
text/132-fm-line-following-computer-vision-pipeline.txt:4,搜「collection of test images」)。原文:值得建一套测试图集:线位不同、有弯、光照不同、地毯强噪,还有「没线」的图——验证行为讲理。 ↩ ↩2 -
出处:「Line-following computer vision pipeline」第 60 段(
text/132-fm-line-following-computer-vision-pipeline.txt:60,搜「Jupyter Notebook」)。原文:Jupyter 是「用 Python 探索测试图像的神器」;单元格 + 即席结果(图与代码同屏);poetry add notebook 安装;在电脑上跑(机器人上太慢)。 ↩ ↩2 ↩3 -
出处:「Line-following computer vision pipeline」第 178 段(
text/132-fm-line-following-computer-vision-pipeline.txt:178,搜「minAreaRect」)与第 200 段(搜「reflect the Y-axis」)。原文:minAreaRect 返回中心、(宽,高)、角度;宽高不定谁长,高长则 θ+90;θ 取负并 %180;Tip:图像 Y=0 在顶部,pose Y=0 在底部,必须翻 Y 轴;示例输出 (91, 239)。 ↩ ↩2 ↩3 ↩4 -
出处:「Line-following computer vision pipeline」第 243 段(
text/132-fm-line-following-computer-vision-pipeline.txt:243,搜「endy = centery」)。原文:转弧度;终点 = 中心 ± 半长沿 θ;Y 项取负翻转坐标系;红线应与黑线重合。 ↩ -
出处:「Line-following with the PID algorithm」第 39 段(
text/133-fm-line-following-with-the-pid-algorithm.txt:39,搜「relative to 0」)。原文:检测器发的位置以 0 为中心——可直接当 PID 误差;PID 输出转向修正,与速度合成电机值。 ↩ -
出处:「Line-following with the PID algorithm」第 101 段(
text/133-fm-line-following-with-the-pid-algorithm.txt:101,搜「smart_reset=True」)。原文:P+I + smart_reset;line_error 为负 = 线在左;pose 取向相反,输出取负;脱线时 reset 清积分但保留最后 turn,继续原转向扫线;速度 0.4 低速好调;每 0.05 秒发电机消息。 ↩ ↩2 ↩3 -
出处:「Line-following with the PID algorithm」第 53 段(
text/133-fm-line-following-with-the-pid-algorithm.txt:53,搜「Keep turning」)与第 64 段(搜「turn correction until we find a line again」)。原文四策略:保持转向/直行/自旋找线/停车(工业安全正解,调试烦);选保持转向——大概率找回,还容忍打印接缝的缝隙。 ↩ ↩2 ↩3 -
出处:「Adding a derivative to the PID」第 10 段(
text/134-fm-adding-a-derivative-to-the-pid.txt:10,搜「change divided by time」)与第 120 段(搜「0.001, 0.0001, 0.0005」)。原文:D = 误差变化 ÷ 时间;无上次误差记 0;日志分行打印 P/I/D 分量;接入循线:D 取 P 的一半(偏猛但实测更顺)。 ↩ ↩2 ↩3 -
出处:「Adding a derivative to the PID」(
text/135-fm-summary.txt:7,搜「crossing the 0 line」)。原文:误差不过零 = 线稳在一侧;弯道段误差本就不期望为 0;初始上轨的大摆除外。 ↩ ↩2 -
出处:「Summary」第 16 段(
text/135-fm-summary.txt:16,搜「different at different speeds」)与第 19 段(搜「worn through the ink」)。原文:不同速度 PID 组合不同,见好就记;测到磨穿瓷砖的墨、只好重印。 ↩ -
出处:「Line-following computer vision pipeline」第 125 段(
text/132-fm-line-following-computer-vision-pipeline.txt:125,搜「lowest resolution you can」)与第 130 段(搜「76,800」)。原文 Tip:计算机视觉用「能干活的分辨率」;320×240 = 76,800 像素;Pi 相机 4608×2592 ≈ 1,200 万像素,是 155 倍;要快就压低分辨率。 ↩ ↩2 -
出处:「Line-following computer vision pipeline」第 25 段(
text/132-fm-line-following-computer-vision-pipeline.txt:25,搜「neural network line detector」)。原文:神经网络循线模型当时没有比别家算法更划算,还容易被骗;Canny+Hough 受噪影响怪;行差法适合轮下传感器不适合前方;轮廓+矩形稳定且复用已有工具。 ↩ -
出处:「Line-following with the PID algorithm」第 7 段(
text/133-fm-line-following-with-the-pid-algorithm.txt:7,搜「assumes brightness」)。原文排错:当前掩码假设亮度对比;色相等别的对比检测不到。 ↩ -
出处:「Challenges」第 18 段(
text/136-fm-challenges.txt:18,搜「sensor arrays」)。原文:PID 与掩码阈值接 config store;中线近则加速;试 center_x 或 theta 作输入;研究传感器阵列——结果可以很平滑。 ↩ -
出处:「Line-following computer vision pipeline」第 148 段(
text/132-fm-line-following-computer-vision-pipeline.txt:148,搜「cv2.inRange(gray, 0, 50)」)。原文:灰度化后按亮度掩码;阈值 50 依光照调;高了会把影子当线;数值越高,越浅的灰都会被算进线。 ↩ -
出处:「Making a line-follower test track」第 30 段(
text/130-fm-making-a-line-follower-test-track.txt:30,搜「visual processing approach」)。原文:专用循线传感器最稳但要加钱;VL53L5CX 看反射率是未证实的怪用法;摄像头已在机上、只需一次改装,代价是处理量与光照依赖。 ↩ -
出处:「Making a line-follower test track」第 77 段(
text/130-fm-making-a-line-follower-test-track.txt:77,搜「15 and 30 mm」)。原文:亮度对比鲜明;线宽一致(15–30 mm);暗部不反光;表面压得住、蹭不坏。 ↩ -
出处:「Capturing test images for computer vision」第 60 段(
text/131-fm-capturing-test-images-for-computer-vision.txt:60,搜「rpicam-still」)。原文:云台低头 80°,rpicam-still 拍图,pyinfra files.get 拉回电脑。 ↩ -
出处:「Capturing test images for computer vision」第 25 段(
text/131-fm-capturing-test-images-for-computer-vision.txt:25,搜「same image 100 times」)。原文:同图跑百次性能对比才有意义;机器人动时不想用不稳的算法驱动。 ↩ -
出处:「Line-following with the PID algorithm」第 132 段(
text/133-fm-line-following-with-the-pid-algorithm.txt:132,搜「prior trajectory」)。原文:脱线时 reset PID 清积分、保留最后 turn,继续原转向扫线。 ↩ -
出处:「Making a line-follower test track」第 132 段(
text/130-fm-making-a-line-follower-test-track.txt:132,搜「obscure」)与第 146 段(搜「recessed」)、第 136 段(搜「rearranging」)、「Capturing test images for computer vision」第 2 段(text/131-fm-capturing-test-images-for-computer-vision.txt:2,搜「secure the Pan/Tilt base and sensor bracket」)。原文:低头会看进传感器组件、遮住部分线;云台底座前移看过车头;传感器支架下移翻转、从顶板下方向上、与云台底座共用螺丝;传感器退进车架,低头不入画;重新布局是机器人开发的常事,偶发重装是流程一部分;长螺栓先手拧、水平居中后再上紧,传感器装回铜柱。 ↩ -
出处:「Line-following computer vision pipeline」第 310 段(
text/132-fm-line-following-computer-vision-pipeline.txt:310,搜「No contours found」)与第 320 段(搜「Detected line is too small」)。原文:无轮廓返回 None;矩形最短边 <10 像素视为非线、返回 None;函数化后便于在一沓测试图上重跑调参。 ↩