
简介面向轨道交通信号系统课程学习者与目标检测实验实践者的车载系统与控制模式实验报告聚焦CBTC环境下的车载软件操作、ATC初始化、RM/CM/AM模式转换、ATS计划下发与运行曲线观察等关键环节。文档完整覆盖实验目的、器材、内容、过程、分析与总结并给出成绩评定标准结合列车加车、方向选择、牵引制动、到站开关门、提前发车与跳停等仿真操作可帮助读者理解目标检测在列车位置与运行状态判断中的作用。压缩包内共1个doc文件约27.52MB结构紧凑适合直接查阅与对照撰写。已有135人学习下载可作为课程实验参考、报告模板与复盘材料辅助梳理操作顺序、分析思路和总结写法。1. 车载控制模式不是档位一次点反顺序为什么会把三台机器拖进重启仿真台架上最容易被当成玄学的一幕三台机器摆好、车也加上了就因为在“ATC 钥匙接通”和“ATC 初始化”之间点反了顺序整个台架黑屏重启。多数人的第一反应是软件有 bug其实这一下点反恰好把 CBTC 车载控制模式的设计意图暴露了出来——RM、CM、AM 从来不是三个驾驶档位而是一台带前置守卫条件的状态机迁移请求被拒绝时车载会销毁当前会话重新注册仿真环境里就表现成集体重启。而实验报告里那些截图判读、曲线识别、车头定位本质上都属于目标检测检测的不是“画面里有什么”而是“系统认为自己在哪、此刻被谁约束”。把这两件事拆开看这套实验才真正立得住否则照着步骤点一遍换台机器、换个分辨率就全乱。2. 三机一列车的 CBTC 拓扑注册、台账与移动授权谁说了算实验室里一列车配三台计算机这不是排场。区域控制器软件跑一台车载软件跑一台ATS 与驾驶台跑一台三台机器各自维护一份状态靠局域网周期性地互相喂数据。任何一台掉线剩下两台的操作就全部作废——这也是为什么一组人操作失误往往会让整组人跟着重来血泪经验就是这么攒出来的。2.1 三本账ZC 的台账、车载的会话、ATS 的计划区域控制器ZC管一段线路账本上记的是“车”而不是“界面”车号、当前位置里程加一个置信区间、运行方向、当前模式、已分配的移动授权终点、以及哪条进路被谁占用。它不关心司机按了什么按钮只认车载周期性上报的位置报告。位置报告里最关键的从来不是那个精确数字而是数字后面那个“不确定度”——列车自己也知道自己测得不准。车载这边维护的是“会话”我注册了没有、位置有没有被采信、当前模式是什么、门关没关、ATO 有没有使能。面板上所有灯态都是这份会话的外在投影不是开关本身。所以“灯亮了”只说明状态成立“点不亮”说明守卫条件没凑齐这两件事在排错时是完全不同的方向。ATS 给的则是意图车次号、目的地、每一站的到发时刻、是否跳停。三本账的刷新节奏差得很远ZC 与车载是百毫秒级的周期刷新ATS 是事件驱动的下发计划、提前发车、设置跳停。把节奏搞混是新手最容易犯的错以为点了“下发计划”列车就该动其实计划只是让车载有能力算出站间曲线动不动的决定权在 ZC 的移动授权和车载的模式守卫上。| 角色 | 维护的状态 | 更新节奏 | 缺失后的直接表现 | | 区域控制器 | 列车台账、进路占用、MA 终点 | 周期重算百毫秒级 | 车载退化到 RM 或触发紧急制动 | | 车载 | 位置报告、模式、门状态、ATO 使能 | 周期上报 事件触发 | ZC 判为通信超时MA 收回 | | ATS | 计划、时刻表、跳停标记 | 事件驱动 | 无站间曲线AM 模式下无反应 |这张表解释了实验里最反直觉的一条现象没下发任务直接操作驾驶台列车有牵引但没速度。牵引是驾驶台本地给的速度需要曲线曲线需要计划计划在 ATS 手里。2.2 “注册成功”背后位置报告凭什么被采信从存车线加车第一步不是点初始化而是在“列车初始位置”里选位置和上下行。界面上写得很轻描淡写——向右为上行反之为下行——但这一选决定了后面所有里程的增长方向。整个 CBTC 的位置体系是沿上行方向单调递增的里程坐标方向选反车载上报的里程就会随行驶反向变化ZC 一看这车的报告和轨道拓扑对不上直接判为不合法。握手报文里至少要有四样东西车号、初始位置里程、运行方向、位置置信区间。ZC 收到后做三件校验车号在管辖范围内且唯一、初始位置落在自己管的那一段、方向与轨道拓扑一致。三件全过才建立台账从这一刻起车载的周期位置报告才被采信任何一件不过车载只能停在 RM靠人目视往前顶到转换轨。注意车号重复是实验室里最隐蔽的失败原因。两组人用了同一个车号加车先注册的赢后注册的那台死活进不了 CM界面提示又只有一句“注册失败”很容易被误判成软件卡死。这也解释了车辆段出场的那条路径从车辆段或停车场出发要等场段信号开放以 RM 模式运行到转换轨再进入正线区域。转换轨是两套管辖范围的分界本质上是一次管辖权的移交移交完成才有资格谈 CM 和 AM。2.3 移动授权能走多远是算出来的移动授权MA终点取三个候选里最小的那个前方进路已经开放到的终点红区边界、前车尾部减去安全余量、以及按当前速度与线路坡度算出的、能安全停下的那一点。ZC 每个周期重算一次把“MA 终点里程 坡度 限速”一起下发。取最小值这一步是整个 CBTC 安全逻辑的核心——任何一条约束算出来更保守就按更保守的走不给“这次应该没事”留余地。CM 模式同样依赖 MA。很多人以为手动驾驶就脱离了系统监督其实 CM 是“人在开但超速就会被 ATP 拉回来”人只能在防护曲线下方活动。真正不需要 MA 的只有 RM而 RM 的代价是限速极低、靠目视保证安全——这就是为什么车辆段出场那段路永远开得慢吞吞。理解了 ZC 给的是“能走到哪”、ATS 给的是“想去哪”第 3 章那张模式迁移表就不需要背了因为它每一条条件的来源都能对回这三本账。3. RM 到 AM 的迁移守卫为什么“点了没反应”其实是条件没凑齐模式按钮点下去没反应是这套实验里最高频的困惑。车载不会告诉你缺什么它只是拒绝。想通这一点得先接受一个前提模式迁移是一次带守卫条件的状态跳转守卫不满足就原地不动而不是“按了但没生效”。3.1 三种模式的本质差别| 模式 | 位置来源 | 是否依赖 MA | 典型限速量级 | 驾驶主体 | | RM | 人工目视 应答器粗定位 | 否 | 25 km/h | 人系统只做记录 | | CM | ZC 采信的位置报告 | 是 | 线路限速受 ATP 曲线压制 | 人系统做超速防护 | | AM | 同上 | 是 | 绿线与黄线的下包络 | ATO人只做监控与开关门 |实验步骤里那种“点亮—选方向—点灭”的两段式操作看起来繁琐实际是请求与确认分离按下是把请求置位车载检查守卫条件条件满足才真正迁移并把灯态锁住点灭表示请求被接受。反过来如果条件是别人给的比如 MA 来自 ZC光点按钮永远不会成功。RM 转 CM 的门槛里最容易忽略的是“速度必须低于 RM 限速”。很多人习惯一边动车一边切模式结果被拒正确做法是先停稳再切。CM 转 AM 的门槛里最容易忽略的是“计划已接收”——没有计划就没有目的地ATO 不知道该跟哪条曲线自然不会接方向盘。3.2 把守卫条件写成代码界面上的灯态背后就是下面这段逻辑写成伪代码之后“顺序不能反”就不再是规定而是推论from dataclasses import dataclass dataclass class OnboardState: atc_key_on: bool False # ATC 钥匙接通会话的前提没有它后面全是空谈 registered: bool False # ZC 注册成功位置报告被采信 position_valid: bool False # 初始位置已确定含方向校验通过 ma_valid: bool False # 移动授权有效来自 ZC 的周期下发 plan_loaded: bool False # ATS 计划已接收站间曲线的前提 doors_closed: bool False # 车门锁闭 speed_kmh: float 0.0 mode: str OFF RM_MAX 25.0 # RM 模式限速实际取值按线路规定 def request_rm(s): if not s.atc_key_on: return None, 拒绝无 ATC 钥匙会话 if not s.position_valid: return None, 拒绝初始位置未确定或方向不合法 return RM, ok def request_cm(s): if s.mode ! RM: return None, 拒绝必须从 RM 起手 if not s.registered: return None, 拒绝ZC 未注册 if not s.ma_valid: return None, 拒绝无有效移动授权 if s.speed_kmh RM_MAX: return None, 拒绝速度高于 RM 限速先停稳 return CM, ok def request_am(s): if s.mode ! CM: return None, 拒绝必须先进入 CM if not s.plan_loaded: return None, 拒绝未收到 ATS 计划无站间曲线 if not s.doors_closed: return None, 拒绝车门未锁闭 if not s.ma_valid: return None, 拒绝进路关闭MA 已失效 return AM, ok三个函数的返回值设计成“模式 原因”是因为排错时人真正需要的是原因。每条拒绝分支都能对回实验里一次具体翻车atc_key_on为假时点初始化车载没有会话上下文报文发出去 ZC 直接丢车载等超时后重连仿真环境里就表现成黑屏重启ma_valid为假时点 AM按钮会亮一下就灭因为进路没开放黄线根本没生成。参数上还有一处细节值得留意RM_MAX不是写死的常数它取决于线路配置和车辆类型写在代码里只是为了让守卫条件可读。真实系统里这类阈值来自配置文件改动要走版本管理——实验环境里随手改一行第二天换台机器复现不出来就会变成悬案。3.3 提前发车与跳停ATO 到底在等什么实验报告里那两个现象——提前到站后 AM 没反应、跳停之后速度几乎立刻顶到 80 km/h——是同一个机制的两面。AM 的发车条件里除了模式守卫还有一条“发车允许”当前时刻不早于计划发车时刻或者有人工触发的提前发车指令。计划里两站之间时间设得太宽列车到站开门之后发车时刻还没到AM 请求就会被静默拒绝。这时候要么等要么点提前发车——本质上就是人工把“发车允许”这个布尔量置真。跳停则是把区间拉长。站间曲线是按“区间长度 站间时间 加减速度”反推出来的速度曲线时间富余时列车根本跑不到限速速度曲线是个矮胖子跳掉中间站之后区间变长同样的时间预算下峰值速度抬升才可能顶到 80 km/h。这段关系可以几行代码算清楚def peak_speed(L_m, T_s, a_acc1.0, a_brk1.0, v_limit22.2): 给定区间长度与站间时间反推速度曲线峰值m/s。22.2 m/s 约合 80 km/h。 v v_limit # 先假设顶着限速跑看时间够不够 t v / a_acc v / a_brk (L_m - v**2 / (2 * a_acc) - v**2 / (2 * a_brk)) / v if t T_s: return v # 时间紧跑到限速为止 # 时间富余二分求满足 T 的巡航速度时间越长解越小 lo, hi 0.1, v_limit for _ in range(60): mid (lo hi) / 2 tm mid / a_acc mid / a_brk (L_m - mid**2 / (2 * a_acc) - mid**2 / (2 * a_brk)) / mid if tm T_s: lo mid else: hi mid return (lo hi) / 2函数里的单调性来自一个朴素事实同样的距离时间给得越多需要的速度越低。所以“站间时间设太长”不是界面上的一个小疏忽它直接决定了速度曲线的形状进而决定了 ATO 有没有可能把车开到限速。反过来如果想让列车跑满 80 km/h要么缩短站间时间要么拉长区间——跳停就是后者。a_acc与a_brk分别对应牵引和制动的加速度量级取值偏大会让曲线变尖峰值速度更高但乘坐舒适性下降这是设计阶段就要权衡的。4. 运行曲线与目标检测绿线、黄线和那条红色线段到底在表达什么界面上那三条线是整套实验里信息密度最高的地方。红色线段是车头位置绿线和黄线是两条不同来源的约束列车实际跑出来的速度永远是两条曲线里更低的那条。看不透这一层实验报告就只能写成操作流水账。4.1 绿线与黄线两个来源一个下包络绿线来自计划。ATS 把车次、目的地、每站到发时刻下发之后车载按区间长度和站间时间反推出一条推荐速度曲线也就是第 3.3 节那个peak_speed的连续版本。它回答的是“按时刻表我应该怎么开”。黄线来自进路。ZC 分配的 MA 终点确定之后车载从终点往回倒推一条常用制动曲线终点速度为零。它回答的是“按当前进路开放情况我最多能开到哪、什么时候必须停”。两条曲线一叠加实际可用的速度就是下包络。进路没开放时黄线压在绿线下面车就慢进路完全开放、时间又紧黄线抬到绿线之上车才按绿线跑。到站开门之后曲线会重新生成原因是两件事同时变了计划推进到了下一个区间进路也重新开放了一次。很多报告里写“曲线变了但不知道为什么”其实就是这两个输入都换了。4.2 用 OpenCV 把三条线读出来实验报告的自动判读、过程回放、批量校验都绕不开从截图里把这几个元素提取出来。颜色阈值加形态学是最稳的起手式确定性好、可解释、不需要标注数据import cv2 import numpy as np # 三类元素的 HSV 区间色相下限/上限 饱和度、明度下限 COLOR_RANGES { green_curve: ((45, 80, 80), (75, 255, 255)), # 站间计划曲线 yellow_curve: ((20, 120, 120), (35, 255, 255)), # 进路 / MA 曲线 train_head: ((0, 140, 140), (8, 255, 255)), # 车头位置红色线段 } def extract_mask(bgr, key): hsv cv2.cvtColor(bgr, cv2.COLOR_BGR2HSV) lo, hi COLOR_RANGES[key] mask cv2.inRange(hsv, np.array(lo), np.array(hi)) kernel np.ones((3, 3), np.uint8) # 细长曲线容易被抗锯齿打断先闭运算接上断点再开运算去掉孤立噪点 mask cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel, iterations2) mask cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel, iterations1) return mask def train_head_px(bgr): mask extract_mask(bgr, train_head) cnts, _ cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not cnts: return None # 车头线段是画面里面积最大的红色连通域按外接矩形面积取最大 c max(cnts, keylambda x: cv2.boundingRect(x)[2] * cv2.boundingRect(x)[3]) x, y, w, h cv2.boundingRect(c) return x w / 2, y h / 2 # 返回线段中心像素坐标几个参数值得展开。HSV 而不是 RGB是因为 HSV 把色相和亮度解耦界面整体调亮或调暗时色相基本不动阈值不用重调但饱和度和明度的下限不能设太高曲线是细线抗锯齿会让边缘像素的饱和度掉下来下限一高曲线就在拐弯处断成几截。闭运算的迭代次数取决于线条粗细和分辨率2 次对 1080p 截图大致够用分辨率翻倍就要跟着调。取最大连通域而不是取全部是假设同一时刻画面里只有一条车头线段——这个假设在正常运行时成立在截图被裁剪过时不成立所以裁剪检测要单独做。4.3 像素到里程标定和“谁在约束”的判定像素坐标本身没有意义得映射到里程。最省事的做法是两点线性标定取两个已知站台中心的像素横坐标与对应里程按比例换算。误差主要来自三处——界面缩放不同机器 DPI 不同同一条曲线在不同屏幕上像素长度不一样、曲线被列车图标遮挡、以及曲线在低速段几乎水平导致定位抖动。第一项靠标定解决后两项只能靠多帧平滑。判定“此刻谁在约束”有个很实用的技巧以车头位置的横坐标为起点向右扫一条竖线分别取这条竖线与绿线、黄线的交点纵坐标。纵坐标对应速度交点更低的那条就是当前的实际约束。这个判定比肉眼看曲线高低可靠得多也方便写进自动判卷脚本——实验报告要求“说明列车受哪条曲线限制”用这个办法能给出可复现的结论而不是凭印象。提示标定用的两个点必须在同一分辨率、同一窗口大小下截取。换了窗口大小还沿用旧标定误差可以达到几十米判读结论会完全反过来。4.4 真实系统里的“目标检测”检测的是里程计把话说清楚一点真实的 CBTC 不靠摄像头定位。列车的位置来自应答器的绝对定位加上测速电机和多普勒雷达的相对定位融合两套数据互相校正得到那个带置信区间的里程。这件事在工程上确实可以叫目标检测——只不过检测的对象是“我在轨道的哪一段”而不是“画面里有什么”。视觉目标检测在轨道场景里当然有用轨旁障碍物、站台门与车门间隙、司机行为监控都是它的地盘但这些功能通常不参与列车的安全制动链路或者走独立的安全通道。把两条链路混为一谈是初学阶段最容易形成的误解也会让实验报告的分析部分写偏。5. 避坑与排查五条踩坑记录从注册失败到阈值漂移5.1 初始化与注册类第一条顺序颠倒导致全台架重启。现象是点完初始化ZC 那边没有任何提示几秒后三台机器同时黑屏重来。原因是初始化请求必须在“ATC 钥匙接通”建立的会话上下文里发出没有会话的初始化报文被 ZC 直接丢弃车载等不到应答就超时重连仿真环境把重连实现成了整机重启。解决办法是把“钥匙—初始化—确认注册成功”当成一个不可拆的动作序列中间不插入任何其他点击。第二条上下行选错。现象是列车能动但一进正线就触发制动或者曲线根本不生成。原因是方向决定了里程的增长方向选反之后车载上报的里程与轨道拓扑相反ZC 判为位置不合法MA 无法延伸。解决办法是加车之前先确认站台在界面的哪一侧——向右为上行这条规则要落到具体界面上看一眼而不是凭记忆。5.2 计划与曲线类第三条没下发计划就操作驾驶台。现象是推牵引有响应速度表却纹丝不动。原因是没有车次号和目的地车载算不出站间曲线ATO 没有可跟踪的目标。解决办法是先看 ATS 那边的计划是否真的下发到本车再确认车载界面右下角的信息区里车次号是否正确。第四条站间时间设太长导致 AM 无响应。现象是到站开关门之后AM 按钮点了没反应重开一遍还是没反应。原因是发车允许时刻未到ATO 在等时间。解决办法是点提前发车把发车允许置真或者回头把运行图上两点之间的横向距离缩短——那就是站间时间的可视化表示。5.3 视觉判读类第五条阈值换台机器就失效。现象是同一条黄色曲线在自己机器上检得干干净净换到隔壁机器就一无所获。原因是不同机器的显示缩放、色彩配置和界面主题会让颜色整体偏移写死的 HSV 区间立刻失效。解决办法是放弃硬编码截图上先取一块已知颜色的色块比如图例里的绿线样本求它的 HSV 均值再按均值上下浮动一个容差生成区间。顺带把opencv-python和numpy的版本在环境里锁住这两个包版本不匹配时import cv2会直接崩报错信息跟图像处理毫无关系能让人查半天。6. 取舍为什么仿真判读用颜色阈值而真实列车不敢用摄像头定位这套实验里其实藏着两种完全不同的“检测”它们的分工不是历史遗留而是安全等级压出来的结果。| 方案 | 精度量级 | 可达到的安全完整性 | 对环境的依赖 | 典型用途 | | 颜色阈值 几何 | 标定后米级 | 无不可用于安全功能 | 强主题、缩放、分辨率都会影响 | 仿真判读、过程回放、自动判卷 | | 应答器 测速融合 | 厘米到米级 | 可做到高等级 | 弱与显示无关 | 真实 CBTC 的列车定位 | | 深度学习目标检测 | 取决于训练集与场景 | 通常非安全相关 | 中受光照与天气影响 | 障碍物、间隙、行为监控 |选颜色阈值做判读理由不是它先进而是它可解释。一个自动判卷脚本跑出“第 137 帧检测到黄线低于绿线”这种结论人能回到那一帧去核对换成黑盒模型出了问题连后悔药都没有。代价是脆——换机器、换分辨率、换主题都可能失效所以阈值必须自适应标定必须每次重做。真实列车不敢用摄像头做定位理由同样朴素视觉的输出无法用数学证明其保守性。安全功能要的是“在最坏情况下也不会更激进”而颜色分割在逆光、雨雾、镜头脏污时给的可能是任意值。应答器加测速融合虽然也有误差但误差边界可以被严格包住这才能进安全链路。想验证这套判读到底靠不靠谱有个笨办法很有效把每一帧的检测结果和车载日志按时间戳对齐逐帧比模式字段、速度字段和 MA 终点。日志是唯一的黑匣子界面只是它的投影投影读错了日志立刻能抓出来。我一般会在跑完整条线路之后抽三段比对——出场那段、进站制动那段、跳停加速那段这三段的速度变化最剧烈错一帧都藏不住。从那以后我每次开局前都强制走一遍“钥匙—初始化—方向—计划”四步核对哪怕上一轮刚成功过判读脚本也一律先跑自适应取色再谈结果不再相信任何写死的阈值。这套实验的完整报告、截图序列和判读脚本都在资源里想复现的直接照着顺序走一遍比看十遍步骤说明管用。希望帮到你。本文还有配套的精品资源点击获取