1. 数据采集与预处理

任务目标

本案例完成一个最小可复现的驾驶数据采集与预处理闭环:从模拟器(CARLA)采集图像、速度、转向角数据,执行清洗(剔除异常帧)、标注(生成转向角标签)并转换为标准数据集格式(HDF5)。最终交付一个可直接运行的Python脚本,输出可被后续模仿学习模型直接加载的数据文件。

环境依赖

  • Python 3.8+
  • CARLA 0.9.14(模拟器,需单独启动服务端)
  • numpy、h5py、opencv-python
  • 安装命令:pip install numpy h5py opencv-python

实现方案

脚本通过CARLA Python API连接模拟器,控制车辆沿随机路线行驶,以10Hz频率同步采集前方摄像头图像(640×480 RGB)、车辆速度(km/h)和转向角(弧度)。采集完成后执行三步预处理:清洗——剔除图像亮度异常(过曝/过暗)及车速为0的静止帧;标注——将转向角作为唯一标签,与图像一一对应;格式转换——将图像压缩为JPEG字节流存入HDF5文件,同时保存速度数组和标签数组,形成可直接用于训练的数据集。

关键完整代码

import carla
import numpy as np
import cv2
import h5py
import time
import math

class DataCollector:
    def __init__(self, host='localhost', port=2000):
        self.client = carla.Client(host, port)
        self.client.set_timeout(10.0)
        self.world = self.client.get_world()
        self.bp_lib = self.world.get_blueprint_library()
        
        # 车辆与传感器配置
        self.vehicle_bp = self.bp_lib.filter('vehicle.tesla.model3')[0]
        self.camera_bp = self.bp_lib.find('sensor.camera.rgb')
        self.camera_bp.set_attribute('image_size_x', '640')
        self.camera_bp.set_attribute('image_size_y', '480')
        self.camera_bp.set_attribute('fov', '90')
        
        self.vehicle = None
        self.camera = None
        self.images = []
        self.speeds = []
        self.steerings = []
        self._image = None
        self._speed = 0.0
        self._steering = 0.0
        
    def _camera_callback(self, image):
        """将CARLA原始图像转为RGB数组"""
        img = np.frombuffer(image.raw_data, dtype=np.uint8)
        img = img.reshape((image.height, image.width, 4))
        self._image = img[:, :, :3].copy()
        
    def _control_callback(self, control):
        """记录当前控制指令"""
        self._steering = control.steer
        
    def start(self):
        """生成车辆并附加传感器"""
        spawn_point = self.world.get_map().get_spawn_points()[0]
        self.vehicle = self.world.spawn_actor(self.vehicle_bp, spawn_point)
        
        # 摄像头挂载到车辆前挡风位置
        cam_transform = carla.Transform(carla.Location(x=1.5, z=1.8))
        self.camera = self.world.spawn_actor(self.camera_bp, cam_transform, attach_to=self.vehicle)
        self.camera.listen(lambda img: self._camera_callback(img))
        
        # 启用自动驾驶模式(模拟器内置控制器)
        self.vehicle.set_autopilot(True)
        
    def collect(self, duration=30):
        """采集指定时长的数据"""
        start_time = time.time()
        while time.time() - start_time < duration:
            if self._image is not None:
                # 读取车辆速度(km/h)
                vel = self.vehicle.get_velocity()
                self._speed = 3.6 * math.sqrt(vel.x**2 + vel.y**2 + vel.z**2)
                # 读取当前转向角(由自动驾驶控制器产生)
                control = self.vehicle.get_control()
                self._steering = control.steer
                
                self.images.append(self._image)
                self.speeds.append(self._speed)
                self.steerings.append(self._steering)
                self._image = None  # 重置,等待下一帧
            time.sleep(0.1)  # 10Hz采样
            
    def preprocess(self):
        """清洗数据并返回有效索引"""
        valid_idx = []
        for i, (img, spd) in enumerate(zip(self.images, self.speeds)):
            # 条件1:剔除静止帧(速度 < 0.5 km/h)
            if spd < 0.5:
                continue
            # 条件2:剔除过暗/过曝帧(灰度均值超出[40, 200])
            gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
            mean_brightness = gray.mean()
            if mean_brightness < 40 or mean_brightness > 200:
                continue
            valid_idx.append(i)
        return valid_idx
    
    def save_hdf5(self, valid_idx, output_path='driving_data.h5'):
        """将有效数据保存为HDF5格式"""
        with h5py.File(output_path, 'w') as f:
            # 图像压缩存储
            img_ds = f.create_dataset('images', (len(valid_idx),), dtype='S' + str(640*480*3))
            for j, i in enumerate(valid_idx):
                img_bytes = cv2.imencode('.jpg', self.images[i])[1].tobytes()
                img_ds[j] = np.frombuffer(img_bytes, dtype=np.uint8)
            
            # 速度与转向角
            f.create_dataset('speeds', data=np.array([self.speeds[i] for i in valid_idx]))
            f.create_dataset('steerings', data=np.array([self.steerings[i] for i in valid_idx]))
            
    def cleanup(self):
        """释放资源"""
        if self.camera:
            self.camera.destroy()
        if self.vehicle:
            self.vehicle.destroy()

if __name__ == '__main__':
    collector = DataCollector()
    try:
        collector.start()
        print("开始采集数据...")
        collector.collect(duration=30)  # 采集30秒
        print(f"原始帧数: {len(collector.images)}")
        
        valid = collector.preprocess()
        print(f"清洗后有效帧数: {len(valid)}")
        
        collector.save_hdf5(valid)
        print("数据已保存至 driving_data.h5")
    finally:
        collector.cleanup()

运行与验证

  1. 启动CARLA模拟器:在CARLA安装目录运行 ./CarlaUE4.sh(Linux)或 CarlaUE4.exe(Windows)
  2. 运行采集脚本:python collect_data.py
  3. 验证数据完整性:执行以下Python代码检查输出文件
import h5py
with h5py.File('driving_data.h5', 'r') as f:
    print("图像数量:", len(f['images']))
    print("速度数组形状:", f['speeds'].shape)
    print("转向角范围:", f['steerings'][:].min(), "~", f['steerings'][:].max())
    # 解码第一张图像验证
    import cv2, numpy as np
    img_bytes = f['images'][0]
    img = cv2.imdecode(np.frombuffer(img_bytes, dtype=np.uint8), cv2.IMREAD_COLOR)
    print("图像尺寸:", img.shape)

通过条件:脚本无报错运行完毕;HDF5文件中图像数量大于0且与速度/转向角数组长度一致;解码后的图像尺寸为(480, 640, 3);转向角数值范围在[-1, 1]之间(CARLA转向角归一化值)。

常见问题

  • 连接失败:确认CARLA服务端已启动,且端口(默认2000)未被占用
  • 无数据采集:检查摄像头回调是否触发,可在_camera_callback中添加打印语句调试
  • 清洗后数据为空:若车辆未移动(速度始终为0),可调整采集路线或增加采集时长