标签: 驾驶员监测

  • 首次引导启动门:条款版本、培训版本与 DMS 进度三个独立契约

    结论:首次引导的启动资格由三个互相独立的契约拼成:条款版本、培训版本与设备启动条件。大屏与 mici 两套 UI 都只在窗口构造时读取一次布尔快照,训练 UI 完成不等于驾驶员监测或摄像头的实车验证;hardwared 真正看的是持久键里的版本值,而不是 UI 是否关闭。

    版本契约:terms_version 与 training_version

    源码中 terms_version="2" 与 training_version="0.2.0" 分别与 HasAcceptedTerms、CompletedTrainingVersion 比较;两键在参数定义中标记为持久化,条款键另有一段。两套 UI 均在窗口构造时读取一次布尔快照,未完成才由主布局推入导航栈。

    hardwared 把匹配条款和培训版本分别列入 accepted_terms、completed_training 启动条件,同时要求非 DoUninstall。只有 started_ts is None 时,启动条件才合入 should_start;已上路后版本变化不是这段代码的即时退场门。UI 是否关闭也不是启动条件,持久键的版本值才是跨进程依据。开发/回放夹具可直接写入这两键,因此不能把其存在视为用户真实完成培训的证明。

    这里有一个容易被忽略的顺序问题:两套 UI 的 completed 属性都是构造时快照,点击后只写 Params,并不重新赋这两个布尔值。因此「本次会话里 UI 已消失」与「hardwared 下次启动会读到版本匹配」是两件事——跨进程依据只能是持久键里的版本值,而不是界面状态。

    大屏函数链:图片热区与第九步

    函数/组件 行为 边界
    OnboardingWindow.__init__()、completed 构造时读条款/培训版本,决定先显示条款还是培训。 _accepted_terms、_training_done 是构造快照;点击后写 Params,但本实例不重新赋这两个布尔值。
    _on_terms_accepted()、_on_completed_training() 同步写对应版本值;如果培训早已完成,接受条款后直接退场。 这是软件版本确认,不验证摄像头、CAN 或 DMS 当前可用。
    TrainingGuide._load_image_paths()、_preload_thread() 从 assets/training/stepN.png 数字排序,首图立即加载,其余在线程解码并在 UI 线程上传纹理。 点击热区来自固定 STEP_RECTS;图片数/顺序变化必须同步热区与录音选择步号。
    TrainingGuide._handle_mouse_release() 命中本步热区才前进;第 9 步按额外矩形选择 RecordFront,末页可重看或完成并 pop_widget()。 这些是图片坐标热区,不是语义化控件;不同分辨率/素材改版需实屏验收。
    DeclinePage._on_uninstall_clicked() 写 DoUninstall=True 并请求关闭 UI。 真正卸载由 manager 观察参数、清理进程后调用硬件层卸载。

    mici 函数链:DMS 进度与缓存状态风险

    函数/组件 行为 边界
    OnboardingWindow.__init__()、show_event() 读取版本快照,创建条款/培训页面;显示时延长交互超时并提高驻车亮度。 hide_event() 恢复覆盖设置,但子页导航也可触发隐藏回调,生命周期需实屏确认。
    _on_terms_accepted() 写条款版本后无条件推入培训页。 与大屏不同:若旧培训版本已匹配而只需重签条款,mici 仍要求再次走培训 UI。
    TrainingGuidePreDMTutorial.show_event() 设置 IsDriverViewEnabled=True,使驻车时可运行座舱采集/监测链。 此参数是预览进程门,不是模型就绪回执;close() 会写回 false。
    TrainingGuideDMTutorial._update_state() 曾收到 driverMonitoringState 后,用检测到人脸且朝向中心的条件累积约 4 秒进度,达到阈值才启用继续按钮。 只检 recv_frame != 0,没有同时要求两条 DMS 消息本帧更新、存活、有效或年龄上限;一条曾合格的缓存状态在后续 UI 帧仍可能推动进度。
    TrainingGuideRecordFront 接受/拒绝各写 RecordFront,随后进入完成回调。 RecordFront 影响座舱录制配置,不能与 DMS 必需的短时预览混为同一授权。
    _on_completed_training()、close() 写培训版本、关 IsDriverViewEnabled、回调主布局退场。 退场与 Params 写入分步完成;窗口实例的 completed 属性仍是构造时快照。

    小屏的 DMS 教程读取左右驾驶位模型结果,并从 driverMonitoringState 读取策略与脸部状态;预览中的绿色图标还可被 set_force_active(True) 强制呈现,不能拿图标颜色证明教程输入有效。

    DMS 进度的静态推论

    TrainingGuideDMTutorial._update_state() 的进度累积条件值得单独强调:它只检查 recv_frame != 0,没有同时要求两条 DMS 消息本帧更新、存活、有效或年龄上限。因此一条曾合格的缓存状态在后续 UI 帧仍可能推动进度。这属于静态推论,源文档未做断流复现。

    换句话说,「培训完成」这一步记录的是用户在 UI 上走完了编号图片与约 4 秒的进度条,而不是 DMS 在真实车辆环境下的可用性结论。把前者当作后者,是引导流程最常被误读的地方。

    硬件与插件化要求

    第一,把「用户确认的条款版本」「培训完成记录」「摄像头/DMS 当前健康」「行车启动许可」分别存储和审计,不要让 UI 插件直接伪造版本键或持有设备卸载权限。第二,小屏 DMS 进度应绑定新鲜的成对消息、相机帧 ID、有效位和单调时间,丢帧/失联时冻结或回退,并在模型/摄像头/安装支架换版后重新验收。第三,RecordFront 是独立的隐私选择,硬件设计应分别证明短时座舱预览、可选录制和远端上传的权限及启停回执;对大屏图片热区和 mici 导航栈做不同尺寸、快速连点、掉电和中途退出测试。

    这三条要求背后是同一个判断:引导流程能证明的只是「用户在界面上完成了对应步骤」。它不能证明摄像头当前成像正常、DMS 模型在真实座舱光照下可用,也不能证明持久键没有被回放夹具写入。因此条款、培训、设备健康与行车许可必须各自留痕、各自可审计。

    常见问题

    条款和培训版本在哪里决定是否启动?

    hardwared 把匹配条款和培训版本分别列入 accepted_terms、completed_training 启动条件,并要求非 DoUninstall;只有 started_ts is None 时才合入 should_start。UI 是否关闭不是启动条件。

    大屏和 mici 在重签条款时的行为一致吗?

    不一致。mici 的 _on_terms_accepted() 写条款版本后无条件推入培训页;即使旧培训版本已经匹配而只需重签条款,mici 仍要求再次走培训 UI。大屏则在培训早已完成时于接受条款后直接退场。

    小屏 DMS 教程的进度条一定能代表监测可用吗?

    不能。TrainingGuideDMTutorial._update_state() 只检 recv_frame != 0,没有同时要求两条 DMS 消息本帧更新、存活、有效或年龄上限;一条曾合格的缓存状态在后续 UI 帧仍可能推动进度,源文档也未做断流复现。

    RecordFront 与 DMS 预览是同一个授权吗?

    不是。RecordFront 影响座舱录制配置,是独立的隐私选择,不能与 DMS 必需的短时预览混为同一授权;两者应有各自的权限与启停回执。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/ONBOARDING_TERMS_TRAINING_DMS_STARTUP_GATE_DEEP_DIVE.md(取用了版本契约、启动门、大屏与 mici 函数链及 DMS 进度边界)。
    • 项目 02 —— openpilot-research/DRIVER_MONITORING_POLICY_DEEP_DIVE.md(仅用于确认 DMS 状态与策略的消费边界)。

    说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

    更新记录

    首次发布 2026-10-11 最近核对:2026-10-11

  • 驾驶员监测与在线估计:多进程反馈环与有效位分层

    结论:驾驶员监测、相机标定、设备姿态与在线参数学习构成一个带反馈的多进程图,而不是单向初始化序列:modeld 的相机里程计帮助标定与姿态估计,而标定与在线延迟估计又反过来改变模型输入变换与动作时间。两个必须分清的有效位层次是:dmonitoringd 在订阅输入无效时不更新策略,却仍发布有效位为假的监测状态;deviceMotion 顶层有效位取决于滤波器初始化与滤波时间,inputsOK、sensorsOK、posenetOK 是额外健康指标而非同一件事。本文为固定源码快照 c8fb906 的静态调用链分析,模型文件仍是 LFS 指针,未运行设备传感器、驾驶员模型或实车参数学习。

    带反馈的多进程图

    座舱视频流进入座舱模型进程生成 driverStateV2,监测策略进程以它为轮询主题生成 driverMonitoringState,供行车状态进程与控制进程消费。另一条链上,道路模型进程产出相机里程计,分别驱动标定进程与定位进程,IMU 也送入定位;标定结果又回送给模型与定位进程。定位发布的 deviceMotion 同时驱动在线参数、扭矩与延迟三个估计进程,它们的结果再回流到控制进程,延迟估计还回流到模型进程。这种闭环意味着插件化时必须防止循环依赖中的版本不一致、过期消息,以及把历史估计当成实时观测。

    座舱模型与监测策略

    组件 关键入口 读入与输出
    座舱模型 ModelState.run() 从座舱视频缓冲建立输入,做鱼眼 warp 与模型推理
    预测解码 parse_model_output() 解码左右驾驶位人脸姿态、眼睛与眨眼、墨镜、手机、睡眠和方向盘位置概率
    消息填充 get_driverstate_packet() 发布 driverStateV2 的帧 ID、模型耗时和左右驾驶位特征
    策略循环 dmonitoringd_thread() 以 driverStateV2 为轮询主题;输入检查通过才调用策略
    注意力策略 DriverMonitoring.run_step() 合并人脸预测、车速、档位、方向盘与油门交互、辅助驾驶使能及标定
    告警与锁定 _update_events() 注意力递减与恢复;达阈值后升级一至三级告警,累计无响应与锁定

    dmonitoringd_thread() 在订阅输入无效时不更新策略,但仍发布有效位为假的监测状态。controlsd 消费无响应强制减速字段形成控制状态里的强制减速标记,行车状态进程还根据监测事件处理使能。调试或驾驶员视角模式在源码中有单独的示例分支,不能用它的默认速度和使能值代表实际行车行为。

    相机标定与设备姿态

    标定器只采用满足车速、直行、角速度和不确定度条件的相机里程计,更新安装俯仰与偏航、相机间角度和高度的分块统计;状态区分未标定、重标定、已标定与无效,安装姿态分散度过大时平滑重标定,发布的状态里含进度、安装角、分散度和高度,非汽车模式有专用默认值。姿态滤波器按时间顺序处理加速度计、陀螺仪、车速、标定和相机里程计,对时间戳、来源、幅值与方差做检查后更新滤波器,发布 deviceMotion 的姿态、速度、角速度、加速度以及三项健康指标。定位循环以相机里程计驱动发布,把传感器与消息输入按单调时间排序后送入滤波器,并追踪坏输入计数与恢复衰减。

    这里的关键边界是:deviceMotion 顶层有效位取决于滤波器初始化和滤波时间,inputsOK、sensorsOK、posenetOK 是额外健康指标,并不自动等同于顶层有效位。替换传感器时要保留坐标系、时间源、方差和数据来源检查;只给姿态角数值而没有这些元数据,不足以替代现有传感器输入。

    车辆在线参数、扭矩与延迟

    进程 关键入口 输出与消费关系
    paramsd VehicleParamsLearner.handle_log()、get_msg() 估计转向比、轮胎刚度、侧倾及方向盘零偏,发布车辆参数供控制进程构建车辆模型与转向补偿
    torqued TorqueEstimator.handle_log()、get_msg() 估计横向加速度因子、偏置和摩擦并发布;扭矩控制器在消息检查通过且参数启用时采用
    lagd LateralLagEstimator.handle_log()、update_estimate() 比较期望与实际横向响应估计执行器延迟,发布给模型与控制进程

    三个进程都以 deviceMotion 为主要节拍读取消息:paramsd 在新 deviceMotion 上发布车辆参数,torqued 与 lagd 约每五帧发布一次(源码注释称 4 Hz)。paramsd 对非有限滤波状态重置并把细分估计有效性写进消息;lagd 当块数不足或方差过大时把状态标成未估计或无效,延迟回退初始值;torqued 对不足或无效样本不会宣称新估计有效,并对异常估计做重置。三者都周期性写参数供重启后恢复,但持久缓存不是实时消息的替代品。

    对硬件与插件化的约束

    第一,座舱摄像头插件需要交付可追溯的帧 ID、时间戳、图像几何和标定输入,驾驶员监测策略及状态机必须继续能判断数据是否新鲜有效。第二,IMU 与相机插件要声明轴向、坐标系、采样频率、时间基准和噪声模型,姿态滤波器当前有明确的来源、时间和物理量合理性检查。第三,在线参数学习插件不能直接把无效估计写成车辆执行命令,应保留有效位与细分状态、回退值和参数版本。第四,回放验证至少覆盖丢帧、时间戳倒序、相机标定漂移、传感器失效、驾驶员无响应和在线估计不收敛;实车控制最终仍经过车型接口与 panda 安全钩子。

    常见问题

    deviceMotion 的顶层有效位等于所有健康指标吗?

    不等于。顶层有效位在源码中取决于滤波器初始化和滤波时间,而 inputsOK、sensorsOK、posenetOK 是额外健康指标,两者是分层关系。因此拿到姿态角数值时,还要同时看来源、时间源与这三项指标,才能判断输入是否可用。

    驾驶员监测输入无效时会怎样?

    dmonitoringd_thread() 在订阅输入无效时不更新策略,但仍发布有效位为假的监测状态。也就是说监测状态存在不代表策略刚被刷新;下游消费无响应强制减速字段时,必须结合有效位与状态机判断,而不是只看字段是否存在。

    paramsd、torqued、lagd 的发布节拍一样吗?

    不完全一样。三者都以 deviceMotion 为主节拍,paramsd 在新 deviceMotion 上发布车辆参数,而 torqued 与 lagd 约每五帧发布一次(源码注释称 4 Hz)。因此不同估计值的时间新鲜度需要分别判断。

    为什么把在线估计当实时观测会出问题?

    因为这些进程是带反馈的闭环:标定与在线延迟估计会反过来改变模型输入变换和动作时间,消息可能来自不同采样时刻甚至历史估计。插件化时必须防止循环依赖里的版本不一致与过期消息,并保留有效位、回退值与参数版本,不能把持久缓存当实时观测。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/MONITORING_LOCALIZATION_DEEP_DIVE.md(取用了多进程反馈图、座舱模型与监测策略、标定与姿态、在线参数三进程与插件约束)。
    • 项目 02 —— openpilot-research/CALIBRATIOND_STATE_PERSISTENCE_DEEP_DIVE.md(仅用于确认标定状态与持久化的分工位置)。

    说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

    更新记录

    首次发布 2026-10-11 最近核对:2026-10-11

  • 座舱预览的双屏入口、面框绘制与声音主题边界

    结论:座舱摄像头预览是一条展示 / 测试路径,源码里没有任何一处把「预览能画出面框、能发声」当成驾驶员监测(DMS)已就绪的证明。两个入口(大屏设备页与 mici 设备页)都只在 ui_state.is_offroad 为真时允许点击;预览把 driverStateV2 与 driverMonitoringState 拼在一起绘制,但这两条消息之间没有同帧对齐或新鲜度门;mici 预览发布的 selfdriveState 用的是 new_message() 默认的 valid=False,而声音服务只按「是否 updated」读取 alertSound,不检查有效位。固定快照基线为 c8fb906。

    双屏入口:点击门与构造时写入的参数

    预览按钮存在两个入口:大屏设备页(device.py 的 L60 附近)与 mici 设备页(device_layout.py 的 L183 附近)。两者都用 ui_state.is_offroad 在行车时禁用预览按钮——这是一道点击入口门,不是对话框运行期间的持续安全检查。

    更值得注意的是参数写入的时机。大屏 CabinCameraDialog.__init__() 在构造时就同步写 IsDriverViewEnabled=True,并追加一个交互超时 pop_widget 回调;而不是等到 show_event()。反复构造会累积回调。mici 的 show_event() 才写 demo 参数、把交互超时延长到 300 秒、清 DriverTooDistracted 并建立 selfdriveState 发布端;hide_event() 关 demo 参数、恢复超时。

    IsDriverViewEnabled 由 dmonitoringd_thread() 每 40 个 driverStateV2.frameId 读取一次。demo 下只要有有效驾驶员模型消息,就运行 DriverMonitoring.run_step(..., demo=True),使用固定的模拟速度、使能和标定输入。因此 UI 写参数不是立刻切换策略,预览中的警示等级也不是实车速度 / 档位条件下的策略结果。

    入口 关键行为 契约边界
    大屏设备页 / mici 设备页 以 is_offroad 禁用行车预览按钮 点击入口门,非运行期持续检查
    大屏 CabinCameraDialog.__init__() 构造时即写 IsDriverViewEnabled=True,追加超时回调 反复构造累积回调
    mici show_event() / hide_event() 写 / 清 demo 参数,超时 300 秒,建发布端 首次 show_event() 在发布端创建前发 _publish_alert_sound(None),不发送
    mici 培训 CabinCameraSetupDialog 复用基类,_render() 不调用 _render_dm_alerts() 培训绘制路径与设备页声音路径不可混同

    面框绘制:两条消息没有对齐门

    大屏 _render() / _draw_face_detection() 在无帧时显示启动文案,有帧时按 driverStateV2 的左右驾驶位概率、faceProb>0.7 与姿态标准差绘制面框。绘制侧没有检查这条模型消息的年龄或 valid,面框坐标是近似公式。

    mici 侧更复杂:driverMonitoringState 决定面部检测门,而面框与眼镜概率使用 driverStateV2 字段。两个消息没有同帧对齐,也没有新鲜度门。无帧时 mici 仍会发布一次无声消息。这意味着「画出了一个面框」既不能证明模型消息是新鲜的,也不能证明它与当前画面属于同一帧。对自研相机 / 计算板的接口而言,画面帧、driverStateV2 与 driverMonitoringState 应分别显示采样年龄与有效性;模型面框必须与具体相机标定 / 帧时间关联,不能只按静态坐标公式判定安装合格。

    声音总线:同主题发布与非阻塞有效位

    mici 设备页在画面可见时每次绘制都执行 _render_dm_alerts(),从而发布 selfdriveState。_publish_alert_sound() 把 alertLevel 映射为预警 / 提示 / 紧急声音,用 messaging.new_message('selfdriveState') 发布一条只填了 alertSound 的消息。

    问题在于有效位。new_message() 默认 valid=False,此处没有覆写;其他 selfdriveState 字段仍是默认值。而 Soundd.get_audible_alert() 只检查 sm.updated['selfdriveState'],读取 alertSound 而不检查 valid。因此预览可以驱动声音;声卡是否实际播放仍取决于进程运行条件与设备。

    相同主题也被控制、规划、UI 等模块读取,不能假定它只是私有的测试音轨。驻车入口虽然降低了同时行车的概率,但源码未提供预览发布者的独立主题,也没有持续的驻车发布门。建议把预览音效改为独立测试音命令 / 主题,或由声音服务显式接收带来源和驻车门的请求,正式 selfdriveState 保持单一权威发布者。

    风险点 源码事实 影响
    超时回调只增不减 add_interactive_timeout_callback() 只 append,注册的是无参数 gui_app.pop_widget() 多次打开预览后,下次超时可连续弹出多个当前导航页
    offroad 回调未注销 两套 CameraView 构造时注册绑实例回调;close() 释放纹理 / 客户端但不注销 已关闭实例仍可能被列表引用
    泄漏测试被跳过 test_widget_leaks.py 被 skip("segfaults") 不能视为通过的生命周期证据
    mici hide_event() 只关子视图 清参数与超时覆盖,仅 close() 关闭子视图 再显示时底层状态需按各路径分别验证

    生命周期:只追加的回调与未注销的订阅

    大屏和 mici 对话框在每次构造时都调用 Device.add_interactive_timeout_callback(),该函数只 append,而 Device._update_wakefulness() 在超时边沿遍历全部历史回调。它们注册的是无参数的 gui_app.pop_widget(),不是「仅关闭此预览实例」。先后打开多个预览后,下一次超时可连续弹出多个当前导航页——这在源码层面可推导,具体导航栈 / 事件调度需 GUI 故障注入。

    两套底层 CameraView 均在构造时向 UIState 注册绑定实例的 offroad 回调;close() 释放纹理 / 客户端,但未注销该回调。UIState.add_offroad_transition_callback() 也只追加,因此已关闭实例仍可能被列表引用,长期反复打开可能保留对象并在状态切换时调用已关闭视图。

    把预览的「请求驾驶员模型 demo」定义为有所有者、可撤销、带超时的租约,是避免裸布尔 Params 在多个 UI 页面之间互相覆盖的方向;回调订阅、VisionIPC 客户端、纹理与超时覆盖都需要可重入的 show / hide / close 生命周期测试,至少覆盖连续打开、页面切换、超时、摄像头断流、离线转行车与 GUI 重启。

    常见问题

    座舱预览能画出面框,是否说明驾驶员监测已就绪?

    不能。预览把 driverStateV2 与 driverMonitoringState 拼在一起绘制,但两者没有同帧对齐或新鲜度门,绘制侧也不检查模型消息的年龄或 valid;demo 模式还使用固定的模拟速度、使能和标定。预览是展示 / 测试路径,不构成 DMS 安全就绪的证明。

    预览为什么会影响声音?

    mici 预览用 new_message('selfdriveState') 只填 alertSound 发布消息,默认 valid=False;而 Soundd.get_audible_alert() 只检查是否 updated,读取 alertSound 而不看 valid。这就让预览能驱动同一条声音主题,该主题同时被控制、规划、UI 读取。

    反复打开预览会有什么副作用?

    两处回调都只追加不注销:交互超时回调追加无参数 pop_widget(),offroad 回调在 close() 后仍保留引用。源码层面可推导下次超时可能连续弹出多个导航页、已关闭视图仍可能被调用;具体调度与资源增长需故障注入验证。

    预览按钮的驻车限制够不够?

    不够。两个入口只用 is_offroad 在点击时禁用按钮,这是入口门而不是对话框运行期间的持续检查;源码未提供预览发布者的独立主题或持续的驻车发布门。降低同时行车概率不等于运行期安全约束。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/CABIN_PREVIEW_DM_AUDIO_LIFECYCLE_DEEP_DIVE.md(取用了双屏入口、DM 面框绘制、声音主题发布与回调生命周期分析)。

    说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

    更新记录

    首次发布 2026-10-11 最近核对:2026-10-11

  • 视觉 5/8/13 秒 vs 触碰 5/15/25 秒:驾驶员监测双策略与锁定门

    结论:驾驶员监测不是一只倒计时器,而是两套策略 + 两套阈值 + 一套锁定规则的组合。视觉策略的一、二、三级阈值是 5 / 8 / 13 秒,触碰策略是 5 / 15 / 25 秒;三级持续 5 秒记为「持续无响应」,累计两次进入三级或一次持续无响应即触发锁定,锁定时长按第几次锁定取 1 / 5 / 15 / 30 分钟。这些是实现值,不是法规合规结论。

    两条策略,两套阈值

    系统在视觉策略与方向盘触碰策略之间切换:视觉策略看眼睛与头部姿态的分心,触碰策略用于「没有检测到人脸」或「模型不确定度持续偏高」时的回退。两条策略各自保存一份注意力分数(awareness)与衰减步长,不是共享一个百分比。

    切换有明确的约束方向:当视觉策略已进入二级告警以下时,系统会阻止切到更宽松的触碰策略;一旦进入三级,也不允许借切换清除红色告警。从触碰策略回视觉、或反向,会分别保存/恢复各自的记忆值。因此插件化后必须同时保留两份记忆、当前活动策略以及阈值与步长,只序列化一个分数是不够的。

    策略 一级 二级 三级 适用条件
    视觉(vision) 5 秒 8 秒 13 秒 眼睛 / 姿态分心可判定
    触碰(wheeltouch) 5 秒 15 秒 25 秒 无脸,或连续高模型方差回退

    分心判定:姿态、眨眼、手机与睡眠

    面部姿态的计算有一组固定参数:以 1928×1208 的参考座舱图像与计算焦距 598 为基准,用脸在图像中的位置修正模型输出的俯仰与偏航,再减去设备安装外参;横向向右、纵向向下的位置各增加对应的焦角,模型输出的偏航项取负。注意相机配置里原始座舱焦距是 567,策略这里另用 598,两者不能混作同一个已验证的物理焦距。

    分心类别由俯仰/偏航、眨眼、手机与睡眠概率共同标记,姿态的松紧度还会随车速与制动脱离概率插值:先按车速算一个系数(随车速平方衰减、下限 0.2),再把制动脱离概率除以该系数并截到 0 到 0.5 之间——这意味着驾驶模型输出的概率同样参与监测灵敏度,而不只是显示数据。

    判定项 门槛 / 数值 说明
    低速门 vEgo < 2.8 m/s 临近一级时可停止或恢复计时,不等同于解除告警
    驾驶位(RHD)学习 >11 m/s,任一侧有人脸,≥15 秒等效样本 由右侧方向盘概率判定,未完成前用默认值
    人脸可信 选中侧 faceProb > 0.7、姿态标准差 < 0.3 不满足时该帧分心类别不并入
    自然姿态校准 >13 m/s、低方差、未使能或未分心,≥60 秒等效样本 学习驾驶员的自然坐姿偏置
    眨眼判定 受双眼可见性与墨镜概率门控 遮挡或墨镜会改变可用信号

    告警与锁定:什么时候会锁

    告警状态在一级、二级、三级之间推进,注意力分数按动态倍率恢复。锁定的触发条件与告警级别相关:进入三级会累计一次「三级次数」,三级持续到无响应门会增加「无响应次数」,而真正的锁定检查发生在下一次事件更新时——也就是说,进入三级本身并不等于立即锁定。锁定时长按第几次锁定取 1 / 5 / 15 / 30 分钟。

    触发条件 阈值 结果
    持续无响应 三级持续 5 秒 累计无响应次数,下一次事件更新检查锁定
    累计三级 两次进入三级 触发锁定
    一次持续无响应 无响应次数达门 触发锁定
    锁定时长 按第几次锁定 1 / 5 / 15 / 30 分钟

    还有一条容易被误读的规则:当「始终监测」开关已使能、且档位合适,但辅助驾驶本身未使能时,监测会继续运行;临近三级时的豁免也只是阻止本帧继续衰减,若当前分数已低于二级阈值,仍可能显示二级告警。低速门同样不是「视为安全解除」的许可。

    输出与下游:顶层有效位与内层标志分离

    监测输出的顶层「消息有效」由进程的输入检查给出,而内层各级告警标志是独立的另一套概念:顶层无效时,消息里仍可能携带三级告警、强制减速标志与锁定标志。下游把监测等级映射为「分心 1/2/3」或「无响应 1/2/3」事件,锁定或始终监测锁定会产生禁止进入事件;强制减速标志会并入控制状态并写入控制输出,但实际纵向动作还受控制状态与车辆接口约束,不能把它直接理解为制动命令。锁定计数与「过度分心」两个持久键都带「管理器启动时清除」与「点火开启时清除」标志,不是跨点火永久锁定。

    插件化与测试边界

    做插件化时,座舱图像采集与模型推理可以先做影子插件,但安全权威状态机不应由普通插件替换或热更新。接口必须保留左右驾驶位、脸/眼/手机/睡眠概率、姿态方差、时间戳与消息有效位;硬件端需验证相机视角、遮挡、夜间与墨镜条件以及安装外参。插件故障应触发保守退出与人工接管,而不是把缺失的模型结果当成「驾驶员注意力正常」。

    常见问题

    手一直扶着方向盘,是不是就不会报警了?

    不是。触碰策略同样有 5 / 15 / 25 秒的阈值,只是判定依据从视觉分心换成方向盘交互;而在无脸或高模型方差不满足条件时才会回退到触碰策略,交互本身并不等于注意力充足。

    戴墨镜还能正常监测吗?

    会受限。眨眼判断由双眼可见性与墨镜概率门控,遮挡或墨镜会改变可用信号;因此硬件端需要单独验证夜间与墨镜条件,不能只依赖默认光照下的表现。

    被锁定后换一次点火就能解除吗?

    锁定计数与「过度分心」两个持久键都带「管理器启动时清除」与「点火开启时清除」标志,因此不是跨点火永久锁定;但锁定期内仍会按 1 / 5 / 15 / 30 分钟的时长约束相应行为。

    顶层消息标为无效,就表示没有在监测吗?

    不是一回事。顶层「消息有效」由进程输入检查给出,而内层告警标志是独立概念;顶层无效时消息里仍可能携带三级告警、强制减速标志与锁定标志,二者不能互相替代。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/DRIVER_MONITORING_POLICY_DEEP_DIVE.md(视觉/触碰策略阈值、分心判定、告警与锁定、下游安全链与插件边界)

    说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、时限与配置均出自源文档,标注为未验证的结论保留其原有边界;相关阈值是某一提交的实现值,不构成法规合规结论。

    更新记录

    首次发布 2026-10-11 最近核对:2026-10-11

  • 座舱 DMS 推理链:1440×960 warp 与 6 维不确定度解码

    结论:座舱 DMS 的链路是 camerad 座舱帧经固定 1440×960 luma warp 进入 tinygrad 编译模型,输出一维向量再按元数据切片解码为左右驾驶员的脸姿态、位置与八类概率。发布消息的顶层 valid 恒为真,它只表示组包正常,不是摄像头视角、帧年龄、标定有效性与模型置信度都合格。本文为固定源码快照 c8fb906 的静态分析,未运行模型或图像回放。

    进程、构建与消息链

    manager 进程表在驾驶员视角条件下启动模型进程,非 webcam 的 PC 默认禁用。main() 等待 camerad 的座舱 VisionIPC,连接后按流报告的宽高加载模型和 warp 产物;每次 recv() 返回缓冲才运行并发布 driverStateV2。模型输出服务名义 20 Hz,真正频率由相机帧与推理耗时决定。进程只订阅 extrinsicsCalibration,没有在循环中对该主题做 SubMaster.all_checks() 门。

    构建侧 SConscript 从 dmonitoring_model.onnx 编译 dmonitoring_model_tinygrad.pkl,并为源码列出的两组座舱相机尺寸生成独立 dm_warp_<宽>x<高>_tinygrad.pkl。warp 目标为 DM_INPUT_SIZE=(1440,960),只使用亮度平面、边界填充值 16、变换矩阵放在 NPY 设备;NV12 stride/高度/缓冲大小由 get_nv12_info() 计算。缺少匹配尺寸的 warp 产物时,模型初始化会在打开文件处失败,而不是自动支持任意 USB 摄像头。

    组件/函数 实现功能 边界
    ModelState.__init__() 读取 OOB 模型,解析输入形状/输出切片元数据,预分配模型输出及相机尺寸对应 warp。 load_oob() 依平台选 QCOM/METAL/CPU:LLVM,构建产物与后端必须兼容。
    ModelState.run() 写入外参 calib,用 VisionBuf 内存指针建立 tinygrad 张量,执行 warp 与模型,拷贝一维 float32 输出。 按缓冲指针缓存零拷贝张量;前提是上游环形缓冲布局和生命周期稳定。
    slice_outputs() 按 OOB 元数据中的切片把一维输出分为各预测头。 分割布局不硬编码在 Python 中;模型升级必须同步元数据。
    parse_model_output() sigmoid 转概率,脸部描述向量末六维经 safe_exp 变为标准差。 safe_exp() 裁上界 11 抑制指数溢出;这里不做完整 isfinite 校验。
    fill_driver_data() 将左右侧各自的姿态、脸位置/标准差和八类概率写入消息。 姿态取描述向量前三维,位置取第 4-5 维。
    main() 接流、读取最新外参、逐帧推理、解码并发送。 只在有新外参消息时复制 rpyCalib,未显式检查其顶层 valid。

    图像几何与内参选择

    首次收到帧后,main() 按 buf.width 是否等于 _os_fisheye.width 选择 _os_fisheye 或 _ar_ox_fisheye 内参,并计算目标模型内参到原相机内参的逆变换;其后不随外参变化重算图像 warp。八项原 main() 假 VisionIPC/模型探针确认:首个非空帧按宽度是否等于 _os_fisheye.width 选择内参、不检查高度,生成的 model_transform 此后锁存,后续缓冲宽度变化也不重算。recv() 为空时立即继续,既不轮询外参也不发布。

    外参 rpyCalib 被单独送进模型 calib 输入,不是这个固定的图像 warp 矩阵。外参长度不匹配会在推理前抛错;推理抛错则本帧不发布。SEND_RAW_PRED 判断的是非空字符串,值为 '0' 同样会携带原始输出字节。两组相机的宽高/焦距来自硬编码配置,源码还提示其中一组鱼眼焦距近似值可能不精确。自制相机不能只通过改 VisionIPC 流名完成适配,需重做标定/warp 与质量验证。

    输出切片与不确定度解码

    slice_outputs() 仅按元数据切片并加 batch 轴,切片超出原向量时得到短数组而不报长度错误。parse_model_output() 把每侧描述向量的末六维经 safe_exp 转标准差,其余前段作为姿态/位置;fill_driver_data() 只取前三项姿态和第 4-5 项位置及对应五项标准差。极大标准差 logit 在 11 截顶,NaN 仍传播;sigmoid 借用这个截顶指数,因此极负 logit 的概率下限约为 1/(1+exp(11)),不是精确零。

    解码同时输出 LHD/RHD 两套人脸结果及 wheelOnRightProb。下游 DriverMonitoring._update_states() 按 RHD 学习结果选择一侧,然后根据脸概率/姿态标准差决定是否把分心类型纳入提醒。因此 driverStateV2 是预测,不是已完成的驾驶员状态判定。八项原切片/解析/组包探针确认:合成的九维脸描述可在位置切片为空时仍由 get_driverstate_packet() 组出顶层 valid=True 消息,这只证明缺少形状校验,不代表真实模型会输出九维描述。

    构造输入 原函数观察结果 验证范围
    切片超出原向量 得到短数组,不报长度错误 只证明切片函数不独立校验长度。
    极负 logit 概率 下限约 1/(1+exp(11)),非精确零 sigmoid 借用 11 截顶指数。
    位置切片为空的九维描述 仍组出顶层 valid=True 消息 缺少字段完整性校验。
    改变 location_ts 假消息各字段不受影响 发布结构不含相机 SOF 时刻。

    发布函数直接创建 valid=True 的顶层消息;它表示此函数正常组包,不是摄像头视角、帧年龄、标定有效性、模型置信度均合格的保证。main() 调用时传入 vipc_client.timestamp_sof,但 get_driverstate_packet() 不使用该参数;消费者只有消息总线自己的 logMonoTime、帧 ID 和模型耗时,不能从这个字段推断原始相机 SOF。策略端的脸姿态转换使用固定 1928×1208 参考尺寸和焦距 598,而相机配置中的 cabin 焦距为 567;自制镜头/warp 不能只替换上游像素流而保留这套转换假设。

    验证现状与插件边界

    本地 dmonitoring_model.onnx 只有 Git LFS 指针,目录未有运行所需的座舱 tinygrad .pkl/warp 文件,故不能在当前归档执行真实推理或验证数值精度。process_replay 定义座舱模型回放,使用 cabinCameraState/VisionIPC 帧作为输入并忽略运行耗时字段;这仅证明仓库有回放入口,不代表当前快照已通过。环境变量 SEND_RAW_PRED 非空时把原始浮点输出写进 rawPredictions;此数据可能含座舱行为信息,日志/插件应考虑隐私和存储边界。

    插件化时,可先把座舱采集和推理作为只读影子插件,契约至少包含像素格式、宽高/stride/缓冲生命周期、内参与 warp 版本、外参来源、模型/输出切片版本、SOF 时间戳、帧年龄、概率及标准差校验、隐私分级。权威 driverStateV2 只能有一个发布者;插件不能把缺帧或低质量预测宣称为「驾驶员注意」,也不能绕开 dmonitoringd 安全策略与独立执行网关。

    常见问题

    driverStateV2 的 valid=True 能说明什么?

    它只表示 get_driverstate_packet() 这个函数正常组包,不是摄像头视角、帧年龄、标定有效性、模型置信度均合格的保证。探针确认合成的九维脸描述在位置切片为空时仍可组出顶层 valid=True 消息。

    座舱相机换成别的分辨率还能直接用吗?

    不能。warp 目标是固定的 DM_INPUT_SIZE=(1440,960),构建脚本只为源码列出的两组座舱相机尺寸生成 dm_warp_<宽>x<高>_tinygrad.pkl。缺少匹配尺寸的 warp 产物时,模型初始化会在打开文件处失败。

    为什么极负的 logit 概率不是 0?

    因为 safe_exp() 把指数上界裁到 11,而 sigmoid 借用了这个截顶指数,所以极负 logit 的概率下限约为 1/(1+exp(11)),并非精确零。

    帧 ID 能用来还原相机的原始时刻吗?

    不能。main() 虽然传入 vipc_client.timestamp_sof,但 get_driverstate_packet() 不使用该参数,发布结构里只有帧 ID、CPU/模型耗时与总线时间戳,不能据此推断原始相机 SOF。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/DRIVER_MONITORING_MODEL_PIPELINE_DEEP_DIVE.md(座舱图像、模型推理与预测契约、输出解码与几何假设)。
    • 项目 02 —— 同目录探针记录:六项初始化产物、八项主循环帧、七项缓冲缓存、八项切片/解析/组包、七项脸姿态几何隔离探针结论。

    说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

    更新记录

    首次发布 2026-10-11 最近核对:2026-10-11