
上一篇文章中,我们分享了基于状态机的时序匹配规则,通过标准化的特征工程与多阶段时序匹配,实现了低代码、精准、灵活且强大的复杂事件提取能力,成功解决了从海量帧级感知数据中,高效挖掘变道、cut-in、刹车等基础驾驶事件的核心问题。
但在实际工程落地过程中我们发现,若所有复杂场景都依赖这种帧级时序匹配方式提取,不可避免会出现逻辑重复、规则冗余的问题——比如挖掘“变道后cut-in”“cut-in导致刹车”等复合场景时,需要重复编写时序匹配逻辑;不同场景的事件关联的逻辑无法复用,迭代新场景时需要重新开发,大幅增加了开发与维护成本。
基于此,我们结合工程实践,设计了事件驱动的分层语义图谱方案,将上一篇提到的状态机时序事件提取能力,与语义图谱的灵活关联能力深度融合,形成「帧级计算-事件抽象-图谱关联」的完整自动驾驶场景挖掘体系。该方案既保留了状态机对时序事件的精准提取优势,又通过图谱解决了事件关联、场景复用的核心痛点,实现了从“单一事件提取”到“复杂场景高效挖掘”的升级,同时降低了场景挖掘的上手门槛与维护成本。
本文将从设计动因、行业选型对比、核心思想、建模实践、查询落地、工程优化五个维度,和大家详细拆解这套方案的落地细节,承接上篇的状态机实践,形成可直接复用的自动驾驶复杂场景挖掘方法论。
上一篇我们提到,基于状态机的时序匹配机制,能将模糊的业务需求拆解为有序的状态转移,精准提取出变道、cut-in、刹车、横穿等基础驾驶事件,解决了“从帧级数据到事件级抽象”的核心问题。但在实际场景挖掘中,纯状态机方案和传统存储方案仍存在明显痛点:
状态机的核心能力是时序匹配与事件提取,其优势在于精准、灵活,但在复杂场景挖掘中,存在三个核心短板:
当前自动驾驶场景挖掘的公开实践中,多数方案以湖仓一体架构为核心存储底座:非结构化原始数据(雷达点云、图像、视频)存储在对象存储(OSS/S3),结构化数据/元数据存储在SQL表(ClickHouse/Hive/PostgreSQL等)。SQL表具备成熟、稳定、通用的优势,适合数据管理、清洗、筛选与聚合分析,但在复杂场景挖掘,尤其是多事件关联、时序行为分析中,仍存在明显的适配性不足:
本文提出一种事件驱动分层语义图谱方案,并非抛弃湖仓一体的SQL表架构,而是与对象存储、SQL表、状态机形成互补,各司其职、分层协作:
这种分层协作的方式,既沿用了行业成熟的湖仓一体技术底座,又通过图谱弥补了复杂场景挖掘的短板,是兼顾落地性与实用性的工程选择。
基于以上痛点与行业现状,我们的核心诉求很明确:通过分层解耦,让专业的组件做专业的事,减少逻辑冗余,提升场景挖掘的效率与复用性:
事件驱动的分层语义图谱的核心设计思想是分层降维与事件一等公民,并与传统数仓ODS-DWD-DWS-ADS分层体系、湖仓一体架构、上篇的状态机框架深度融合,形成一套工业级的自动驾驶场景挖掘架构。
整个架构分为四层,各层各司其职、无冗余且高度复用,实现从帧级数据到复杂场景理解的全链路覆盖:
为了保证图谱的简洁性、可维护性和查询易用性,我们在设计时制定了3个核心原则,避免过度抽象和冗余建模:
建模是图谱落地的核心,本部分将从实体设计、边设计两个维度,结合自动驾驶的典型场景(变道、cut-in),给出可直接复用的建模方案,所有模型均支持Neo4j/NebulaGraph等主流图数据库,配套Cypher示例可直接运行。

