简介:这是一份用于毕业设计的高分项目资源,围绕Python与CARLA模拟器构建高性能分布式自动驾驶仿真系统,适合计算机、人工智能、自动化等专业学生用于毕设、课设或初期项目立项。资源共18个文件,容量约68KB,核心为13个Python脚本,覆盖仿真同步、传感器配置、手动控制、数据记录等模块,另含2个Markdown说明文档、1个YAML服务配置及数据压缩包,便于快速理解工程结构并二次开发。截至当前已有316人学习,具备一定参考热度。内容包含可运行源码、详细文档与数据资料,已在macOS、Windows、Linux多平台验证通过;读者既可将其作为完整的毕设实现方案,也可按模块拆解学习,或改写成自己的仿真实验框架,对于进阶练习和答辩展示都有直接帮助。
1. 为什么毕业设计做自动驾驶仿真,最好先从 CARLA 分布式版本开始
毕业设计题目挂着“自动驾驶”四个字,经常被导师追问第一句:你的系统在真实环境跑过吗?直接上真车不现实,单机跑 CARLA 又会在多车汇聚时掉到每秒几帧。这套基于 Python 与 CARLA 的高性能分布式自动驾驶仿真系统,把同步、数据采集、客户端控制和日志记录拆成独立模块,并提供synchronization_test.py做链路验证。它解决的不只是“跑一个仿真画面”,而是多客户端共用一条可信时间轴、传感器数据能对齐、性能瓶颈能被日志量化的毕设级问题。适合软件工程、人工智能、自动化、通信工程专业做毕设或课设,也适合想把仿真当数据集生产工具的开发者。
2. 先看清 HPD-Carla 的模块骨架:从文件清单到同步原理
2.1 一开箱先做“文件考古”:五层结构一眼识别
打开 HPD-Carla-main,第一件事不是找 README,而是先把源码文件名按功能分一下组。我在复现这个项目时,发现它的命名比大多数毕设规范:控制类以manual_开头,同步类有synchronization关键字,数据层集中在carladata和carladata_definition.md。下面是项目里实际出现的主要文件映射:
| 功能层 | 文件 | 在系统里的角色 |
|---|---|---|
| 仿真主循环 | carlaSimulation.py | 装配所有模块,启动分布式任务调度 |
| 同步机制 | synchronization.py、synchronization_test.py、synchronizeModule.py | 管理和验证多客户端仿真步 |
| 配置中心 | configureModule.py、carlaservers.yaml、constants.py | 维护服务器地址、同步参数、全局常量 |
| 数据层 | carladata.py、carladata_definition.md | 定义数据帧格式,统一传感器输出 |
| 客户端入口 | manual_control.py、manual_client.py、manual_control_self.py、manual_control_sensor.py | 人工驾驶、传感器采集、自车控制 |
| 日志与诊断 | logModule.py | 记录 step、延迟、超时和队列情况 |
这个分层里最值得关注的不是carlaSimulation.py,而是synchronization.py。很多单机仿真 demo 会把“服务器 tick”和“算法控制”混在一个循环里,看起来跑得动,一旦换成多客户端就必乱。HPD-Carla 把同步抽成独立模块,所有其他模块只要拿到同一个 tick 信号就能协作,这是它在架构上站得住的原因。
manual_control_self.py和manual_control_sensor.py分成两个文件也很有用意:一个负责“车怎么开”,一个负责“数据怎么采”。这样你在毕设里替换控制算法时,不需要动采集链路;反过来想换数据类型,也不用碰车辆控制。
2.2 同步模式为什么是核心:一个仿真步要等所有 agent
CARLA 默认是异步模式,服务器自己按内部时钟推进仿真。单客户端感知不出问题,但分布式场景下,A 车在 t 时刻发出减速指令,B 车到 t+1 才拿到世界状态,时间戳就错位了。项目里的同步层把 CARLA 的 tick 变成一道屏障:每一步必须等所有客户端 agent 汇报完,仿真才推进。
我拆这类项目时,第一件事就是确认同步入口。下面这段是这类同步模块最典型的封装方式:
# 同步模块核心封装(常见实现方式) class SynchronizedSim: def __init__(self, world, fixed_dt=0.05, timeout=30): self.world = world settings = world.get_settings() settings.synchronous_mode = True settings.fixed_delta_seconds = fixed_dt self.world.apply_settings(settings) self.timeout = timeout self._pending = set() def register(self, agent_id): # 每个参与计算的 agent 都要先注册,同步器会等待它完成 self._pending.add(agent_id) def complete(self, agent_id): # agent 完成本步计算后调用,告诉同步器“我这边好了” self._pending.discard(agent_id) def tick(self): # 只有所有 agent 都汇报完之后,才推进一步 if self._pending: raise RuntimeError(f"pending agents: {self._pending}") self.world.tick()核心思路是维护一个_pending集合:每次推进前先等集合清空。fixed_dt=0.05代表仿真里的一步是 50ms,也就是 20FPS 的逻辑帧率。如果设成0.1,物理计算压力小一半,但控制精度会下降;设成0.02,车辆动力学更细腻,CPU 开销明显上升。timeout是客户端网络等待上限,一般写 30 秒即可,不建议为了追求速度设成 5 秒,多客户端场景下容易出现“一票否决”式的误报。
项目里synchronization_test.py就是专门验证这条链路的。它的存在很实用:如果环境版本不对、服务器没启动或网络不通,这个脚本会先报错,而不是让你跑到一半才发现问题。
2.3 配置集中管理:carlaservers.yaml 的参数不要凭感觉填
分布式系统最怕参数散落在代码里。carlaservers.yaml把服务器列表和同步参数集中在一起,configureModule.py负责读取。常见的配置长这样:
# carlaservers.yaml 配置示例 servers: - host: 127.0.0.1 port: 2000 timeout: 30 map: Town01 - host: 192.168.1.101 port: 2000 timeout: 30 map: Town03 sync: enabled: true fixed_delta_seconds: 0.05 max_latency: 0.2参数的含义并不复杂,但有几个容易被忽略:
| 参数 | 取值参考 | 为什么重要 |
|---|---|---|
host | 127.0.0.1或局域网 IP | 决定 client 连哪台 CARLA 服务器 |
port | 默认2000 | 必须和服务器启动时的-carla-rpc-port一致 |
timeout | 30 秒 | 连接和请求的超时阈值,太小会在加载地图时误报 |
map | Town01、Town03等 | 首次连接时是否切换地图,切换地图代价很高 |
fixed_delta_seconds | 0.05 | 同步模式下每步仿真推进的时间 |
max_latency | 0.2 秒 | 等待回包的容忍上限,超过则丢步继续 |
这里我要重点说一下max_latency。多客户端场景里,每台机器性能不同,同一个 tick 的完成时间天然有快有慢。如果这个值设成 0,同步器会无限等待最慢的客户端,整体仿真帧率会被一台机器拖死。设成 0.2,意味着允许客户端晚到 200ms,晚到的数据会被丢弃并记日志。这是用“少量丢步”换“整体帧率稳定”的经典思路,也是分布式仿真调参最容易玄学的点:不是越小越好,而是要和你的网络和计算资源匹配。
constants.py里通常放的是跨平台路径、默认 host、默认端口这类全局变量。我在多系统上复现时发现,路径处理是否收敛到constants.py,直接决定了项目能不能在 Windows 和 Linux 之间无缝切换。硬编码的/分隔符在 Windows 上大概率翻车,这一点后面避坑章还会专门说。
3. 把分布式仿真系统跑起来:环境、配置与主程序联调
3.1 环境准备:版本第一,步骤就三步
资源说明里写了 mac / Windows 10/11 / Linux 都测试过,但你要理解一件事:CARLA 的服务器端一般仍是 Linux 或 Windows 上跑游戏引擎,mac 上通常跑的是 Python 客户端代码。所以跨平台测试成功,指的是这套 Python 客户端能在三种系统上连接和调度远程服务器。
环境准备我一般就三步:先建虚拟环境,再安装carlaPython 客户端库,最后确认版本对齐。
# 1. 创建并激活虚拟环境,避免污染系统 Python python3 -m venv carla_env source carla_env/bin/activate # 2. 如果项目带了 requirements.txt 就按项目走 pip install -r requirements.txt # 3. 否则至少保证 carla Python API 和服务器版本一致 pip install carla第二步里说的requirements.txt如果项目根目录没有,不要慌,项目源码里通常还会手动依赖numpy、shapely、PyYAML这几个常见库。pip install carla安装的是客户端 RPC 通信库,不是游戏引擎本身。这一步最容易踩的坑是版本:CARLA 对大版本和小版本都很敏感,服务器是 0.9.x,客户端 pip 包也必须对应到同一个小版本,否则序列化协议对不上,连接成功但拿不到世界数据。
Python 解释器版本也建议控制在 3.8 到 3.10 之间,太新的 Python 对部分 CARLA 依赖库支持还不够稳。这是我实际遇到过的坑,不是理论问题。
3.2 启动服务器,先跑同步自检
先启动 CARLA 服务器。Linux 上最常见的命令是:
# 启动 CARLA 服务端,RPC 端口固定为 2000 ./CarlaUE4.sh -carla-rpc-port=2000 -quality-level=Low如果是 Windows 环境,对应的可执行文件是CarlaUE4.exe,参数通用:
CarlaUE4.exe -carla-rpc-port=2000 -quality-level=Low-quality-level=Low是省资源的关键。分布式仿真争抢的是 CPU 和内存,画质先降下来,能显著提升 tick 稳定性。跑仿真实验不是看画面,画质优先级永远排在帧率后面。
服务器起来之后,先用项目自带的synchronization_test.py做一次链路自检:
# 同步链路自检:这一步通过,才说明环境真的通了 python synchronization_test.py --host 127.0.0.1 --port 2000这个脚本会连续跑一批仿真步,如果输出里出现 timeout 或 loading 卡住,就直接退出排查,不要再往下启动主程序。为了避免“连上但版本不对”的隐蔽问题,我习惯先跑一段版本对比:
# 版本对齐检查:排掉 80% 连接异常的快速手段 client = carla.Client("127.0.0.1", 2000) client.set_timeout(30) print("server version:", client.get_server_version()) print("client version:", client.get_client_version())逻辑说明:get_server_version()返回的是 CARLA 服务端版本号,get_client_version()返回的是当前 Python API 版本。两者不一致时,后续 API 调用可能报timeout,也可能静默失败。版本对齐是一切调度工作的前置条件,不做这一步就直接跑主程序,很容易把时间浪费在查网络问题上。
3.3 修改 carlaservers.yaml,启动主程序
环境验证通过后,编辑carlaservers.yaml里的host、port和map。单机调试就保持127.0.0.1,需要分布式时改成各服务器所在机器的局域网 IP。启动主程序:
# 主程序入口:优先看 README 是否给出标准命令 python carlaSimulation.py --config carlaservers.yaml如果程序不认--config参数,直接运行python carlaSimulation.py即可,配置读取由configureModule.py完成。
启动过程大概分四个阶段:读取配置文件、连接 CARLA 服务器并加载地图、初始化同步器、注册各个客户端 agent。我在第一次跑的时候最明显的感知是:日志模块会把每个阶段的行时间打出来。如果日志停在哪一步不再前进,问题就锁在哪一步。比如停在地图加载,说明服务器还在加载资产,多等一会儿即可;停在 agent 注册,说明某个manual_脚本没有正常启动。
3.4 用 viewer 与 clientview.py 做可视化验证
主程序启动后,我一般会再开一个可视化客户端。
# 打开独立视图,观察当前仿真世界 python clientview.py如果项目根目录下的viewer是启动脚本,也可以按 README 里的说明运行。clientview.py的价值在于它是“另一个客户端进程”:它不直接参与主程序内部逻辑,而是作为外部观察者连接同一个 CARLA 服务器。只要clientview.py能看到车辆移动和传感器标记,就证明多客户端连接确实成立,而不是只有一个主进程在自说自话。
这个验证对毕设答辩很重要。评审老师看到你从两个独立窗口同时观察同一个仿真场景,比看一张单机截图更有说服力。黑屏时先别怀疑代码,绝大多数是host连错,或者服务器还在加载地图。此时回去看日志里有没有world ready之类的关键字。
3.5 logModule.py:日志不是黑匣子,是性能仪表盘
logModule.py负责把每个仿真步的关键指标落盘。运行主程序时,可以开启另一个终端实时观察:
# 实时观察仿真步推进、延迟和错误 tail -f logs/simulation.log日志里通常包含三类信息:一是 step 序号和时间戳,二是每个 step 的耗时和延迟,三是异常事件比如 timeout、pending agent 超时、传感器队列溢出。我在调参时最依赖的是延迟一行,它直接反映分布式同步是否健康。如果连续几个 step 延迟持续上升,说明某个客户端计算跟不上,而不是随机抖动。
4. 客户端控制脚本的分工:从手动车到传感器采集
4.1 manual 系列不是重复代码:四种客户端定位差异
项目里有四个带manual的脚本,初次接触容易以为是重复代码。实际它们的定位完全不同:
| 脚本 | 主要用途 | 典型场景 |
|---|---|---|
manual_control.py | 最基础的键盘控制 | 快速确认车辆能响应 |
manual_client.py | 轻量客户端连接 | 只读取状态,不做控制 |
manual_control_self.py | 自车控制 | 控制 ego vehicle 完成闭环测试 |
manual_control_sensor.py | 带传感器采集的控制 | 收集图像、点云等数据 |
我的判断是:manual_control_sensor.py是这套源码里最接近“自动驾驶数据链路”的入口。它把车辆控制、传感器回调、数据打包三件事放在同一个脚本里,方便你直接跑通“采集-存储”这条线。
4.2 manual_control_sensor.py:从传感器回调到数据帧
传感器采集的关键不是拿到数据,而是让数据带上正确的仿真时间和对应的车辆状态。常见做法是在回调里把原始数据放进队列,再打上 step、时间戳、车辆坐标:
# manual_control_sensor.py 中的回调逻辑(示意) def sensor_callback(data, sensor_id): frame = { "step": sim_step, # 当前全局仿真步 "sensor_id": sensor_id, # 摄像头/激光雷达标识 "timestamp": data.timestamp, # 传感器内部时间戳 "raw": data.raw_data, # 原始像素或点云数据 "transform": to_transform(actor) # 车辆位姿 } queue.put(frame)参数说明:sim_step由同步器在每次world.tick()前更新,data.timestamp来自 CARLA 的传感器消息,transform是自车的世界坐标和朝向。这三者缺一个,后续做感知数据对齐就会对不上。
毕设里如果做感知方向,这套采集链路可以直接复制来生成数据集;如果做控制方向,则不用关心raw,重点研究manual_control_self.py。
4.3 manual_control_self.py:把“自车控制”接进同步主循环
manual_control_self.py的关键不是“手动”,而是它提供了一个可以被算法替换的 agent 外壳。改造思路很清晰:把键盘读取换成算法输出,把vehicle.apply_control()保留不变。
# 把手动控制脚本改造成可注册的 agent(常见做法) class SelfDrivingAgent: def __init__(self, vehicle, sync): self.vehicle = vehicle self.sync = sync self.sync.register("ego_1") def update(self): # 你的控制算法在这里计算油门、转向、刹车 control = carla.VehicleControl(throttle=0.5, steer=0.0, brake=0.0) self.vehicle.apply_control(control) self.sync.complete("ego_1") # 通知同步器本步完成逻辑说明:sync.register在初始化时把该 agent 加入等待集合;update()每仿真步被调用一次,算法算完控制指令后调用complete(),同步器才会继续推进下一步。这套模式与第 2 章里的同步器设计是配套的。
参数说明:throttle范围 0 到 1,steer范围 -1 到 1,brake范围 0 到 1,都是 CARLA VehicleControl 的标准限制。如果你的算法输出的是加速度目标,还需要先转成油门和刹车,这是另一个话题。
4.4 carladata.py 与 carladata_definition.md:数据格式可对齐
carladata_definition.md是项目的“数据字典”,carladata.py是它的代码实现。建议做毕设时先看这个文档,再看代码。它约定的核心字段大致是:
| 字段 | 类型 | 说明 |
|---|---|---|
step | int | 全局仿真步 |
timestamp | float | 服务器时间戳 |
vehicle_id | int | actor 编号 |
transform | list | x、y、z、yaw、pitch、roll |
sensor_data | dict | 传感器名到原始数据的映射 |
这个结构设计得好,后续不管接入视觉模型还是控制算法,都只要定义“读什么字段、写什么字段”,不需要动同步和采集模块。这也是为什么我把这个项目定位成“可扩展的仿真框架”而不是一次性 demo。
5. 分布式自动驾驶仿真高频踩坑与排查手册
5.1 现象 → 原因 → 解决:四条必踩的坑
第一条:连接成功,但client.get_world()拿不到世界数据。
现象是客户端不报错,但 world 一直是空,打印对象显示None。原因通常是两个:服务器还在加载地图,或者 Python API 与服务端版本不匹配。解决方法是先做版本对比,再client.set_timeout(60),最后加载地图。版本对比代码在第 3.2 节已经给了,这一步花不了十秒钟。
第二条:同步模式下多客户端越跑越慢。
现象是开始每秒能跑 20 步,几分钟后掉到 2 步,CPU 也没有跑到 100%。原因大概率是某个 agent 的处理时间不稳定,导致同步器一直在等最慢的那台机器。解决方法是把max_latency调大一点,并让同步器记录丢步次数。如果日志显示一直没丢步,就说明等待不是网络问题,而是计算本身出现了局部慢任务,此时优先查传感器队列是否异常堆积。
第三条:传感器数据“慢半拍”,图像和点云对不上。
现象是摄像头标的物位置和激光雷达点云差了一个车身长度。原因是传感器回调队列里积压了大量旧帧,程序读取的是队头最老的数据。解决方法是每个仿真步开始前清空队列:
# 每个仿真步开始前清理旧数据,避免读到上一帧 camera_queue.queue.clear() lidar_queue.queue.clear()这一步很基础,但我在多个项目里都见过因为漏掉它导致数据全部作废的情况。分布式的数据对齐不是靠运气,是靠显式清理和打时间戳。
第四条:Windows 下运行报目录不存在或数据保存失败。
现象是同一个项目在 Linux 没问题,换到 Windows 就频繁找日志目录失败。原因是代码里出现了硬编码的/路径分隔符。解决方法是统一走constants.py里的os.path.join,并且在创建目录时加exist_ok=True:
import os save_dir = os.path.join(constants.DATA_DIR, "raw") os.makedirs(save_dir, exist_ok=True)第五条:端口冲突导致第二个服务器起不来。
现象是启动第二个 CARLA 服务器时直接报 bind error。原因是多实例都用了默认 2000 端口。解决方法是让第二台服务器启动时指定新端口,例如-carla-rpc-port=2002,并且同步修改carlaservers.yaml里对应的port。
5.2 快速排查:三件套直接定位问题
遇到问题不要乱改代码,先按三件套走一遍:
# 1. 看实时日志,确认是等待还是超时 tail -f logs/simulation.log # 2. 检查连接版本,排除 RPC 不兼容 python -c "import carla; c=carla.Client('127.0.0.1', 2000); c.set_timeout(30); print(c.get_server_version()); print(c.get_client_version())" # 3. 跑同步自检,确认链路是否健康 python synchronization_test.py --host 127.0.0.1 --port 2000如果自检通过但主程序失败,说明环境没问题,问题在配置或模块调用顺序;如果自检失败,说明问题在网络或版本层,先把环境修好再继续。
6. 把同步测试变成回归基线:两分钟定位性能退化
6.1 每次改动配置,先跑一遍 synchronization_test.py
这个脚本不只用于首次验证,更应该当回归基线用。每次调整fixed_delta_seconds、max_latency或服务器地址,都先跑一遍自检,确认链路健康再进主程序。我见过有人在答辩前调参,把同步模式意外关掉了,整个系统的多车协同全部失效,但因为自检脚本一直没过,最后才发现问题不是算法,是开关关了。从那以后,我每次改配置都强制要求自己走一遍同步测试。
6.2 用日志模块算平均延迟,超过阈值就回滚
logModule.py落盘的日志可以直接用来量化性能退化。写一个小脚本读取延迟指标:
import re log_text = open("logs/simulation.log", encoding="utf-8").read() latencies = [float(x) for x in re.findall(r"latency=([0-9.]+)ms", log_text)] if latencies: avg_latency = sum(latencies) / len(latencies) max_latency = max(latencies) print(f"avg={avg_latency:.2f}ms max={max_latency:.2f}ms")如果平均延迟比上一批配置高了 20% 以上,我会先回滚配置,而不是继续优化算法。因为分布式仿真里帧率下降往往由同步等待引起,先排除系统瓶颈,再谈算法改进。这也是我踩过一次大坑得来的习惯:某次为了提升画质把-quality-level改成 Epic,结果同步延迟翻了三倍,我还以为是控制算法的问题,折腾了一整天才发现是渲染负载把服务器拖垮了。从那以后我每次改配置都会强制走一遍同步测试、算一次延迟均值,两分钟就能判断改动有没有伤到核心链路。这套复现流程,希望帮到你。
本文还有配套的精品资源,点击获取