自动驾驶-M1-环境搭建-CARLA 仿真器部署
这是整个课程的数据源头和闭环评测场。 本节不只是"装上能跑",还要理解同步模式——这个概念不懂,后面采的数据全是错的。
一、进入环境
前边已经搭建好了L20 算力显卡的开发环境,这里直接进入:
cd ~carla/work
# 切换环境
conda activate ad
# 启动仿真器
./start_carla.sh
二、测试联通性
先用最小脚本确认客户端能连上服务端:test_connect.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""CARLA 连通性测试 —— 确认服务端可用、看看有哪些资源"""
import carla
import sys
def main(host="localhost", port=2000):
print(f"连接 {host}:{port} ...")
client = carla.Client(host, port)
client.set_timeout(15.0) # 首次连接可能较慢,给足超时
try:
version = client.get_server_version()
except RuntimeError as e:
print(f"✗ 连接失败: {e}")
print(" 排查: 服务端起了吗?端口对吗?防火墙?")
return 1
print(f"✓ 服务端版本: {version}")
print(f"✓ 客户端版本: {client.get_client_version()}")
if version != client.get_client_version():
print(" ⚠ 版本不一致,可能出现诡异问题,建议统一")
world = client.get_world()
m = world.get_map()
print(f"\n当前地图: {m.name}")
# 可用地图列表
maps = client.get_available_maps()
print(f"\n可用地图 ({len(maps)} 个):")
for name in sorted(maps):
print(f" {name.split('/')[-1]}")
# 生成点数量 —— 决定能放多少车
spawn_points = m.get_spawn_points()
print(f"\n生成点数量: {len(spawn_points)}")
# 车辆蓝图
bp_lib = world.get_blueprint_library()
vehicles = bp_lib.filter("vehicle.*")
print(f"车辆蓝图: {len(vehicles)} 种,例如:")
for bp in list(vehicles)[:5]:
print(f" {bp.id}")
# 传感器蓝图 —— 后面要用的
sensors = bp_lib.filter("sensor.*")
print(f"\n传感器蓝图: {len(sensors)} 种,关键的几个:")
for key in ["sensor.camera.rgb", "sensor.lidar.ray_cast",
"sensor.camera.depth", "sensor.other.imu"]:
found = bp_lib.filter(key)
mark = "✓" if len(found) > 0 else "✗"
print(f" {mark} {key}")
# 当前仿真设置
settings = world.get_settings()
print(f"\n当前仿真设置:")
print(f" 同步模式 : {settings.synchronous_mode}")
print(f" 固定时间步长 : {settings.fixed_delta_seconds}")
if not settings.synchronous_mode:
print(" 提示: 当前是异步模式,采数据前记得切同步")
print("\n✓ 连通性测试通过\n")
return 0
if __name__ == "__main__":
h = sys.argv[1] if len(sys.argv) > 1 else "localhost"
p = int(sys.argv[2]) if len(sys.argv) > 2 else 2000
sys.exit(main(h, p))
执行结果:
cd /home/leoking/carla/work/courses/M1
(ad) leoking@leoking-desktop:~/carla/work/courses/M1$ python test_connect.py
连接 localhost:2000 ...
✓ 客户端版本: 0.9.15
当前地图: Carla/Maps/Town10HD_Opt
可用地图 (12 个):
Town01
Town01_Opt
Town02
Town02_Opt
Town03
Town03_Opt
Town04
Town04_Opt
Town05
Town05_Opt
Town10HD
Town10HD_Opt
生成点数量: 155
车辆蓝图: 41 种,例如:
vehicle.audi.a2
vehicle.citroen.c3
vehicle.chevrolet.impala
vehicle.dodge.charger_police_2020
vehicle.micro.microlino
传感器蓝图: 16 种,关键的几个:
✓ sensor.camera.rgb
✓ sensor.lidar.ray_cast
✓ sensor.camera.depth
✓ sensor.other.imu
当前仿真设置:
同步模式 : False
固定时间步长 : None
提示: 当前是异步模式,采数据前记得切同步
✓ 连通性测试通过
(ad) leoking@leoking-desktop:~/carla/work/courses/M1$
完整采集脚本
下面这个脚本是可以直接用于 M2 数据采集的雏形。它包含了所有正确做法: 同步模式、传感器队列、异常安全的资源清理。
▸ 三个关键设计,理解了受用整个课程
① 传感器队列:不用 listen(callback) 直接存盘,而是把数据放进队列, 主循环 tick 后统一取——这样才能保证"这一帧的所有传感器数据"配成一组。
② try/finally 清理:CARLA 的 actor 不会自动销毁。 脚本崩了不清理,仿真世界里会残留一堆幽灵车,越积越多直到卡死。
③ 恢复异步:退出时要把世界设回异步, 否则下次别人连上来会发现仿真"卡住不动"(因为在等谁调 tick)。
collect_demo.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
CARLA 多相机同步采集 —— 最小可运行版本
这是 M2 数据采集的雏形,演示了三个必须掌握的正确做法:
1. 同步模式:客户端驱动仿真步进,保证时间戳对齐
2. 传感器队列:一帧内所有传感器数据配成一组
3. 资源清理:异常安全,不留幽灵 actor
用法:
python collect_demo.py --frames 100 --out ./data/run01
"""
import argparse
import os
import queue
import random
import json
import carla
import numpy as np
# ---------------- 相机布局 ----------------
# 仿照 nuScenes 的 6 路环视布局
# x 向前, y 向右, z 向上;yaw 单位为度,正为顺时针(向右)
CAMERA_RIG = {
"CAM_FRONT": {"x": 1.5, "y": 0.0, "z": 2.4, "yaw": 0, "fov": 70},
"CAM_FRONT_LEFT": {"x": 1.3, "y": -0.5, "z": 2.4, "yaw": -55, "fov": 70},
"CAM_FRONT_RIGHT": {"x": 1.3, "y": 0.5, "z": 2.4, "yaw": 55, "fov": 70},
"CAM_BACK": {"x": -1.5, "y": 0.0, "z": 2.4, "yaw": 180, "fov": 110},
"CAM_BACK_LEFT": {"x": -1.0, "y": -0.5, "z": 2.4, "yaw": -110, "fov": 70},
"CAM_BACK_RIGHT": {"x": -1.0, "y": 0.5, "z": 2.4, "yaw": 110, "fov": 70},
}
IMG_W, IMG_H = 800, 450 # 先用小分辨率快速验证,正式采集再调大
class SyncCollector:
"""同步采集器:管理世界设置、actor 生命周期、传感器队列"""
def __init__(self, host="localhost", port=2000, fps=20):
self.client = carla.Client(host, port)
self.client.set_timeout(15.0)
self.world = self.client.get_world()
self.fps = fps
self.actors = [] # 所有需要销毁的 actor
self.sensors = {} # name -> sensor actor
self.queues = {} # name -> queue,存传感器输出
self._orig_settings = None
self.vehicle = None
# ---------- 世界设置 ----------
def setup_sync(self):
"""切换到同步模式,并记住原设置以便恢复"""
self._orig_settings = self.world.get_settings()
settings = self.world.get_settings()
settings.synchronous_mode = True
settings.fixed_delta_seconds = 1.0 / self.fps
# 关键:让子步长稳定,物理仿真更可靠
settings.substepping = True
settings.max_substep_delta_time = 0.01
settings.max_substeps = 10
self.world.apply_settings(settings)
# Traffic Manager 也必须切同步,否则 NPC 车行为异常
self.tm = self.client.get_trafficmanager(8000)
self.tm.set_synchronous_mode(True)
print(f"[setup] 同步模式已开启, {self.fps} FPS "
f"(每帧 {1.0/self.fps:.3f}s)")
def restore(self):
"""恢复原始设置 —— 退出时必须调用,否则影响他人使用"""
if self._orig_settings is not None:
self.world.apply_settings(self._orig_settings)
print("[cleanup] 已恢复原始仿真设置")
try:
self.tm.set_synchronous_mode(False)
except Exception:
pass
# ---------- 生成主车 ----------
def spawn_vehicle(self, model="vehicle.tesla.model3"):
bp_lib = self.world.get_blueprint_library()
bp = bp_lib.filter(model)[0]
bp.set_attribute("role_name", "ego")
spawn_points = self.world.get_map().get_spawn_points()
if not spawn_points:
raise RuntimeError("地图没有可用生成点")
# 尝试多个生成点,避免"被占用"直接失败
random.shuffle(spawn_points)
for sp in spawn_points:
vehicle = self.world.try_spawn_actor(bp, sp)
if vehicle is not None:
self.vehicle = vehicle
self.actors.append(vehicle)
print(f"[spawn] 主车已生成: {model}")
return vehicle
raise RuntimeError("所有生成点都被占用,无法生成主车")
# ---------- 挂载相机 ----------
def attach_cameras(self, rig=None):
"""按 rig 配置挂载多路相机,每路配一个队列"""
rig = rig or CAMERA_RIG
bp_lib = self.world.get_blueprint_library()
for name, cfg in rig.items():
bp = bp_lib.find("sensor.camera.rgb")
bp.set_attribute("image_size_x", str(IMG_W))
bp.set_attribute("image_size_y", str(IMG_H))
bp.set_attribute("fov", str(cfg["fov"]))
# 传感器输出间隔设为 0 = 每帧都输出(同步模式下由 tick 驱动)
bp.set_attribute("sensor_tick", "0.0")
transform = carla.Transform(
carla.Location(x=cfg["x"], y=cfg["y"], z=cfg["z"]),
carla.Rotation(yaw=cfg["yaw"]),
)
cam = self.world.spawn_actor(bp, transform, attach_to=self.vehicle)
# 关键:用队列而不是直接存盘
q = queue.Queue()
cam.listen(q.put)
self.sensors[name] = cam
self.queues[name] = q
self.actors.append(cam)
print(f"[sensor] 已挂载 {len(self.sensors)} 路相机")
# ---------- 交通流 ----------
def spawn_traffic(self, n_vehicles=30):
"""生成 NPC 车辆,让场景不那么空"""
bp_lib = self.world.get_blueprint_library()
blueprints = bp_lib.filter("vehicle.*")
spawn_points = self.world.get_map().get_spawn_points()
random.shuffle(spawn_points)
count = 0
for sp in spawn_points:
if count >= n_vehicles:
break
bp = random.choice(blueprints)
npc = self.world.try_spawn_actor(bp, sp)
if npc is not None:
npc.set_autopilot(True, self.tm.get_port())
self.actors.append(npc)
count += 1
print(f"[traffic] 已生成 {count} 辆 NPC")
# ---------- 主循环 ----------
def run(self, n_frames, out_dir, warmup=20):
os.makedirs(out_dir, exist_ok=True)
for name in self.sensors:
os.makedirs(os.path.join(out_dir, name), exist_ok=True)
# 主车开自动驾驶(用 CARLA 内置 TM,先保证能跑起来)
self.vehicle.set_autopilot(True, self.tm.get_port())
# 预热:让物理稳定、传感器进入状态,这些帧不保存
print(f"[run] 预热 {warmup} 帧...")
for _ in range(warmup):
self.world.tick()
for q in self.queues.values():
q.get(timeout=5.0) # 丢弃预热数据
print(f"[run] 开始采集 {n_frames} 帧 → {out_dir}")
meta_records = []
for i in range(n_frames):
# 1) 推进仿真一帧,拿到本帧编号
frame_id = self.world.tick()
# 2) 从每个队列取出本帧数据
# 正确性关键:校验 image.frame 与 frame_id 一致
frame_data = {}
for name, q in self.queues.items():
while True:
img = q.get(timeout=5.0)
if img.frame == frame_id:
frame_data[name] = img
break
# 帧号落后就继续取,直到追上(正常情况不会发生)
if img.frame > frame_id:
raise RuntimeError(
f"传感器 {name} 帧号超前,同步逻辑有误")
# 3) 存盘
for name, img in frame_data.items():
path = os.path.join(out_dir, name, f"{i:06d}.jpg")
img.save_to_disk(path)
# 4) 记录自车位姿(后面做 BEV 时必需)
tf = self.vehicle.get_transform()
vel = self.vehicle.get_velocity()
meta_records.append({
"index": i,
"carla_frame": frame_id,
"timestamp": i / self.fps,
"location": [tf.location.x, tf.location.y, tf.location.z],
"rotation": [tf.rotation.pitch, tf.rotation.yaw, tf.rotation.roll],
"velocity": [vel.x, vel.y, vel.z],
"speed_kmh": 3.6 * np.sqrt(vel.x**2 + vel.y**2 + vel.z**2),
})
if (i + 1) % 20 == 0:
print(f" 已采集 {i+1}/{n_frames} 帧")
# 5) 保存元数据 + 相机外参(BEV 投影要用)
meta = {
"fps": self.fps,
"image_size": [IMG_W, IMG_H],
"camera_rig": CAMERA_RIG,
"frames": meta_records,
}
with open(os.path.join(out_dir, "meta.json"), "w") as f:
json.dump(meta, f, indent=2)
print(f"[run] 完成,元数据已写入 meta.json")
# ---------- 清理 ----------
def cleanup(self):
"""销毁所有 actor —— 顺序很重要:先传感器后车辆"""
for name, sensor in self.sensors.items():
try:
sensor.stop()
except Exception:
pass
# 批量销毁比逐个快很多
if self.actors:
self.client.apply_batch(
[carla.command.DestroyActor(a) for a in self.actors])
print(f"[cleanup] 已销毁 {len(self.actors)} 个 actor")
self.actors.clear()
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--host", default="localhost")
ap.add_argument("--port", type=int, default=2000)
ap.add_argument("--frames", type=int, default=100)
ap.add_argument("--fps", type=int, default=20)
ap.add_argument("--traffic", type=int, default=30)
ap.add_argument("--out", default="./data/run01")
args = ap.parse_args()
collector = SyncCollector(args.host, args.port, args.fps)
try:
collector.setup_sync()
collector.spawn_vehicle()
collector.attach_cameras()
collector.spawn_traffic(args.traffic)
collector.run(args.frames, args.out)
except KeyboardInterrupt:
print("\n用户中断")
finally:
# finally 保证异常时也能清理,这一点非常重要
collector.cleanup()
collector.restore()
if __name__ == "__main__":
main()
采集执行:
(ad) leoking@leoking-desktop:~/carla/work/courses/M1$ python collect_demo_v2.py
[setup] 同步模式已开启, 20 FPS (每帧 0.050s)
[spawn] 主车已生成: vehicle.tesla.model3
[sensor] 已挂载 6 路相机
[traffic] 已生成 30 辆 NPC
[run] 预热 20 帧...
[run] 开始采集 100 帧 → ./data/run01
已采集 20/100 帧
已采集 40/100 帧
已采集 60/100 帧
已采集 80/100 帧
已采集 100/100 帧
[run] 完成,元数据已写入 meta.json
[cleanup] 已销毁 37 个 actor
[cleanup] 已恢复原始仿真设置
(ad) leoking@leoking-desktop:~/carla/work/courses/M1$

为者常成,行者常至
自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)