先cut-in再cut-out场景语义图谱(简化示意)
所有实体仅存储静态/全局信息,动态变化和时序信息均通过边表达,且整个图谱中同一实体仅创建一次,避免冗余。5类核心实体的设计规范和示例如下:
(ego:Ego {id: "ego_001", model: "xxx", length: 4.8}) | |||
(obj:Object {id: "obj_123", type: "car", width: 1.8}) | |||
(lane:Infra {id: "lane_001", type: "lane", attr: "self_lane"}) | |||
(e:Event {id: "ev_456", type: "cut-in", subject: "obj_123", confidence: 0.98, start_frame: 100, end_frame: 120}) | |||
(s:Stage {id: "stage_001", name: "cross_boundary"}) |
关键设计点:事件实体作为核心,需包含全局起止帧和主体,这是连接实体和事件的关键;事件阶段实体做通用化设计,所有同类事件复用同一套阶段,无需为每个事件单独创建阶段。
边是图谱表达动态变化和关联关系的核心,结合工程实际需求,我们按属性特性采用两种轻量化存储方式,既避免边数量爆炸,又保证场景分析的精准性:
List存储「帧号列表+对应属性值列表」,单条边覆盖完整时序,保留逐帧细节;所有边均为有向边,保证关系表达的唯一性,4类核心边的设计规范、示例和使用场景如下:
设计目的:表达自车、他车、交通设施的动态属性变化,通过“按属性特性选存储方式”的策略,平衡“时序细节保留”与“存储/查询效率”。
边类型:HAS_XXX(XXX为属性名,如HAS_SPEED/HAS_POSITION/HAS_COLOR)
核心属性(List存储,适用于变化敏感属性):
frame_list:帧号列表(升序排列),如[100,101,102,...,120];value_list:对应帧号的属性值列表,与frame_list一一对应,如速度列表[85,84,83,...,75];start_frame/end_frame:属性稳定区间的起止帧;value:区间内的属性值/均值;示例1:自车100-105帧速度(List存储,变化敏感)
(ego:Ego {id: "ego_001"})-[:HAS_SPEED { frame_list: [100,101,102,103,104,105], value_list: [85,84.5,84,83.5,83,82.5]}]->()示例2:他车100-120帧车道位置(区间存储,低频稳定)
(obj:Object {id: "obj_123"})-[:IN_LANE { start_frame: 100, end_frame: 105, value: "left_adjacent"}]->(lane:Infra {id: "lane_002"})(obj:Object {id: "obj_123"})-[:IN_LANE { start_frame: 106, end_frame: 120, value: "self_lane"}]->(lane:Infra {id: "lane_001"})(tl:Infra {id: "tl_001", type: "traffic_light"})-[:HAS_COLOR { start_frame: 100, end_frame: 120, value: "red"}]->()设计目的:表达不同实体之间的关联关系(自动驾驶场景最核心的实体关联),这类关系通常随帧变化,因此需携带时序属性,是挖掘“自车-车道、他车-自车、车道-车道”关联场景的关键。
实体-实体边类型举例:
IN_LANE | |||
ADJACENT_TO | |||
FOLLOW | |||
OVERTAKE | |||
NEAR_TO |
核心属性:
IN_LANE/FOLLOW):start_frame/end_frame(关联关系的起止帧)+ value(可选,如车道归属状态);ADJACENT_TO):无时序属性(车道相邻关系为固定物理属性); start_frame/end_frame,避免“静态关联”误表达“动态时序关系”;IN_LANE边)。示例1:他车100-120帧车道归属(动态关联,区间存储)
// 100-105帧:他车在左邻车道(obj:Object {id: "obj_123"})-[:IN_LANE { start_frame: 100, end_frame: 105, value: "left_adjacent"}]->(lane_adj:Infra {id: "lane_002", type: "lane"});// 106-120帧:他车切入自车道(cut-in核心关联)(obj:Object {id: "obj_123"})-[:IN_LANE { start_frame: 106, end_frame: 120, value: "self_lane"}]->(lane_self:Infra {id: "lane_001", type: "lane"});示例2:车道间相邻(静态关联,无时序)
// 自车道与左邻车道相邻(lane_self:Infra {id: "lane_001", type: "lane"})-[:ADJACENT_TO {direction: "left"}]->(lane_adj:Infra {id: "lane_002", type: "lane"});// 自车道与右邻车道相邻(lane_self:Infra {id: "lane_001", type: "lane"})-[:ADJACENT_TO {direction: "right"}]->(lane_adj_right:Infra {id: "lane_003", type: "lane"});示例3:他车100-110帧跟随自车(动态关联,区间存储)
(obj:Object {id: "obj_123"})-[:FOLLOW { start_frame: 100, end_frame: 110, value: "follow_ego"}]->(ego:Ego {id: "ego_001"});设计目的:明确某一事件由哪个实体发起、发生在哪个环境中,实现“事件-实体”的快速溯源。
核心边类型:
INITIATE:实体发起事件(如他车发起cut-in、自车发起变道);HAPPEN_IN:事件发生在某一交通设施中(如cut-in事件发生在自车车道)。(obj:Object {id: "obj_123"})-[:INITIATE]->(e:Event {id: "ev_456", type: "cut-in"})(e:Event {id: "ev_456", type: "cut-in"})-[:HAPPEN_IN]->(lane:Infra {id: "lane_001", attr: "self_lane"})设计目的:对有明确阶段的复杂事件(如变道、cut-in)做阶段拆解,保留事件的关键时序节点,支撑“事件阶段溯源”“关键帧统计”等需求,承接上篇状态机的多阶段状态转移逻辑。
边类型:HAS_STAGE
核心属性:start_frame(阶段开始帧)、end_frame(阶段结束帧)、key_frame(阶段关键帧,可选)
建模原则:阶段与上篇状态机的状态转移一一对应,仅保留核心阶段(一般3-4个),不做帧级拆分。
示例:变道事件包含“横向移动、跨越边界、再次居中”三个阶段
// 先定义通用阶段实体(仅创建一次)(s1:Stage {id: "s1", name: "lateral_movement"}),(s2:Stage {id: "s2", name: "cross_boundary"}),(s3:Stage {id: "s3", name: "recenter"});// 变道事件关联各阶段(e:Event {id: "ev_789", type: "lane_change"})-[:HAS_STAGE {start_frame: 100, end_frame: 110}]->(s1),(e)-[:HAS_STAGE {start_frame: 111, end_frame: 119, key_frame: 115}]->(s2),(e)-[:HAS_STAGE {start_frame: 120, end_frame: 130}]->(s3);设计目的:这是图谱实现复合场景挖掘的核心,表达事件之间的时序、因果、共现关系,替代传统的多事件联表查询,大幅简化复合场景的查询逻辑。核心边类型:
TIME_ADJACENT:时序相邻(A结束后N帧内发生B),属性:interval;CAUSE:因果关系(A导致B);CO_OCCUR:同时发生。示例:变道后5帧内cut-in,cut-in导致刹车(e1:Event {type: "lane_change"})-[:TIME_ADJACENT {interval: 5}]->(e2:Event {type: "cut-in"}),(e2)-[:CAUSE]->(e3:Event {type: "brake"});结合以上实体和边的设计,我们以他车cut-in自车车道这一典型场景为例,给出完整的图谱建模Cypher示例,该示例可直接复制到Neo4j中运行,形成可视化的场景图谱:
// 1. 创建核心实体CREATE (ego:Ego {id: "ego_001", model: "test_model"}), (obj:Object {id: "obj_123", type: "car", width: 1.8}), (lane_adj:Infra {id: "lane_002", type: "lane", attr: "left_adjacent"}), (lane_self:Infra {id: "lane_001", type: "lane", attr: "self_lane"}), (e:Event {id: "ev_456", type: "cut-in", subject: "obj_123", confidence: 0.98, start_frame: 100, end_frame: 120}), (s1:Stage {id: "s1", name: "approaching_boundary"}), (s2:Stage {id: "s2", name: "crossing_boundary"}), (s3:Stage {id: "s3", name: "centered_in_front"});// 2. 创建实体属性边(单实体动态属性)CREATE // 自车速度(List存储,变化敏感) (ego)-[:HAS_SPEED { frame_list: [100,101,102,103,104,105], value_list: [85,84.5,84,83.5,83,82.5] }]->(), // 他车坐标(List存储,变化敏感) (obj)-[:HAS_POSITION { frame_list: [100,101,102,103,104,105], value_list: [[10.1,20.2],[10.3,20.4],[10.5,20.6],[10.7,20.8],[10.9,21.0],[11.1,21.2]] }]->();// 3. 创建实体-实体边(核心cut-in关联)CREATE // 车道相邻关系(静态) (lane_self)-[:ADJACENT_TO {direction: "left"}]->(lane_adj), // 他车车道归属(动态,区间存储) (obj)-[:IN_LANE {start_frame: 100, end_frame: 105, value: "left_adjacent"}]->(lane_adj), (obj)-[:IN_LANE {start_frame: 106, end_frame: 120, value: "self_lane"}]->(lane_self), // 他车cut-in前跟随自车(动态) (obj)-[:FOLLOW {start_frame: 100, end_frame: 105, value: "follow_ego"}]->(ego);// 4. 创建实体-事件边CREATE (obj)-[:INITIATE]->(e), (e)-[:HAPPEN_IN]->(lane_self);// 5. 创建事件-阶段边CREATE (e)-[:HAS_STAGE {start_frame: 100, end_frame: 105, key_frame: 103}]->(s1), (e)-[:HAS_STAGE {start_frame: 106, end_frame: 110, key_frame: 108}]->(s2), (e)-[:HAS_STAGE {start_frame: 111, end_frame: 120, key_frame: 115}]->(s3);// 6. 创建事件-事件边(cut-in导致刹车)CREATE (e_brake:Event {id: "ev_002", type: "brake", subject: "ego_001", start_frame: 110, end_frame: 125}), (e)-[:CAUSE]->(e_brake);针对不同存储方式的时序属性,我们提供贴合工程实际的Cypher查询方法,无需复杂逻辑即可提取指定帧/区间的属性值,新手也能快速上手。
MATCH (ego:Ego {id: "ego_001"})-[r:HAS_SPEED]->()WHERE 102 IN r.frame_list// 找到102帧在frame_list中的索引,提取对应value_list的值WITH ego, r, indexOf(r.frame_list, 102) AS idxRETURN ego.id, r.value_list[idx] AS speed_102frame;MATCH (obj:Object {id: "obj_123"})-[r:IN_LANE]->(lane:Infra)WHERE r.start_frame <= 108 AND r.end_frame >= 108RETURN obj.id, lane.attr AS lane_position, r.value AS lane_status;业务背景:挖掘“他车切入自车车道后又快速切出”的风险场景,需同时满足“同一主体+cut-in结束后10帧内触发cut-out+时序关联”。
// 1. 创建核心实体(自车、他车、事件)CREATE (ego:Ego {id: "ego_001", model: "test_model"}), (obj:Object {id: "obj_123", type: "car", width: 1.8}), (e_cutin:Event {id: "ev_cutin_001", type: "cut-in", subject: "obj_123", start_frame: 100, end_frame: 110}), (e_cutout:Event {id: "ev_cutout_001", type: "cut-out", subject: "obj_123", start_frame: 115, end_frame: 125});// 2. 建立事件-事件时序关联(cut-in结束后5帧触发cut-out,interval=5)CREATE (e_cutin)-[:TIME_ADJACENT {interval: 5}]->(e_cutout);// 3. 建立实体-事件关联CREATE (obj)-[:INITIATE]->(e_cutin), (obj)-[:INITIATE]->(e_cutout), (e_cutin)-[:HAPPEN_IN]->(lane:Infra {id: "lane_001", attr: "self_lane"}), (e_cutout)-[:HAPPEN_IN]->(lane:Infra {id: "lane_003", attr: "right_adjacent"});MATCH // 匹配同一主体的cut-in事件 (obj:Object)-[:INITIATE]->(e1:Event {type: "cut-in"}), // 匹配同一主体的cut-out事件,且与cut-in时序相邻 (obj)-[:INITIATE]->(e2:Event {type: "cut-out"}), (e1)-[r:TIME_ADJACENT]->(e2)WHERE // 限定cut-in结束后10帧内触发cut-out(风险场景阈值) r.interval <= 10 AND // 确保事件主体一致(避免不同车辆的cut-in/cut-out误匹配) e1.subject = e2.subjectRETURN obj.id AS vehicle_id, e1.id AS cutin_event_id, e1.start_frame AS cutin_start, e1.end_frame AS cutin_end, e2.id AS cutout_event_id, e2.start_frame AS cutout_start, r.interval AS interval_between_events;业务背景:分析“他车cut-in导致自车先减速避险,随后恢复加速”的典型场景,需结合「事件关联」+「速度属性时序分析」。
// 为自车添加100-125帧的速度属性(模拟先减速后加速:85→70→80)CREATE (ego:Ego {id: "ego_001"})-[:HAS_SPEED { frame_list: [100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115], value_list: [85,83,80,78,75,72,70,71,73,75,77,78,79,80,81,82] }]->();MATCH // 匹配cut-in事件 (e:Event {type: "cut-in", id: "ev_cutin_001"}), // 匹配自车速度属性(List存储) (ego:Ego {id: "ego_001"})-[r:HAS_SPEED]->()WHERE // 事件帧区间与速度帧区间重叠 e.start_frame <= last(r.frame_list) AND e.end_frame >= head(r.frame_list)WITH e, ego, r, // 1. 提取cut-in事件区间内的所有速度值(100-110帧) [i IN range(0, size(r.frame_list)-1) WHERE r.frame_list[i] BETWEEN e.start_frame AND e.end_frame | {frame: r.frame_list[i], speed: r.value_list[i]}] AS cutin_speed_list, // 2. 筛选减速阶段(速度持续下降,帧号100-106) [x IN cutin_speed_list WHERE x.frame BETWEEN 100 AND 106] AS decelerate_phase, // 3. 筛选加速阶段(速度持续上升,帧号107-110) [x IN cutin_speed_list WHERE x.frame BETWEEN 107 AND 110] AS accelerate_phaseWHERE // 判定“先减速后加速”:减速阶段末速度 < 减速阶段初速度,加速阶段末速度 > 加速阶段初速度 last(decelerate_phase).speed < head(decelerate_phase).speed AND last(accelerate_phase).speed > head(accelerate_phase).speedRETURN e.id AS cutin_event_id, decelerate_phase AS decelerate_detail, // 减速阶段详情(帧号+速度) accelerate_phase AS accelerate_detail, // 加速阶段详情(帧号+速度) // 计算减速/加速的幅度 head(decelerate_phase).speed - last(decelerate_phase).speed AS decelerate_amplitude, last(accelerate_phase).speed - head(accelerate_phase).speed AS accelerate_amplitude;MATCH // 匹配cut-in→cut-out事件链 (e1:Event {type: "cut-in", id: "ev_cutin_001"})-[r:TIME_ADJACENT]->(e2:Event {type: "cut-out", id: "ev_cutout_001"}), // 匹配自车速度属性 (ego:Ego {id: "ego_001"})-[rs:HAS_SPEED]->()WITH e1, e2, rs, // 确定cut-in→cut-out的完整帧区间 e1.start_frame AS total_start, e2.end_frame AS total_end, // 提取完整区间内的速度值 [i IN range(0, size(rs.frame_list)-1) WHERE rs.frame_list[i] BETWEEN e1.start_frame AND e2.end_frame | rs.value_list[i]] AS total_speed_listRETURN total_start AS event_chain_start, total_end AS event_chain_end, avg(unnest(total_speed_list)) AS avg_speed, // 全程平均速度 min(unnest(total_speed_list)) AS min_speed, // 全程最低速度(避险减速极值) max(unnest(total_speed_list)) AS max_speed, // 全程最高速度 // 计算速度波动率(标准差/均值) stdev(unnest(total_speed_list)) / a一套方案能否在工业级场景中落地,除了核心建模和查询,还需要考虑性能、可维护性、鲁棒性等工程问题。结合我们的实践经验,给出5个关键的工程优化策略,避免图谱陷入“建模优美但落地困难”的困境。
从工程落地角度,时序属性的存储方式需结合业务分析需求和数据特性灵活选择:
自动驾驶的感知数据是持续产生的,图谱无需全量重建,而是基于增量数据做增量构建:
整个体系的帧号/时间戳必须严格对齐,这是状态机和图谱融合的关键:
为图谱的核心字段建立索引,大幅提升查询速度,索引仅需建立一次,后续增量构建无需重复创建:
id字段建立唯一索引(如:Ego(id)/:Event(id));type/subject/start_frame字段建立复合索引(如:Event(type, subject, start_frame));frame_list/start_frame字段建立索引,加速帧号筛选。图谱的核心目标是简化查询,而非“追求抽象的完美”,因此需严格避免过度抽象:
承接上篇
介绍了数据挖掘工程中实际使用的一种时序匹配方法,结合使用声明语言和命令式语言,简洁、低门槛、使用灵活
数据挖掘团队,公众号:智驾具身数据挖掘【场景挖掘】自动驾驶复杂场景挖掘(一):时序匹配规则实践
,基于状态机的时序匹配规则,提出了事件驱动的分层语义图谱方案,形成了「湖仓一体存储 + 状态机事件提取 + 图谱语义关联」的完整场景挖掘体系,实现了从“单一事件提取”到“复杂场景高效挖掘”的升级。
结合上篇的状态机和本篇的分层语义图谱,整个自动驾驶复杂场景挖掘方案具备4个核心优势:
尽管当前方案已解决“复杂场景关联难、逻辑冗余”的核心问题,但在规模化工程落地中仍面临两个关键挑战:
基于以上痛点,后续也将在「降低场景挖掘的门槛与认知负荷」展开探索,核心大方向包括:
除此之外,我们也将持续探索自动化事件关联、可视化图谱配置、实时化场景挖掘、多模态融合等方向,不断完善自动驾驶复杂场景挖掘的全链路能力。
自动驾驶的场景挖掘是一个“从数据到信息,从信息到知识,从知识到场景”的过程,状态机和事件驱动的分层语义图谱,正是这个过程中的两个核心抓手。
本篇文章完成了“图谱建模与查询实践”的核心拆解,后续系列将持续分享自动驾驶场景挖掘的工程化实践经验,敬请关注。
本文所有建模和查询示例正在构建开源版本,有兴趣的小伙伴可在评论区留言!