From 5de89d64e28d26c89e87fcd9fcecb224b3fe3752 Mon Sep 17 00:00:00 2001 From: Z-w-7799 <1011069369@qq.com> Date: Thu, 18 Dec 2025 14:45:21 +0800 Subject: [PATCH 01/20] =?UTF-8?q?=E6=96=B0=E5=A2=9ELiDAR=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=A4=84=E7=90=86=EF=BC=8C=E5=A2=9E=E5=BC=BA=E4=BC=A0=E6=84=9F?= =?UTF-8?q?=E5=99=A8=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../annotation_generator.py | 168 +++ .../config_manager.py | 108 ++ .../data_analyzer.py | 337 ++++++ .../data_validator.py | 309 ++++++ .../lidar_processor.py | 274 +++++ src/enhance_pedestrian_safety/main.py | 989 +++++++++++------- .../scene_manager.py | 245 +++++ 7 files changed, 2036 insertions(+), 394 deletions(-) create mode 100644 src/enhance_pedestrian_safety/annotation_generator.py create mode 100644 src/enhance_pedestrian_safety/config_manager.py create mode 100644 src/enhance_pedestrian_safety/data_analyzer.py create mode 100644 src/enhance_pedestrian_safety/data_validator.py create mode 100644 src/enhance_pedestrian_safety/lidar_processor.py create mode 100644 src/enhance_pedestrian_safety/scene_manager.py diff --git a/src/enhance_pedestrian_safety/annotation_generator.py b/src/enhance_pedestrian_safety/annotation_generator.py new file mode 100644 index 0000000000..d443854d96 --- /dev/null +++ b/src/enhance_pedestrian_safety/annotation_generator.py @@ -0,0 +1,168 @@ +import json +import os +import numpy as np +from datetime import datetime + + +class AnnotationGenerator: + """标注生成器""" + + def __init__(self, output_dir): + self.output_dir = output_dir + self.annotations_dir = os.path.join(output_dir, "annotations") + os.makedirs(self.annotations_dir, exist_ok=True) + + self.frame_annotations = {} + self.object_counter = 0 + + def detect_objects(self, world, frame_num, timestamp): + """检测场景中的物体""" + annotations = { + 'frame_id': frame_num, + 'timestamp': timestamp, + 'objects': [], + 'camera_info': {} + } + + try: + actors = world.get_actors() + + for actor in actors: + obj_type = actor.type_id + + if 'vehicle' in obj_type or 'walker' in obj_type: + obj_info = self._extract_object_info(actor) + if obj_info: + annotations['objects'].append(obj_info) + self.object_counter += 1 + + self._save_annotations(frame_num, annotations) + return annotations + + except Exception as e: + print(f"物体检测失败: {e}") + return annotations + + def _extract_object_info(self, actor): + """提取物体信息""" + try: + bbox = actor.bounding_box + location = actor.get_location() + velocity = actor.get_velocity() + + obj_info = { + 'id': actor.id, + 'type': actor.type_id, + 'class': self._get_object_class(actor.type_id), + 'location': { + 'x': float(location.x), + 'y': float(location.y), + 'z': float(location.z) + }, + 'velocity': { + 'x': float(velocity.x), + 'y': float(velocity.y), + 'z': float(velocity.z) + }, + 'bounding_box': { + 'extent': { + 'x': float(bbox.extent.x), + 'y': float(bbox.extent.y), + 'z': float(bbox.extent.z) + } + }, + 'attributes': { + 'is_alive': actor.is_alive + } + } + + return obj_info + + except Exception as e: + return None + + def _get_object_class(self, type_id): + """获取物体类别""" + type_lower = type_id.lower() + + if 'vehicle' in type_lower: + if 'tesla' in type_lower: + return 'car' + elif 'audi' in type_lower: + return 'car' + elif 'mini' in type_lower: + return 'car' + elif 'mercedes' in type_lower: + return 'car' + elif 'nissan' in type_lower: + return 'car' + elif 'bmw' in type_lower: + return 'car' + else: + return 'vehicle' + + elif 'walker' in type_lower: + return 'pedestrian' + + elif 'traffic' in type_lower: + return 'traffic_light' + + else: + return 'unknown' + + def _save_annotations(self, frame_num, annotations): + """保存标注到文件""" + filename = f"frame_{frame_num:06d}.json" + filepath = os.path.join(self.annotations_dir, filename) + + with open(filepath, 'w', encoding='utf-8') as f: + json.dump(annotations, f, indent=2, ensure_ascii=False) + + # 同时更新总标注文件 + self.frame_annotations[frame_num] = annotations + self._update_master_annotation() + + def _update_master_annotation(self): + """更新主标注文件""" + master_file = os.path.join(self.annotations_dir, "annotations.json") + + master_data = { + 'total_frames': len(self.frame_annotations), + 'total_objects': self.object_counter, + 'frames': list(self.frame_annotations.keys()), + 'created': datetime.now().isoformat() + } + + with open(master_file, 'w', encoding='utf-8') as f: + json.dump(master_data, f, indent=2, ensure_ascii=False) + + def generate_summary(self): + """生成标注摘要""" + vehicle_count = 0 + pedestrian_count = 0 + other_count = 0 + + for frame_data in self.frame_annotations.values(): + for obj in frame_data.get('objects', []): + if obj['class'] in ['car', 'vehicle']: + vehicle_count += 1 + elif obj['class'] == 'pedestrian': + pedestrian_count += 1 + else: + other_count += 1 + + summary = { + 'total_frames': len(self.frame_annotations), + 'total_objects': self.object_counter, + 'vehicles': vehicle_count, + 'pedestrians': pedestrian_count, + 'other_objects': other_count, + 'average_objects_per_frame': self.object_counter / len( + self.frame_annotations) if self.frame_annotations else 0 + } + + summary_file = os.path.join(self.annotations_dir, "summary.json") + with open(summary_file, 'w', encoding='utf-8') as f: + json.dump(summary, f, indent=2, ensure_ascii=False) + + return summary \ No newline at end of file diff --git a/src/enhance_pedestrian_safety/config_manager.py b/src/enhance_pedestrian_safety/config_manager.py new file mode 100644 index 0000000000..38622ee18a --- /dev/null +++ b/src/enhance_pedestrian_safety/config_manager.py @@ -0,0 +1,108 @@ +import json +import os +import argparse + + +class ConfigManager: + + @staticmethod + def load_config(config_file=None): + config = { + 'scenario': { + 'name': 'multi_sensor_scene', + 'town': 'Town10HD', + 'weather': 'clear', + 'time_of_day': 'noon', + 'duration': 60, + 'seed': 42 + }, + 'traffic': { + 'ego_vehicles': 1, + 'background_vehicles': 8, + 'pedestrians': 6, + 'traffic_lights': True + }, + 'sensors': { + 'vehicle_cameras': 4, + 'infrastructure_cameras': 4, + 'lidar_sensors': 1, + 'radar_sensors': 0, + 'image_size': [1280, 720], + 'capture_interval': 2.0, + 'lidar_config': { + 'channels': 32, + 'range': 100, + 'points_per_second': 56000, + 'rotation_frequency': 10 + } + }, + 'output': { + 'data_dir': 'cvips_dataset', + 'save_raw': True, + 'save_stitched': True, + 'save_annotations': False, + 'save_lidar': True, + 'save_fusion': True, + 'validate_data': True, + 'run_analysis': False + } + } + + if config_file and os.path.exists(config_file): + try: + with open(config_file, 'r') as f: + user_config = json.load(f) + ConfigManager._deep_update(config, user_config) + except: + pass + + return config + + @staticmethod + def _deep_update(original, update): + for key, value in update.items(): + if key in original and isinstance(original[key], dict) and isinstance(value, dict): + ConfigManager._deep_update(original[key], value) + else: + original[key] = value + + @staticmethod + def merge_args(config, args): + if args.scenario: + config['scenario']['name'] = args.scenario + if args.town: + config['scenario']['town'] = args.town + if args.weather: + config['scenario']['weather'] = args.weather + if args.time_of_day: + config['scenario']['time_of_day'] = args.time_of_day + if args.duration: + config['scenario']['duration'] = args.duration + if args.seed: + config['scenario']['seed'] = args.seed + + if args.num_vehicles: + config['traffic']['background_vehicles'] = args.num_vehicles + if args.num_pedestrians: + config['traffic']['pedestrians'] = args.num_pedestrians + + if args.capture_interval: + config['sensors']['capture_interval'] = args.capture_interval + + if args.enable_lidar: + config['sensors']['lidar_sensors'] = 1 + config['output']['save_lidar'] = True + + if args.enable_fusion: + config['output']['save_fusion'] = True + + if args.enable_annotations: + config['output']['save_annotations'] = True + + if args.skip_validation: + config['output']['validate_data'] = False + + if args.run_analysis: + config['output']['run_analysis'] = True + + return config \ No newline at end of file diff --git a/src/enhance_pedestrian_safety/data_analyzer.py b/src/enhance_pedestrian_safety/data_analyzer.py new file mode 100644 index 0000000000..a5802c2dc4 --- /dev/null +++ b/src/enhance_pedestrian_safety/data_analyzer.py @@ -0,0 +1,337 @@ +import os +import json +import numpy as np +from collections import defaultdict + + +class DataAnalyzer: + """数据分析器 - 生成数据集统计信息""" + + @staticmethod + def analyze_dataset(data_dir): + """分析数据集并生成详细报告""" + print(f"分析数据集: {data_dir}") + + analysis = { + 'basic_stats': DataAnalyzer._get_basic_stats(data_dir), + 'file_distribution': DataAnalyzer._analyze_file_distribution(data_dir), + 'object_statistics': DataAnalyzer._analyze_objects(data_dir), + 'temporal_analysis': DataAnalyzer._analyze_temporal(data_dir), + 'quality_metrics': DataAnalyzer._calculate_quality_metrics(data_dir) + } + + # 生成评分 + analysis['overall_score'] = DataAnalyzer._calculate_overall_score(analysis) + + # 保存分析结果 + DataAnalyzer._save_analysis_report(data_dir, analysis) + + # 打印摘要 + DataAnalyzer._print_analysis_summary(analysis) + + return analysis + + @staticmethod + def _get_basic_stats(data_dir): + """获取基本统计信息""" + stats = { + 'total_size_mb': DataAnalyzer._get_directory_size(data_dir), + 'file_count': 0, + 'directory_count': 0, + 'data_types': defaultdict(int) + } + + for root, dirs, files in os.walk(data_dir): + stats['directory_count'] += len(dirs) + stats['file_count'] += len(files) + + for file in files: + ext = os.path.splitext(file)[1].lower() + if ext in ['.png', '.jpg', '.jpeg']: + stats['data_types']['images'] += 1 + elif ext == '.json': + stats['data_types']['json'] += 1 + elif ext == '.txt': + stats['data_types']['text'] += 1 + elif ext == '.bin': + stats['data_types']['binary'] += 1 + else: + stats['data_types']['other'] += 1 + + return stats + + @staticmethod + def _get_directory_size(path): + """计算目录大小""" + total = 0 + for dirpath, dirnames, filenames in os.walk(path): + for f in filenames: + fp = os.path.join(dirpath, f) + if os.path.exists(fp): + total += os.path.getsize(fp) + return round(total / (1024 * 1024), 2) + + @staticmethod + def _analyze_file_distribution(data_dir): + """分析文件分布""" + distribution = {} + + # 分析原始图像 + raw_dirs = ['vehicle', 'infrastructure'] + for raw_dir in raw_dirs: + path = os.path.join(data_dir, "raw", raw_dir) + if os.path.exists(path): + camera_stats = {} + for camera_dir in os.listdir(path): + camera_path = os.path.join(path, camera_dir) + if os.path.isdir(camera_path): + images = [f for f in os.listdir(camera_path) + if f.endswith(('.png', '.jpg', '.jpeg'))] + camera_stats[camera_dir] = len(images) + distribution[f'raw_{raw_dir}'] = camera_stats + + # 分析拼接图像 + stitched_dir = os.path.join(data_dir, "stitched") + if os.path.exists(stitched_dir): + stitched_images = [f for f in os.listdir(stitched_dir) + if f.endswith(('.jpg', '.jpeg', '.png'))] + distribution['stitched'] = len(stitched_images) + + # 分析标注文件 + annotations_dir = os.path.join(data_dir, "annotations") + if os.path.exists(annotations_dir): + json_files = [f for f in os.listdir(annotations_dir) if f.endswith('.json')] + distribution['annotations'] = len(json_files) + + return distribution + + @staticmethod + def _analyze_objects(data_dir): + """分析物体统计""" + annotations_dir = os.path.join(data_dir, "annotations") + + if not os.path.exists(annotations_dir): + return {'total_objects': 0, 'by_class': {}, 'by_frame': {}} + + object_stats = { + 'total_objects': 0, + 'by_class': defaultdict(int), + 'by_frame': defaultdict(int), + 'class_distribution': {} + } + + json_files = [f for f in os.listdir(annotations_dir) + if f.endswith('.json') and f.startswith('frame_')] + + for json_file in json_files: + try: + with open(os.path.join(annotations_dir, json_file), 'r') as f: + data = json.load(f) + + frame_id = data.get('frame_id', 0) + objects = data.get('objects', []) + + object_stats['by_frame'][frame_id] = len(objects) + object_stats['total_objects'] += len(objects) + + for obj in objects: + obj_class = obj.get('class', 'unknown') + object_stats['by_class'][obj_class] += 1 + + except Exception as e: + print(f"分析标注文件 {json_file} 失败: {e}") + + # 计算类分布百分比 + if object_stats['total_objects'] > 0: + for obj_class, count in object_stats['by_class'].items(): + object_stats['class_distribution'][obj_class] = round( + count / object_stats['total_objects'] * 100, 2) + + return object_stats + + @staticmethod + def _analyze_temporal(data_dir): + """分析时间分布""" + temporal_stats = { + 'frame_intervals': [], + 'total_duration': 0, + 'frame_rate': 0 + } + + metadata_file = os.path.join(data_dir, "metadata", "collection_info.json") + if os.path.exists(metadata_file): + try: + with open(metadata_file, 'r') as f: + metadata = json.load(f) + + collection_stats = metadata.get('collection_stats', {}) + temporal_stats['total_duration'] = collection_stats.get('duration_seconds', 0) + temporal_stats['frame_rate'] = collection_stats.get('average_fps', 0) + + except Exception as e: + print(f"分析元数据失败: {e}") + + return temporal_stats + + @staticmethod + def _calculate_quality_metrics(data_dir): + """计算质量指标""" + quality_metrics = { + 'completeness_score': 0, + 'consistency_score': 0, + 'diversity_score': 0, + 'issues_found': [] + } + + # 检查完整性 + required_dirs = ["raw/vehicle", "raw/infrastructure", "stitched", "metadata"] + missing_dirs = [] + + for dir_path in required_dirs: + if not os.path.exists(os.path.join(data_dir, dir_path)): + missing_dirs.append(dir_path) + + if missing_dirs: + quality_metrics['issues_found'].append(f"缺失目录: {missing_dirs}") + quality_metrics['completeness_score'] = 50 + else: + quality_metrics['completeness_score'] = 100 + + # 检查一致性(图像数量) + raw_vehicle = os.path.join(data_dir, "raw", "vehicle") + if os.path.exists(raw_vehicle): + camera_counts = [] + for camera_dir in os.listdir(raw_vehicle): + camera_path = os.path.join(raw_vehicle, camera_dir) + if os.path.isdir(camera_path): + images = [f for f in os.listdir(camera_path) + if f.endswith('.png')] + camera_counts.append(len(images)) + + if camera_counts: + max_diff = max(camera_counts) - min(camera_counts) + if max_diff > 5: + quality_metrics['issues_found'].append(f"摄像头图像数量不一致: 差异{max_diff}") + quality_metrics['consistency_score'] = 70 + else: + quality_metrics['consistency_score'] = 95 + + # 多样性评分(基于物体类别) + object_stats = DataAnalyzer._analyze_objects(data_dir) + num_classes = len(object_stats.get('by_class', {})) + + if num_classes >= 5: + quality_metrics['diversity_score'] = 90 + elif num_classes >= 3: + quality_metrics['diversity_score'] = 70 + else: + quality_metrics['diversity_score'] = 50 + quality_metrics['issues_found'].append(f"物体类别较少: {num_classes}类") + + return quality_metrics + + @staticmethod + def _calculate_overall_score(analysis): + """计算总体评分""" + weights = { + 'completeness': 0.3, + 'consistency': 0.25, + 'diversity': 0.25, + 'temporal': 0.2 + } + + quality = analysis['quality_metrics'] + + score = ( + quality['completeness_score'] * weights['completeness'] + + quality['consistency_score'] * weights['consistency'] + + quality['diversity_score'] * weights['diversity'] + ) + + # 时间因素 + temporal = analysis['temporal_analysis'] + if temporal['frame_rate'] >= 2.0: + score += 20 * weights['temporal'] + elif temporal['frame_rate'] >= 1.0: + score += 15 * weights['temporal'] + else: + score += 5 * weights['temporal'] + + return round(score, 1) + + @staticmethod + def _save_analysis_report(data_dir, analysis): + """保存分析报告""" + report_file = os.path.join(data_dir, "metadata", "dataset_analysis.json") + + with open(report_file, 'w', encoding='utf-8') as f: + json.dump(analysis, f, indent=2, ensure_ascii=False) + + print(f"数据集分析报告保存: {report_file}") + + @staticmethod + def _print_analysis_summary(analysis): + """打印分析摘要""" + print("\n" + "=" * 60) + print("数据集分析摘要") + print("=" * 60) + + basic = analysis['basic_stats'] + print(f"\n基本统计:") + print(f" 总大小: {basic['total_size_mb']} MB") + print(f" 文件数: {basic['file_count']}") + print(f" 目录数: {basic['directory_count']}") + + print(f" 数据类型:") + for data_type, count in basic['data_types'].items(): + print(f" {data_type}: {count}") + + distribution = analysis['file_distribution'] + print(f"\n文件分布:") + for key, value in distribution.items(): + if isinstance(value, dict): + print(f" {key}:") + for subkey, subvalue in value.items(): + print(f" {subkey}: {subvalue}") + else: + print(f" {key}: {value}") + + objects = analysis['object_statistics'] + print(f"\n物体统计:") + print(f" 总物体数: {objects['total_objects']}") + if objects['by_class']: + print(f" 类别分布:") + for obj_class, count in objects['by_class'].items(): + percentage = objects['class_distribution'].get(obj_class, 0) + print(f" {obj_class}: {count} ({percentage}%)") + + temporal = analysis['temporal_analysis'] + print(f"\n时间分析:") + print(f" 总时长: {temporal['total_duration']:.1f}秒") + print(f" 平均帧率: {temporal['frame_rate']:.2f} FPS") + + quality = analysis['quality_metrics'] + print(f"\n质量指标:") + print(f" 完整性: {quality['completeness_score']}/100") + print(f" 一致性: {quality['consistency_score']}/100") + print(f" 多样性: {quality['diversity_score']}/100") + + if quality['issues_found']: + print(f" 发现的问题 ({len(quality['issues_found'])}):") + for issue in quality['issues_found'][:3]: + print(f" - {issue}") + if len(quality['issues_found']) > 3: + print(f" ... 还有 {len(quality['issues_found']) - 3} 个问题") + + print(f"\n总体评分: {analysis['overall_score']}/100") + + if analysis['overall_score'] >= 90: + print("✓ 数据集质量优秀") + elif analysis['overall_score'] >= 75: + print("✓ 数据集质量良好") + elif analysis['overall_score'] >= 60: + print("⚠ 数据集质量一般") + else: + print("✗ 数据集质量需要改进") + + print("=" * 60) \ No newline at end of file diff --git a/src/enhance_pedestrian_safety/data_validator.py b/src/enhance_pedestrian_safety/data_validator.py new file mode 100644 index 0000000000..f015231a42 --- /dev/null +++ b/src/enhance_pedestrian_safety/data_validator.py @@ -0,0 +1,309 @@ +import os +import json + + +class DataValidator: + + @staticmethod + def validate_dataset(data_dir): + print(f"验证数据集: {data_dir}") + + validation_results = { + 'directory_structure': DataValidator._check_directory_structure(data_dir), + 'raw_images': DataValidator._validate_raw_images(data_dir), + 'stitched_images': DataValidator._validate_stitched_images(data_dir), + 'annotations': DataValidator._validate_annotations(data_dir), + 'metadata': DataValidator._validate_metadata(data_dir), + 'lidar_data': DataValidator._validate_lidar_data(data_dir) + } + + validation_results['overall_score'] = DataValidator._calculate_score(validation_results) + + DataValidator._save_validation_report(data_dir, validation_results) + DataValidator._print_validation_report(validation_results) + + return validation_results + + @staticmethod + def _check_directory_structure(data_dir): + required_dirs = [ + "raw/vehicle", + "raw/infrastructure", + "stitched", + "metadata" + ] + + missing_dirs = [] + for dir_path in required_dirs: + full_path = os.path.join(data_dir, dir_path) + if not os.path.exists(full_path): + missing_dirs.append(dir_path) + + status = 'PASS' if len(missing_dirs) == 0 else 'FAIL' + + result = { + 'status': status, + 'missing_directories': missing_dirs, + 'required_directories': required_dirs + } + + return result + + @staticmethod + def _validate_raw_images(data_dir): + raw_dirs = ["vehicle", "infrastructure"] + results = {} + + for raw_dir in raw_dirs: + path = os.path.join(data_dir, "raw", raw_dir) + if not os.path.exists(path): + results[raw_dir] = {'status': 'MISSING', 'count': 0, 'errors': []} + continue + + camera_dirs = [] + if os.path.exists(path): + camera_dirs = [d for d in os.listdir(path) if os.path.isdir(os.path.join(path, d))] + + total_images = 0 + errors = [] + + for camera_dir in camera_dirs: + camera_path = os.path.join(path, camera_dir) + images = [f for f in os.listdir(camera_path) if f.endswith(('.png', '.jpg', '.jpeg'))] + + for img_file in images: + img_path = os.path.join(camera_path, img_file) + try: + if os.path.getsize(img_path) == 0: + errors.append(f"空文件: {img_file}") + except: + errors.append(f"文件访问失败: {img_file}") + + total_images += len(images) + + if len(errors) == 0 and total_images > 0: + status = 'PASS' + elif len(errors) < 3 and total_images > 0: + status = 'WARNING' + else: + status = 'FAIL' + + results[raw_dir] = { + 'status': status, + 'count': total_images, + 'errors': errors + } + + return results + + @staticmethod + def _validate_stitched_images(data_dir): + stitched_dir = os.path.join(data_dir, "stitched") + + if not os.path.exists(stitched_dir): + return {'status': 'MISSING', 'count': 0, 'errors': []} + + images = [f for f in os.listdir(stitched_dir) if f.endswith(('.jpg', '.jpeg', '.png'))] + errors = [] + + for img_file in images: + img_path = os.path.join(stitched_dir, img_file) + try: + if os.path.getsize(img_path) == 0: + errors.append(f"空文件: {img_file}") + except: + errors.append(f"文件访问失败: {img_file}") + + if len(errors) == 0 and len(images) > 0: + status = 'PASS' + elif len(errors) < 3 and len(images) > 0: + status = 'WARNING' + else: + status = 'FAIL' + + return { + 'status': status, + 'count': len(images), + 'errors': errors + } + + @staticmethod + def _validate_annotations(data_dir): + annotations_dir = os.path.join(data_dir, "annotations") + + if not os.path.exists(annotations_dir): + return {'status': 'MISSING', 'count': 0, 'errors': []} + + json_files = [f for f in os.listdir(annotations_dir) if f.endswith('.json')] + errors = [] + valid_files = 0 + + for json_file in json_files: + json_path = os.path.join(annotations_dir, json_file) + try: + with open(json_path, 'r') as f: + data = json.load(f) + valid_files += 1 + except Exception as e: + errors.append(f"无效的JSON文件: {json_file} - {str(e)}") + + if len(errors) == 0 and valid_files > 0: + status = 'PASS' + elif len(errors) < 3 and valid_files > 0: + status = 'WARNING' + else: + status = 'FAIL' + + return { + 'status': status, + 'count': valid_files, + 'errors': errors + } + + @staticmethod + def _validate_metadata(data_dir): + metadata_dir = os.path.join(data_dir, "metadata") + + if not os.path.exists(metadata_dir): + return {'status': 'MISSING', 'count': 0, 'errors': []} + + json_files = [f for f in os.listdir(metadata_dir) if f.endswith('.json')] + errors = [] + valid_files = 0 + + for json_file in json_files: + json_path = os.path.join(metadata_dir, json_file) + try: + with open(json_path, 'r') as f: + data = json.load(f) + valid_files += 1 + except Exception as e: + errors.append(f"无效的JSON文件: {json_file} - {str(e)}") + + if len(errors) == 0 and valid_files > 0: + status = 'PASS' + elif len(errors) < 3 and valid_files > 0: + status = 'WARNING' + else: + status = 'FAIL' + + return { + 'status': status, + 'count': valid_files, + 'errors': errors + } + + @staticmethod + def _validate_lidar_data(data_dir): + lidar_dir = os.path.join(data_dir, "lidar") + + if not os.path.exists(lidar_dir): + return {'status': 'MISSING', 'count': 0, 'errors': []} + + bin_files = [f for f in os.listdir(lidar_dir) if f.endswith('.bin')] + errors = [] + valid_files = 0 + + for bin_file in bin_files: + bin_path = os.path.join(lidar_dir, bin_file) + try: + if os.path.getsize(bin_path) > 0: + valid_files += 1 + else: + errors.append(f"空文件: {bin_file}") + except: + errors.append(f"文件访问失败: {bin_file}") + + if len(errors) == 0 and valid_files > 0: + status = 'PASS' + elif len(errors) < 3 and valid_files > 0: + status = 'WARNING' + else: + status = 'FAIL' + + return { + 'status': status, + 'count': valid_files, + 'errors': errors + } + + @staticmethod + def _calculate_score(results): + weights = { + 'directory_structure': 0.15, + 'raw_images': 0.25, + 'stitched_images': 0.15, + 'annotations': 0.1, + 'metadata': 0.15, + 'lidar_data': 0.2 + } + + score = 0 + for key, weight in weights.items(): + if key not in results: + score += 30 * weight + continue + + result = results[key] + if 'status' not in result: + score += 30 * weight + continue + + if result['status'] == 'PASS': + score += 100 * weight + elif result['status'] == 'WARNING': + score += 70 * weight + elif result['status'] in ['FAIL', 'MISSING']: + score += 30 * weight + else: + score += 50 * weight + + return round(score, 1) + + @staticmethod + def _save_validation_report(data_dir, results): + report_path = os.path.join(data_dir, "metadata", "validation_report.json") + + with open(report_path, 'w', encoding='utf-8') as f: + json.dump(results, f, indent=2, ensure_ascii=False) + + @staticmethod + def _print_validation_report(results): + print("\n" + "=" * 60) + print("数据集验证报告") + print("=" * 60) + + for key, result in results.items(): + if key == 'overall_score': + continue + + print(f"\n{key.replace('_', ' ').title()}:") + + if 'status' in result: + print(f" 状态: {result['status']}") + else: + print(f" 状态: 未知") + + if 'count' in result: + print(f" 数量: {result['count']}") + + if 'errors' in result and result['errors']: + print(f" 错误 ({len(result['errors'])}):") + for error in result['errors'][:3]: + print(f" - {error}") + if len(result['errors']) > 3: + print(f" ... 还有 {len(result['errors']) - 3} 个错误") + + print(f"\n总体评分: {results.get('overall_score', 0)}/100") + + overall_score = results.get('overall_score', 0) + if overall_score >= 90: + print("✓ 数据集质量优秀") + elif overall_score >= 70: + print("✓ 数据集质量良好") + elif overall_score >= 50: + print("⚠ 数据集质量一般") + else: + print("✗ 数据集质量需要改进") + + print("=" * 60) \ No newline at end of file diff --git a/src/enhance_pedestrian_safety/lidar_processor.py b/src/enhance_pedestrian_safety/lidar_processor.py new file mode 100644 index 0000000000..fdcc61637b --- /dev/null +++ b/src/enhance_pedestrian_safety/lidar_processor.py @@ -0,0 +1,274 @@ +import numpy as np +import struct +import os +import json +import threading +from datetime import datetime + + +class LidarProcessor: + + def __init__(self, output_dir): + self.output_dir = output_dir + self.lidar_dir = os.path.join(output_dir, "lidar") + os.makedirs(self.lidar_dir, exist_ok=True) + + self.calibration_dir = os.path.join(output_dir, "calibration") + os.makedirs(self.calibration_dir, exist_ok=True) + + self.frame_counter = 0 + self.data_lock = threading.Lock() + + self._init_calibration_files() + + def _init_calibration_files(self): + lidar_intrinsic = { + "channels": 32, + "range": 100.0, + "points_per_second": 56000, + "rotation_frequency": 10.0, + "horizontal_fov": 360.0, + "vertical_fov": 30.0, + "upper_fov": 10.0, + "lower_fov": -20.0 + } + + lidar_extrinsic = { + "translation": [0.0, 0.0, 2.5], + "rotation": [0.0, 0.0, 0.0], + "matrix": [ + [1.0, 0.0, 0.0, 0.0], + [0.0, 1.0, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0], + [0.0, 0.0, 0.0, 1.0] + ] + } + + intrinsic_file = os.path.join(self.calibration_dir, "lidar_intrinsic.json") + extrinsic_file = os.path.join(self.calibration_dir, "lidar_extrinsic.json") + + with open(intrinsic_file, 'w') as f: + json.dump(lidar_intrinsic, f, indent=2) + + with open(extrinsic_file, 'w') as f: + json.dump(lidar_extrinsic, f, indent=2) + + def process_lidar_data(self, lidar_data, frame_num): + with self.data_lock: + self.frame_counter = frame_num + + points = self._carla_lidar_to_numpy(lidar_data) + + if points is None or points.shape[0] == 0: + return None + + bin_path = self._save_as_bin(points, frame_num) + npy_path = self._save_as_npy(points, frame_num) + csv_path = self._save_as_csv(points, frame_num) + + metadata = self._generate_metadata(points, bin_path, npy_path, csv_path) + + return metadata + + def _carla_lidar_to_numpy(self, lidar_data): + try: + points = np.frombuffer(lidar_data.raw_data, dtype=np.float32) + points = np.reshape(points, (int(points.shape[0] / 4), 4)) + + points = points[:, :3] + + return points + + except Exception as e: + print(f"LiDAR数据转换失败: {e}") + + try: + points = [] + for point in lidar_data: + points.append([point.x, point.y, point.z]) + + return np.array(points, dtype=np.float32) + except: + return None + + def _save_as_bin(self, points, frame_num): + bin_path = os.path.join(self.lidar_dir, f"lidar_{frame_num:06d}.bin") + + points_with_intensity = np.zeros((points.shape[0], 4), dtype=np.float32) + points_with_intensity[:, :3] = points + points_with_intensity[:, 3] = 0.0 + + points_with_intensity.tofile(bin_path) + + return bin_path + + def _save_as_npy(self, points, frame_num): + npy_path = os.path.join(self.lidar_dir, f"lidar_{frame_num:06d}.npy") + np.save(npy_path, points) + return npy_path + + def _save_as_csv(self, points, frame_num): + csv_path = os.path.join(self.lidar_dir, f"lidar_{frame_num:06d}.csv") + + sample_points = points[:min(1000, points.shape[0])] + + header = "x,y,z\n" + + with open(csv_path, 'w') as f: + f.write(header) + np.savetxt(f, sample_points, delimiter=',', fmt='%.6f') + + return csv_path + + def _generate_metadata(self, points, bin_path, npy_path, csv_path): + metadata = { + 'frame_id': self.frame_counter, + 'timestamp': datetime.now().isoformat(), + 'point_count': int(points.shape[0]), + 'file_paths': { + 'bin': os.path.basename(bin_path), + 'npy': os.path.basename(npy_path), + 'csv': os.path.basename(csv_path) + }, + 'statistics': { + 'x_range': [float(points[:, 0].min()), float(points[:, 0].max())], + 'y_range': [float(points[:, 1].min()), float(points[:, 1].max())], + 'z_range': [float(points[:, 2].min()), float(points[:, 2].max())], + 'mean': [float(points[:, 0].mean()), float(points[:, 1].mean()), float(points[:, 2].mean())], + 'std': [float(points[:, 0].std()), float(points[:, 1].std()), float(points[:, 2].std())] + }, + 'file_sizes': { + 'bin': os.path.getsize(bin_path) if os.path.exists(bin_path) else 0, + 'npy': os.path.getsize(npy_path) if os.path.exists(npy_path) else 0, + 'csv': os.path.getsize(csv_path) if os.path.exists(csv_path) else 0 + } + } + + meta_path = os.path.join(self.lidar_dir, f"lidar_meta_{self.frame_counter:06d}.json") + with open(meta_path, 'w') as f: + json.dump(metadata, f, indent=2) + + return metadata + + def generate_lidar_summary(self): + if not os.path.exists(self.lidar_dir): + return None + + bin_files = [f for f in os.listdir(self.lidar_dir) if f.endswith('.bin')] + npy_files = [f for f in os.listdir(self.lidar_dir) if f.endswith('.npy')] + meta_files = [f for f in os.listdir(self.lidar_dir) if f.endswith('.json')] + + total_points = 0 + for bin_file in bin_files: + bin_path = os.path.join(self.lidar_dir, bin_file) + if os.path.exists(bin_path): + file_size = os.path.getsize(bin_path) + points_in_file = file_size // (4 * 4) + total_points += points_in_file + + summary = { + 'total_frames': len(bin_files), + 'total_points': total_points, + 'average_points_per_frame': total_points // max(len(bin_files), 1), + 'file_types': { + 'bin': len(bin_files), + 'npy': len(npy_files), + 'meta': len(meta_files) + }, + 'total_size_mb': round(sum(os.path.getsize(os.path.join(self.lidar_dir, f)) + for f in bin_files + npy_files + meta_files) / (1024 * 1024), 2) + } + + summary_path = os.path.join(self.output_dir, "metadata", "lidar_summary.json") + with open(summary_path, 'w') as f: + json.dump(summary, f, indent=2) + + return summary + + +class MultiSensorFusion: + + def __init__(self, output_dir): + self.output_dir = output_dir + self.fusion_dir = os.path.join(output_dir, "fusion") + os.makedirs(self.fusion_dir, exist_ok=True) + + self.calibration_data = {} + self._load_calibration() + + def _load_calibration(self): + calibration_dir = os.path.join(self.output_dir, "calibration") + + if os.path.exists(calibration_dir): + for file in os.listdir(calibration_dir): + if file.endswith('.json'): + filepath = os.path.join(calibration_dir, file) + try: + with open(filepath, 'r') as f: + data = json.load(f) + sensor_name = file.replace('.json', '') + self.calibration_data[sensor_name] = data + except: + pass + + def create_synchronization_file(self, frame_num, sensor_data): + sync_data = { + 'frame_id': frame_num, + 'timestamp': datetime.now().isoformat(), + 'sensors': {} + } + + for sensor_type, data_path in sensor_data.items(): + if data_path and os.path.exists(data_path): + sync_data['sensors'][sensor_type] = { + 'file_path': os.path.basename(data_path), + 'file_size': os.path.getsize(data_path), + 'timestamp': datetime.fromtimestamp(os.path.getctime(data_path)).isoformat() + } + + sync_file = os.path.join(self.fusion_dir, f"sync_{frame_num:06d}.json") + with open(sync_file, 'w') as f: + json.dump(sync_data, f, indent=2) + + return sync_file + + def generate_fusion_report(self): + if not os.path.exists(self.fusion_dir): + return None + + sync_files = [f for f in os.listdir(self.fusion_dir) if f.endswith('.json')] + + report = { + 'total_sync_frames': len(sync_files), + 'calibration_data': list(self.calibration_data.keys()), + 'sensor_types': set(), + 'frame_range': [] + } + + if sync_files: + frame_ids = [] + for sync_file in sync_files: + try: + frame_id = int(sync_file.split('_')[1].split('.')[0]) + frame_ids.append(frame_id) + + filepath = os.path.join(self.fusion_dir, sync_file) + with open(filepath, 'r') as f: + data = json.load(f) + + for sensor_type in data.get('sensors', {}).keys(): + report['sensor_types'].add(sensor_type) + + except: + continue + + if frame_ids: + report['frame_range'] = [min(frame_ids), max(frame_ids)] + + report['sensor_types'] = list(report['sensor_types']) + + report_path = os.path.join(self.output_dir, "metadata", "fusion_report.json") + with open(report_path, 'w') as f: + json.dump(report, f, indent=2) + + return report \ No newline at end of file diff --git a/src/enhance_pedestrian_safety/main.py b/src/enhance_pedestrian_safety/main.py index 41616f043f..64f7caf9ea 100644 --- a/src/enhance_pedestrian_safety/main.py +++ b/src/enhance_pedestrian_safety/main.py @@ -6,561 +6,762 @@ import traceback import math import threading +import json from datetime import datetime -from PIL import Image, ImageDraw, ImageFont from carla_utils import setup_carla_path, import_carla_module +from config_manager import ConfigManager +from annotation_generator import AnnotationGenerator +from data_validator import DataValidator +from scene_manager import SceneManager +from data_analyzer import DataAnalyzer +from lidar_processor import LidarProcessor, MultiSensorFusion carla_egg_path, remaining_argv = setup_carla_path() carla = import_carla_module() -class ImageStitcher: +class Log: + @staticmethod + def info(msg): + print(f"[INFO] {msg}") + + @staticmethod + def warning(msg): + print(f"[WARNING] {msg}") + + @staticmethod + def error(msg): + print(f"[ERROR] {msg}") + + @staticmethod + def debug(msg): + print(f"[DEBUG] {msg}") + + +class WeatherSystem: + WEATHER_PRESETS = { + 'clear': {'cloudiness': 10, 'precipitation': 0, 'wind': 5}, + 'rainy': {'cloudiness': 90, 'precipitation': 80, 'wind': 15}, + 'cloudy': {'cloudiness': 70, 'precipitation': 10, 'wind': 10}, + 'foggy': {'cloudiness': 50, 'precipitation': 0, 'fog_density': 40} + } + + @staticmethod + def create_weather(weather_type, time_of_day): + weather = carla.WeatherParameters() + + if weather_type in WeatherSystem.WEATHER_PRESETS: + preset = WeatherSystem.WEATHER_PRESETS[weather_type] + weather.cloudiness = preset.get('cloudiness', 30) + weather.precipitation = preset.get('precipitation', 0) + weather.wind_intensity = preset.get('wind', 5) + if 'fog_density' in preset: + weather.fog_density = preset['fog_density'] + + if time_of_day == 'noon': + weather.sun_altitude_angle = 75 + elif time_of_day == 'sunset': + weather.sun_altitude_angle = 15 + elif time_of_day == 'night': + weather.sun_altitude_angle = -20 + + return weather + + +class ImageProcessor: def __init__(self, output_dir): self.output_dir = output_dir - self.stitched_dir = os.path.join(output_dir, "stitched_images") + self.stitched_dir = os.path.join(output_dir, "stitched") os.makedirs(self.stitched_dir, exist_ok=True) - self.font = self._load_font() + def stitch(self, image_paths, frame_num, view_type="vehicle"): + try: + from PIL import Image, ImageDraw + except ImportError: + Log.warning("PIL未安装,跳过图像拼接") + return False - def _load_font(self): - font_paths = [ - "arial.ttf", - "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", - "/System/Library/Fonts/Helvetica.ttc" - ] + positions = [(10, 10), (660, 10), (10, 390), (660, 390)] - for font_path in font_paths: - try: - return ImageFont.truetype(font_path, 20) - except: - continue - return ImageFont.load_default() - - def stitch_ego_vehicle_images(self, image_paths, frame_num): - positions = { - 'front_wide': (10, 10), - 'front_narrow': (660, 10), - 'right_side': (10, 390), - 'left_side': (660, 390) - } + canvas = Image.new('RGB', (640 * 2 + 20, 360 * 2 + 20), (40, 40, 40)) + draw = ImageDraw.Draw(canvas) - images = [] - for cam_name in positions.keys(): - img_path = image_paths.get(cam_name) + for idx, (cam_name, img_path) in enumerate(list(image_paths.items())[:4]): if img_path and os.path.exists(img_path): try: img = Image.open(img_path).resize((640, 360)) - images.append((cam_name, img)) except: - images.append((cam_name, Image.new('RGB', (640, 360), (100, 100, 100)))) + img = Image.new('RGB', (640, 360), (80, 80, 80)) else: - images.append((cam_name, Image.new('RGB', (640, 360), (100, 100, 100)))) + img = Image.new('RGB', (640, 360), (80, 80, 80)) - if len(images) < 4: - return False + canvas.paste(img, positions[idx]) + draw.text((positions[idx][0] + 5, positions[idx][1] + 5), + cam_name, fill=(255, 255, 200)) - canvas = Image.new('RGB', (640 * 2 + 20, 360 * 2 + 20), (50, 50, 50)) + output_path = os.path.join(self.stitched_dir, f"{view_type}_{frame_num:06d}.jpg") + canvas.save(output_path, "JPEG", quality=90) + return True - for cam_name, img in images: - x, y = positions[cam_name] - canvas.paste(img, (x, y)) - draw = ImageDraw.Draw(canvas) - label = cam_name.replace('_', ' ').title() - draw.text((x + 10, y + 10), label, fill=(255, 255, 255), font=self.font) - draw = ImageDraw.Draw(canvas) - title = f"CVIPS - Frame {frame_num:04d}" - timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - draw.text((canvas.width // 2 - 150, 5), title, fill=(255, 255, 255), font=self.font) - draw.text((10, canvas.height - 30), timestamp, fill=(200, 200, 200), font=self.font) +class TrafficManager: + def __init__(self, world, config): + self.world = world + self.config = config + self.vehicles = [] + self.pedestrians = [] - output_path = os.path.join(self.stitched_dir, f"ego_stitched_{frame_num:04d}.jpg") - canvas.save(output_path, "JPEG", quality=95) + seed = config['scenario'].get('seed', random.randint(1, 1000)) + random.seed(seed) + Log.info(f"随机种子: {seed}") - return True + def spawn_ego_vehicle(self): + blueprint_lib = self.world.get_blueprint_library() + common_vehicles = [ + 'vehicle.tesla.model3', + 'vehicle.audi.tt', + 'vehicle.mini.cooperst', + 'vehicle.nissan.micra' + ] -class PedestrianController: - STATE_WAITING = "waiting" - STATE_CROSSING = "crossing" - STATE_WALKING = "walking" - STATE_STOPPED = "stopped" + for vtype in common_vehicles: + if blueprint_lib.filter(vtype): + vehicle_bp = random.choice(blueprint_lib.filter(vtype)) + break + else: + vehicle_bp = random.choice(blueprint_lib.filter('vehicle.*')) - def __init__(self, world): - self.world = world - self.pedestrians = [] - self.running = True + spawn_points = self.world.get_map().get_spawn_points() + if not spawn_points: + return None - def spawn_pedestrian(self, location, behavior_type="crossing"): + spawn_point = random.choice(spawn_points) + try: + vehicle = self.world.spawn_actor(vehicle_bp, spawn_point) + vehicle.set_autopilot(True) + vehicle.apply_control(carla.VehicleControl(throttle=0.2)) + Log.info(f"主车: {vehicle.type_id}") + return vehicle + except Exception as e: + Log.warning(f"主车生成失败: {e}") + return None + + def spawn_traffic(self, center_location): + vehicles = self._spawn_vehicles() + pedestrians = self._spawn_pedestrians(center_location) + + Log.info(f"交通生成: {vehicles}辆车, {pedestrians}个行人") + return vehicles + pedestrians + + def _spawn_vehicles(self): blueprint_lib = self.world.get_blueprint_library() - ped_bps = list(blueprint_lib.filter('walker.pedestrian.*')) + spawn_points = self.world.get_map().get_spawn_points() - if not ped_bps: - return None + if not spawn_points: + return 0 - ped_bp = random.choice(ped_bps) - location.z += 1.0 + num_vehicles = min(self.config['traffic']['background_vehicles'], 10) + spawned = 0 - try: - pedestrian = self.world.spawn_actor(ped_bp, carla.Transform(location)) - controller_bp = blueprint_lib.find('controller.ai.walker') - controller = self.world.spawn_actor(controller_bp, carla.Transform(), attach_to=pedestrian) - controller.start() - - behavior_state = { - 'state': self.STATE_WAITING if behavior_type in ["crossing", "hesitant"] else self.STATE_WALKING, - 'wait_start': time.time(), - 'wait_duration': random.uniform(2.0, 5.0), - 'target': None, - 'original': location, - 'behavior': behavior_type - } + for _ in range(num_vehicles): + try: + vehicle_bp = random.choice(blueprint_lib.filter('vehicle.*')) + spawn_point = random.choice(spawn_points) + vehicle = self.world.spawn_actor(vehicle_bp, spawn_point) + vehicle.set_autopilot(True) + self.vehicles.append(vehicle) + spawned += 1 + except: + pass - if behavior_type == "walking": - behavior_state['target'] = self._get_random_location() + return spawned - self.pedestrians.append((pedestrian, controller, behavior_state)) - return pedestrian + def _spawn_pedestrians(self, center_location): + blueprint_lib = self.world.get_blueprint_library() - except Exception as e: - print(f"生成行人失败: {e}") - return None + num_peds = min(self.config['traffic']['pedestrians'], 8) + spawned = 0 - def _get_random_location(self): - try: - return self.world.get_random_location_from_navigation() - except: - return None + for _ in range(num_peds): + try: + ped_bps = list(blueprint_lib.filter('walker.pedestrian.*')) + if not ped_bps: + continue - def update_behaviors(self): - current_time = time.time() + ped_bp = random.choice(ped_bps) - for pedestrian, controller, state in self.pedestrians: - if not pedestrian.is_alive or not controller.is_alive: - continue + angle = random.uniform(0, 2 * math.pi) + distance = random.uniform(5.0, 12.0) - if state['behavior'] == "crossing": - self._update_crossing(pedestrian, controller, state, current_time) - elif state['behavior'] == "hesitant": - self._update_hesitant(pedestrian, controller, state, current_time) - else: - self._update_walking(pedestrian, controller, state, current_time) - - def _update_crossing(self, pedestrian, controller, state, current_time): - if state['state'] == self.STATE_WAITING: - if current_time - state['wait_start'] >= state['wait_duration']: - target = carla.Location( - x=state['original'].x + random.uniform(15.0, 25.0), - y=state['original'].y + random.uniform(-5.0, 5.0), - z=state['original'].z + location = carla.Location( + x=center_location.x + distance * math.cos(angle), + y=center_location.y + distance * math.sin(angle), + z=center_location.z + 0.5 ) - state['target'] = target - state['state'] = self.STATE_CROSSING - state['cross_start'] = current_time - controller.go_to_location(target) - - elif state['state'] == self.STATE_CROSSING: - distance = pedestrian.get_location().distance(state['target']) - if distance < 2.0 or current_time - state['cross_start'] > 15.0: - state['state'] = self.STATE_STOPPED - - def _update_hesitant(self, pedestrian, controller, state, current_time): - if state['state'] == self.STATE_WAITING: - if current_time - state['wait_start'] >= state['wait_duration']: - target = self._get_random_location() - if target: - state['target'] = target - state['state'] = self.STATE_WALKING - state['walk_start'] = current_time - state['walk_duration'] = random.uniform(3.0, 8.0) - controller.go_to_location(target) - - elif state['state'] == self.STATE_WALKING: - if current_time - state['walk_start'] >= state['walk_duration']: - state['state'] = self.STATE_WAITING - state['wait_start'] = current_time - state['wait_duration'] = random.uniform(1.0, 4.0) - - def _update_walking(self, pedestrian, controller, state, current_time): - if state['state'] == self.STATE_WALKING and state['target']: - distance = pedestrian.get_location().distance(state['target']) - if distance < 2.0: - new_target = self._get_random_location() - if new_target: - state['target'] = new_target - controller.go_to_location(new_target) - - def start_updates(self): - def update_loop(): - while self.running: - try: - self.update_behaviors() - time.sleep(0.5) - except Exception as e: - print(f"行为更新错误: {e}") - time.sleep(1.0) - threading.Thread(target=update_loop, daemon=True).start() + pedestrian = self.world.spawn_actor(ped_bp, carla.Transform(location)) + self.pedestrians.append(pedestrian) + spawned += 1 + except Exception as e: + Log.debug(f"行人生成失败: {e}") + + return spawned def cleanup(self): - self.running = False - for pedestrian, controller, _ in self.pedestrians: + Log.info("清理交通...") + + for vehicle in self.vehicles: + try: + if vehicle.is_alive: + vehicle.destroy() + except: + pass + + for pedestrian in self.pedestrians: try: - if controller.is_alive: - controller.stop() - controller.destroy() if pedestrian.is_alive: pedestrian.destroy() except: pass + + self.vehicles.clear() self.pedestrians.clear() -class DataGenerator: - def __init__(self, args): - self.args = args - self.client = None - self.world = None - self.actors = [] +class SensorManager: + def __init__(self, world, config, data_dir): + self.world = world + self.config = config + self.data_dir = data_dir self.sensors = [] - self.frame_count = 0 - self.last_capture_time = 0 - self.setup_output_directory() - self.stitcher = ImageStitcher(self.output_dir) - self.ped_controller = None + self.frame_counter = 0 + self.last_capture_time = 0 - self.image_buffer = {} + self.vehicle_buffer = {} + self.infra_buffer = {} self.buffer_lock = threading.Lock() - def setup_output_directory(self): - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - self.output_dir = os.path.join("cvips_data", f"{self.args.scenario}_{timestamp}") + self.image_processor = ImageProcessor(data_dir) + self.lidar_processor = None + self.fusion_manager = None - self.raw_dirs = {} - for view in ['front_wide', 'front_narrow', 'right_side', 'left_side']: - dir_path = os.path.join(self.output_dir, "raw", "ego_vehicle", view) - os.makedirs(dir_path, exist_ok=True) - self.raw_dirs[view] = dir_path + if config['sensors'].get('lidar_sensors', 0) > 0: + self.lidar_processor = LidarProcessor(data_dir) - print(f"数据输出目录: {self.output_dir}") + if config['output'].get('save_fusion', False): + self.fusion_manager = MultiSensorFusion(data_dir) - def connect_to_server(self): - for attempt in range(1, 6): - try: - self.client = carla.Client('localhost', 2000) - self.client.set_timeout(15.0) - - if self.args.town: - self.world = self.client.load_world(self.args.town) - else: - self.world = self.client.get_world() - - settings = self.world.get_settings() - settings.synchronous_mode = False - self.world.apply_settings(settings) + def setup_cameras(self, vehicle, center_location): + vehicle_cams = self._setup_vehicle_cameras(vehicle) + infra_cams = self._setup_infrastructure_cameras(center_location) - print(f"连接成功! 地图: {self.world.get_map().name}") - return True + Log.info(f"摄像头: {vehicle_cams}车辆 + {infra_cams}基础设施") + return vehicle_cams + infra_cams - except Exception as e: - print(f"尝试 {attempt}/5 失败: {str(e)[:80]}") - if attempt < 5: - time.sleep(3) + def _setup_vehicle_cameras(self, vehicle): + if not vehicle: + return 0 - return False + camera_configs = { + 'front_wide': {'loc': (2.0, 0, 1.8), 'rot': (0, -3, 0), 'fov': 100}, + 'front_narrow': {'loc': (2.0, 0, 1.6), 'rot': (0, 0, 0), 'fov': 60}, + 'right_side': {'loc': (0.5, 1.0, 1.5), 'rot': (0, -2, 45), 'fov': 90}, + 'left_side': {'loc': (0.5, -1.0, 1.5), 'rot': (0, -2, -45), 'fov': 90} + } - def setup_scene(self): - self.ped_controller = PedestrianController(self.world) + installed = 0 + for cam_name, config_data in camera_configs.items(): + if self._create_camera(cam_name, config_data, vehicle, 'vehicle'): + installed += 1 - self.set_weather() - time.sleep(2.0) + return installed - ego_vehicle = self.spawn_ego_vehicle() - if not ego_vehicle: - return None + def _setup_infrastructure_cameras(self, center_location): + camera_configs = [ + {'name': 'north', 'offset': (0, -20, 12), 'rotation': (0, -25, 180)}, + {'name': 'south', 'offset': (0, 20, 12), 'rotation': (0, -25, 0)}, + {'name': 'east', 'offset': (20, 0, 12), 'rotation': (0, -25, -90)}, + {'name': 'west', 'offset': (-20, 0, 12), 'rotation': (0, -25, 90)} + ] - self.spawn_pedestrians() - self.spawn_background_vehicles() - time.sleep(5.0) + installed = 0 + for cam_config in camera_configs: + sensor_config = { + 'loc': ( + center_location.x + cam_config['offset'][0], + center_location.y + cam_config['offset'][1], + center_location.z + cam_config['offset'][2] + ), + 'rot': cam_config['rotation'], + 'fov': 90 + } - self.ped_controller.start_updates() - return ego_vehicle + if self._create_camera(cam_config['name'], sensor_config, None, 'infrastructure'): + installed += 1 - def set_weather(self): - weather = carla.WeatherParameters() + return installed - if self.args.weather == 'clear': - weather.sun_altitude_angle = 75 - weather.cloudiness = 5.0 - elif self.args.weather == 'rainy': - weather.sun_altitude_angle = 40 - weather.cloudiness = 90.0 - weather.precipitation = 60.0 - else: - weather.sun_altitude_angle = 60 - weather.cloudiness = 70.0 + def setup_lidar(self, vehicle): + if not vehicle or not self.config['sensors'].get('lidar_sensors', 0) > 0: + return 0 - if self.args.time_of_day == 'night': - weather.sun_altitude_angle = -10 - elif self.args.time_of_day == 'sunset': - weather.sun_altitude_angle = 5 + try: + blueprint_lib = self.world.get_blueprint_library() + lidar_bp = blueprint_lib.find('sensor.lidar.ray_cast') + + lidar_config = self.config['sensors'].get('lidar_config', {}) + + lidar_bp.set_attribute('channels', str(lidar_config.get('channels', 32))) + lidar_bp.set_attribute('range', str(lidar_config.get('range', 100))) + lidar_bp.set_attribute('points_per_second', str(lidar_config.get('points_per_second', 56000))) + lidar_bp.set_attribute('rotation_frequency', str(lidar_config.get('rotation_frequency', 10))) + + # 添加更多LiDAR参数 + lidar_bp.set_attribute('upper_fov', '10') + lidar_bp.set_attribute('lower_fov', '-20') + lidar_bp.set_attribute('horizontal_fov', '360') + + lidar_location = carla.Location(x=0, y=0, z=2.5) + lidar_rotation = carla.Rotation(0, 0, 0) + lidar_transform = carla.Transform(lidar_location, lidar_rotation) + + lidar_sensor = self.world.spawn_actor(lidar_bp, lidar_transform, attach_to=vehicle) + + def lidar_callback(lidar_data): + current_time = time.time() + if current_time - self.last_capture_time >= self.config['sensors']['capture_interval']: + if self.lidar_processor: + try: + metadata = self.lidar_processor.process_lidar_data(lidar_data, self.frame_counter) + if metadata and self.fusion_manager: + # 尝试获取最新的车辆图像 + vehicle_image_path = None + with self.buffer_lock: + if self.vehicle_buffer: + # 取第一个摄像头的图像 + for cam_name, img_path in self.vehicle_buffer.items(): + if os.path.exists(img_path): + vehicle_image_path = img_path + break + + sensor_data = { + 'lidar': os.path.join(self.data_dir, "lidar", f"lidar_{self.frame_counter:06d}.bin") + } + if vehicle_image_path: + sensor_data['camera'] = vehicle_image_path + + self.fusion_manager.create_synchronization_file(self.frame_counter, sensor_data) + except Exception as e: + print(f"LiDAR处理失败: {e}") + + lidar_sensor.listen(lidar_callback) + self.sensors.append(lidar_sensor) + + print("LiDAR传感器已安装") + return 1 - self.world.set_weather(weather) + except Exception as e: + print(f"LiDAR安装失败: {e}") + return 0 + def _create_camera(self, name, config, parent, sensor_type): + try: + blueprint = self.world.get_blueprint_library().find('sensor.camera.rgb') - def spawn_ego_vehicle(self): - blueprint_lib = self.world.get_blueprint_library() - vehicle_types = ['vehicle.tesla.model3', 'vehicle.audi.tt', 'vehicle.mini.cooperst'] + img_size = self.config['sensors'].get('image_size', [1280, 720]) + blueprint.set_attribute('image_size_x', str(img_size[0])) + blueprint.set_attribute('image_size_y', str(img_size[1])) + blueprint.set_attribute('fov', str(config.get('fov', 90))) - vehicle_bp = None - for vtype in vehicle_types: - if blueprint_lib.filter(vtype): - vehicle_bp = random.choice(blueprint_lib.filter(vtype)) - break + location = carla.Location(config['loc'][0], config['loc'][1], config['loc'][2]) + rotation = carla.Rotation(config['rot'][0], config['rot'][1], config['rot'][2]) + transform = carla.Transform(location, rotation) - if not vehicle_bp: - vehicle_bp = random.choice(blueprint_lib.filter('vehicle.*')) + if parent: + camera = self.world.spawn_actor(blueprint, transform, attach_to=parent) + else: + camera = self.world.spawn_actor(blueprint, transform) - spawn_points = self.world.get_map().get_spawn_points() - if not spawn_points: - return None + save_dir = os.path.join(self.data_dir, "raw", sensor_type, name) + os.makedirs(save_dir, exist_ok=True) - spawn_point = random.choice(spawn_points) + callback = self._create_callback(save_dir, name, sensor_type) + camera.listen(callback) - try: - vehicle = self.world.spawn_actor(vehicle_bp, spawn_point) - self.actors.append(vehicle) - vehicle.set_autopilot(True) - vehicle.apply_control(carla.VehicleControl(throttle=0.2, steer=0.0)) + self.sensors.append(camera) + return True - print(f"主车辆: {vehicle.type_id}") - return vehicle except Exception as e: - print(f"生成主车辆失败: {e}") - return None - - def spawn_pedestrians(self): - spawn_points = self.world.get_map().get_spawn_points() - if not spawn_points: - return - - print(f"生成 {self.args.num_smart_pedestrians} 个行人...") + Log.warning(f"创建摄像头 {name} 失败: {e}") + return False - behaviors = ['crossing', 'hesitant', 'walking'] + def _create_callback(self, save_dir, name, sensor_type): + capture_interval = self.config['sensors']['capture_interval'] + + def callback(image): + current_time = time.time() + + if current_time - self.last_capture_time >= capture_interval: + self.frame_counter += 1 + self.last_capture_time = current_time + + filename = os.path.join(save_dir, f"{name}_{self.frame_counter:06d}.png") + image.save_to_disk(filename, carla.ColorConverter.Raw) + + with self.buffer_lock: + if sensor_type == 'vehicle': + self.vehicle_buffer[name] = filename + if len(self.vehicle_buffer) >= 4: + self.image_processor.stitch(self.vehicle_buffer, self.frame_counter, 'vehicle') + self.vehicle_buffer.clear() + else: + self.infra_buffer[name] = filename + if len(self.infra_buffer) >= 4: + self.image_processor.stitch(self.infra_buffer, self.frame_counter, 'infrastructure') + self.infra_buffer.clear() + + return callback + + def get_frame_count(self): + return self.frame_counter + + def generate_sensor_summary(self): + summary = { + 'total_sensors': len(self.sensors), + 'frame_count': self.frame_counter, + 'lidar_data': None, + 'fusion_data': None + } - for _ in range(self.args.num_smart_pedestrians): - behavior = random.choice(behaviors) - spawn_point = random.choice(spawn_points) + if self.lidar_processor: + summary['lidar_data'] = self.lidar_processor.generate_lidar_summary() - if self.ped_controller.spawn_pedestrian(spawn_point.location, behavior): - self.actors.append(self.ped_controller.pedestrians[-1][0]) + if self.fusion_manager: + summary['fusion_data'] = self.fusion_manager.generate_fusion_report() - def spawn_background_vehicles(self): - blueprint_lib = self.world.get_blueprint_library() - spawn_points = self.world.get_map().get_spawn_points() + return summary - if not spawn_points: - return - - spawned = 0 - for _ in range(min(5, self.args.num_background_vehicles)): + def cleanup(self): + Log.info(f"清理 {len(self.sensors)} 个传感器...") + for sensor in self.sensors: try: - vehicle_bp = random.choice(blueprint_lib.filter('vehicle.*')) - spawn_point = random.choice(spawn_points) - - vehicle = self.world.spawn_actor(vehicle_bp, spawn_point) - self.actors.append(vehicle) - vehicle.set_autopilot(True) - spawned += 1 + sensor.stop() + sensor.destroy() except: pass + self.sensors.clear() - print(f"背景车辆: {spawned} 辆") - def setup_cameras(self, vehicle): - if not vehicle: - return False +class DataCollector: + def __init__(self, config): + self.config = config + self.client = None + self.world = None + self.ego_vehicle = None + self.scene_center = None - blueprint_lib = self.world.get_blueprint_library() + self.setup_directories() - camera_configs = [ - ('front_wide', carla.Location(x=2.0, z=1.8), carla.Rotation(pitch=-3.0), 100), - ('front_narrow', carla.Location(x=2.0, z=1.6), carla.Rotation(pitch=0), 60), - ('right_side', carla.Location(x=0.5, y=1.0, z=1.5), carla.Rotation(pitch=-2.0, yaw=45), 90), - ('left_side', carla.Location(x=0.5, y=-1.0, z=1.5), carla.Rotation(pitch=-2.0, yaw=-45), 90), + self.traffic_manager = None + self.sensor_manager = None + + self.start_time = None + self.is_running = False + self.collected_frames = 0 + + def setup_directories(self): + scenario = self.config['scenario'] + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + + self.output_dir = os.path.join( + self.config['output']['data_dir'], + f"{scenario['name']}_{scenario['town']}_{timestamp}" + ) + + directories = [ + "raw/vehicle", + "raw/infrastructure", + "stitched", + "lidar", + "fusion", + "calibration", + "metadata" ] - installed = 0 + for subdir in directories: + os.makedirs(os.path.join(self.output_dir, subdir), exist_ok=True) + + Log.info(f"数据目录: {self.output_dir}") - for name, location, rotation, fov in camera_configs: + def connect(self): + for attempt in range(1, 6): try: - camera_bp = blueprint_lib.find('sensor.camera.rgb') - camera_bp.set_attribute('image_size_x', '1280') - camera_bp.set_attribute('image_size_y', '720') - camera_bp.set_attribute('fov', str(fov)) - - camera = self.world.spawn_actor( - camera_bp, - carla.Transform(location, rotation), - attach_to=vehicle - ) + self.client = carla.Client('localhost', 2000) + self.client.set_timeout(10.0) - def make_callback(save_dir, cam_name): - def callback(image): - current_time = time.time() + town = self.config['scenario']['town'] + self.world = self.client.load_world(town) - if current_time - self.last_capture_time >= self.args.capture_interval: - self.frame_count += 1 - self.last_capture_time = current_time + settings = self.world.get_settings() + settings.synchronous_mode = False + self.world.apply_settings(settings) - raw_filename = f"{save_dir}/{cam_name}_{self.frame_count:04d}.png" - image.save_to_disk(raw_filename, carla.ColorConverter.Raw) + Log.info(f"连接成功: {town}") + return True - with self.buffer_lock: - self.image_buffer[cam_name] = raw_filename + except Exception as e: + Log.warning(f"连接尝试 {attempt}/5 失败: {str(e)[:50]}") + time.sleep(2) - if len(self.image_buffer) == 4: - self.stitcher.stitch_ego_vehicle_images(self.image_buffer, self.frame_count) - self.image_buffer.clear() + return False - return callback + def setup_scene(self): + weather_cfg = self.config['scenario'] + weather = WeatherSystem.create_weather(weather_cfg['weather'], weather_cfg['time_of_day']) + self.world.set_weather(weather) + Log.info(f"天气: {weather_cfg['weather']}, 时间: {weather_cfg['time_of_day']}") - camera.listen(make_callback(self.raw_dirs[name], name)) - self.actors.append(camera) - self.sensors.append(camera) - installed += 1 + spawn_points = self.world.get_map().get_spawn_points() + if spawn_points: + self.scene_center = spawn_points[len(spawn_points) // 2].location + else: + self.scene_center = carla.Location(0, 0, 0) - except Exception as e: - print(f"安装 {name} 摄像头失败: {e}") + self.traffic_manager = TrafficManager(self.world, self.config) + + self.ego_vehicle = self.traffic_manager.spawn_ego_vehicle() + if not self.ego_vehicle: + Log.error("主车生成失败") + return False + + self.traffic_manager.spawn_traffic(self.scene_center) + + time.sleep(3.0) + return True - print(f"摄像头安装: {installed}/4") - return installed == 4 + def setup_sensors(self): + self.sensor_manager = SensorManager(self.world, self.config, self.output_dir) + + cameras = self.sensor_manager.setup_cameras(self.ego_vehicle, self.scene_center) + if cameras == 0: + Log.error("没有摄像头安装成功") + return False + + lidars = self.sensor_manager.setup_lidar(self.ego_vehicle) + Log.info(f"传感器: {cameras}摄像头 + {lidars}LiDAR") + + return True def collect_data(self): - print(f"\n开始数据收集...") - print(f"时长: {self.args.total_dura.tion}秒, 间隔: {self.args.capture_interval}秒") + duration = self.config['scenario']['duration'] + Log.info(f"开始数据收集,时长: {duration}秒") + + self.start_time = time.time() + self.is_running = True - start_time = time.time() - self.frame_count = 0 - self.last_capture_time = start_time + last_update = time.time() try: - while time.time() - start_time < self.args.total_duration: - elapsed = time.time() - start_time - remaining = self.args.total_duration - elapsed + while time.time() - self.start_time < duration and self.is_running: + current_time = time.time() + elapsed = current_time - self.start_time - if int(elapsed) % 10 == 0: - progress = (elapsed / self.args.total_duration) * 100 - print(f"进度: {elapsed:.0f}/{self.args.total_duration}秒 ({progress:.1f}%) | " - f"批次: {self.frame_count} | 剩余: {remaining:.0f}秒") + if current_time - last_update >= 5.0: + frames = self.sensor_manager.get_frame_count() + progress = (elapsed / duration) * 100 - time.sleep(0.1) + Log.info(f"进度: {elapsed:.0f}/{duration}秒 ({progress:.1f}%) | 帧数: {frames}") + last_update = current_time - print(f"\n数据收集完成! 总批次: {self.frame_count}") + time.sleep(0.05) except KeyboardInterrupt: - print(f"\n数据收集中断, 已收集 {self.frame_count} 批次") + Log.info("数据收集被用户中断") + finally: + self.is_running = False + elapsed = time.time() - self.start_time + + self.collected_frames = self.sensor_manager.get_frame_count() + Log.info(f"收集完成: {self.collected_frames}帧, 用时: {elapsed:.1f}秒") + + self._save_metadata() + self._print_summary() + + def _save_metadata(self): + metadata = { + 'scenario': self.config['scenario'], + 'traffic': self.config['traffic'], + 'sensors': self.config['sensors'], + 'output': self.config['output'], + 'collection': { + 'duration': round(time.time() - self.start_time, 2), + 'total_frames': self.collected_frames, + 'frame_rate': round(self.collected_frames / max(time.time() - self.start_time, 0.1), 2) + } + } + + if self.sensor_manager: + sensor_summary = self.sensor_manager.generate_sensor_summary() + metadata['sensor_summary'] = sensor_summary + + meta_path = os.path.join(self.output_dir, "metadata", "collection_info.json") + with open(meta_path, 'w', encoding='utf-8') as f: + json.dump(metadata, f, indent=2, ensure_ascii=False) - self.display_summary() + Log.info(f"元数据保存: {meta_path}") - def display_summary(self): + def _print_summary(self): print("\n" + "=" * 60) - print("数据收集摘要:") + print("数据收集摘要") print("=" * 60) - stitched_dir = os.path.join(self.output_dir, "stitched_images") + # 统计图像 + stitched_dir = os.path.join(self.output_dir, "stitched") if os.path.exists(stitched_dir): stitched_files = [f for f in os.listdir(stitched_dir) if f.endswith('.jpg')] print(f"拼接图像: {len(stitched_files)} 张") - raw_dir = os.path.join(self.output_dir, "raw") - if os.path.exists(raw_dir): - total_raw = 0 - for root, dirs, files in os.walk(raw_dir): - total_raw += len([f for f in files if f.endswith('.png')]) - print(f"原始图像: {total_raw} 张") + # 统计LiDAR + lidar_dir = os.path.join(self.output_dir, "lidar") + if os.path.exists(lidar_dir): + bin_files = [f for f in os.listdir(lidar_dir) if f.endswith('.bin')] + npy_files = [f for f in os.listdir(lidar_dir) if f.endswith('.npy')] + print(f"LiDAR数据: {len(bin_files)} .bin文件, {len(npy_files)} .npy文件") + + if bin_files: + total_points = 0 + for bin_file in bin_files[:3]: + bin_path = os.path.join(lidar_dir, bin_file) + if os.path.exists(bin_path): + file_size = os.path.getsize(bin_path) + points_in_file = file_size // (4 * 4) + total_points += points_in_file + print(f" 估计总点数: {total_points:,}") + + # 统计融合数据 + fusion_dir = os.path.join(self.output_dir, "fusion") + if os.path.exists(fusion_dir): + sync_files = [f for f in os.listdir(fusion_dir) if f.endswith('.json')] + print(f"融合数据: {len(sync_files)} 个同步文件") + + print(f"\n输出目录: {self.output_dir}") + print("=" * 60) + + def run_validation(self): + if self.config['output'].get('validate_data', True): + Log.info("运行数据验证...") + DataValidator.validate_dataset(self.output_dir) - print(f"\n数据目录: {self.output_dir}") + def run_analysis(self): + if self.config['output'].get('run_analysis', False): + Log.info("运行数据分析...") + DataAnalyzer.analyze_dataset(self.output_dir) def cleanup(self): - if self.ped_controller: - self.ped_controller.cleanup() + Log.info("清理场景...") - for sensor in self.sensors: - try: - sensor.stop() - except: - pass + if self.sensor_manager: + self.sensor_manager.cleanup() - destroyed = 0 - for actor in self.actors: + if self.traffic_manager: + self.traffic_manager.cleanup() + + if self.ego_vehicle and self.ego_vehicle.is_alive: try: - if actor.is_alive: - actor.destroy() - destroyed += 1 + self.ego_vehicle.destroy() except: pass - print(f"清理 {destroyed} 个actor") - self.actors.clear() - self.sensors.clear() + Log.info("清理完成") def main(): - parser = argparse.ArgumentParser(description='CVIPS 数据生成器') + parser = argparse.ArgumentParser(description='CVIPS 多传感器数据收集系统') - parser.add_argument('--scenario', type=str, default='pedestrian_scene', help='场景名称') + # 基础参数 + parser.add_argument('--config', type=str, help='配置文件路径') + parser.add_argument('--scenario', type=str, default='multi_sensor_scene', help='场景名称') parser.add_argument('--town', type=str, default='Town10HD', - choices=['Town03', 'Town04', 'Town05', 'Town10HD'], help='CARLA地图') + choices=['Town03', 'Town04', 'Town05', 'Town10HD'], help='地图') parser.add_argument('--weather', type=str, default='clear', - choices=['clear', 'rainy', 'cloudy'], help='天气条件') + choices=['clear', 'rainy', 'cloudy', 'foggy'], help='天气') parser.add_argument('--time-of-day', type=str, default='noon', choices=['noon', 'sunset', 'night'], help='时间') - parser.add_argument('--num-smart-pedestrians', type=int, default=6, help='行人数') - parser.add_argument('--num-background-vehicles', type=int, default=4, help='背景车辆数') - parser.add_argument('--total-duration', type=int, default=90, help='总时长(秒)') - parser.add_argument('--capture-interval', type=float, default=2.5, help='捕捉间隔(秒)') + + # 交通参数 + parser.add_argument('--num-vehicles', type=int, default=8, help='背景车辆数') + parser.add_argument('--num-pedestrians', type=int, default=6, help='行人数') + + # 收集参数 + parser.add_argument('--duration', type=int, default=60, help='收集时长(秒)') + parser.add_argument('--capture-interval', type=float, default=2.0, help='捕捉间隔(秒)') + parser.add_argument('--seed', type=int, help='随机种子') + + # 传感器参数 + parser.add_argument('--enable-lidar', action='store_true', help='启用LiDAR传感器') + parser.add_argument('--enable-fusion', action='store_true', help='启用多传感器融合') + parser.add_argument('--enable-annotations', action='store_true', help='启用自动标注') + + # 功能参数 + parser.add_argument('--run-analysis', action='store_true', help='运行数据集分析') + parser.add_argument('--skip-validation', action='store_true', help='跳过数据验证') args = parser.parse_args(remaining_argv) - if args.capture_interval < 2.0: - args.capture_interval = 2.0 + # 加载配置 + config = ConfigManager.load_config(args.config) + config = ConfigManager.merge_args(config, args) - print(f"\n场景配置:") - print(f" 场景: {args.scenario}") - print(f" 地图: {args.town}") - print(f" 天气/时间: {args.weather}/{args.time_of_day}") - print(f" 行人: {args.num_smart_pedestrians}个") - print(f" 车辆: {args.num_background_vehicles}辆") - print(f" 时长: {args.total_duration}秒") - print(f" 间隔: {args.capture_interval}秒") + # 显示配置 + print("\n" + "=" * 60) + print("CVIPS 多传感器数据收集系统 v9.0") + print("=" * 60) - generator = DataGenerator(args) + print(f"场景: {config['scenario']['name']}") + print(f"地图: {config['scenario']['town']}") + print(f"天气/时间: {config['scenario']['weather']}/{config['scenario']['time_of_day']}") + print(f"时长: {config['scenario']['duration']}秒") + print(f"交通: {config['traffic']['background_vehicles']}车辆 + {config['traffic']['pedestrians']}行人") + + print(f"传感器:") + print( + f" 摄像头: {config['sensors']['vehicle_cameras']}车辆 + {config['sensors']['infrastructure_cameras']}基础设施") + print(f" LiDAR: {'启用' if config['sensors']['lidar_sensors'] > 0 else '禁用'}") + print(f" 融合: {'启用' if config['output']['save_fusion'] else '禁用'}") + print(f" 标注: {'启用' if config['output']['save_annotations'] else '禁用'}") + + collector = DataCollector(config) try: - if not generator.connect_to_server(): + if not collector.connect(): + print("连接CARLA服务器失败") return - ego_vehicle = generator.setup_scene() - if not ego_vehicle: - generator.cleanup() + if not collector.setup_scene(): + print("场景设置失败") + collector.cleanup() return - if not generator.setup_cameras(ego_vehicle): - generator.cleanup() + if not collector.setup_sensors(): + print("传感器设置失败") + collector.cleanup() return - generator.collect_data() + collector.collect_data() + + collector.run_analysis() + collector.run_validation() except KeyboardInterrupt: - print("\n程序中断") + print("\n程序被用户中断") except Exception as e: print(f"\n运行错误: {e}") traceback.print_exc() finally: - generator.cleanup() - print(f"\n数据保存到: {generator.output_dir}") + collector.cleanup() + print(f"\n数据集已保存到: {collector.output_dir}") if __name__ == "__main__": diff --git a/src/enhance_pedestrian_safety/scene_manager.py b/src/enhance_pedestrian_safety/scene_manager.py new file mode 100644 index 0000000000..dbe424d7d4 --- /dev/null +++ b/src/enhance_pedestrian_safety/scene_manager.py @@ -0,0 +1,245 @@ +import os +import json +import random +import math +import carla + + +class SceneManager: + """场景管理器 - 支持多种预定义场景""" + + # 场景预设配置 + SCENES = { + 'intersection_4way': { + 'description': '四路交叉口场景', + 'pedestrian_behavior': ['crossing', 'walking'], + 'vehicle_density': 0.7, + 'pedestrian_density': 0.8, + 'weather_variations': ['clear', 'cloudy'] + }, + 'highway': { + 'description': '高速公路场景', + 'pedestrian_behavior': [], + 'vehicle_density': 0.9, + 'pedestrian_density': 0.0, + 'weather_variations': ['clear', 'rainy'] + }, + 'urban_street': { + 'description': '城市街道场景', + 'pedestrian_behavior': ['walking', 'crossing', 'waiting'], + 'vehicle_density': 0.6, + 'pedestrian_density': 0.7, + 'weather_variations': ['clear', 'cloudy', 'rainy'] + }, + 'night_scene': { + 'description': '夜间场景', + 'pedestrian_behavior': ['walking'], + 'vehicle_density': 0.5, + 'pedestrian_density': 0.4, + 'weather_variations': ['clear'], + 'time_of_day': 'night' + }, + 'rainy_intersection': { + 'description': '雨天交叉口', + 'pedestrian_behavior': ['crossing'], + 'vehicle_density': 0.6, + 'pedestrian_density': 0.5, + 'weather_variations': ['rainy'] + } + } + + @staticmethod + def setup_scene(world, config, scene_type='intersection_4way'): + """设置特定场景""" + if scene_type not in SceneManager.SCENES: + print(f"未知场景类型: {scene_type}") + return config + + scene_config = SceneManager.SCENES[scene_type] + + # 更新配置 + if 'time_of_day' in scene_config: + config['scenario']['time_of_day'] = scene_config['time_of_day'] + + if scene_config['weather_variations']: + config['scenario']['weather'] = random.choice(scene_config['weather_variations']) + + # 调整交通密度 + base_vehicles = config['traffic']['background_vehicles'] + base_pedestrians = config['traffic']['pedestrians'] + + config['traffic']['background_vehicles'] = int(base_vehicles * scene_config['vehicle_density']) + config['traffic']['pedestrians'] = int(base_pedestrians * scene_config['pedestrian_density']) + + # 设置行人行为 + config['traffic']['pedestrian_behaviors'] = scene_config['pedestrian_behavior'] + + # 应用场景特定设置 + SceneManager._apply_scene_specifics(world, scene_type) + + return config + + @staticmethod + def _apply_scene_specifics(world, scene_type): + """应用场景特定的设置""" + try: + if scene_type == 'highway': + # 高速公路场景:设置较高车速 + for actor in world.get_actors(): + if 'vehicle' in actor.type_id: + try: + actor.enable_constant_velocity(carla.Vector3D(25, 0, 0)) + except: + pass + + elif scene_type == 'night_scene': + # 夜间场景:调整灯光 + for actor in world.get_actors(): + if 'vehicle' in actor.type_id: + try: + light_state = carla.VehicleLightState.Position | carla.VehicleLightState.LowBeam + actor.set_light_state(light_state) + except: + pass + + except Exception as e: + print(f"场景特定设置失败: {e}") + + @staticmethod + def spawn_traffic_cones(world, center_location, num_cones=10): + """生成交通锥桶""" + blueprint_lib = world.get_blueprint_library() + cone_bp = blueprint_lib.find('static.prop.roadcone') + + cones = [] + for i in range(num_cones): + try: + angle = random.uniform(0, 2 * math.pi) + distance = random.uniform(3.0, 8.0) + + location = carla.Location( + x=center_location.x + distance * math.cos(angle), + y=center_location.y + distance * math.sin(angle), + z=center_location.z + 0.2 + ) + + rotation = carla.Rotation(0, random.uniform(0, 360), 0) + transform = carla.Transform(location, rotation) + + cone = world.spawn_actor(cone_bp, transform) + cones.append(cone) + + except Exception as e: + print(f"生成交通锥桶失败: {e}") + + return cones + + @staticmethod + def spawn_construction_barriers(world, center_location, num_barriers=5): + """生成施工障碍物""" + blueprint_lib = world.get_blueprint_library() + barrier_bp = blueprint_lib.find('static.prop.constructionbarrier') + + barriers = [] + for i in range(num_barriers): + try: + angle = random.uniform(0, 2 * math.pi) + distance = random.uniform(5.0, 12.0) + + location = carla.Location( + x=center_location.x + distance * math.cos(angle), + y=center_location.y + distance * math.sin(angle), + z=center_location.z + 0.2 + ) + + rotation = carla.Rotation(0, random.uniform(0, 360), 0) + transform = carla.Transform(location, rotation) + + barrier = world.spawn_actor(barrier_bp, transform) + barriers.append(barrier) + + except Exception as e: + print(f"生成施工障碍物失败: {e}") + + return barriers + + @staticmethod + def setup_traffic_accident(world, center_location, severity='minor'): + """设置交通事故场景""" + blueprint_lib = world.get_blueprint_library() + + # 生成事故车辆 + accident_vehicles = [] + for i in range(2): # 两车事故 + try: + vehicle_bp = random.choice(list(blueprint_lib.filter('vehicle.*'))) + + # 稍微偏移位置,模拟碰撞 + offset_x = random.uniform(-3.0, 3.0) if i == 0 else random.uniform(2.0, 5.0) + offset_y = random.uniform(-2.0, 2.0) if i == 0 else random.uniform(-5.0, -2.0) + + location = carla.Location( + x=center_location.x + offset_x, + y=center_location.y + offset_y, + z=center_location.z + 0.5 + ) + + # 设置碰撞角度 + rotation = carla.Rotation( + 0, + random.uniform(-30, 30) + (90 if i == 1 else 0), + 0 + ) + + transform = carla.Transform(location, rotation) + vehicle = world.spawn_actor(vehicle_bp, transform) + + # 根据严重程度设置车辆损坏 + if severity == 'major': + vehicle.set_light_state(carla.VehicleLightState.Hazard) + + accident_vehicles.append(vehicle) + + except Exception as e: + print(f"生成事故车辆失败: {e}") + + # 生成应急车辆(警车、救护车) + emergency_vehicles = [] + try: + police_bp = blueprint_lib.find('vehicle.dodge.charger_police') + if police_bp: + location = carla.Location( + x=center_location.x - 8.0, + y=center_location.y + 4.0, + z=center_location.z + 0.5 + ) + rotation = carla.Rotation(0, 45, 0) + police = world.spawn_actor(police_bp, carla.Transform(location, rotation)) + police.set_light_state(carla.VehicleLightState.Special1) + emergency_vehicles.append(police) + except: + pass + + return accident_vehicles, emergency_vehicles + + @staticmethod + def save_scene_description(output_dir, scene_type, config, extra_info=None): + """保存场景描述文件""" + scene_info = { + 'scene_type': scene_type, + 'description': SceneManager.SCENES.get(scene_type, {}).get('description', '未知场景'), + 'config': config, + 'created': SceneManager._get_timestamp(), + 'extra_info': extra_info or {} + } + + scene_file = os.path.join(output_dir, "metadata", "scene_description.json") + with open(scene_file, 'w', encoding='utf-8') as f: + json.dump(scene_info, f, indent=2, ensure_ascii=False) + + print(f"场景描述保存: {scene_file}") + + @staticmethod + def _get_timestamp(): + from datetime import datetime + return datetime.now().isoformat() \ No newline at end of file From 8704ed79206674fae74ca61434cdb609f762db50 Mon Sep 17 00:00:00 2001 From: Z-w-7799 <1011069369@qq.com> Date: Thu, 18 Dec 2025 22:29:03 +0800 Subject: [PATCH 02/20] =?UTF-8?q?=E5=A4=9A=E8=BD=A6=E8=BE=86=E5=8D=8F?= =?UTF-8?q?=E5=90=8C=E4=B8=8EV2X=E9=80=9A=E4=BF=A1=E6=A8=A1=E6=8B=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config_manager.py | 28 ++ .../data_analyzer.py | 175 ++++++- .../data_validator.py | 204 +++++++- src/enhance_pedestrian_safety/main.py | 299 ++++++++++-- .../multi_vehicle_manager.py | 434 ++++++++++++++++++ .../v2x_communication.py | 328 +++++++++++++ 6 files changed, 1379 insertions(+), 89 deletions(-) create mode 100644 src/enhance_pedestrian_safety/multi_vehicle_manager.py create mode 100644 src/enhance_pedestrian_safety/v2x_communication.py diff --git a/src/enhance_pedestrian_safety/config_manager.py b/src/enhance_pedestrian_safety/config_manager.py index 38622ee18a..499384a98f 100644 --- a/src/enhance_pedestrian_safety/config_manager.py +++ b/src/enhance_pedestrian_safety/config_manager.py @@ -36,6 +36,23 @@ def load_config(config_file=None): 'rotation_frequency': 10 } }, + 'v2x': { + 'enabled': True, + 'communication_range': 300.0, + 'bandwidth': 10.0, + 'latency_mean': 0.05, + 'latency_std': 0.01, + 'packet_loss_rate': 0.01, + 'message_types': ['bsm', 'spat', 'map', 'rsm'] + }, + 'cooperative': { + 'num_coop_vehicles': 2, + 'enable_shared_perception': True, + 'enable_traffic_warnings': True, + 'enable_maneuver_coordination': False, + 'data_fusion_interval': 1.0, + 'max_shared_objects': 50 + }, 'output': { 'data_dir': 'cvips_dataset', 'save_raw': True, @@ -43,6 +60,8 @@ def load_config(config_file=None): 'save_annotations': False, 'save_lidar': True, 'save_fusion': True, + 'save_cooperative': True, + 'save_v2x_messages': True, 'validate_data': True, 'run_analysis': False } @@ -86,9 +105,15 @@ def merge_args(config, args): if args.num_pedestrians: config['traffic']['pedestrians'] = args.num_pedestrians + if args.num_coop_vehicles: + config['cooperative']['num_coop_vehicles'] = args.num_coop_vehicles + if args.capture_interval: config['sensors']['capture_interval'] = args.capture_interval + if args.enable_v2x: + config['v2x']['enabled'] = True + if args.enable_lidar: config['sensors']['lidar_sensors'] = 1 config['output']['save_lidar'] = True @@ -96,6 +121,9 @@ def merge_args(config, args): if args.enable_fusion: config['output']['save_fusion'] = True + if args.enable_cooperative: + config['output']['save_cooperative'] = True + if args.enable_annotations: config['output']['save_annotations'] = True diff --git a/src/enhance_pedestrian_safety/data_analyzer.py b/src/enhance_pedestrian_safety/data_analyzer.py index a5802c2dc4..069d6d2ec7 100644 --- a/src/enhance_pedestrian_safety/data_analyzer.py +++ b/src/enhance_pedestrian_safety/data_analyzer.py @@ -17,6 +17,7 @@ def analyze_dataset(data_dir): 'file_distribution': DataAnalyzer._analyze_file_distribution(data_dir), 'object_statistics': DataAnalyzer._analyze_objects(data_dir), 'temporal_analysis': DataAnalyzer._analyze_temporal(data_dir), + 'cooperative_data': DataAnalyzer._analyze_cooperative_data(data_dir), 'quality_metrics': DataAnalyzer._calculate_quality_metrics(data_dir) } @@ -77,18 +78,21 @@ def _analyze_file_distribution(data_dir): distribution = {} # 分析原始图像 - raw_dirs = ['vehicle', 'infrastructure'] + raw_dirs = [] + raw_path = os.path.join(data_dir, "raw") + if os.path.exists(raw_path): + raw_dirs = [d for d in os.listdir(raw_path) if os.path.isdir(os.path.join(raw_path, d))] + for raw_dir in raw_dirs: path = os.path.join(data_dir, "raw", raw_dir) - if os.path.exists(path): - camera_stats = {} - for camera_dir in os.listdir(path): - camera_path = os.path.join(path, camera_dir) - if os.path.isdir(camera_path): - images = [f for f in os.listdir(camera_path) - if f.endswith(('.png', '.jpg', '.jpeg'))] - camera_stats[camera_dir] = len(images) - distribution[f'raw_{raw_dir}'] = camera_stats + camera_stats = {} + for camera_dir in os.listdir(path): + camera_path = os.path.join(path, camera_dir) + if os.path.isdir(camera_path): + images = [f for f in os.listdir(camera_path) + if f.endswith(('.png', '.jpg', '.jpeg'))] + camera_stats[camera_dir] = len(images) + distribution[f'raw_{raw_dir}'] = camera_stats # 分析拼接图像 stitched_dir = os.path.join(data_dir, "stitched") @@ -103,6 +107,19 @@ def _analyze_file_distribution(data_dir): json_files = [f for f in os.listdir(annotations_dir) if f.endswith('.json')] distribution['annotations'] = len(json_files) + # 分析LiDAR数据 + lidar_dir = os.path.join(data_dir, "lidar") + if os.path.exists(lidar_dir): + bin_files = [f for f in os.listdir(lidar_dir) if f.endswith('.bin')] + npy_files = [f for f in os.listdir(lidar_dir) if f.endswith('.npy')] + distribution['lidar'] = {'bin': len(bin_files), 'npy': len(npy_files)} + + # 分析融合数据 + fusion_dir = os.path.join(data_dir, "fusion") + if os.path.exists(fusion_dir): + json_files = [f for f in os.listdir(fusion_dir) if f.endswith('.json')] + distribution['fusion'] = len(json_files) + return distribution @staticmethod @@ -111,7 +128,7 @@ def _analyze_objects(data_dir): annotations_dir = os.path.join(data_dir, "annotations") if not os.path.exists(annotations_dir): - return {'total_objects': 0, 'by_class': {}, 'by_frame': {}} + return {'total_objects': 0, 'by_class': {}, 'by_frame': {}, 'class_distribution': {}} object_stats = { 'total_objects': 0, @@ -173,6 +190,78 @@ def _analyze_temporal(data_dir): return temporal_stats + @staticmethod + def _analyze_cooperative_data(data_dir): + """分析协同数据""" + coop_dir = os.path.join(data_dir, "cooperative") + + if not os.path.exists(coop_dir): + return { + 'v2x_messages': 0, + 'shared_perception': 0, + 'vehicles_count': 0, + 'communication_stats': {} + } + + # V2X消息统计 + v2x_dir = os.path.join(coop_dir, "v2x_messages") + v2x_files = [] + if os.path.exists(v2x_dir): + v2x_files = [f for f in os.listdir(v2x_dir) if f.endswith('.json')] + + # 共享感知统计 + perception_dir = os.path.join(coop_dir, "shared_perception") + perception_files = [] + if os.path.exists(perception_dir): + perception_files = [f for f in os.listdir(perception_dir) if f.endswith('.json')] + + # 读取协同摘要 + coop_summary = {} + summary_file = os.path.join(coop_dir, "cooperative_summary.json") + if os.path.exists(summary_file): + try: + with open(summary_file, 'r') as f: + coop_summary = json.load(f) + except: + pass + + # 分析V2X消息内容 + v2x_stats = { + 'total_messages': len(v2x_files), + 'message_types': defaultdict(int), + 'average_message_size': 0 + } + + if v2x_files: + total_size = 0 + for v2x_file in v2x_files[:min(10, len(v2x_files))]: # 抽样分析 + try: + with open(os.path.join(v2x_dir, v2x_file), 'r') as f: + data = json.load(f) + message_type = data.get('message', {}).get('message_type', 'unknown') + v2x_stats['message_types'][message_type] += 1 + + file_size = os.path.getsize(os.path.join(v2x_dir, v2x_file)) + total_size += file_size + except: + pass + + v2x_stats['average_message_size'] = round(total_size / max(len(v2x_files), 1), 2) + + analysis = { + 'v2x_messages': len(v2x_files), + 'shared_perception_frames': len(perception_files), + 'total_vehicles': coop_summary.get('total_vehicles', 0), + 'ego_vehicles': coop_summary.get('ego_vehicles', 0), + 'cooperative_vehicles': coop_summary.get('cooperative_vehicles', 0), + 'v2x_stats': v2x_stats, + 'shared_objects_count': coop_summary.get('shared_objects_count', 0), + 'communication_range': coop_summary.get('communication_range', 0), + 'collaborative_detections': coop_summary.get('v2x_stats', {}).get('collaborative_detections', 0) + } + + return analysis + @staticmethod def _calculate_quality_metrics(data_dir): """计算质量指标""" @@ -180,20 +269,22 @@ def _calculate_quality_metrics(data_dir): 'completeness_score': 0, 'consistency_score': 0, 'diversity_score': 0, + 'cooperative_score': 0, 'issues_found': [] } # 检查完整性 - required_dirs = ["raw/vehicle", "raw/infrastructure", "stitched", "metadata"] + required_dirs = ["raw/vehicle", "raw/infrastructure", "stitched", "metadata", "cooperative"] missing_dirs = [] for dir_path in required_dirs: - if not os.path.exists(os.path.join(data_dir, dir_path)): + full_path = os.path.join(data_dir, dir_path) + if not os.path.exists(full_path): missing_dirs.append(dir_path) if missing_dirs: quality_metrics['issues_found'].append(f"缺失目录: {missing_dirs}") - quality_metrics['completeness_score'] = 50 + quality_metrics['completeness_score'] = 100 - (len(missing_dirs) * 20) else: quality_metrics['completeness_score'] = 100 @@ -209,7 +300,7 @@ def _calculate_quality_metrics(data_dir): camera_counts.append(len(images)) if camera_counts: - max_diff = max(camera_counts) - min(camera_counts) + max_diff = max(camera_counts) - min(camera_counts) if camera_counts else 0 if max_diff > 5: quality_metrics['issues_found'].append(f"摄像头图像数量不一致: 差异{max_diff}") quality_metrics['consistency_score'] = 70 @@ -228,16 +319,27 @@ def _calculate_quality_metrics(data_dir): quality_metrics['diversity_score'] = 50 quality_metrics['issues_found'].append(f"物体类别较少: {num_classes}类") + # 协同评分 + cooperative_data = DataAnalyzer._analyze_cooperative_data(data_dir) + if cooperative_data['v2x_messages'] > 10 and cooperative_data['shared_perception_frames'] > 5: + quality_metrics['cooperative_score'] = 90 + elif cooperative_data['v2x_messages'] > 0 or cooperative_data['shared_perception_frames'] > 0: + quality_metrics['cooperative_score'] = 60 + else: + quality_metrics['cooperative_score'] = 30 + quality_metrics['issues_found'].append("协同数据较少") + return quality_metrics @staticmethod def _calculate_overall_score(analysis): """计算总体评分""" weights = { - 'completeness': 0.3, - 'consistency': 0.25, - 'diversity': 0.25, - 'temporal': 0.2 + 'completeness': 0.25, + 'consistency': 0.20, + 'diversity': 0.15, + 'cooperative': 0.20, + 'temporal': 0.20 } quality = analysis['quality_metrics'] @@ -245,7 +347,8 @@ def _calculate_overall_score(analysis): score = ( quality['completeness_score'] * weights['completeness'] + quality['consistency_score'] * weights['consistency'] + - quality['diversity_score'] * weights['diversity'] + quality['diversity_score'] * weights['diversity'] + + quality['cooperative_score'] * weights['cooperative'] ) # 时间因素 @@ -257,7 +360,7 @@ def _calculate_overall_score(analysis): else: score += 5 * weights['temporal'] - return round(score, 1) + return round(min(score, 100), 1) @staticmethod def _save_analysis_report(data_dir, analysis): @@ -292,7 +395,11 @@ def _print_analysis_summary(analysis): if isinstance(value, dict): print(f" {key}:") for subkey, subvalue in value.items(): - print(f" {subkey}: {subvalue}") + if isinstance(subvalue, dict): + for subsubkey, subsubvalue in subvalue.items(): + print(f" {subsubkey}: {subsubvalue}") + else: + print(f" {subkey}: {subvalue}") else: print(f" {key}: {value}") @@ -305,6 +412,22 @@ def _print_analysis_summary(analysis): percentage = objects['class_distribution'].get(obj_class, 0) print(f" {obj_class}: {count} ({percentage}%)") + # 协同数据分析 + cooperative = analysis['cooperative_data'] + print(f"\n协同数据分析:") + print(f" V2X消息: {cooperative['v2x_messages']}") + print(f" 共享感知帧: {cooperative['shared_perception_frames']}") + print(f" 车辆总数: {cooperative['total_vehicles']}") + print(f" 主车: {cooperative['ego_vehicles']}") + print(f" 协同车: {cooperative['cooperative_vehicles']}") + print(f" 共享对象数: {cooperative['shared_objects_count']}") + print(f" 协作检测数: {cooperative['collaborative_detections']}") + + if cooperative['v2x_stats']['message_types']: + print(f" V2X消息类型:") + for msg_type, count in cooperative['v2x_stats']['message_types'].items(): + print(f" {msg_type}: {count}") + temporal = analysis['temporal_analysis'] print(f"\n时间分析:") print(f" 总时长: {temporal['total_duration']:.1f}秒") @@ -315,6 +438,7 @@ def _print_analysis_summary(analysis): print(f" 完整性: {quality['completeness_score']}/100") print(f" 一致性: {quality['consistency_score']}/100") print(f" 多样性: {quality['diversity_score']}/100") + print(f" 协同性: {quality['cooperative_score']}/100") if quality['issues_found']: print(f" 发现的问题 ({len(quality['issues_found'])}):") @@ -325,11 +449,12 @@ def _print_analysis_summary(analysis): print(f"\n总体评分: {analysis['overall_score']}/100") - if analysis['overall_score'] >= 90: + overall_score = analysis['overall_score'] + if overall_score >= 90: print("✓ 数据集质量优秀") - elif analysis['overall_score'] >= 75: + elif overall_score >= 75: print("✓ 数据集质量良好") - elif analysis['overall_score'] >= 60: + elif overall_score >= 60: print("⚠ 数据集质量一般") else: print("✗ 数据集质量需要改进") diff --git a/src/enhance_pedestrian_safety/data_validator.py b/src/enhance_pedestrian_safety/data_validator.py index f015231a42..fa67f6425b 100644 --- a/src/enhance_pedestrian_safety/data_validator.py +++ b/src/enhance_pedestrian_safety/data_validator.py @@ -14,7 +14,9 @@ def validate_dataset(data_dir): 'stitched_images': DataValidator._validate_stitched_images(data_dir), 'annotations': DataValidator._validate_annotations(data_dir), 'metadata': DataValidator._validate_metadata(data_dir), - 'lidar_data': DataValidator._validate_lidar_data(data_dir) + 'lidar_data': DataValidator._validate_lidar_data(data_dir), + 'cooperative_data': DataValidator._validate_cooperative_data(data_dir), + 'fusion_data': DataValidator._validate_fusion_data(data_dir) } validation_results['overall_score'] = DataValidator._calculate_score(validation_results) @@ -30,7 +32,11 @@ def _check_directory_structure(data_dir): "raw/vehicle", "raw/infrastructure", "stitched", - "metadata" + "metadata", + "cooperative", + "cooperative/v2x_messages", + "cooperative/shared_perception", + "fusion" ] missing_dirs = [] @@ -51,14 +57,17 @@ def _check_directory_structure(data_dir): @staticmethod def _validate_raw_images(data_dir): - raw_dirs = ["vehicle", "infrastructure"] + raw_path = os.path.join(data_dir, "raw") + + if not os.path.exists(raw_path): + return {'vehicle': {'status': 'MISSING', 'count': 0, 'errors': []}, + 'infrastructure': {'status': 'MISSING', 'count': 0, 'errors': []}} + + raw_dirs = [d for d in os.listdir(raw_path) if os.path.isdir(os.path.join(raw_path, d))] results = {} for raw_dir in raw_dirs: path = os.path.join(data_dir, "raw", raw_dir) - if not os.path.exists(path): - results[raw_dir] = {'status': 'MISSING', 'count': 0, 'errors': []} - continue camera_dirs = [] if os.path.exists(path): @@ -143,6 +152,13 @@ def _validate_annotations(data_dir): try: with open(json_path, 'r') as f: data = json.load(f) + + # 检查基本结构 + required_keys = ['frame_id', 'objects'] + for key in required_keys: + if key not in data: + errors.append(f"缺失必要键: {key} in {json_file}") + valid_files += 1 except Exception as e: errors.append(f"无效的JSON文件: {json_file} - {str(e)}") @@ -201,19 +217,152 @@ def _validate_lidar_data(data_dir): return {'status': 'MISSING', 'count': 0, 'errors': []} bin_files = [f for f in os.listdir(lidar_dir) if f.endswith('.bin')] + npy_files = [f for f in os.listdir(lidar_dir) if f.endswith('.npy')] + json_files = [f for f in os.listdir(lidar_dir) if f.endswith('.json')] + errors = [] - valid_files = 0 + valid_bin_files = 0 for bin_file in bin_files: bin_path = os.path.join(lidar_dir, bin_file) try: if os.path.getsize(bin_path) > 0: - valid_files += 1 + valid_bin_files += 1 else: errors.append(f"空文件: {bin_file}") except: errors.append(f"文件访问失败: {bin_file}") + total_files = len(bin_files) + len(npy_files) + len(json_files) + + if len(errors) == 0 and valid_bin_files > 0: + status = 'PASS' + elif len(errors) < 3 and valid_bin_files > 0: + status = 'WARNING' + else: + status = 'FAIL' + + return { + 'status': status, + 'count': total_files, + 'bin_files': len(bin_files), + 'npy_files': len(npy_files), + 'json_files': len(json_files), + 'errors': errors + } + + @staticmethod + def _validate_cooperative_data(data_dir): + """验证协同数据""" + coop_dir = os.path.join(data_dir, "cooperative") + + if not os.path.exists(coop_dir): + return {'status': 'MISSING', 'count': 0, 'errors': [], 'v2x_messages': 0, 'shared_perception': 0} + + errors = [] + + # 检查V2X消息目录 + v2x_dir = os.path.join(coop_dir, "v2x_messages") + v2x_files = [] + if os.path.exists(v2x_dir): + v2x_files = [f for f in os.listdir(v2x_dir) if f.endswith('.json')] + for v2x_file in v2x_files[:min(5, len(v2x_files))]: # 检查前5个文件 + try: + with open(os.path.join(v2x_dir, v2x_file), 'r') as f: + data = json.load(f) + + # 检查必要字段 + required_keys = ['message', 'recipients', 'transmission_time'] + for key in required_keys: + if key not in data: + errors.append(f"V2X消息缺失字段 {key}: {v2x_file}") + except Exception as e: + errors.append(f"V2X消息文件无效: {v2x_file} - {str(e)}") + else: + errors.append("V2X消息目录不存在") + + # 检查共享感知目录 + perception_dir = os.path.join(coop_dir, "shared_perception") + perception_files = [] + if os.path.exists(perception_dir): + perception_files = [f for f in os.listdir(perception_dir) if f.endswith('.json')] + for perception_file in perception_files[:min(5, len(perception_files))]: + try: + with open(os.path.join(perception_dir, perception_file), 'r') as f: + data = json.load(f) + + # 检查必要字段 + required_keys = ['frame_id', 'timestamp', 'shared_objects'] + for key in required_keys: + if key not in data: + errors.append(f"共享感知文件缺失字段 {key}: {perception_file}") + except Exception as e: + errors.append(f"共享感知文件无效: {perception_file} - {str(e)}") + else: + errors.append("共享感知目录不存在") + + # 检查协同摘要 + summary_file = os.path.join(coop_dir, "cooperative_summary.json") + if not os.path.exists(summary_file): + errors.append("协同摘要文件不存在") + else: + try: + with open(summary_file, 'r') as f: + data = json.load(f) + + # 检查摘要字段 + required_keys = ['total_vehicles', 'ego_vehicles', 'cooperative_vehicles', 'v2x_stats'] + for key in required_keys: + if key not in data: + errors.append(f"协同摘要缺失字段: {key}") + except Exception as e: + errors.append(f"协同摘要文件无效: {str(e)}") + + total_files = len(v2x_files) + len(perception_files) + + if len(errors) == 0 and total_files > 0: + status = 'PASS' + elif len(errors) < 3 and total_files > 0: + status = 'WARNING' + else: + status = 'FAIL' + + return { + 'status': status, + 'count': total_files, + 'v2x_messages': len(v2x_files), + 'shared_perception': len(perception_files), + 'errors': errors + } + + @staticmethod + def _validate_fusion_data(data_dir): + """验证融合数据""" + fusion_dir = os.path.join(data_dir, "fusion") + + if not os.path.exists(fusion_dir): + return {'status': 'MISSING', 'count': 0, 'errors': []} + + json_files = [f for f in os.listdir(fusion_dir) if f.endswith('.json')] + errors = [] + valid_files = 0 + + for json_file in json_files[:min(5, len(json_files))]: # 检查前5个文件 + json_path = os.path.join(fusion_dir, json_file) + try: + with open(json_path, 'r') as f: + data = json.load(f) + + # 检查必要字段 + required_keys = ['frame_id', 'timestamp', 'sensors'] + for key in required_keys: + if key not in data: + errors.append(f"融合文件缺失字段 {key}: {json_file}") + + valid_files += 1 + except Exception as e: + errors.append(f"融合文件无效: {json_file} - {str(e)}") + if len(errors) == 0 and valid_files > 0: status = 'PASS' elif len(errors) < 3 and valid_files > 0: @@ -223,19 +372,21 @@ def _validate_lidar_data(data_dir): return { 'status': status, - 'count': valid_files, + 'count': len(json_files), 'errors': errors } @staticmethod def _calculate_score(results): weights = { - 'directory_structure': 0.15, - 'raw_images': 0.25, - 'stitched_images': 0.15, - 'annotations': 0.1, - 'metadata': 0.15, - 'lidar_data': 0.2 + 'directory_structure': 0.10, + 'raw_images': 0.20, + 'stitched_images': 0.10, + 'annotations': 0.10, + 'metadata': 0.10, + 'lidar_data': 0.15, + 'cooperative_data': 0.15, + 'fusion_data': 0.10 } score = 0 @@ -280,13 +431,34 @@ def _print_validation_report(results): print(f"\n{key.replace('_', ' ').title()}:") if 'status' in result: - print(f" 状态: {result['status']}") + status_icon = '✓' if result['status'] == 'PASS' else '⚠' if result['status'] == 'WARNING' else '✗' + print(f" 状态: {status_icon} {result['status']}") else: print(f" 状态: 未知") if 'count' in result: print(f" 数量: {result['count']}") + # 特定类型数据的额外信息 + if key == 'raw_images' and isinstance(result, dict): + for subkey, subresult in result.items(): + if isinstance(subresult, dict) and 'count' in subresult: + print(f" {subkey}: {subresult['count']} 图像") + + if key == 'cooperative_data' and isinstance(result, dict): + if 'v2x_messages' in result: + print(f" V2X消息: {result['v2x_messages']}") + if 'shared_perception' in result: + print(f" 共享感知: {result['shared_perception']}") + + if key == 'lidar_data' and isinstance(result, dict): + if 'bin_files' in result: + print(f" BIN文件: {result['bin_files']}") + if 'npy_files' in result: + print(f" NPY文件: {result['npy_files']}") + if 'json_files' in result: + print(f" JSON文件: {result['json_files']}") + if 'errors' in result and result['errors']: print(f" 错误 ({len(result['errors'])}):") for error in result['errors'][:3]: diff --git a/src/enhance_pedestrian_safety/main.py b/src/enhance_pedestrian_safety/main.py index 64f7caf9ea..2f855d36cc 100644 --- a/src/enhance_pedestrian_safety/main.py +++ b/src/enhance_pedestrian_safety/main.py @@ -16,6 +16,8 @@ from scene_manager import SceneManager from data_analyzer import DataAnalyzer from lidar_processor import LidarProcessor, MultiSensorFusion +from multi_vehicle_manager import MultiVehicleManager +from v2x_communication import V2XCommunication carla_egg_path, remaining_argv = setup_carla_path() carla = import_carla_module() @@ -254,14 +256,14 @@ def __init__(self, world, config, data_dir): if config['output'].get('save_fusion', False): self.fusion_manager = MultiSensorFusion(data_dir) - def setup_cameras(self, vehicle, center_location): - vehicle_cams = self._setup_vehicle_cameras(vehicle) + def setup_cameras(self, vehicle, center_location, vehicle_id=0): + vehicle_cams = self._setup_vehicle_cameras(vehicle, vehicle_id) infra_cams = self._setup_infrastructure_cameras(center_location) Log.info(f"摄像头: {vehicle_cams}车辆 + {infra_cams}基础设施") return vehicle_cams + infra_cams - def _setup_vehicle_cameras(self, vehicle): + def _setup_vehicle_cameras(self, vehicle, vehicle_id): if not vehicle: return 0 @@ -274,7 +276,7 @@ def _setup_vehicle_cameras(self, vehicle): installed = 0 for cam_name, config_data in camera_configs.items(): - if self._create_camera(cam_name, config_data, vehicle, 'vehicle'): + if self._create_camera(cam_name, config_data, vehicle, 'vehicle', vehicle_id): installed += 1 return installed @@ -304,7 +306,7 @@ def _setup_infrastructure_cameras(self, center_location): return installed - def setup_lidar(self, vehicle): + def setup_lidar(self, vehicle, vehicle_id=0): if not vehicle or not self.config['sensors'].get('lidar_sensors', 0) > 0: return 0 @@ -319,7 +321,6 @@ def setup_lidar(self, vehicle): lidar_bp.set_attribute('points_per_second', str(lidar_config.get('points_per_second', 56000))) lidar_bp.set_attribute('rotation_frequency', str(lidar_config.get('rotation_frequency', 10))) - # 添加更多LiDAR参数 lidar_bp.set_attribute('upper_fov', '10') lidar_bp.set_attribute('lower_fov', '-20') lidar_bp.set_attribute('horizontal_fov', '360') @@ -337,11 +338,9 @@ def lidar_callback(lidar_data): try: metadata = self.lidar_processor.process_lidar_data(lidar_data, self.frame_counter) if metadata and self.fusion_manager: - # 尝试获取最新的车辆图像 vehicle_image_path = None with self.buffer_lock: if self.vehicle_buffer: - # 取第一个摄像头的图像 for cam_name, img_path in self.vehicle_buffer.items(): if os.path.exists(img_path): vehicle_image_path = img_path @@ -366,7 +365,8 @@ def lidar_callback(lidar_data): except Exception as e: print(f"LiDAR安装失败: {e}") return 0 - def _create_camera(self, name, config, parent, sensor_type): + + def _create_camera(self, name, config, parent, sensor_type, vehicle_id=0): try: blueprint = self.world.get_blueprint_library().find('sensor.camera.rgb') @@ -384,10 +384,15 @@ def _create_camera(self, name, config, parent, sensor_type): else: camera = self.world.spawn_actor(blueprint, transform) - save_dir = os.path.join(self.data_dir, "raw", sensor_type, name) + # 为不同车辆创建不同目录 + if sensor_type == 'vehicle' and vehicle_id > 0: + save_dir = os.path.join(self.data_dir, "raw", f"vehicle_{vehicle_id}", name) + else: + save_dir = os.path.join(self.data_dir, "raw", sensor_type, name) + os.makedirs(save_dir, exist_ok=True) - callback = self._create_callback(save_dir, name, sensor_type) + callback = self._create_callback(save_dir, name, sensor_type, vehicle_id) camera.listen(callback) self.sensors.append(camera) @@ -397,7 +402,7 @@ def _create_camera(self, name, config, parent, sensor_type): Log.warning(f"创建摄像头 {name} 失败: {e}") return False - def _create_callback(self, save_dir, name, sensor_type): + def _create_callback(self, save_dir, name, sensor_type, vehicle_id=0): capture_interval = self.config['sensors']['capture_interval'] def callback(image): @@ -414,7 +419,8 @@ def callback(image): if sensor_type == 'vehicle': self.vehicle_buffer[name] = filename if len(self.vehicle_buffer) >= 4: - self.image_processor.stitch(self.vehicle_buffer, self.frame_counter, 'vehicle') + self.image_processor.stitch(self.vehicle_buffer, self.frame_counter, + f'vehicle_{vehicle_id}') self.vehicle_buffer.clear() else: self.infra_buffer[name] = filename @@ -459,13 +465,15 @@ def __init__(self, config): self.config = config self.client = None self.world = None - self.ego_vehicle = None + self.ego_vehicles = [] # 多个主车 self.scene_center = None self.setup_directories() self.traffic_manager = None - self.sensor_manager = None + self.sensor_managers = {} # 车辆ID -> SensorManager + self.multi_vehicle_manager = None + self.v2x_communication = None self.start_time = None self.is_running = False @@ -481,12 +489,15 @@ def setup_directories(self): ) directories = [ - "raw/vehicle", + "raw/vehicle_1", # 主车1 + "raw/vehicle_2", # 主车2(如果有) "raw/infrastructure", "stitched", "lidar", "fusion", "calibration", + "cooperative", + "v2x_messages", "metadata" ] @@ -531,26 +542,74 @@ def setup_scene(self): self.traffic_manager = TrafficManager(self.world, self.config) - self.ego_vehicle = self.traffic_manager.spawn_ego_vehicle() - if not self.ego_vehicle: + # 生成多个主车 + num_ego_vehicles = min(self.config['cooperative'].get('num_coop_vehicles', 2) + 1, 3) + for i in range(num_ego_vehicles): + ego_vehicle = self.traffic_manager.spawn_ego_vehicle() + if ego_vehicle: + self.ego_vehicles.append(ego_vehicle) + Log.info(f"主车 {i + 1} 生成: {ego_vehicle.type_id}") + + if not self.ego_vehicles: Log.error("主车生成失败") return False self.traffic_manager.spawn_traffic(self.scene_center) + # 初始化V2X通信 + if self.config['v2x']['enabled']: + self.v2x_communication = V2XCommunication(self.config['v2x']) + + # 注册车辆到V2X网络 + for i, vehicle in enumerate(self.ego_vehicles): + location = vehicle.get_location() + self.v2x_communication.register_node( + f'vehicle_{vehicle.id}', + (location.x, location.y, location.z), + {'type': 'vehicle', 'capabilities': ['bsm', 'rsm']} + ) + + # 初始化多车辆管理器 + self.multi_vehicle_manager = MultiVehicleManager( + self.world, + self.config, + self.output_dir + ) + + # 设置主车 + self.multi_vehicle_manager.ego_vehicles = self.ego_vehicles + + # 生成协同车辆 + num_coop_vehicles = self.config['cooperative'].get('num_coop_vehicles', 2) + coop_vehicles = self.multi_vehicle_manager.spawn_cooperative_vehicles(num_coop_vehicles) + + # 注册协同车辆到V2X网络 + if self.v2x_communication: + for vehicle in coop_vehicles: + location = vehicle.get_location() + self.v2x_communication.register_node( + f'vehicle_{vehicle.id}', + (location.x, location.y, location.z), + {'type': 'vehicle', 'capabilities': ['bsm', 'rsm']} + ) + time.sleep(3.0) return True def setup_sensors(self): - self.sensor_manager = SensorManager(self.world, self.config, self.output_dir) + # 为每个主车设置传感器 + for i, vehicle in enumerate(self.ego_vehicles): + sensor_manager = SensorManager(self.world, self.config, self.output_dir) - cameras = self.sensor_manager.setup_cameras(self.ego_vehicle, self.scene_center) - if cameras == 0: - Log.error("没有摄像头安装成功") - return False + cameras = sensor_manager.setup_cameras(vehicle, self.scene_center, i + 1) + if cameras == 0: + Log.error(f"车辆 {i + 1} 没有摄像头安装成功") + return False + + lidars = sensor_manager.setup_lidar(vehicle, i + 1) + Log.info(f"车辆 {i + 1} 传感器: {cameras}摄像头 + {lidars}LiDAR") - lidars = self.sensor_manager.setup_lidar(self.ego_vehicle) - Log.info(f"传感器: {cameras}摄像头 + {lidars}LiDAR") + self.sensor_managers[vehicle.id] = sensor_manager return True @@ -562,17 +621,39 @@ def collect_data(self): self.is_running = True last_update = time.time() + last_v2x_update = time.time() + last_perception_share = time.time() try: while time.time() - self.start_time < duration and self.is_running: current_time = time.time() elapsed = current_time - self.start_time + # 更新车辆状态 + if self.multi_vehicle_manager: + self.multi_vehicle_manager.update_vehicle_states() + + # V2X通信更新 + if self.v2x_communication and current_time - last_v2x_update >= 0.1: + self._update_v2x_communication() + last_v2x_update = current_time + + # 共享感知数据 + if (self.config['cooperative'].get('enable_shared_perception', True) and + current_time - last_perception_share >= 2.0): + self._share_perception_data() + last_perception_share = current_time + + # 定期保存共享感知 + frame_count = sum(mgr.get_frame_count() for mgr in self.sensor_managers.values()) + if frame_count % 10 == 0 and self.multi_vehicle_manager: + self.multi_vehicle_manager.save_shared_perception(frame_count) + if current_time - last_update >= 5.0: - frames = self.sensor_manager.get_frame_count() + total_frames = sum(mgr.get_frame_count() for mgr in self.sensor_managers.values()) progress = (elapsed / duration) * 100 - Log.info(f"进度: {elapsed:.0f}/{duration}秒 ({progress:.1f}%) | 帧数: {frames}") + Log.info(f"进度: {elapsed:.0f}/{duration}秒 ({progress:.1f}%) | 总帧数: {total_frames}") last_update = current_time time.sleep(0.05) @@ -583,17 +664,99 @@ def collect_data(self): self.is_running = False elapsed = time.time() - self.start_time - self.collected_frames = self.sensor_manager.get_frame_count() + self.collected_frames = sum(mgr.get_frame_count() for mgr in self.sensor_managers.values()) Log.info(f"收集完成: {self.collected_frames}帧, 用时: {elapsed:.1f}秒") self._save_metadata() self._print_summary() + def _update_v2x_communication(self): + """更新V2X通信""" + if not self.v2x_communication: + return + + # 为每辆车发送基本安全消息 + for vehicle in self.ego_vehicles + self.multi_vehicle_manager.cooperative_vehicles: + if not vehicle.is_alive: + continue + + try: + location = vehicle.get_location() + velocity = vehicle.get_velocity() + speed = math.sqrt(velocity.x ** 2 + velocity.y ** 2 + velocity.z ** 2) + + vehicle_data = { + 'position': (location.x, location.y, location.z), + 'speed': speed, + 'heading': vehicle.get_transform().rotation.yaw, + 'acceleration': (0, 0, 0) # 简化处理 + } + + self.v2x_communication.broadcast_basic_safety_message( + f'vehicle_{vehicle.id}', + vehicle_data + ) + except: + pass + + # 处理接收到的消息 + for vehicle in self.ego_vehicles: + messages = self.v2x_communication.get_messages_for_node(f'vehicle_{vehicle.id}') + if messages: + Log.debug(f"车辆 {vehicle.id} 收到 {len(messages)} 条V2X消息") + + def _share_perception_data(self): + """共享感知数据""" + if not self.multi_vehicle_manager or not self.config['cooperative']['enable_shared_perception']: + return + + # 模拟车辆感知数据(简化处理,实际应从传感器获取) + for vehicle in self.ego_vehicles + self.multi_vehicle_manager.cooperative_vehicles: + if not vehicle.is_alive: + continue + + # 模拟检测到的物体 + detected_objects = self._simulate_object_detection(vehicle) + + if detected_objects: + self.multi_vehicle_manager.share_perception_data(vehicle.id, detected_objects) + + def _simulate_object_detection(self, vehicle): + """模拟对象检测(简化)""" + detected_objects = [] + + # 获取车辆周围的其他车辆 + for other_vehicle in self.ego_vehicles + self.multi_vehicle_manager.cooperative_vehicles: + if other_vehicle.id == vehicle.id or not other_vehicle.is_alive: + continue + + try: + location = other_vehicle.get_location() + distance = vehicle.get_location().distance(location) + + # 模拟检测范围(50米) + if distance < 50.0: + obj_data = { + 'class': 'vehicle', + 'position': {'x': location.x, 'y': location.y, 'z': location.z}, + 'velocity': {'x': 0, 'y': 0, 'z': 0}, + 'confidence': max(0.7, 1.0 - distance / 50.0), + 'size': {'width': 2.0, 'length': 4.5, 'height': 1.5}, + 'id': other_vehicle.id + } + detected_objects.append(obj_data) + except: + pass + + return detected_objects + def _save_metadata(self): metadata = { 'scenario': self.config['scenario'], 'traffic': self.config['traffic'], 'sensors': self.config['sensors'], + 'v2x': self.config['v2x'], + 'cooperative': self.config['cooperative'], 'output': self.config['output'], 'collection': { 'duration': round(time.time() - self.start_time, 2), @@ -602,9 +765,19 @@ def _save_metadata(self): } } - if self.sensor_manager: - sensor_summary = self.sensor_manager.generate_sensor_summary() - metadata['sensor_summary'] = sensor_summary + # 传感器摘要 + sensor_summaries = {} + for vehicle_id, sensor_manager in self.sensor_managers.items(): + sensor_summaries[vehicle_id] = sensor_manager.generate_sensor_summary() + metadata['sensor_summaries'] = sensor_summaries + + # V2X通信状态 + if self.v2x_communication: + metadata['v2x_status'] = self.v2x_communication.get_network_status() + + # 协同摘要 + if self.multi_vehicle_manager: + metadata['cooperative_summary'] = self.multi_vehicle_manager.generate_summary() meta_path = os.path.join(self.output_dir, "metadata", "collection_info.json") with open(meta_path, 'w', encoding='utf-8') as f: @@ -640,11 +813,24 @@ def _print_summary(self): total_points += points_in_file print(f" 估计总点数: {total_points:,}") - # 统计融合数据 - fusion_dir = os.path.join(self.output_dir, "fusion") - if os.path.exists(fusion_dir): - sync_files = [f for f in os.listdir(fusion_dir) if f.endswith('.json')] - print(f"融合数据: {len(sync_files)} 个同步文件") + # 统计协同数据 + coop_dir = os.path.join(self.output_dir, "cooperative") + if os.path.exists(coop_dir): + v2x_files = len([f for f in os.listdir(os.path.join(coop_dir, "v2x_messages")) if f.endswith('.json')]) + perception_files = len( + [f for f in os.listdir(os.path.join(coop_dir, "shared_perception")) if f.endswith('.json')]) + print(f"协同数据: {v2x_files} V2X消息, {perception_files} 共享感知文件") + + # V2X统计 + if self.v2x_communication: + v2x_status = self.v2x_communication.get_network_status() + print(f"V2X通信: {v2x_status['stats']['messages_sent']} 发送, " + f"{v2x_status['stats']['messages_received']} 接收, " + f"{v2x_status['stats']['messages_dropped']} 丢包") + + # 车辆统计 + print(f"车辆总数: {len(self.ego_vehicles)} 主车 + " + f"{len(self.multi_vehicle_manager.cooperative_vehicles)} 协同车") print(f"\n输出目录: {self.output_dir}") print("=" * 60) @@ -662,27 +848,39 @@ def run_analysis(self): def cleanup(self): Log.info("清理场景...") - if self.sensor_manager: - self.sensor_manager.cleanup() + # 清理传感器 + for sensor_manager in self.sensor_managers.values(): + sensor_manager.cleanup() + # 清理交通 if self.traffic_manager: self.traffic_manager.cleanup() - if self.ego_vehicle and self.ego_vehicle.is_alive: - try: - self.ego_vehicle.destroy() - except: - pass + # 清理协同管理 + if self.multi_vehicle_manager: + self.multi_vehicle_manager.cleanup() + + # 清理V2X通信 + if self.v2x_communication: + self.v2x_communication.stop() + + # 清理车辆 + for vehicle in self.ego_vehicles: + if vehicle and vehicle.is_alive: + try: + vehicle.destroy() + except: + pass Log.info("清理完成") def main(): - parser = argparse.ArgumentParser(description='CVIPS 多传感器数据收集系统') + parser = argparse.ArgumentParser(description='CVIPS 多车辆协同数据收集系统 v10.0') # 基础参数 parser.add_argument('--config', type=str, help='配置文件路径') - parser.add_argument('--scenario', type=str, default='multi_sensor_scene', help='场景名称') + parser.add_argument('--scenario', type=str, default='multi_vehicle_cooperative', help='场景名称') parser.add_argument('--town', type=str, default='Town10HD', choices=['Town03', 'Town04', 'Town05', 'Town10HD'], help='地图') parser.add_argument('--weather', type=str, default='clear', @@ -693,6 +891,7 @@ def main(): # 交通参数 parser.add_argument('--num-vehicles', type=int, default=8, help='背景车辆数') parser.add_argument('--num-pedestrians', type=int, default=6, help='行人数') + parser.add_argument('--num-coop-vehicles', type=int, default=2, help='协同车辆数') # 收集参数 parser.add_argument('--duration', type=int, default=60, help='收集时长(秒)') @@ -702,6 +901,8 @@ def main(): # 传感器参数 parser.add_argument('--enable-lidar', action='store_true', help='启用LiDAR传感器') parser.add_argument('--enable-fusion', action='store_true', help='启用多传感器融合') + parser.add_argument('--enable-v2x', action='store_true', help='启用V2X通信') + parser.add_argument('--enable-cooperative', action='store_true', help='启用协同感知') parser.add_argument('--enable-annotations', action='store_true', help='启用自动标注') # 功能参数 @@ -716,21 +917,23 @@ def main(): # 显示配置 print("\n" + "=" * 60) - print("CVIPS 多传感器数据收集系统 v9.0") + print("CVIPS 多车辆协同数据收集系统 v10.0") print("=" * 60) print(f"场景: {config['scenario']['name']}") print(f"地图: {config['scenario']['town']}") print(f"天气/时间: {config['scenario']['weather']}/{config['scenario']['time_of_day']}") print(f"时长: {config['scenario']['duration']}秒") - print(f"交通: {config['traffic']['background_vehicles']}车辆 + {config['traffic']['pedestrians']}行人") + print(f"交通: {config['traffic']['background_vehicles']}背景车辆 + {config['traffic']['pedestrians']}行人") + print(f"协同: {config['cooperative']['num_coop_vehicles']} 协同车辆") print(f"传感器:") print( f" 摄像头: {config['sensors']['vehicle_cameras']}车辆 + {config['sensors']['infrastructure_cameras']}基础设施") print(f" LiDAR: {'启用' if config['sensors']['lidar_sensors'] > 0 else '禁用'}") print(f" 融合: {'启用' if config['output']['save_fusion'] else '禁用'}") - print(f" 标注: {'启用' if config['output']['save_annotations'] else '禁用'}") + print(f" V2X: {'启用' if config['v2x']['enabled'] else '禁用'}") + print(f" 协同: {'启用' if config['output']['save_cooperative'] else '禁用'}") collector = DataCollector(config) diff --git a/src/enhance_pedestrian_safety/multi_vehicle_manager.py b/src/enhance_pedestrian_safety/multi_vehicle_manager.py new file mode 100644 index 0000000000..0f4492829c --- /dev/null +++ b/src/enhance_pedestrian_safety/multi_vehicle_manager.py @@ -0,0 +1,434 @@ +import random +import math +import carla +import time +import json +import os +from collections import defaultdict +from typing import List, Dict, Tuple, Optional +from dataclasses import dataclass, asdict + + +@dataclass +class VehicleState: + """车辆状态信息""" + vehicle_id: int + type_id: str + position: Tuple[float, float, float] + velocity: Tuple[float, float, float] + rotation: Tuple[float, float, float] + timestamp: float + sensors_available: List[str] + + +@dataclass +class V2XMessage: + """V2X通信消息""" + sender_id: int + message_type: str # 'state', 'object', 'warning', 'control' + data: Dict + timestamp: float + ttl: float # 生存时间 + priority: int # 优先级 + + +class MultiVehicleManager: + """多车辆协同管理器""" + + def __init__(self, world, config, output_dir): + self.world = world + self.config = config + self.output_dir = output_dir + + # 创建协同数据目录 + self.coop_dir = os.path.join(output_dir, "cooperative") + os.makedirs(self.coop_dir, exist_ok=True) + os.makedirs(os.path.join(self.coop_dir, "v2x_messages"), exist_ok=True) + os.makedirs(os.path.join(self.coop_dir, "shared_perception"), exist_ok=True) + + # 车辆管理 + self.ego_vehicles = [] # 主车辆列表 + self.cooperative_vehicles = [] # 协同车辆列表 + self.vehicle_states = {} # 车辆ID -> 车辆状态 + self.vehicle_sensors = {} # 车辆ID -> 传感器列表 + + # V2X通信 + self.v2x_messages = [] + self.communication_range = config.get('v2x', {}).get('communication_range', 300.0) # 通信范围 + self.message_buffer = defaultdict(list) # 车辆ID -> 接收的消息列表 + + # 协同感知 + self.shared_objects = [] # 共享的感知对象 + self.fusion_results = {} # 融合结果 + + # 统计 + self.stats = { + 'total_messages': 0, + 'successful_transmissions': 0, + 'collaborative_detections': 0, + 'data_exchange_mb': 0.0 + } + + def spawn_cooperative_vehicles(self, num_vehicles: int = 3) -> List[carla.Actor]: + """生成协同车辆""" + blueprint_lib = self.world.get_blueprint_library() + spawn_points = self.world.get_map().get_spawn_points() + + if not spawn_points: + print("警告:无生成点") + return [] + + # 车辆类型 + vehicle_types = [ + 'vehicle.tesla.model3', + 'vehicle.audi.tt', + 'vehicle.nissan.patrol', + 'vehicle.bmw.grandtourer', + 'vehicle.mercedes.coupe' + ] + + spawned_vehicles = [] + + for i in range(min(num_vehicles, len(spawn_points))): + try: + # 选择车辆类型 + vtype = random.choice(vehicle_types) + vehicle_bp = random.choice(blueprint_lib.filter(vtype)) + + # 设置车辆属性 + vehicle_bp.set_attribute('role_name', f'coop_vehicle_{i}') + + # 选择生成点 + spawn_point = spawn_points[i % len(spawn_points)] + + # 调整位置,使车辆不在同一位置 + offset_x = random.uniform(-5.0, 5.0) + offset_y = random.uniform(-5.0, 5.0) + location = carla.Location( + x=spawn_point.location.x + offset_x, + y=spawn_point.location.y + offset_y, + z=spawn_point.location.z + ) + + # 轻微调整朝向 + rotation = carla.Rotation( + pitch=spawn_point.rotation.pitch, + yaw=spawn_point.rotation.yaw + random.uniform(-15, 15), + roll=spawn_point.rotation.roll + ) + + transform = carla.Transform(location, rotation) + + # 生成车辆 + vehicle = self.world.spawn_actor(vehicle_bp, transform) + + # 设置自动驾驶 + vehicle.set_autopilot(True) + + # 添加到列表 + self.cooperative_vehicles.append(vehicle) + spawned_vehicles.append(vehicle) + + # 初始化车辆状态 + self.vehicle_states[vehicle.id] = VehicleState( + vehicle_id=vehicle.id, + type_id=vehicle.type_id, + position=(0, 0, 0), + velocity=(0, 0, 0), + rotation=(0, 0, 0), + timestamp=time.time(), + sensors_available=[] + ) + + print(f"协同车辆 {i + 1} 生成: {vehicle.type_id}") + + except Exception as e: + print(f"生成协同车辆失败: {e}") + + return spawned_vehicles + + def update_vehicle_states(self): + """更新所有车辆状态""" + current_time = time.time() + + for vehicle in self.ego_vehicles + self.cooperative_vehicles: + try: + if vehicle.is_alive: + location = vehicle.get_location() + velocity = vehicle.get_velocity() + rotation = vehicle.get_transform().rotation + + self.vehicle_states[vehicle.id] = VehicleState( + vehicle_id=vehicle.id, + type_id=vehicle.type_id, + position=(location.x, location.y, location.z), + velocity=(velocity.x, velocity.y, velocity.z), + rotation=(rotation.pitch, rotation.yaw, rotation.roll), + timestamp=current_time, + sensors_available=self.vehicle_sensors.get(vehicle.id, []) + ) + except: + pass + + def create_v2x_message(self, sender_id: int, message_type: str, data: Dict, + priority: int = 1) -> V2XMessage: + """创建V2X消息""" + message = V2XMessage( + sender_id=sender_id, + message_type=message_type, + data=data, + timestamp=time.time(), + ttl=5.0, # 5秒生存时间 + priority=priority + ) + + self.v2x_messages.append(message) + self.stats['total_messages'] += 1 + + return message + + def broadcast_message(self, message: V2XMessage): + """广播消息给范围内的车辆""" + if message.sender_id not in self.vehicle_states: + return + + sender_state = self.vehicle_states[message.sender_id] + sender_pos = sender_state.position + + recipients = [] + + # 检查所有车辆是否在通信范围内 + for vehicle in self.ego_vehicles + self.cooperative_vehicles: + if vehicle.id != message.sender_id and vehicle.id in self.vehicle_states: + receiver_state = self.vehicle_states[vehicle.id] + receiver_pos = receiver_state.position + + # 计算距离 + distance = math.sqrt( + (sender_pos[0] - receiver_pos[0]) ** 2 + + (sender_pos[1] - receiver_pos[1]) ** 2 + + (sender_pos[2] - receiver_pos[2]) ** 2 + ) + + if distance <= self.communication_range: + recipients.append(vehicle.id) + + # 添加到接收者消息缓冲区 + self.message_buffer[vehicle.id].append({ + 'message': message, + 'receive_time': time.time(), + 'signal_strength': 1.0 - (distance / self.communication_range) + }) + + self.stats['successful_transmissions'] += len(recipients) + + # 保存消息 + self._save_v2x_message(message, recipients) + + return recipients + + def share_perception_data(self, vehicle_id: int, detected_objects: List[Dict]): + """共享感知数据""" + if not detected_objects: + return + + # 创建感知消息 + perception_data = { + 'vehicle_id': vehicle_id, + 'timestamp': time.time(), + 'objects': detected_objects, + 'vehicle_state': asdict( + self.vehicle_states.get(vehicle_id, VehicleState(0, '', (0, 0, 0), (0, 0, 0), (0, 0, 0), 0, []))) + } + + message = self.create_v2x_message( + vehicle_id, + 'perception', + perception_data, + priority=2 # 感知数据优先级较高 + ) + + # 广播消息 + recipients = self.broadcast_message(message) + + # 融合共享的感知数据 + if recipients: + self._fuse_shared_perception(vehicle_id, detected_objects, recipients) + + return message + + def share_traffic_warning(self, vehicle_id: int, warning_type: str, + location: Tuple[float, float, float], + severity: str = 'medium'): + """共享交通警告""" + warning_data = { + 'warning_type': warning_type, # 'accident', 'congestion', 'hazard', 'construction' + 'location': location, + 'severity': severity, + 'timestamp': time.time(), + 'source_vehicle': vehicle_id + } + + message = self.create_v2x_message( + vehicle_id, + 'warning', + warning_data, + priority=3 # 警告消息优先级最高 + ) + + self.broadcast_message(message) + + return message + + def _fuse_shared_perception(self, source_id: int, objects: List[Dict], recipients: List[int]): + """融合共享的感知数据""" + fused_objects = [] + + for obj in objects: + # 转换为全局坐标系(简化处理,实际需要坐标变换) + global_obj = obj.copy() + global_obj['source_vehicles'] = [source_id] + global_obj['confidence'] = obj.get('confidence', 0.8) # 降低置信度 + + # 检查是否已有类似对象 + matched = False + for existing_obj in self.shared_objects: + # 简单的对象匹配(基于位置) + if self._objects_match(global_obj, existing_obj): + # 更新现有对象 + existing_obj['source_vehicles'].append(source_id) + existing_obj['confidence'] = min(1.0, existing_obj.get('confidence', 0) + 0.1) + existing_obj['update_time'] = time.time() + matched = True + break + + if not matched: + global_obj['detection_time'] = time.time() + fused_objects.append(global_obj) + + self.shared_objects.extend(fused_objects) + + # 清理旧对象 + current_time = time.time() + self.shared_objects = [ + obj for obj in self.shared_objects + if current_time - obj.get('detection_time', 0) < 10.0 # 保留10秒内的对象 + ] + + if fused_objects: + self.stats['collaborative_detections'] += len(fused_objects) + + def _objects_match(self, obj1: Dict, obj2: Dict, distance_threshold: float = 5.0) -> bool: + """判断两个对象是否匹配""" + if obj1.get('class') != obj2.get('class'): + return False + + # 计算位置距离 + pos1 = obj1.get('position', {'x': 0, 'y': 0, 'z': 0}) + pos2 = obj2.get('position', {'x': 0, 'y': 0, 'z': 0}) + + distance = math.sqrt( + (pos1['x'] - pos2['x']) ** 2 + + (pos1['y'] - pos2['y']) ** 2 + + (pos1['z'] - pos2['z']) ** 2 + ) + + return distance < distance_threshold + + def get_shared_perception_for_vehicle(self, vehicle_id: int) -> List[Dict]: + """获取车辆可用的共享感知数据""" + shared_data = [] + + for obj in self.shared_objects: + # 检查对象是否在车辆视野内(简化) + shared_data.append(obj) + + return shared_data + + def coordinate_maneuvers(self, maneuvers: List[Dict]): + """协调多车辆机动""" + # 分配优先级和时序 + coordinated = [] + + for i, maneuver in enumerate(maneuvers): + coordinated_maneuver = maneuver.copy() + coordinated_maneuver['sequence'] = i + coordinated_maneuver['start_time'] = time.time() + i * 2.0 # 间隔2秒 + coordinated.append(coordinated_maneuver) + + # 发送协调消息 + message = self.create_v2x_message( + maneuver.get('vehicle_id', 0), + 'coordination', + coordinated_maneuver, + priority=2 + ) + + self.broadcast_message(message) + + return coordinated + + def _save_v2x_message(self, message: V2XMessage, recipients: List[int]): + """保存V2X消息到文件""" + message_data = { + 'message': asdict(message), + 'recipients': recipients, + 'transmission_time': time.time() + } + + filename = f"v2x_{int(time.time() * 1000)}_{message.sender_id}_{message.message_type}.json" + filepath = os.path.join(self.coop_dir, "v2x_messages", filename) + + with open(filepath, 'w', encoding='utf-8') as f: + json.dump(message_data, f, indent=2, ensure_ascii=False) + + def save_shared_perception(self, frame_num: int): + """保存共享感知数据""" + data = { + 'frame_id': frame_num, + 'timestamp': time.time(), + 'shared_objects': self.shared_objects, + 'active_vehicles': len(self.ego_vehicles + self.cooperative_vehicles), + 'stats': self.stats + } + + filepath = os.path.join(self.coop_dir, "shared_perception", f"frame_{frame_num:06d}.json") + + with open(filepath, 'w', encoding='utf-8') as f: + json.dump(data, f, indent=2, ensure_ascii=False) + + def generate_summary(self): + """生成协同摘要报告""" + summary = { + 'total_vehicles': len(self.ego_vehicles) + len(self.cooperative_vehicles), + 'ego_vehicles': len(self.ego_vehicles), + 'cooperative_vehicles': len(self.cooperative_vehicles), + 'v2x_stats': self.stats, + 'shared_objects_count': len(self.shared_objects), + 'communication_range': self.communication_range, + 'average_messages_per_vehicle': self.stats['total_messages'] / max(1, len(self.ego_vehicles) + len( + self.cooperative_vehicles)) + } + + filepath = os.path.join(self.coop_dir, "cooperative_summary.json") + + with open(filepath, 'w', encoding='utf-8') as f: + json.dump(summary, f, indent=2, ensure_ascii=False) + + return summary + + def cleanup(self): + """清理资源""" + print("清理协同车辆...") + + for vehicle in self.cooperative_vehicles: + try: + if vehicle.is_alive: + vehicle.destroy() + except: + pass + + self.cooperative_vehicles.clear() + self.ego_vehicles.clear() + self.vehicle_states.clear() + self.message_buffer.clear() \ No newline at end of file diff --git a/src/enhance_pedestrian_safety/v2x_communication.py b/src/enhance_pedestrian_safety/v2x_communication.py new file mode 100644 index 0000000000..63079cc46a --- /dev/null +++ b/src/enhance_pedestrian_safety/v2x_communication.py @@ -0,0 +1,328 @@ +import socket +import threading +import json +import time +import struct +import numpy as np +from typing import Dict, List, Any, Optional +from dataclasses import dataclass, asdict +import queue + + +@dataclass +class V2XMessage: + """V2X通信消息""" + message_id: str + sender_id: str + message_type: str # 'bsm', 'spat', 'map', 'rsm', 'rsa' + data: Dict[str, Any] + timestamp: float + ttl: float + priority: int + position: Optional[tuple] = None + + +class V2XCommunication: + """V2X通信模拟器""" + + def __init__(self, config: Dict): + self.config = config + self.enabled = config.get('enabled', True) + + # 通信参数 + self.communication_range = config.get('communication_range', 300.0) # 通信范围(米) + self.bandwidth = config.get('bandwidth', 10.0) # 带宽(Mbps) + self.latency_mean = config.get('latency_mean', 0.05) # 平均延迟(秒) + self.latency_std = config.get('latency_std', 0.01) # 延迟标准差 + self.packet_loss_rate = config.get('packet_loss_rate', 0.01) # 丢包率 + + # 消息管理 + self.message_queue = queue.PriorityQueue() + self.received_messages = [] + self.message_counter = 0 + + # 网络模拟 + self.network_nodes = {} # 节点ID -> 节点信息 + self.connections = {} # 连接状态 + + # 统计信息 + self.stats = { + 'messages_sent': 0, + 'messages_received': 0, + 'messages_dropped': 0, + 'total_latency': 0.0, + 'bandwidth_used': 0.0 + } + + # 启动消息处理线程 + if self.enabled: + self.running = True + self.processor_thread = threading.Thread(target=self._message_processor, daemon=True) + self.processor_thread.start() + + def register_node(self, node_id: str, position: tuple, capabilities: Dict): + """注册网络节点(车辆)""" + self.network_nodes[node_id] = { + 'position': position, + 'capabilities': capabilities, + 'last_seen': time.time(), + 'status': 'online' + } + + def unregister_node(self, node_id: str): + """注销网络节点""" + if node_id in self.network_nodes: + del self.network_nodes[node_id] + + def send_message(self, message: V2XMessage) -> bool: + """发送V2X消息""" + if not self.enabled: + return False + + # 模拟网络延迟和丢包 + if np.random.random() < self.packet_loss_rate: + self.stats['messages_dropped'] += 1 + return False + + # 添加延迟 + latency = np.random.normal(self.latency_mean, self.latency_std) + delivery_time = time.time() + max(0, latency) + + # 添加到消息队列(按优先级和发送时间排序) + priority = -message.priority # 负号因为PriorityQueue是小顶堆 + self.message_queue.put((priority, delivery_time, message)) + + self.stats['messages_sent'] += 1 + self.stats['total_latency'] += latency + + return True + + def broadcast_basic_safety_message(self, node_id: str, vehicle_data: Dict) -> Optional[V2XMessage]: + """广播基本安全消息(BSM)""" + bsm_data = { + 'msgCnt': self.message_counter % 128, + 'id': node_id, + 'secMark': int(time.time() * 1000) % 60000, + 'position': vehicle_data.get('position', (0, 0, 0)), + 'accuracy': vehicle_data.get('accuracy', {'semiMajor': 1.0, 'semiMinor': 1.0}), + 'transmissionAndSpeed': { + 'transmission': vehicle_data.get('transmission', 'automatic'), + 'speed': vehicle_data.get('speed', 0.0) + }, + 'heading': vehicle_data.get('heading', 0.0), + 'steeringWheelAngle': vehicle_data.get('steering_wheel_angle', 0.0), + 'accelerationSet4Way': vehicle_data.get('acceleration', {'long': 0, 'lat': 0, 'vert': 0, 'yaw': 0}), + 'brakeSystemStatus': vehicle_data.get('brake_status', {'wheelBrakes': '00000'}), + 'vehicleSize': vehicle_data.get('size', {'width': 1.8, 'length': 4.5}) + } + + message = V2XMessage( + message_id=f"bsm_{node_id}_{self.message_counter}", + sender_id=node_id, + message_type='bsm', + data=bsm_data, + timestamp=time.time(), + ttl=1.0, + priority=1, + position=vehicle_data.get('position') + ) + + self.message_counter += 1 + + if self.send_message(message): + return message + + return None + + def broadcast_signal_phase_and_timing(self, intersection_id: str, spat_data: Dict) -> Optional[V2XMessage]: + """广播信号相位和时序消息(SPaT)""" + message = V2XMessage( + message_id=f"spat_{intersection_id}_{self.message_counter}", + sender_id=intersection_id, + message_type='spat', + data=spat_data, + timestamp=time.time(), + ttl=5.0, + priority=2, + position=spat_data.get('intersection_position') + ) + + self.message_counter += 1 + + if self.send_message(message): + return message + + return None + + def broadcast_map_data(self, map_id: str, map_data: Dict) -> Optional[V2XMessage]: + """广播地图数据(MAP)""" + message = V2XMessage( + message_id=f"map_{map_id}_{self.message_counter}", + sender_id=map_id, + message_type='map', + data=map_data, + timestamp=time.time(), + ttl=30.0, # 地图数据TTL较长 + priority=1, + position=map_data.get('reference_point') + ) + + self.message_counter += 1 + + if self.send_message(message): + return message + + return None + + def broadcast_roadside_safety_message(self, rsu_id: str, warning_data: Dict) -> Optional[V2XMessage]: + """广播路侧安全消息(RSM/RSA)""" + message = V2XMessage( + message_id=f"rsm_{rsu_id}_{self.message_counter}", + sender_id=rsu_id, + message_type='rsm', + data=warning_data, + timestamp=time.time(), + ttl=3.0, + priority=3, # 安全消息优先级高 + position=warning_data.get('event_position') + ) + + self.message_counter += 1 + + if self.send_message(message): + return message + + return None + + def get_reachable_nodes(self, sender_id: str, sender_position: tuple) -> List[str]: + """获取可达的网络节点""" + reachable = [] + + for node_id, node_info in self.network_nodes.items(): + if node_id == sender_id: + continue + + # 计算距离 + distance = self._calculate_distance(sender_position, node_info['position']) + + if distance <= self.communication_range: + # 模拟信号衰减 + signal_strength = 1.0 - (distance / self.communication_range) + if signal_strength > 0.3: # 最小信号强度阈值 + reachable.append(node_id) + + return reachable + + def _calculate_distance(self, pos1: tuple, pos2: tuple) -> float: + """计算两点间距离""" + if not pos1 or not pos2: + return float('inf') + + return np.sqrt( + (pos1[0] - pos2[0]) ** 2 + + (pos1[1] - pos2[1]) ** 2 + + (pos1[2] - pos2[2]) ** 2 + ) + + def _message_processor(self): + """消息处理线程""" + while self.running: + try: + # 获取下一个要传递的消息 + if not self.message_queue.empty(): + priority, delivery_time, message = self.message_queue.get_nowait() + + current_time = time.time() + + if current_time >= delivery_time: + # 消息已到传递时间 + self._deliver_message(message) + else: + # 重新放回队列 + self.message_queue.put((priority, delivery_time, message)) + time.sleep(0.001) # 短暂休眠 + else: + time.sleep(0.01) + + except queue.Empty: + time.sleep(0.01) + except Exception as e: + print(f"消息处理器错误: {e}") + + def _deliver_message(self, message: V2XMessage): + """传递消息到接收者""" + if not message.position: + return + + # 获取可达节点 + reachable_nodes = self.get_reachable_nodes(message.sender_id, message.position) + + # 模拟带宽限制 + message_size = len(json.dumps(asdict(message)).encode('utf-8')) / 1024 / 1024 # MB + bandwidth_required = message_size * 8 * len(reachable_nodes) # Mbps + + if bandwidth_required > self.bandwidth * 0.8: # 带宽超过80%时随机丢包 + drop_probability = min(1.0, bandwidth_required / self.bandwidth - 0.8) + reachable_nodes = [n for n in reachable_nodes if np.random.random() > drop_probability] + + # 添加到接收消息列表 + for node_id in reachable_nodes: + received_msg = { + 'message': asdict(message), + 'receiver_id': node_id, + 'receive_time': time.time(), + 'signal_strength': 1.0 - (self._calculate_distance(message.position, self.network_nodes[node_id][ + 'position']) / self.communication_range) + } + + self.received_messages.append(received_msg) + self.stats['messages_received'] += 1 + + # 更新带宽使用统计 + self.stats['bandwidth_used'] += bandwidth_required + + def get_messages_for_node(self, node_id: str, message_types: List[str] = None) -> List[Dict]: + """获取指定节点的消息""" + messages = [] + + for msg_record in self.received_messages: + if msg_record['receiver_id'] == node_id: + message = msg_record['message'] + if not message_types or message['message_type'] in message_types: + messages.append(msg_record) + + # 清理已处理的消息 + self.received_messages = [ + msg for msg in self.received_messages + if msg['receiver_id'] != node_id or + (message_types and msg['message']['message_type'] not in message_types) + ] + + return messages + + def get_network_status(self) -> Dict: + """获取网络状态""" + return { + 'active_nodes': len(self.network_nodes), + 'stats': self.stats, + 'bandwidth_utilization': (self.stats['bandwidth_used'] / ( + self.bandwidth * time.time())) * 100 if time.time() > 0 else 0, + 'message_delivery_rate': self.stats['messages_received'] / max(1, self.stats['messages_sent']), + 'average_latency': self.stats['total_latency'] / max(1, self.stats['messages_sent']) + } + + def reset_stats(self): + """重置统计信息""" + self.stats = { + 'messages_sent': 0, + 'messages_received': 0, + 'messages_dropped': 0, + 'total_latency': 0.0, + 'bandwidth_used': 0.0 + } + + def stop(self): + """停止通信模拟""" + self.running = False + if hasattr(self, 'processor_thread'): + self.processor_thread.join(timeout=1.0) \ No newline at end of file From 7eee0e6d09b99b9d609b1f8ac1fbd519cbe9c947 Mon Sep 17 00:00:00 2001 From: Z-w-7799 <1011069369@qq.com> Date: Fri, 19 Dec 2025 10:15:15 +0800 Subject: [PATCH 03/20] =?UTF-8?q?=E4=BC=A0=E6=84=9F=E5=99=A8=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=A2=9E=E5=BC=BA=E5=92=8C=E5=A4=9A=E5=A4=A9=E6=B0=94?= =?UTF-8?q?=E6=A8=A1=E6=8B=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config_manager.py | 32 +- .../data_analyzer.py | 83 -- .../data_validator.py | 53 -- src/enhance_pedestrian_safety/main.py | 384 +++++---- .../sensor_enhancer.py | 784 ++++++++++++++++++ 5 files changed, 1006 insertions(+), 330 deletions(-) create mode 100644 src/enhance_pedestrian_safety/sensor_enhancer.py diff --git a/src/enhance_pedestrian_safety/config_manager.py b/src/enhance_pedestrian_safety/config_manager.py index 9e80f69f2d..54477406ba 100644 --- a/src/enhance_pedestrian_safety/config_manager.py +++ b/src/enhance_pedestrian_safety/config_manager.py @@ -36,7 +36,6 @@ def load_config(config_file=None): 'rotation_frequency': 10 } }, - 'v2x': { 'enabled': True, 'communication_range': 300.0, @@ -54,8 +53,15 @@ def load_config(config_file=None): 'data_fusion_interval': 1.0, 'max_shared_objects': 50 }, - - + 'enhancement': { + 'enabled': True, + 'enable_random': True, + 'quality_check': True, + 'save_original': True, + 'save_enhanced': True, + 'calibration_generation': True, + 'enhanced_dir_name': 'enhanced' + }, 'output': { 'data_dir': 'cvips_dataset', 'save_raw': True, @@ -63,13 +69,12 @@ def load_config(config_file=None): 'save_annotations': False, 'save_lidar': True, 'save_fusion': True, - 'save_cooperative': True, 'save_v2x_messages': True, - - + 'save_enhanced': True, 'validate_data': True, - 'run_analysis': False + 'run_analysis': False, + 'run_quality_check': True } } @@ -111,7 +116,6 @@ def merge_args(config, args): if args.num_pedestrians: config['traffic']['pedestrians'] = args.num_pedestrians - if args.num_coop_vehicles: config['cooperative']['num_coop_vehicles'] = args.num_coop_vehicles @@ -121,10 +125,8 @@ def merge_args(config, args): if args.enable_v2x: config['v2x']['enabled'] = True - - if args.capture_interval: - config['sensors']['capture_interval'] = args.capture_interval - + if args.enable_enhancement: + config['enhancement']['enabled'] = True if args.enable_lidar: config['sensors']['lidar_sensors'] = 1 @@ -133,18 +135,18 @@ def merge_args(config, args): if args.enable_fusion: config['output']['save_fusion'] = True - if args.enable_cooperative: config['output']['save_cooperative'] = True - - if args.enable_annotations: config['output']['save_annotations'] = True if args.skip_validation: config['output']['validate_data'] = False + if args.skip_quality_check: + config['output']['run_quality_check'] = False + if args.run_analysis: config['output']['run_analysis'] = True diff --git a/src/enhance_pedestrian_safety/data_analyzer.py b/src/enhance_pedestrian_safety/data_analyzer.py index f805fe2fcb..069d6d2ec7 100644 --- a/src/enhance_pedestrian_safety/data_analyzer.py +++ b/src/enhance_pedestrian_safety/data_analyzer.py @@ -17,10 +17,7 @@ def analyze_dataset(data_dir): 'file_distribution': DataAnalyzer._analyze_file_distribution(data_dir), 'object_statistics': DataAnalyzer._analyze_objects(data_dir), 'temporal_analysis': DataAnalyzer._analyze_temporal(data_dir), - 'cooperative_data': DataAnalyzer._analyze_cooperative_data(data_dir), - - 'quality_metrics': DataAnalyzer._calculate_quality_metrics(data_dir) } @@ -81,7 +78,6 @@ def _analyze_file_distribution(data_dir): distribution = {} # 分析原始图像 - raw_dirs = [] raw_path = os.path.join(data_dir, "raw") if os.path.exists(raw_path): @@ -98,20 +94,6 @@ def _analyze_file_distribution(data_dir): camera_stats[camera_dir] = len(images) distribution[f'raw_{raw_dir}'] = camera_stats - raw_dirs = ['vehicle', 'infrastructure'] - for raw_dir in raw_dirs: - path = os.path.join(data_dir, "raw", raw_dir) - if os.path.exists(path): - camera_stats = {} - for camera_dir in os.listdir(path): - camera_path = os.path.join(path, camera_dir) - if os.path.isdir(camera_path): - images = [f for f in os.listdir(camera_path) - if f.endswith(('.png', '.jpg', '.jpeg'))] - camera_stats[camera_dir] = len(images) - distribution[f'raw_{raw_dir}'] = camera_stats - - # 分析拼接图像 stitched_dir = os.path.join(data_dir, "stitched") if os.path.exists(stitched_dir): @@ -125,7 +107,6 @@ def _analyze_file_distribution(data_dir): json_files = [f for f in os.listdir(annotations_dir) if f.endswith('.json')] distribution['annotations'] = len(json_files) - # 分析LiDAR数据 lidar_dir = os.path.join(data_dir, "lidar") if os.path.exists(lidar_dir): @@ -139,8 +120,6 @@ def _analyze_file_distribution(data_dir): json_files = [f for f in os.listdir(fusion_dir) if f.endswith('.json')] distribution['fusion'] = len(json_files) - - return distribution @staticmethod @@ -149,12 +128,8 @@ def _analyze_objects(data_dir): annotations_dir = os.path.join(data_dir, "annotations") if not os.path.exists(annotations_dir): - return {'total_objects': 0, 'by_class': {}, 'by_frame': {}, 'class_distribution': {}} - return {'total_objects': 0, 'by_class': {}, 'by_frame': {}} - - object_stats = { 'total_objects': 0, 'by_class': defaultdict(int), @@ -216,7 +191,6 @@ def _analyze_temporal(data_dir): return temporal_stats @staticmethod - def _analyze_cooperative_data(data_dir): """分析协同数据""" coop_dir = os.path.join(data_dir, "cooperative") @@ -289,45 +263,28 @@ def _analyze_cooperative_data(data_dir): return analysis @staticmethod - - def _calculate_quality_metrics(data_dir): """计算质量指标""" quality_metrics = { 'completeness_score': 0, 'consistency_score': 0, 'diversity_score': 0, - 'cooperative_score': 0, - - 'issues_found': [] } # 检查完整性 - required_dirs = ["raw/vehicle", "raw/infrastructure", "stitched", "metadata", "cooperative"] missing_dirs = [] for dir_path in required_dirs: full_path = os.path.join(data_dir, dir_path) if not os.path.exists(full_path): - - required_dirs = ["raw/vehicle", "raw/infrastructure", "stitched", "metadata"] - missing_dirs = [] - - for dir_path in required_dirs: - if not os.path.exists(os.path.join(data_dir, dir_path)): - missing_dirs.append(dir_path) if missing_dirs: quality_metrics['issues_found'].append(f"缺失目录: {missing_dirs}") - quality_metrics['completeness_score'] = 100 - (len(missing_dirs) * 20) - - quality_metrics['completeness_score'] = 50 - else: quality_metrics['completeness_score'] = 100 @@ -343,11 +300,7 @@ def _calculate_quality_metrics(data_dir): camera_counts.append(len(images)) if camera_counts: - max_diff = max(camera_counts) - min(camera_counts) if camera_counts else 0 - - max_diff = max(camera_counts) - min(camera_counts) - if max_diff > 5: quality_metrics['issues_found'].append(f"摄像头图像数量不一致: 差异{max_diff}") quality_metrics['consistency_score'] = 70 @@ -366,7 +319,6 @@ def _calculate_quality_metrics(data_dir): quality_metrics['diversity_score'] = 50 quality_metrics['issues_found'].append(f"物体类别较少: {num_classes}类") - # 协同评分 cooperative_data = DataAnalyzer._analyze_cooperative_data(data_dir) if cooperative_data['v2x_messages'] > 10 and cooperative_data['shared_perception_frames'] > 5: @@ -377,26 +329,17 @@ def _calculate_quality_metrics(data_dir): quality_metrics['cooperative_score'] = 30 quality_metrics['issues_found'].append("协同数据较少") - - return quality_metrics @staticmethod def _calculate_overall_score(analysis): """计算总体评分""" weights = { - 'completeness': 0.25, 'consistency': 0.20, 'diversity': 0.15, 'cooperative': 0.20, 'temporal': 0.20 - - 'completeness': 0.3, - 'consistency': 0.25, - 'diversity': 0.25, - 'temporal': 0.2 - } quality = analysis['quality_metrics'] @@ -404,12 +347,8 @@ def _calculate_overall_score(analysis): score = ( quality['completeness_score'] * weights['completeness'] + quality['consistency_score'] * weights['consistency'] + - quality['diversity_score'] * weights['diversity'] + quality['cooperative_score'] * weights['cooperative'] - - quality['diversity_score'] * weights['diversity'] - ) # 时间因素 @@ -421,12 +360,8 @@ def _calculate_overall_score(analysis): else: score += 5 * weights['temporal'] - return round(min(score, 100), 1) - return round(score, 1) - - @staticmethod def _save_analysis_report(data_dir, analysis): """保存分析报告""" @@ -460,15 +395,11 @@ def _print_analysis_summary(analysis): if isinstance(value, dict): print(f" {key}:") for subkey, subvalue in value.items(): - if isinstance(subvalue, dict): for subsubkey, subsubvalue in subvalue.items(): print(f" {subsubkey}: {subsubvalue}") else: print(f" {subkey}: {subvalue}") - - print(f" {subkey}: {subvalue}") - else: print(f" {key}: {value}") @@ -481,7 +412,6 @@ def _print_analysis_summary(analysis): percentage = objects['class_distribution'].get(obj_class, 0) print(f" {obj_class}: {count} ({percentage}%)") - # 协同数据分析 cooperative = analysis['cooperative_data'] print(f"\n协同数据分析:") @@ -498,8 +428,6 @@ def _print_analysis_summary(analysis): for msg_type, count in cooperative['v2x_stats']['message_types'].items(): print(f" {msg_type}: {count}") - - temporal = analysis['temporal_analysis'] print(f"\n时间分析:") print(f" 总时长: {temporal['total_duration']:.1f}秒") @@ -510,11 +438,8 @@ def _print_analysis_summary(analysis): print(f" 完整性: {quality['completeness_score']}/100") print(f" 一致性: {quality['consistency_score']}/100") print(f" 多样性: {quality['diversity_score']}/100") - print(f" 协同性: {quality['cooperative_score']}/100") - - if quality['issues_found']: print(f" 发现的问题 ({len(quality['issues_found'])}):") for issue in quality['issues_found'][:3]: @@ -524,20 +449,12 @@ def _print_analysis_summary(analysis): print(f"\n总体评分: {analysis['overall_score']}/100") - overall_score = analysis['overall_score'] if overall_score >= 90: print("✓ 数据集质量优秀") elif overall_score >= 75: print("✓ 数据集质量良好") elif overall_score >= 60: - - if analysis['overall_score'] >= 90: - print("✓ 数据集质量优秀") - elif analysis['overall_score'] >= 75: - print("✓ 数据集质量良好") - elif analysis['overall_score'] >= 60: - print("⚠ 数据集质量一般") else: print("✗ 数据集质量需要改进") diff --git a/src/enhance_pedestrian_safety/data_validator.py b/src/enhance_pedestrian_safety/data_validator.py index 80c65d52f9..fa67f6425b 100644 --- a/src/enhance_pedestrian_safety/data_validator.py +++ b/src/enhance_pedestrian_safety/data_validator.py @@ -14,13 +14,9 @@ def validate_dataset(data_dir): 'stitched_images': DataValidator._validate_stitched_images(data_dir), 'annotations': DataValidator._validate_annotations(data_dir), 'metadata': DataValidator._validate_metadata(data_dir), - 'lidar_data': DataValidator._validate_lidar_data(data_dir), 'cooperative_data': DataValidator._validate_cooperative_data(data_dir), 'fusion_data': DataValidator._validate_fusion_data(data_dir) - - 'lidar_data': DataValidator._validate_lidar_data(data_dir) - } validation_results['overall_score'] = DataValidator._calculate_score(validation_results) @@ -36,15 +32,11 @@ def _check_directory_structure(data_dir): "raw/vehicle", "raw/infrastructure", "stitched", - "metadata", "cooperative", "cooperative/v2x_messages", "cooperative/shared_perception", "fusion" - - "metadata" - ] missing_dirs = [] @@ -65,7 +57,6 @@ def _check_directory_structure(data_dir): @staticmethod def _validate_raw_images(data_dir): - raw_path = os.path.join(data_dir, "raw") if not os.path.exists(raw_path): @@ -73,20 +64,11 @@ def _validate_raw_images(data_dir): 'infrastructure': {'status': 'MISSING', 'count': 0, 'errors': []}} raw_dirs = [d for d in os.listdir(raw_path) if os.path.isdir(os.path.join(raw_path, d))] - - raw_dirs = ["vehicle", "infrastructure"] - results = {} for raw_dir in raw_dirs: path = os.path.join(data_dir, "raw", raw_dir) - - if not os.path.exists(path): - results[raw_dir] = {'status': 'MISSING', 'count': 0, 'errors': []} - continue - - camera_dirs = [] if os.path.exists(path): camera_dirs = [d for d in os.listdir(path) if os.path.isdir(os.path.join(path, d))] @@ -171,15 +153,12 @@ def _validate_annotations(data_dir): with open(json_path, 'r') as f: data = json.load(f) - # 检查基本结构 required_keys = ['frame_id', 'objects'] for key in required_keys: if key not in data: errors.append(f"缺失必要键: {key} in {json_file}") - - valid_files += 1 except Exception as e: errors.append(f"无效的JSON文件: {json_file} - {str(e)}") @@ -238,32 +217,22 @@ def _validate_lidar_data(data_dir): return {'status': 'MISSING', 'count': 0, 'errors': []} bin_files = [f for f in os.listdir(lidar_dir) if f.endswith('.bin')] - npy_files = [f for f in os.listdir(lidar_dir) if f.endswith('.npy')] json_files = [f for f in os.listdir(lidar_dir) if f.endswith('.json')] errors = [] valid_bin_files = 0 - errors = [] - valid_files = 0 - - for bin_file in bin_files: bin_path = os.path.join(lidar_dir, bin_file) try: if os.path.getsize(bin_path) > 0: - valid_bin_files += 1 - - valid_files += 1 - else: errors.append(f"空文件: {bin_file}") except: errors.append(f"文件访问失败: {bin_file}") - total_files = len(bin_files) + len(npy_files) + len(json_files) if len(errors) == 0 and valid_bin_files > 0: @@ -394,8 +363,6 @@ def _validate_fusion_data(data_dir): except Exception as e: errors.append(f"融合文件无效: {json_file} - {str(e)}") - - if len(errors) == 0 and valid_files > 0: status = 'PASS' elif len(errors) < 3 and valid_files > 0: @@ -405,18 +372,13 @@ def _validate_fusion_data(data_dir): return { 'status': status, - 'count': len(json_files), - - 'count': valid_files, - 'errors': errors } @staticmethod def _calculate_score(results): weights = { - 'directory_structure': 0.10, 'raw_images': 0.20, 'stitched_images': 0.10, @@ -425,14 +387,6 @@ def _calculate_score(results): 'lidar_data': 0.15, 'cooperative_data': 0.15, 'fusion_data': 0.10 - - 'directory_structure': 0.15, - 'raw_images': 0.25, - 'stitched_images': 0.15, - 'annotations': 0.1, - 'metadata': 0.15, - 'lidar_data': 0.2 - } score = 0 @@ -477,19 +431,14 @@ def _print_validation_report(results): print(f"\n{key.replace('_', ' ').title()}:") if 'status' in result: - status_icon = '✓' if result['status'] == 'PASS' else '⚠' if result['status'] == 'WARNING' else '✗' print(f" 状态: {status_icon} {result['status']}") - - print(f" 状态: {result['status']}") - else: print(f" 状态: 未知") if 'count' in result: print(f" 数量: {result['count']}") - # 特定类型数据的额外信息 if key == 'raw_images' and isinstance(result, dict): for subkey, subresult in result.items(): @@ -510,8 +459,6 @@ def _print_validation_report(results): if 'json_files' in result: print(f" JSON文件: {result['json_files']}") - - if 'errors' in result and result['errors']: print(f" 错误 ({len(result['errors'])}):") for error in result['errors'][:3]: diff --git a/src/enhance_pedestrian_safety/main.py b/src/enhance_pedestrian_safety/main.py index bd8376d1a8..b6f4c384c5 100644 --- a/src/enhance_pedestrian_safety/main.py +++ b/src/enhance_pedestrian_safety/main.py @@ -7,6 +7,8 @@ import math import threading import json +import cv2 +import numpy as np from datetime import datetime from carla_utils import setup_carla_path, import_carla_module @@ -16,11 +18,9 @@ from scene_manager import SceneManager from data_analyzer import DataAnalyzer from lidar_processor import LidarProcessor, MultiSensorFusion - from multi_vehicle_manager import MultiVehicleManager from v2x_communication import V2XCommunication - - +from sensor_enhancer import SensorDataEnhancer, SensorCalibrator, DataQualityMonitor carla_egg_path, remaining_argv = setup_carla_path() carla = import_carla_module() @@ -253,29 +253,34 @@ def __init__(self, world, config, data_dir): self.lidar_processor = None self.fusion_manager = None + # 新增:数据增强组件 + self.enhancer = None + self.calibrator = None + self.quality_monitor = None + + if config['enhancement']['enabled']: + self.enhancer = SensorDataEnhancer(config) + self.calibrator = SensorCalibrator(config) + self.quality_monitor = DataQualityMonitor(data_dir) + + # 创建增强数据目录 + enhanced_dir = os.path.join(data_dir, config['enhancement'].get('enhanced_dir_name', 'enhanced')) + os.makedirs(enhanced_dir, exist_ok=True) + if config['sensors'].get('lidar_sensors', 0) > 0: self.lidar_processor = LidarProcessor(data_dir) if config['output'].get('save_fusion', False): self.fusion_manager = MultiSensorFusion(data_dir) - def setup_cameras(self, vehicle, center_location, vehicle_id=0): vehicle_cams = self._setup_vehicle_cameras(vehicle, vehicle_id) - - def setup_cameras(self, vehicle, center_location): - vehicle_cams = self._setup_vehicle_cameras(vehicle) - infra_cams = self._setup_infrastructure_cameras(center_location) Log.info(f"摄像头: {vehicle_cams}车辆 + {infra_cams}基础设施") return vehicle_cams + infra_cams - def _setup_vehicle_cameras(self, vehicle, vehicle_id): - - def _setup_vehicle_cameras(self, vehicle): - if not vehicle: return 0 @@ -288,11 +293,7 @@ def _setup_vehicle_cameras(self, vehicle): installed = 0 for cam_name, config_data in camera_configs.items(): - if self._create_camera(cam_name, config_data, vehicle, 'vehicle', vehicle_id): - - if self._create_camera(cam_name, config_data, vehicle, 'vehicle'): - installed += 1 return installed @@ -322,11 +323,7 @@ def _setup_infrastructure_cameras(self, center_location): return installed - def setup_lidar(self, vehicle, vehicle_id=0): - - def setup_lidar(self, vehicle): - if not vehicle or not self.config['sensors'].get('lidar_sensors', 0) > 0: return 0 @@ -341,10 +338,6 @@ def setup_lidar(self, vehicle): lidar_bp.set_attribute('points_per_second', str(lidar_config.get('points_per_second', 56000))) lidar_bp.set_attribute('rotation_frequency', str(lidar_config.get('rotation_frequency', 10))) - - - # 添加更多LiDAR参数 - lidar_bp.set_attribute('upper_fov', '10') lidar_bp.set_attribute('lower_fov', '-20') lidar_bp.set_attribute('horizontal_fov', '360') @@ -362,17 +355,9 @@ def lidar_callback(lidar_data): try: metadata = self.lidar_processor.process_lidar_data(lidar_data, self.frame_counter) if metadata and self.fusion_manager: - - vehicle_image_path = None - with self.buffer_lock: - if self.vehicle_buffer: - - # 尝试获取最新的车辆图像 vehicle_image_path = None with self.buffer_lock: if self.vehicle_buffer: - # 取第一个摄像头的图像 - for cam_name, img_path in self.vehicle_buffer.items(): if os.path.exists(img_path): vehicle_image_path = img_path @@ -398,11 +383,7 @@ def lidar_callback(lidar_data): print(f"LiDAR安装失败: {e}") return 0 - def _create_camera(self, name, config, parent, sensor_type, vehicle_id=0): - - def _create_camera(self, name, config, parent, sensor_type): - try: blueprint = self.world.get_blueprint_library().find('sensor.camera.rgb') @@ -420,7 +401,6 @@ def _create_camera(self, name, config, parent, sensor_type): else: camera = self.world.spawn_actor(blueprint, transform) - # 为不同车辆创建不同目录 if sensor_type == 'vehicle' and vehicle_id > 0: save_dir = os.path.join(self.data_dir, "raw", f"vehicle_{vehicle_id}", name) @@ -430,11 +410,6 @@ def _create_camera(self, name, config, parent, sensor_type): os.makedirs(save_dir, exist_ok=True) callback = self._create_callback(save_dir, name, sensor_type, vehicle_id) - save_dir = os.path.join(self.data_dir, "raw", sensor_type, name) - os.makedirs(save_dir, exist_ok=True) - - callback = self._create_callback(save_dir, name, sensor_type) - camera.listen(callback) self.sensors.append(camera) @@ -444,11 +419,7 @@ def _create_camera(self, name, config, parent, sensor_type): Log.warning(f"创建摄像头 {name} 失败: {e}") return False - def _create_callback(self, save_dir, name, sensor_type, vehicle_id=0): - - def _create_callback(self, save_dir, name, sensor_type): - capture_interval = self.config['sensors']['capture_interval'] def callback(image): @@ -458,28 +429,112 @@ def callback(image): self.frame_counter += 1 self.last_capture_time = current_time - filename = os.path.join(save_dir, f"{name}_{self.frame_counter:06d}.png") - image.save_to_disk(filename, carla.ColorConverter.Raw) + # 原始图像保存路径 + original_filename = os.path.join(save_dir, f"{name}_{self.frame_counter:06d}.png") + + # 保存原始图像 + image.save_to_disk(original_filename, carla.ColorConverter.Raw) + + # 数据增强处理 + if self.enhancer and self.config['enhancement']['enabled']: + enhanced_image = self._enhance_sensor_data(image, name, sensor_type) + + # 保存增强后的图像 + if self.config['enhancement']['save_enhanced']: + enhanced_dir = os.path.join( + self.data_dir, + self.config['enhancement'].get('enhanced_dir_name', 'enhanced'), + sensor_type, + name + ) + os.makedirs(enhanced_dir, exist_ok=True) + + enhanced_filename = os.path.join( + enhanced_dir, + f"{name}_{self.frame_counter:06d}_enhanced.png" + ) + + # 转换为RGB格式 + img_array = cv2.cvtColor(enhanced_image, cv2.COLOR_RGB2BGR) + cv2.imwrite(enhanced_filename, img_array) + + # 生成增强元数据 + metadata = { + 'frame_id': self.frame_counter, + 'sensor_type': sensor_type, + 'sensor_name': name, + 'vehicle_id': vehicle_id, + 'enhancement_methods': self.enhancer.enhancement_methods, + 'original_path': original_filename, + 'enhanced_path': enhanced_filename, + 'timestamp': datetime.now().isoformat() + } + + # 保存元数据 + meta_filename = enhanced_filename.replace('.png', '_meta.json') + with open(meta_filename, 'w', encoding='utf-8') as f: + json.dump(metadata, f, indent=2, ensure_ascii=False) + + # 质量检查 + if self.quality_monitor and self.config['output']['run_quality_check']: + quality_result = self.quality_monitor.check_image_quality(original_filename) + self.quality_monitor.update_metrics('images', quality_result) + + if not quality_result.get('valid', False): + print(f"⚠ 图像质量问题: {original_filename} - {quality_result.get('error', '')}") with self.buffer_lock: if sensor_type == 'vehicle': - self.vehicle_buffer[name] = filename + self.vehicle_buffer[name] = original_filename if len(self.vehicle_buffer) >= 4: - self.image_processor.stitch(self.vehicle_buffer, self.frame_counter, f'vehicle_{vehicle_id}') - - self.image_processor.stitch(self.vehicle_buffer, self.frame_counter, 'vehicle') - self.vehicle_buffer.clear() else: - self.infra_buffer[name] = filename + self.infra_buffer[name] = original_filename if len(self.infra_buffer) >= 4: self.image_processor.stitch(self.infra_buffer, self.frame_counter, 'infrastructure') self.infra_buffer.clear() return callback + def _enhance_sensor_data(self, carla_image, name: str, sensor_type: str) -> np.ndarray: + """增强传感器数据""" + # 将CARLA图像转换为numpy数组 + img_array = np.frombuffer(carla_image.raw_data, dtype=np.uint8) + img_array = img_array.reshape((carla_image.height, carla_image.width, 4)) + img_array = img_array[:, :, :3] # 去掉alpha通道 + + # 根据传感器类型应用增强 + if sensor_type == 'camera': + enhanced_image = self.enhancer.enhance_image(img_array, 'camera') + else: + enhanced_image = img_array # 其他传感器暂时不增强 + + return enhanced_image + + def generate_calibration_files(self, vehicle_locations: list, + camera_positions: list): + """生成传感器校准文件""" + if self.calibrator and self.config['enhancement']['calibration_generation']: + self.calibrator.generate_calibration_files( + self.data_dir, + vehicle_locations, + camera_positions + ) + + def generate_enhancement_report(self): + """生成增强报告""" + if self.enhancer: + return self.enhancer.generate_enhancement_report(self.data_dir) + return None + + def generate_quality_report(self): + """生成质量报告""" + if self.quality_monitor: + return self.quality_monitor.generate_quality_report() + return None + def get_frame_count(self): return self.frame_counter @@ -515,24 +570,16 @@ def __init__(self, config): self.config = config self.client = None self.world = None - - self.ego_vehicles = [] # 多个主车 - - self.ego_vehicle = None - + self.ego_vehicles = [] self.scene_center = None self.setup_directories() self.traffic_manager = None - - self.sensor_managers = {} # 车辆ID -> SensorManager + self.sensor_managers = {} self.multi_vehicle_manager = None self.v2x_communication = None - self.sensor_manager = None - - self.start_time = None self.is_running = False self.collected_frames = 0 @@ -547,21 +594,17 @@ def setup_directories(self): ) directories = [ - - "raw/vehicle_1", # 主车1 - "raw/vehicle_2", # 主车2(如果有) - - "raw/vehicle", - + "raw/vehicle_1", + "raw/vehicle_2", "raw/infrastructure", "stitched", "lidar", "fusion", "calibration", - "cooperative", "v2x_messages", - + "enhanced/vehicle", + "enhanced/infrastructure", "metadata" ] @@ -606,7 +649,6 @@ def setup_scene(self): self.traffic_manager = TrafficManager(self.world, self.config) - # 生成多个主车 num_ego_vehicles = min(self.config['cooperative'].get('num_coop_vehicles', 2) + 1, 3) for i in range(num_ego_vehicles): @@ -616,16 +658,11 @@ def setup_scene(self): Log.info(f"主车 {i + 1} 生成: {ego_vehicle.type_id}") if not self.ego_vehicles: - - self.ego_vehicle = self.traffic_manager.spawn_ego_vehicle() - if not self.ego_vehicle: - Log.error("主车生成失败") return False self.traffic_manager.spawn_traffic(self.scene_center) - # 初始化V2X通信 if self.config['v2x']['enabled']: self.v2x_communication = V2XCommunication(self.config['v2x']) @@ -663,13 +700,34 @@ def setup_scene(self): {'type': 'vehicle', 'capabilities': ['bsm', 'rsm']} ) + # 在场景设置完成后,如果有传感器管理器,生成校准文件 + if hasattr(self, 'sensor_managers') and self.sensor_managers: + # 获取车辆和相机位置信息 + vehicle_locations = [] + camera_positions = [] + for vehicle in self.ego_vehicles: + location = vehicle.get_location() + vehicle_locations.append({ + 'id': vehicle.id, + 'position': [location.x, location.y, location.z], + 'rotation': [0, 0, 0] + }) + + # 简化:假设相机位置相对于车辆 + camera_positions.append({ + 'translation': [2.0, 0, 1.5], # 相机相对于车辆的位置 + 'rotation': [0, 0, 0] # 相机的旋转 + }) + + # 为每个传感器管理器生成校准文件 + for sensor_manager in self.sensor_managers.values(): + sensor_manager.generate_calibration_files(vehicle_locations, camera_positions) time.sleep(3.0) return True def setup_sensors(self): - # 为每个主车设置传感器 for i, vehicle in enumerate(self.ego_vehicles): sensor_manager = SensorManager(self.world, self.config, self.output_dir) @@ -684,17 +742,6 @@ def setup_sensors(self): self.sensor_managers[vehicle.id] = sensor_manager - self.sensor_manager = SensorManager(self.world, self.config, self.output_dir) - - cameras = self.sensor_manager.setup_cameras(self.ego_vehicle, self.scene_center) - if cameras == 0: - Log.error("没有摄像头安装成功") - return False - - lidars = self.sensor_manager.setup_lidar(self.ego_vehicle) - Log.info(f"传感器: {cameras}摄像头 + {lidars}LiDAR") - - return True def collect_data(self): @@ -705,7 +752,6 @@ def collect_data(self): self.is_running = True last_update = time.time() - last_v2x_update = time.time() last_perception_share = time.time() @@ -714,7 +760,6 @@ def collect_data(self): current_time = time.time() elapsed = current_time - self.start_time - # 更新车辆状态 if self.multi_vehicle_manager: self.multi_vehicle_manager.update_vehicle_states() @@ -740,13 +785,6 @@ def collect_data(self): progress = (elapsed / duration) * 100 Log.info(f"进度: {elapsed:.0f}/{duration}秒 ({progress:.1f}%) | 总帧数: {total_frames}") - - if current_time - last_update >= 5.0: - frames = self.sensor_manager.get_frame_count() - progress = (elapsed / duration) * 100 - - Log.info(f"进度: {elapsed:.0f}/{duration}秒 ({progress:.1f}%) | 帧数: {frames}") - last_update = current_time time.sleep(0.05) @@ -757,17 +795,20 @@ def collect_data(self): self.is_running = False elapsed = time.time() - self.start_time - self.collected_frames = sum(mgr.get_frame_count() for mgr in self.sensor_managers.values()) + Log.info(f"收集完成: {self.collected_frames}帧, 用时: {elapsed:.1f}秒") - self.collected_frames = self.sensor_manager.get_frame_count() + # 生成增强报告 + if self.config['enhancement']['enabled']: + self._generate_enhancement_reports() - Log.info(f"收集完成: {self.collected_frames}帧, 用时: {elapsed:.1f}秒") + # 生成质量报告 + if self.config['output']['run_quality_check']: + self._generate_quality_reports() self._save_metadata() self._print_summary() - def _update_v2x_communication(self): """更新V2X通信""" if not self.v2x_communication: @@ -848,17 +889,28 @@ def _simulate_object_detection(self, vehicle): return detected_objects + def _generate_enhancement_reports(self): + """生成增强报告""" + Log.info("生成增强报告...") + for sensor_manager in self.sensor_managers.values(): + if hasattr(sensor_manager, 'enhancer'): + sensor_manager.generate_enhancement_report() + + def _generate_quality_reports(self): + """生成质量报告""" + Log.info("生成质量报告...") + for sensor_manager in self.sensor_managers.values(): + if hasattr(sensor_manager, 'quality_monitor'): + sensor_manager.quality_monitor.print_quality_summary() def _save_metadata(self): metadata = { 'scenario': self.config['scenario'], 'traffic': self.config['traffic'], 'sensors': self.config['sensors'], - 'v2x': self.config['v2x'], 'cooperative': self.config['cooperative'], - - + 'enhancement': self.config['enhancement'], 'output': self.config['output'], 'collection': { 'duration': round(time.time() - self.start_time, 2), @@ -867,11 +919,23 @@ def _save_metadata(self): } } - # 传感器摘要 sensor_summaries = {} for vehicle_id, sensor_manager in self.sensor_managers.items(): sensor_summaries[vehicle_id] = sensor_manager.generate_sensor_summary() + + # 添加增强报告 + if hasattr(sensor_manager, 'enhancer'): + enhancement_report = sensor_manager.generate_enhancement_report() + if enhancement_report: + sensor_summaries[vehicle_id]['enhancement_report'] = enhancement_report + + # 添加质量报告 + if hasattr(sensor_manager, 'quality_monitor'): + quality_report = sensor_manager.generate_quality_report() + if quality_report: + sensor_summaries[vehicle_id]['quality_report'] = quality_report + metadata['sensor_summaries'] = sensor_summaries # V2X通信状态 @@ -882,11 +946,6 @@ def _save_metadata(self): if self.multi_vehicle_manager: metadata['cooperative_summary'] = self.multi_vehicle_manager.generate_summary() - if self.sensor_manager: - sensor_summary = self.sensor_manager.generate_sensor_summary() - metadata['sensor_summary'] = sensor_summary - - meta_path = os.path.join(self.output_dir, "metadata", "collection_info.json") with open(meta_path, 'w', encoding='utf-8') as f: json.dump(metadata, f, indent=2, ensure_ascii=False) @@ -898,11 +957,28 @@ def _print_summary(self): print("数据收集摘要") print("=" * 60) - # 统计图像 - stitched_dir = os.path.join(self.output_dir, "stitched") - if os.path.exists(stitched_dir): - stitched_files = [f for f in os.listdir(stitched_dir) if f.endswith('.jpg')] - print(f"拼接图像: {len(stitched_files)} 张") + # 统计原始图像 + raw_dirs = [d for d in os.listdir(self.output_dir) if d.startswith('raw')] + total_raw_images = 0 + for raw_dir in raw_dirs: + raw_path = os.path.join(self.output_dir, raw_dir) + if os.path.exists(raw_path): + # 递归统计图像文件 + for root, dirs, files in os.walk(raw_path): + total_raw_images += len([f for f in files if f.endswith(('.png', '.jpg', '.jpeg'))]) + + print(f"原始图像: {total_raw_images} 张") + + # 统计增强图像 + if self.config['enhancement']['enabled']: + enhanced_dir = os.path.join(self.output_dir, + self.config['enhancement'].get('enhanced_dir_name', 'enhanced')) + if os.path.exists(enhanced_dir): + total_enhanced_images = 0 + for root, dirs, files in os.walk(enhanced_dir): + total_enhanced_images += len([f for f in files if f.endswith(('.png', '.jpg', '.jpeg'))]) + + print(f"增强图像: {total_enhanced_images} 张") # 统计LiDAR lidar_dir = os.path.join(self.output_dir, "lidar") @@ -911,17 +987,6 @@ def _print_summary(self): npy_files = [f for f in os.listdir(lidar_dir) if f.endswith('.npy')] print(f"LiDAR数据: {len(bin_files)} .bin文件, {len(npy_files)} .npy文件") - if bin_files: - total_points = 0 - for bin_file in bin_files[:3]: - bin_path = os.path.join(lidar_dir, bin_file) - if os.path.exists(bin_path): - file_size = os.path.getsize(bin_path) - points_in_file = file_size // (4 * 4) - total_points += points_in_file - print(f" 估计总点数: {total_points:,}") - - # 统计协同数据 coop_dir = os.path.join(self.output_dir, "cooperative") if os.path.exists(coop_dir): @@ -930,6 +995,12 @@ def _print_summary(self): [f for f in os.listdir(os.path.join(coop_dir, "shared_perception")) if f.endswith('.json')]) print(f"协同数据: {v2x_files} V2X消息, {perception_files} 共享感知文件") + # 校准文件 + calib_dir = os.path.join(self.output_dir, "calibration") + if os.path.exists(calib_dir): + calib_files = len([f for f in os.listdir(calib_dir) if f.endswith('.json')]) + print(f"校准文件: {calib_files} 个") + # V2X统计 if self.v2x_communication: v2x_status = self.v2x_communication.get_network_status() @@ -941,13 +1012,6 @@ def _print_summary(self): print(f"车辆总数: {len(self.ego_vehicles)} 主车 + " f"{len(self.multi_vehicle_manager.cooperative_vehicles)} 协同车") - # 统计融合数据 - fusion_dir = os.path.join(self.output_dir, "fusion") - if os.path.exists(fusion_dir): - sync_files = [f for f in os.listdir(fusion_dir) if f.endswith('.json')] - print(f"融合数据: {len(sync_files)} 个同步文件") - - print(f"\n输出目录: {self.output_dir}") print("=" * 60) @@ -964,7 +1028,6 @@ def run_analysis(self): def cleanup(self): Log.info("清理场景...") - # 清理传感器 for sensor_manager in self.sensor_managers.values(): sensor_manager.cleanup() @@ -989,37 +1052,15 @@ def cleanup(self): except: pass - - if self.sensor_manager: - self.sensor_manager.cleanup() - - if self.traffic_manager: - self.traffic_manager.cleanup() - - if self.ego_vehicle and self.ego_vehicle.is_alive: - try: - self.ego_vehicle.destroy() - except: - pass - - Log.info("清理完成") def main(): - - parser = argparse.ArgumentParser(description='CVIPS 多车辆协同数据收集系统 v10.0') - - # 基础参数 - parser.add_argument('--config', type=str, help='配置文件路径') - parser.add_argument('--scenario', type=str, default='multi_vehicle_cooperative', help='场景名称') - - parser = argparse.ArgumentParser(description='CVIPS 多传感器数据收集系统') + parser = argparse.ArgumentParser(description='CVIPS 数据增强采集系统 v11.0') # 基础参数 parser.add_argument('--config', type=str, help='配置文件路径') - parser.add_argument('--scenario', type=str, default='multi_sensor_scene', help='场景名称') - + parser.add_argument('--scenario', type=str, default='enhanced_data_collection', help='场景名称') parser.add_argument('--town', type=str, default='Town10HD', choices=['Town03', 'Town04', 'Town05', 'Town10HD'], help='地图') parser.add_argument('--weather', type=str, default='clear', @@ -1030,11 +1071,8 @@ def main(): # 交通参数 parser.add_argument('--num-vehicles', type=int, default=8, help='背景车辆数') parser.add_argument('--num-pedestrians', type=int, default=6, help='行人数') - parser.add_argument('--num-coop-vehicles', type=int, default=2, help='协同车辆数') - - # 收集参数 parser.add_argument('--duration', type=int, default=60, help='收集时长(秒)') parser.add_argument('--capture-interval', type=float, default=2.0, help='捕捉间隔(秒)') @@ -1043,16 +1081,15 @@ def main(): # 传感器参数 parser.add_argument('--enable-lidar', action='store_true', help='启用LiDAR传感器') parser.add_argument('--enable-fusion', action='store_true', help='启用多传感器融合') - parser.add_argument('--enable-v2x', action='store_true', help='启用V2X通信') parser.add_argument('--enable-cooperative', action='store_true', help='启用协同感知') - - + parser.add_argument('--enable-enhancement', action='store_true', help='启用数据增强') parser.add_argument('--enable-annotations', action='store_true', help='启用自动标注') # 功能参数 parser.add_argument('--run-analysis', action='store_true', help='运行数据集分析') parser.add_argument('--skip-validation', action='store_true', help='跳过数据验证') + parser.add_argument('--skip-quality-check', action='store_true', help='跳过质量检查') args = parser.parse_args(remaining_argv) @@ -1062,35 +1099,24 @@ def main(): # 显示配置 print("\n" + "=" * 60) - - print("CVIPS 多车辆协同数据收集系统 v10.0") - - print("CVIPS 多传感器数据收集系统 v9.0") - + print("CVIPS 数据增强采集系统 v11.0") print("=" * 60) print(f"场景: {config['scenario']['name']}") print(f"地图: {config['scenario']['town']}") print(f"天气/时间: {config['scenario']['weather']}/{config['scenario']['time_of_day']}") print(f"时长: {config['scenario']['duration']}秒") - print(f"交通: {config['traffic']['background_vehicles']}背景车辆 + {config['traffic']['pedestrians']}行人") print(f"协同: {config['cooperative']['num_coop_vehicles']} 协同车辆") - print(f"交通: {config['traffic']['background_vehicles']}车辆 + {config['traffic']['pedestrians']}行人") - - print(f"传感器:") print( f" 摄像头: {config['sensors']['vehicle_cameras']}车辆 + {config['sensors']['infrastructure_cameras']}基础设施") print(f" LiDAR: {'启用' if config['sensors']['lidar_sensors'] > 0 else '禁用'}") print(f" 融合: {'启用' if config['output']['save_fusion'] else '禁用'}") - print(f" V2X: {'启用' if config['v2x']['enabled'] else '禁用'}") print(f" 协同: {'启用' if config['output']['save_cooperative'] else '禁用'}") - - print(f" 标注: {'启用' if config['output']['save_annotations'] else '禁用'}") - + print(f" 增强: {'启用' if config['enhancement']['enabled'] else '禁用'}") collector = DataCollector(config) diff --git a/src/enhance_pedestrian_safety/sensor_enhancer.py b/src/enhance_pedestrian_safety/sensor_enhancer.py new file mode 100644 index 0000000000..f484831c94 --- /dev/null +++ b/src/enhance_pedestrian_safety/sensor_enhancer.py @@ -0,0 +1,784 @@ +""" +传感器数据增强模块 - 提高数据质量和多样性 +""" + +import numpy as np +import cv2 +import random +import os +import json +from PIL import Image, ImageEnhance, ImageFilter +from datetime import datetime +from typing import Dict, List, Tuple, Optional +import carla + + +class SensorDataEnhancer: + """传感器数据增强器""" + + def __init__(self, config: Dict): + self.config = config + self.enhancement_methods = [] + self._setup_enhancement_methods() + + def _setup_enhancement_methods(self): + """设置增强方法""" + # 根据场景配置启用不同的增强方法 + weather = self.config.get('scenario', {}).get('weather', 'clear') + time_of_day = self.config.get('scenario', {}).get('time_of_day', 'noon') + + # 基础增强方法 + self.enhancement_methods = ['normalize'] + + # 根据天气和时间添加特定增强 + if weather == 'rainy': + self.enhancement_methods.extend(['rain_effect', 'motion_blur', 'brightness_adjust']) + elif weather == 'foggy': + self.enhancement_methods.extend(['fog_effect', 'contrast_reduce']) + elif weather == 'night': + self.enhancement_methods.extend(['night_effect', 'noise_add', 'gamma_correction']) + elif weather == 'cloudy': + self.enhancement_methods.extend(['cloud_effect', 'color_temperature']) + + # 随机增强(可选) + if self.config.get('enhancement', {}).get('enable_random', True): + self.enhancement_methods.extend(self._get_random_enhancements()) + + def _get_random_enhancements(self) -> List[str]: + """获取随机增强方法""" + random_methods = [ + 'hue_shift', 'saturation_adjust', 'sharpness_enhance', + 'gaussian_blur', 'jpeg_compression', 'color_jitter' + ] + # 随机选择1-3个增强方法 + num_methods = random.randint(1, 3) + return random.sample(random_methods, num_methods) + + def enhance_image(self, image_data: np.ndarray, sensor_type: str = 'camera') -> np.ndarray: + """ + 增强图像数据 + + Args: + image_data: 原始图像数据 (H, W, C) + sensor_type: 传感器类型 ('camera', 'depth', 'semantic') + + Returns: + 增强后的图像数据 + """ + if sensor_type != 'camera': + # 深度和语义分割图像使用不同的增强 + return self._enhance_non_rgb_image(image_data, sensor_type) + + # 转换为PIL图像进行处理 + pil_image = Image.fromarray(image_data) + + # 按顺序应用增强方法 + for method in self.enhancement_methods: + pil_image = self._apply_enhancement_method(pil_image, method) + + # 确保图像在有效范围内 + enhanced_image = np.array(pil_image) + enhanced_image = np.clip(enhanced_image, 0, 255).astype(np.uint8) + + return enhanced_image + + def _apply_enhancement_method(self, image: Image.Image, method: str) -> Image.Image: + """应用单个增强方法""" + try: + if method == 'normalize': + return self._normalize_image(image) + elif method == 'rain_effect': + return self._add_rain_effect(image) + elif method == 'fog_effect': + return self._add_fog_effect(image) + elif method == 'night_effect': + return self._apply_night_effect(image) + elif method == 'motion_blur': + return self._apply_motion_blur(image) + elif method == 'brightness_adjust': + return self._adjust_brightness(image) + elif method == 'contrast_reduce': + return self._reduce_contrast(image) + elif method == 'noise_add': + return self._add_noise(image) + elif method == 'gamma_correction': + return self._gamma_correction(image) + elif method == 'cloud_effect': + return self._add_cloud_effect(image) + elif method == 'color_temperature': + return self._adjust_color_temperature(image) + elif method == 'hue_shift': + return self._shift_hue(image) + elif method == 'saturation_adjust': + return self._adjust_saturation(image) + elif method == 'sharpness_enhance': + return self._enhance_sharpness(image) + elif method == 'gaussian_blur': + return self._apply_gaussian_blur(image) + elif method == 'jpeg_compression': + return self._simulate_jpeg_compression(image) + elif method == 'color_jitter': + return self._color_jitter(image) + else: + return image + except Exception as e: + print(f"增强方法 {method} 失败: {e}") + return image + + def _enhance_non_rgb_image(self, image_data: np.ndarray, sensor_type: str) -> np.ndarray: + """增强非RGB图像(深度、语义分割)""" + if sensor_type == 'depth': + # 深度图增强:归一化和噪声去除 + normalized = cv2.normalize(image_data, None, 0, 255, cv2.NORM_MINMAX) + # 应用轻微的高斯模糊去除噪声 + enhanced = cv2.GaussianBlur(normalized, (3, 3), 0) + return enhanced.astype(np.uint8) + + elif sensor_type == 'semantic': + # 语义分割图增强:保持类别不变,只做边界平滑 + kernel = np.ones((3, 3), np.uint8) + # 形态学开运算去除小噪声 + enhanced = cv2.morphologyEx(image_data, cv2.MORPH_OPEN, kernel) + return enhanced + + return image_data + + def _normalize_image(self, image: Image.Image) -> Image.Image: + """图像归一化""" + # 转换为numpy数组 + img_array = np.array(image) + + # 归一化到0-255 + if img_array.dtype != np.uint8: + img_array = cv2.normalize(img_array, None, 0, 255, cv2.NORM_MINMAX) + img_array = img_array.astype(np.uint8) + + return Image.fromarray(img_array) + + def _add_rain_effect(self, image: Image.Image) -> Image.Image: + """添加雨滴效果""" + img_array = np.array(image) + h, w, _ = img_array.shape + + # 创建雨滴层 + rain_layer = np.zeros((h, w), dtype=np.float32) + + # 生成随机雨滴位置 + num_drops = random.randint(100, 500) + for _ in range(num_drops): + x = random.randint(0, w - 1) + y = random.randint(0, h - 1) + length = random.randint(5, 20) + thickness = random.randint(1, 2) + brightness = random.uniform(0.7, 0.9) + + # 绘制雨滴(斜线) + for i in range(length): + if y + i < h and x + i < w: + rain_layer[y + i, x + i] += brightness + # 加粗雨滴 + for j in range(thickness): + if x + i + j < w: + rain_layer[y + i, x + i + j] += brightness * 0.5 + + # 模糊雨滴层 + rain_layer = cv2.GaussianBlur(rain_layer, (5, 5), 0) + + # 叠加到原图 + rain_layer_3d = np.stack([rain_layer] * 3, axis=2) + enhanced = cv2.addWeighted(img_array.astype(np.float32), 0.8, + rain_layer_3d * 255, 0.2, 0) + + return Image.fromarray(enhanced.astype(np.uint8)) + + def _add_fog_effect(self, image: Image.Image) -> Image.Image: + """添加雾效""" + img_array = np.array(image) + h, w, _ = img_array.shape + + # 创建雾效层 + fog_intensity = random.uniform(0.3, 0.6) + fog_color = random.choice([200, 210, 220]) # 雾的颜色 + + fog_layer = np.ones((h, w, 3), dtype=np.float32) * fog_color + + # 根据深度添加雾效(这里简化处理,实际应该使用深度图) + # 创建简单的深度渐变(假设图像中心最近) + center_y, center_x = h // 2, w // 2 + y_coords, x_coords = np.ogrid[:h, :w] + distance = np.sqrt((x_coords - center_x) ** 2 + (y_coords - center_y) ** 2) + distance = distance / np.max(distance) # 归一化 + + # 雾效随距离增强 + fog_strength = distance * fog_intensity + fog_strength_3d = np.stack([fog_strength] * 3, axis=2) + + # 混合原图和雾效 + enhanced = img_array.astype(np.float32) * (1 - fog_strength_3d) + \ + fog_layer * fog_strength_3d + + return Image.fromarray(enhanced.astype(np.uint8)) + + def _apply_night_effect(self, image: Image.Image) -> Image.Image: + """应用夜间效果""" + # 降低亮度 + enhancer = ImageEnhance.Brightness(image) + image = enhancer.enhance(random.uniform(0.3, 0.6)) + + # 降低对比度 + enhancer = ImageEnhance.Contrast(image) + image = enhancer.enhance(random.uniform(0.7, 0.9)) + + # 添加暗角效果 + img_array = np.array(image) + h, w, _ = img_array.shape + + # 创建暗角蒙版 + center_y, center_x = h // 2, w // 2 + y_coords, x_coords = np.ogrid[:h, :w] + distance = np.sqrt((x_coords - center_x) ** 2 + (y_coords - center_y) ** 2) + distance = distance / np.max(distance) + + vignette = 1 - distance * 0.3 # 暗角强度 + vignette_3d = np.stack([vignette] * 3, axis=2) + + enhanced = img_array.astype(np.float32) * vignette_3d + enhanced = np.clip(enhanced, 0, 255) + + return Image.fromarray(enhanced.astype(np.uint8)) + + def _apply_motion_blur(self, image: Image.Image) -> Image.Image: + """应用运动模糊""" + img_array = np.array(image) + + # 随机选择模糊方向和强度 + kernel_size = random.choice([5, 7, 9]) + direction = random.choice(['horizontal', 'vertical']) + + if direction == 'horizontal': + kernel = np.zeros((kernel_size, kernel_size)) + kernel[kernel_size // 2, :] = 1.0 / kernel_size + else: # vertical + kernel = np.zeros((kernel_size, kernel_size)) + kernel[:, kernel_size // 2] = 1.0 / kernel_size + + # 应用卷积 + blurred = cv2.filter2D(img_array, -1, kernel) + + # 混合原图和模糊图 + alpha = random.uniform(0.3, 0.7) + enhanced = cv2.addWeighted(img_array, 1 - alpha, blurred, alpha, 0) + + return Image.fromarray(enhanced) + + def _adjust_brightness(self, image: Image.Image) -> Image.Image: + """调整亮度""" + factor = random.uniform(0.8, 1.2) + enhancer = ImageEnhance.Brightness(image) + return enhancer.enhance(factor) + + def _reduce_contrast(self, image: Image.Image) -> Image.Image: + """降低对比度""" + factor = random.uniform(0.7, 0.9) + enhancer = ImageEnhance.Contrast(image) + return enhancer.enhance(factor) + + def _add_noise(self, image: Image.Image) -> Image.Image: + """添加噪声""" + img_array = np.array(image) + + # 选择噪声类型 + noise_type = random.choice(['gaussian', 'salt_pepper']) + + if noise_type == 'gaussian': + # 高斯噪声 + mean = 0 + var = random.uniform(0.001, 0.005) + sigma = var ** 0.5 + gauss = np.random.normal(mean, sigma, img_array.shape) + noisy = img_array + gauss * 255 + noisy = np.clip(noisy, 0, 255) + + else: # salt_pepper + # 椒盐噪声 + amount = random.uniform(0.001, 0.005) + s_vs_p = random.uniform(0.3, 0.7) # 盐 vs 椒的比例 + + noisy = np.copy(img_array) + + # 盐噪声 + num_salt = np.ceil(amount * img_array.size * s_vs_p) + coords = [np.random.randint(0, i - 1, int(num_salt)) for i in img_array.shape] + noisy[coords[0], coords[1], :] = 255 + + # 椒噪声 + num_pepper = np.ceil(amount * img_array.size * (1. - s_vs_p)) + coords = [np.random.randint(0, i - 1, int(num_pepper)) for i in img_array.shape] + noisy[coords[0], coords[1], :] = 0 + + return Image.fromarray(noisy.astype(np.uint8)) + + def _gamma_correction(self, image: Image.Image) -> Image.Image: + """伽马校正""" + img_array = np.array(image).astype(np.float32) / 255.0 + gamma = random.uniform(0.8, 1.2) + + corrected = np.power(img_array, gamma) + corrected = (corrected * 255).astype(np.uint8) + + return Image.fromarray(corrected) + + def _add_cloud_effect(self, image: Image.Image) -> Image.Image: + """添加云层效果""" + img_array = np.array(image) + + # 轻微降低饱和度和对比度,模拟多云天气 + hsv = cv2.cvtColor(img_array, cv2.COLOR_RGB2HSV) + hsv[:, :, 1] = hsv[:, :, 1] * random.uniform(0.8, 0.9) # 降低饱和度 + hsv[:, :, 2] = hsv[:, :, 2] * random.uniform(0.9, 1.0) # 轻微降低亮度 + + enhanced = cv2.cvtColor(hsv, cv2.COLOR_HSV2RGB) + + return Image.fromarray(enhanced) + + def _adjust_color_temperature(self, image: Image.Image) -> Image.Image: + """调整色温""" + img_array = np.array(image).astype(np.float32) + + # 随机选择冷色调或暖色调 + temp_type = random.choice(['warm', 'cool']) + + if temp_type == 'warm': + # 暖色调:增加红色,减少蓝色 + img_array[:, :, 0] *= random.uniform(1.0, 1.1) # 红色通道 + img_array[:, :, 2] *= random.uniform(0.9, 1.0) # 蓝色通道 + else: + # 冷色调:增加蓝色,减少红色 + img_array[:, :, 0] *= random.uniform(0.9, 1.0) # 红色通道 + img_array[:, :, 2] *= random.uniform(1.0, 1.1) # 蓝色通道 + + img_array = np.clip(img_array, 0, 255) + + return Image.fromarray(img_array.astype(np.uint8)) + + def _shift_hue(self, image: Image.Image) -> Image.Image: + """色调偏移""" + img_array = np.array(image) + hsv = cv2.cvtColor(img_array, cv2.COLOR_RGB2HSV) + + # 随机偏移色调 + shift = random.randint(-10, 10) + hsv[:, :, 0] = (hsv[:, :, 0] + shift) % 180 + + enhanced = cv2.cvtColor(hsv, cv2.COLOR_HSV2RGB) + + return Image.fromarray(enhanced) + + def _adjust_saturation(self, image: Image.Image) -> Image.Image: + """调整饱和度""" + factor = random.uniform(0.8, 1.2) + enhancer = ImageEnhance.Color(image) + return enhancer.enhance(factor) + + def _enhance_sharpness(self, image: Image.Image) -> Image.Image: + """增强锐度""" + factor = random.uniform(1.2, 1.5) + enhancer = ImageEnhance.Sharpness(image) + return enhancer.enhance(factor) + + def _apply_gaussian_blur(self, image: Image.Image) -> Image.Image: + """应用高斯模糊""" + kernel_size = random.choice([3, 5]) + return image.filter(ImageFilter.GaussianBlur(radius=kernel_size)) + + def _simulate_jpeg_compression(self, image: Image.Image) -> Image.Image: + """模拟JPEG压缩""" + # 将图像保存为JPEG并重新加载以模拟压缩 + import io + buffer = io.BytesIO() + + # 随机质量 + quality = random.randint(70, 95) + image.save(buffer, format='JPEG', quality=quality) + buffer.seek(0) + + compressed = Image.open(buffer) + return compressed + + def _color_jitter(self, image: Image.Image) -> Image.Image: + """颜色抖动""" + # 应用随机颜色变换 + transforms = [] + + # 随机亮度调整 + if random.random() > 0.5: + brightness = random.uniform(0.8, 1.2) + transforms.append(lambda img: ImageEnhance.Brightness(img).enhance(brightness)) + + # 随机对比度调整 + if random.random() > 0.5: + contrast = random.uniform(0.8, 1.2) + transforms.append(lambda img: ImageEnhance.Contrast(img).enhance(contrast)) + + # 随机饱和度调整 + if random.random() > 0.5: + saturation = random.uniform(0.8, 1.2) + transforms.append(lambda img: ImageEnhance.Color(img).enhance(saturation)) + + # 随机应用变换 + if transforms: + random.shuffle(transforms) + for transform in transforms[:2]: # 最多应用2个变换 + image = transform(image) + + return image + + def save_enhanced_image(self, image_data: np.ndarray, output_path: str, + metadata: Optional[Dict] = None): + """ + 保存增强后的图像和元数据 + + Args: + image_data: 图像数据 + output_path: 输出路径 + metadata: 增强元数据 + """ + # 保存图像 + cv2.imwrite(output_path, cv2.cvtColor(image_data, cv2.COLOR_RGB2BGR)) + + # 保存元数据 + if metadata: + meta_path = output_path.replace('.png', '_meta.json').replace('.jpg', '_meta.json') + with open(meta_path, 'w', encoding='utf-8') as f: + json.dump(metadata, f, indent=2, ensure_ascii=False) + + def generate_enhancement_report(self, output_dir: str): + """生成增强报告""" + report = { + 'timestamp': datetime.now().isoformat(), + 'enhancement_methods': self.enhancement_methods, + 'config': self.config.get('enhancement', {}), + 'weather': self.config.get('scenario', {}).get('weather', 'clear'), + 'time_of_day': self.config.get('scenario', {}).get('time_of_day', 'noon'), + 'statistics': { + 'total_methods': len(self.enhancement_methods), + 'weather_specific_methods': [ + m for m in self.enhancement_methods + if m in ['rain_effect', 'fog_effect', 'night_effect', 'cloud_effect'] + ], + 'quality_methods': [ + m for m in self.enhancement_methods + if m in ['normalize', 'sharpness_enhance', 'gamma_correction'] + ], + 'random_methods': [ + m for m in self.enhancement_methods + if m in ['hue_shift', 'saturation_adjust', 'color_jitter', 'jpeg_compression'] + ] + } + } + + report_path = os.path.join(output_dir, 'enhancement_report.json') + with open(report_path, 'w', encoding='utf-8') as f: + json.dump(report, f, indent=2, ensure_ascii=False) + + return report + + +class SensorCalibrator: + """传感器校准模块""" + + def __init__(self, config: Dict): + self.config = config + self.calibration_data = {} + + def generate_calibration_files(self, output_dir: str, + vehicle_locations: List[Dict], + camera_positions: List[Dict]): + """ + 生成传感器校准文件 + + Args: + output_dir: 输出目录 + vehicle_locations: 车辆位置信息 + camera_positions: 相机位置信息 + """ + calib_dir = os.path.join(output_dir, "calibration") + os.makedirs(calib_dir, exist_ok=True) + + # 1. 生成相机内参 + self._generate_camera_intrinsics(calib_dir) + + # 2. 生成外参(相机到车辆) + self._generate_extrinsics(calib_dir, vehicle_locations, camera_positions) + + # 3. 生成传感器间标定 + self._generate_sensor_calibration(calib_dir) + + # 4. 生成时间同步校准 + self._generate_temporal_calibration(calib_dir) + + print(f"校准文件已生成到: {calib_dir}") + + def _generate_camera_intrinsics(self, calib_dir: str): + """生成相机内参""" + image_size = self.config.get('sensors', {}).get('image_size', [1280, 720]) + width, height = image_size[0], image_size[1] + + # 内参矩阵 [fx, 0, cx; 0, fy, cy; 0, 0, 1] + fx = width * 0.8 # 假设焦距 + fy = fx + cx = width / 2.0 + cy = height / 2.0 + + intrinsics = { + 'camera_matrix': [ + [fx, 0, cx], + [0, fy, cy], + [0, 0, 1] + ], + 'distortion_coefficients': [0.0, 0.0, 0.0, 0.0, 0.0], # 无畸变 + 'image_size': [width, height], + 'fov': 90.0, + 'sensor_type': 'pinhole' + } + + # 为每个相机生成内参(简化处理,实际应该每个相机不同) + for i in range(4): # 假设4个车辆相机 + file_path = os.path.join(calib_dir, f'camera_{i + 1}_intrinsic.json') + with open(file_path, 'w', encoding='utf-8') as f: + json.dump(intrinsics, f, indent=2, ensure_ascii=False) + + # 基础设施相机内参(可能不同) + for i in range(4): # 假设4个基础设施相机 + file_path = os.path.join(calib_dir, f'infra_camera_{i + 1}_intrinsic.json') + # 基础设施相机可能有不同的参数 + infra_intrinsics = intrinsics.copy() + infra_intrinsics['fov'] = 120.0 # 更广的视角 + infra_intrinsics['sensor_type'] = 'fisheye' # 鱼眼相机 + + with open(file_path, 'w', encoding='utf-8') as f: + json.dump(infra_intrinsics, f, indent=2, ensure_ascii=False) + + def _generate_extrinsics(self, calib_dir: str, + vehicle_locations: List[Dict], + camera_positions: List[Dict]): + """生成外参(相机到车辆坐标系)""" + for i, (vehicle_loc, cam_pos) in enumerate(zip(vehicle_locations, camera_positions)): + # 外参矩阵 [R|t] + # 这里简化为单位矩阵,实际应根据安装位置计算 + extrinsics = { + 'vehicle_id': vehicle_loc.get('id', i + 1), + 'translation': cam_pos.get('translation', [0, 0, 0]), + 'rotation': cam_pos.get('rotation', [0, 0, 0]), + 'transform_matrix': [ + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1] + ], + 'timestamp': datetime.now().isoformat() + } + + file_path = os.path.join(calib_dir, f'vehicle_{i + 1}_extrinsic.json') + with open(file_path, 'w', encoding='utf-8') as f: + json.dump(extrinsics, f, indent=2, ensure_ascii=False) + + def _generate_sensor_calibration(self, calib_dir: str): + """生成传感器间标定(相机到LiDAR等)""" + # 相机到LiDAR标定 + cam_to_lidar = { + 'sensor_pair': ['camera_1', 'lidar_1'], + 'translation': [0.5, 0.0, -0.2], # 相机相对于LiDAR的位置 + 'rotation': [0, 0, 0], # 旋转 + 'calibration_method': 'manual', + 'accuracy': 0.01, # 标定精度(米) + 'timestamp': datetime.now().isoformat() + } + + file_path = os.path.join(calib_dir, 'camera_to_lidar_calib.json') + with open(file_path, 'w', encoding='utf-8') as f: + json.dump(cam_to_lidar, f, indent=2, ensure_ascii=False) + + # 基础设施相机到全局坐标系标定 + infra_to_global = { + 'sensor_type': 'infrastructure_camera', + 'global_position': [0, 0, 12], # 安装位置 + 'orientation': [0, -25, 0], # 俯仰角 + 'calibration_method': 'gps_imu', + 'accuracy': 0.05, + 'timestamp': datetime.now().isoformat() + } + + file_path = os.path.join(calib_dir, 'infrastructure_global_calib.json') + with open(file_path, 'w', encoding='utf-8') as f: + json.dump(infra_to_global, f, indent=2, ensure_ascii=False) + + def _generate_temporal_calibration(self, calib_dir: str): + """生成时间同步校准""" + temporal_calib = { + 'sensor_latencies': { + 'camera': 0.033, # 33ms延迟 + 'lidar': 0.010, # 10ms延迟 + 'gps': 0.001, # 1ms延迟 + 'imu': 0.005 # 5ms延迟 + }, + 'sync_method': 'hardware_trigger', + 'sync_accuracy': 0.001, # 1ms同步精度 + 'master_clock': 'gps_time', + 'timestamp': datetime.now().isoformat() + } + + file_path = os.path.join(calib_dir, 'temporal_calibration.json') + with open(file_path, 'w', encoding='utf-8') as f: + json.dump(temporal_calib, f, indent=2, ensure_ascii=False) + + +class DataQualityMonitor: + """数据质量监控器""" + + def __init__(self, output_dir: str): + self.output_dir = output_dir + self.quality_metrics = { + 'images': {'total': 0, 'valid': 0, 'issues': []}, + 'lidar': {'total': 0, 'valid': 0, 'issues': []}, + 'annotations': {'total': 0, 'valid': 0, 'issues': []}, + 'calibration': {'total': 0, 'valid': 0, 'issues': []} + } + + def check_image_quality(self, image_path: str) -> Dict: + """检查图像质量""" + try: + img = cv2.imread(image_path) + if img is None: + return {'valid': False, 'error': '无法读取图像'} + + # 检查图像尺寸 + h, w, c = img.shape + if h == 0 or w == 0: + return {'valid': False, 'error': '图像尺寸无效'} + + # 检查图像是否全黑或全白 + mean_brightness = np.mean(img) + if mean_brightness < 10 or mean_brightness > 245: + return {'valid': False, 'warning': f'图像过暗或过亮: {mean_brightness}'} + + # 检查图像对比度 + contrast = np.std(img) + if contrast < 20: + return {'valid': True, 'warning': f'图像对比度过低: {contrast}'} + + return {'valid': True, 'dimensions': (w, h), 'brightness': mean_brightness, 'contrast': contrast} + + except Exception as e: + return {'valid': False, 'error': str(e)} + + def check_lidar_quality(self, lidar_path: str) -> Dict: + """检查LiDAR数据质量""" + try: + # 检查文件是否存在和大小 + if not os.path.exists(lidar_path): + return {'valid': False, 'error': '文件不存在'} + + file_size = os.path.getsize(lidar_path) + if file_size == 0: + return {'valid': False, 'error': '文件为空'} + + # 如果是.bin文件,检查点云数据 + if lidar_path.endswith('.bin'): + points = np.fromfile(lidar_path, dtype=np.float32) + num_points = len(points) // 4 # 假设每个点4个float + + if num_points < 100: + return {'valid': False, 'error': f'点云数量过少: {num_points}'} + + return { + 'valid': True, + 'num_points': num_points, + 'file_size': file_size + } + + return {'valid': True, 'file_size': file_size} + + except Exception as e: + return {'valid': False, 'error': str(e)} + + def update_metrics(self, data_type: str, check_result: Dict): + """更新质量指标""" + self.quality_metrics[data_type]['total'] += 1 + + if check_result.get('valid', False): + self.quality_metrics[data_type]['valid'] += 1 + else: + self.quality_metrics[data_type]['issues'].append(check_result.get('error', '未知错误')) + + def generate_quality_report(self) -> Dict: + """生成质量报告""" + report = { + 'timestamp': datetime.now().isoformat(), + 'summary': {}, + 'issues_by_type': {}, + 'quality_score': 0 + } + + total_valid = 0 + total_count = 0 + + for data_type, metrics in self.quality_metrics.items(): + total = metrics['total'] + valid = metrics['valid'] + + if total > 0: + valid_ratio = valid / total + report['summary'][data_type] = { + 'total': total, + 'valid': valid, + 'valid_ratio': round(valid_ratio * 100, 2), + 'issues_count': len(metrics['issues']) + } + + total_valid += valid + total_count += total + + # 记录问题 + if metrics['issues']: + report['issues_by_type'][data_type] = metrics['issues'][:10] # 只显示前10个问题 + + # 计算总体质量分数 + if total_count > 0: + report['quality_score'] = round((total_valid / total_count) * 100, 2) + + # 保存报告 + report_path = os.path.join(self.output_dir, 'data_quality_report.json') + with open(report_path, 'w', encoding='utf-8') as f: + json.dump(report, f, indent=2, ensure_ascii=False) + + return report + + def print_quality_summary(self): + """打印质量摘要""" + print("\n" + "=" * 60) + print("数据质量报告") + print("=" * 60) + + report = self.generate_quality_report() + + for data_type, summary in report['summary'].items(): + print(f"\n{data_type.upper()}:") + print(f" 总数: {summary['total']}") + print(f" 有效: {summary['valid']}") + print(f" 有效率: {summary['valid_ratio']}%") + if summary['issues_count'] > 0: + print(f" 问题数: {summary['issues_count']}") + + print(f"\n总体质量分数: {report['quality_score']}/100") + + if report['quality_score'] >= 90: + print("✓ 数据质量优秀") + elif report['quality_score'] >= 75: + print("✓ 数据质量良好") + elif report['quality_score'] >= 60: + print("⚠ 数据质量一般") + else: + print("✗ 数据质量需要改进") + + print("=" * 60) \ No newline at end of file From 8dcf6e21ea6427f5565539f1d20356fe8667dc4a Mon Sep 17 00:00:00 2001 From: Z-w-7799 <1011069369@qq.com> Date: Fri, 19 Dec 2025 14:20:02 +0800 Subject: [PATCH 04/20] =?UTF-8?q?=E5=86=85=E5=AD=98=E5=92=8C=E6=80=A7?= =?UTF-8?q?=E8=83=BD=E4=BC=98=E5=8C=96=EF=BC=8C=E6=95=B0=E6=8D=AE=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E6=A0=87=E5=87=86=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config_manager.py | 67 +- .../lidar_processor.py | 380 ++++++++-- src/enhance_pedestrian_safety/main.py | 675 +++++++++++------- 3 files changed, 791 insertions(+), 331 deletions(-) diff --git a/src/enhance_pedestrian_safety/config_manager.py b/src/enhance_pedestrian_safety/config_manager.py index 54477406ba..0ef2d0bff3 100644 --- a/src/enhance_pedestrian_safety/config_manager.py +++ b/src/enhance_pedestrian_safety/config_manager.py @@ -20,7 +20,9 @@ def load_config(config_file=None): 'ego_vehicles': 1, 'background_vehicles': 8, 'pedestrians': 6, - 'traffic_lights': True + 'traffic_lights': True, + 'batch_spawn': True, + 'max_spawn_attempts': 5 }, 'sensors': { 'vehicle_cameras': 4, @@ -33,7 +35,9 @@ def load_config(config_file=None): 'channels': 32, 'range': 100, 'points_per_second': 56000, - 'rotation_frequency': 10 + 'rotation_frequency': 10, + 'max_points_per_frame': 100000, + 'downsample_ratio': 0.5 } }, 'v2x': { @@ -62,6 +66,27 @@ def load_config(config_file=None): 'calibration_generation': True, 'enhanced_dir_name': 'enhanced' }, + 'performance': { + 'batch_size': 5, + 'enable_compression': True, + 'compression_level': 3, + 'enable_downsampling': True, + 'enable_memory_cache': True, + 'max_cache_size': 50, + 'image_processing': { + 'compress_images': True, + 'compression_quality': 85 + }, + 'lidar_processing': { + 'batch_size': 10, + 'enable_compression': True, + 'enable_downsampling': True, + 'max_points_per_frame': 100000 + }, + 'fusion': { + 'fusion_cache_size': 100 + } + }, 'output': { 'data_dir': 'cvips_dataset', 'save_raw': True, @@ -74,7 +99,8 @@ def load_config(config_file=None): 'save_enhanced': True, 'validate_data': True, 'run_analysis': False, - 'run_quality_check': True + 'run_quality_check': True, + 'output_format': 'standard' } } @@ -116,38 +142,51 @@ def merge_args(config, args): if args.num_pedestrians: config['traffic']['pedestrians'] = args.num_pedestrians - if args.num_coop_vehicles: + if hasattr(args, 'num_coop_vehicles') and args.num_coop_vehicles: config['cooperative']['num_coop_vehicles'] = args.num_coop_vehicles - if args.capture_interval: + if hasattr(args, 'capture_interval') and args.capture_interval: config['sensors']['capture_interval'] = args.capture_interval - if args.enable_v2x: + if hasattr(args, 'enable_v2x') and args.enable_v2x: config['v2x']['enabled'] = True - if args.enable_enhancement: + if hasattr(args, 'enable_enhancement') and args.enable_enhancement: config['enhancement']['enabled'] = True - if args.enable_lidar: + if hasattr(args, 'enable_lidar') and args.enable_lidar: config['sensors']['lidar_sensors'] = 1 config['output']['save_lidar'] = True - if args.enable_fusion: + if hasattr(args, 'enable_fusion') and args.enable_fusion: config['output']['save_fusion'] = True - if args.enable_cooperative: + if hasattr(args, 'enable_cooperative') and args.enable_cooperative: config['output']['save_cooperative'] = True - if args.enable_annotations: + if hasattr(args, 'enable_annotations') and args.enable_annotations: config['output']['save_annotations'] = True - if args.skip_validation: + if hasattr(args, 'skip_validation') and args.skip_validation: config['output']['validate_data'] = False - if args.skip_quality_check: + if hasattr(args, 'skip_quality_check') and args.skip_quality_check: config['output']['run_quality_check'] = False - if args.run_analysis: + if hasattr(args, 'run_analysis') and args.run_analysis: config['output']['run_analysis'] = True + # 性能参数 + if hasattr(args, 'batch_size') and args.batch_size: + config['performance']['batch_size'] = args.batch_size + + if hasattr(args, 'enable_compression') and args.enable_compression: + config['performance']['enable_compression'] = True + + if hasattr(args, 'enable_downsampling') and args.enable_downsampling: + config['performance']['enable_downsampling'] = True + + if hasattr(args, 'output_format') and args.output_format: + config['output']['output_format'] = args.output_format + return config \ No newline at end of file diff --git a/src/enhance_pedestrian_safety/lidar_processor.py b/src/enhance_pedestrian_safety/lidar_processor.py index fdcc61637b..c9a960ac5f 100644 --- a/src/enhance_pedestrian_safety/lidar_processor.py +++ b/src/enhance_pedestrian_safety/lidar_processor.py @@ -3,12 +3,15 @@ import os import json import threading +import gc from datetime import datetime +import zlib +import pickle class LidarProcessor: - def __init__(self, output_dir): + def __init__(self, output_dir, config=None): self.output_dir = output_dir self.lidar_dir = os.path.join(output_dir, "lidar") os.makedirs(self.lidar_dir, exist_ok=True) @@ -19,6 +22,17 @@ def __init__(self, output_dir): self.frame_counter = 0 self.data_lock = threading.Lock() + # 性能优化:批量处理设置 + self.batch_size = config.get('batch_size', 10) if config else 10 + self.point_cloud_batch = [] + self.enable_compression = config.get('enable_compression', True) if config else True + self.compression_level = config.get('compression_level', 3) if config else 3 + + # 内存管理 + self.max_points_per_frame = config.get('max_points_per_frame', 100000) if config else 100000 + self.enable_downsampling = config.get('enable_downsampling', True) if config else True + self.downsample_ratio = config.get('downsample_ratio', 0.5) if config else 0.5 + self._init_calibration_files() def _init_calibration_files(self): @@ -62,65 +76,175 @@ def process_lidar_data(self, lidar_data, frame_num): if points is None or points.shape[0] == 0: return None + # 下采样以减少内存使用 + if self.enable_downsampling and points.shape[0] > self.max_points_per_frame: + points = self._downsample_point_cloud(points) + + # 添加到批处理 + self.point_cloud_batch.append((frame_num, points)) + + # 如果达到批处理大小,保存批处理数据 + if len(self.point_cloud_batch) >= self.batch_size: + self._save_batch() + + # 保存单个文件(向后兼容) bin_path = self._save_as_bin(points, frame_num) npy_path = self._save_as_npy(points, frame_num) - csv_path = self._save_as_csv(points, frame_num) - metadata = self._generate_metadata(points, bin_path, npy_path, csv_path) + # 生成V2XFormer兼容格式 + v2xformer_path = self._save_as_v2xformer_format(points, frame_num) + + metadata = self._generate_metadata(points, bin_path, npy_path, v2xformer_path) return metadata def _carla_lidar_to_numpy(self, lidar_data): + """高效转换LiDAR数据""" try: + # 使用内存视图减少内存分配 points = np.frombuffer(lidar_data.raw_data, dtype=np.float32) points = np.reshape(points, (int(points.shape[0] / 4), 4)) + # 只保留位置信息(x, y, z),忽略强度 points = points[:, :3] + # 清理临时数组 + del lidar_data + gc.collect() + return points except Exception as e: print(f"LiDAR数据转换失败: {e}") + # 备选转换方法 try: points = [] - for point in lidar_data: + for i in range(0, len(lidar_data), 4): + point = lidar_data[i:i + 4] points.append([point.x, point.y, point.z]) return np.array(points, dtype=np.float32) except: return None + def _downsample_point_cloud(self, points): + """下采样点云以减少内存使用""" + if points.shape[0] <= self.max_points_per_frame: + return points + + # 随机下采样 + indices = np.random.choice(points.shape[0], + int(points.shape[0] * self.downsample_ratio), + replace=False) + downsampled = points[indices] + + # 清理内存 + del points + gc.collect() + + return downsampled + + def _save_batch(self): + """批量保存点云数据(提高效率)""" + if not self.point_cloud_batch: + return + + batch_data = [] + for frame_num, points in self.point_cloud_batch: + batch_data.append({ + 'frame_num': frame_num, + 'points': points.tolist(), + 'num_points': points.shape[0] + }) + + # 生成批处理文件名 + min_frame = min([item[0] for item in self.point_cloud_batch]) + max_frame = max([item[0] for item in self.point_cloud_batch]) + batch_filename = f"lidar_batch_{min_frame:06d}_{max_frame:06d}.json" + batch_path = os.path.join(self.lidar_dir, batch_filename) + + # 压缩保存 + if self.enable_compression: + compressed_data = zlib.compress( + json.dumps(batch_data).encode('utf-8'), + level=self.compression_level + ) + with open(batch_path, 'wb') as f: + f.write(compressed_data) + else: + with open(batch_path, 'w') as f: + json.dump(batch_data, f) + + # 清理批处理缓存 + self.point_cloud_batch.clear() + gc.collect() + + print(f"批量保存LiDAR数据: {batch_filename}") + def _save_as_bin(self, points, frame_num): + """保存为二进制格式(兼容KITTI)""" bin_path = os.path.join(self.lidar_dir, f"lidar_{frame_num:06d}.bin") + # 添加强度信息(全为1) points_with_intensity = np.zeros((points.shape[0], 4), dtype=np.float32) points_with_intensity[:, :3] = points - points_with_intensity[:, 3] = 0.0 + points_with_intensity[:, 3] = 1.0 # 强度值 points_with_intensity.tofile(bin_path) return bin_path def _save_as_npy(self, points, frame_num): + """保存为numpy格式""" npy_path = os.path.join(self.lidar_dir, f"lidar_{frame_num:06d}.npy") - np.save(npy_path, points) + + # 使用内存映射减少内存使用 + memmap_array = np.lib.format.open_memmap( + npy_path, + mode='w+', + dtype=np.float32, + shape=points.shape + ) + memmap_array[:] = points[:] + memmap_array.flush() + return npy_path - def _save_as_csv(self, points, frame_num): - csv_path = os.path.join(self.lidar_dir, f"lidar_{frame_num:06d}.csv") + def _save_as_v2xformer_format(self, points, frame_num): + """保存为V2XFormer兼容格式""" + v2x_dir = os.path.join(self.output_dir, "v2xformer_format") + os.makedirs(v2x_dir, exist_ok=True) - sample_points = points[:min(1000, points.shape[0])] + # 创建V2XFormer格式的数据结构 + v2x_data = { + 'frame_id': frame_num, + 'timestamp': datetime.now().timestamp(), + 'point_cloud': { + 'points': points.tolist(), + 'num_points': points.shape[0], + 'range': [np.min(points[:, 0]), np.max(points[:, 0]), + np.min(points[:, 1]), np.max(points[:, 1]), + np.min(points[:, 2]), np.max(points[:, 2])] + }, + 'metadata': { + 'sensor_type': 'lidar', + 'format_version': '1.0', + 'coordinate_system': 'carla_world' + } + } - header = "x,y,z\n" + # 保存为压缩的JSON格式 + filename = f"{frame_num:06d}.pkl.gz" + filepath = os.path.join(v2x_dir, filename) - with open(csv_path, 'w') as f: - f.write(header) - np.savetxt(f, sample_points, delimiter=',', fmt='%.6f') + # 使用pickle+gzip压缩保存 + with gzip.open(filepath, 'wb') as f: + pickle.dump(v2x_data, f, protocol=pickle.HIGHEST_PROTOCOL) - return csv_path + return filepath - def _generate_metadata(self, points, bin_path, npy_path, csv_path): + def _generate_metadata(self, points, bin_path, npy_path, v2xformer_path): metadata = { 'frame_id': self.frame_counter, 'timestamp': datetime.now().isoformat(), @@ -128,7 +252,7 @@ def _generate_metadata(self, points, bin_path, npy_path, csv_path): 'file_paths': { 'bin': os.path.basename(bin_path), 'npy': os.path.basename(npy_path), - 'csv': os.path.basename(csv_path) + 'v2xformer': os.path.basename(v2xformer_path) }, 'statistics': { 'x_range': [float(points[:, 0].min()), float(points[:, 0].max())], @@ -137,10 +261,11 @@ def _generate_metadata(self, points, bin_path, npy_path, csv_path): 'mean': [float(points[:, 0].mean()), float(points[:, 1].mean()), float(points[:, 2].mean())], 'std': [float(points[:, 0].std()), float(points[:, 1].std()), float(points[:, 2].std())] }, - 'file_sizes': { - 'bin': os.path.getsize(bin_path) if os.path.exists(bin_path) else 0, - 'npy': os.path.getsize(npy_path) if os.path.exists(npy_path) else 0, - 'csv': os.path.getsize(csv_path) if os.path.exists(csv_path) else 0 + 'processing_info': { + 'downsampled': self.enable_downsampling and points.shape[0] > self.max_points_per_frame, + 'downsample_ratio': self.downsample_ratio if self.enable_downsampling else 1.0, + 'compression_enabled': self.enable_compression, + 'compression_level': self.compression_level } } @@ -150,33 +275,57 @@ def _generate_metadata(self, points, bin_path, npy_path, csv_path): return metadata + def flush_batch(self): + """强制刷新批处理数据""" + if self.point_cloud_batch: + self._save_batch() + def generate_lidar_summary(self): + """生成LiDAR数据摘要(优化版)""" if not os.path.exists(self.lidar_dir): return None - bin_files = [f for f in os.listdir(self.lidar_dir) if f.endswith('.bin')] - npy_files = [f for f in os.listdir(self.lidar_dir) if f.endswith('.npy')] - meta_files = [f for f in os.listdir(self.lidar_dir) if f.endswith('.json')] + # 快速统计文件 + import glob + bin_files = glob.glob(os.path.join(self.lidar_dir, "*.bin")) + npy_files = glob.glob(os.path.join(self.lidar_dir, "*.npy")) + batch_files = glob.glob(os.path.join(self.lidar_dir, "*batch*.json")) + v2x_files = glob.glob(os.path.join(self.output_dir, "v2xformer_format", "*.pkl.gz")) total_points = 0 - for bin_file in bin_files: - bin_path = os.path.join(self.lidar_dir, bin_file) - if os.path.exists(bin_path): - file_size = os.path.getsize(bin_path) - points_in_file = file_size // (4 * 4) + total_size = 0 + + # 采样统计,避免读取所有文件 + sample_files = bin_files[:10] if len(bin_files) > 10 else bin_files + for bin_file in sample_files: + if os.path.exists(bin_file): + file_size = os.path.getsize(bin_file) + total_size += file_size + # 估算点数 + points_in_file = file_size // (4 * 4) # 4个float32,每个4字节 total_points += points_in_file + # 根据采样估算总数 + if sample_files: + avg_points_per_file = total_points / len(sample_files) + total_points_estimated = avg_points_per_file * len(bin_files) + else: + total_points_estimated = 0 + summary = { 'total_frames': len(bin_files), - 'total_points': total_points, - 'average_points_per_frame': total_points // max(len(bin_files), 1), + 'total_points_estimated': int(total_points_estimated), + 'average_points_per_frame': int(avg_points_per_file) if sample_files else 0, 'file_types': { 'bin': len(bin_files), 'npy': len(npy_files), - 'meta': len(meta_files) + 'batch': len(batch_files), + 'v2xformer': len(v2x_files) }, - 'total_size_mb': round(sum(os.path.getsize(os.path.join(self.lidar_dir, f)) - for f in bin_files + npy_files + meta_files) / (1024 * 1024), 2) + 'total_size_mb': round(total_size / (1024 * 1024), 2), + 'compression_ratio': round(sum([os.path.getsize(f) for f in batch_files]) / + max(1, sum([os.path.getsize(f) for f in bin_files])), 2) + if bin_files and batch_files else 1.0 } summary_path = os.path.join(self.output_dir, "metadata", "lidar_summary.json") @@ -187,86 +336,167 @@ def generate_lidar_summary(self): class MultiSensorFusion: + """多传感器融合(优化版)""" - def __init__(self, output_dir): + def __init__(self, output_dir, config=None): self.output_dir = output_dir self.fusion_dir = os.path.join(output_dir, "fusion") os.makedirs(self.fusion_dir, exist_ok=True) self.calibration_data = {} + self.fusion_cache = {} # 融合缓存 + self.cache_size = config.get('fusion_cache_size', 100) if config else 100 + self._load_calibration() def _load_calibration(self): calibration_dir = os.path.join(self.output_dir, "calibration") if os.path.exists(calibration_dir): - for file in os.listdir(calibration_dir): - if file.endswith('.json'): - filepath = os.path.join(calibration_dir, file) + # 批量读取校准文件 + calibration_files = [] + for root, dirs, files in os.walk(calibration_dir): + for file in files: + if file.endswith('.json'): + calibration_files.append(os.path.join(root, file)) + + # 并行读取校准文件 + import concurrent.futures + with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor: + future_to_file = { + executor.submit(self._load_calibration_file, file): file + for file in calibration_files + } + + for future in concurrent.futures.as_completed(future_to_file): + file = future_to_file[future] try: - with open(filepath, 'r') as f: - data = json.load(f) - sensor_name = file.replace('.json', '') - self.calibration_data[sensor_name] = data - except: - pass + sensor_name, data = future.result() + if sensor_name and data: + self.calibration_data[sensor_name] = data + except Exception as e: + print(f"加载校准文件 {file} 失败: {e}") + + def _load_calibration_file(self, filepath): + """加载单个校准文件""" + try: + with open(filepath, 'r') as f: + data = json.load(f) + sensor_name = os.path.basename(filepath).replace('.json', '') + return sensor_name, data + except: + return None, None def create_synchronization_file(self, frame_num, sensor_data): + """创建同步文件(优化版)""" + # 检查缓存 + cache_key = f"{frame_num}_{hash(str(sensor_data))}" + if cache_key in self.fusion_cache: + return self.fusion_cache[cache_key] + sync_data = { 'frame_id': frame_num, - 'timestamp': datetime.now().isoformat(), - 'sensors': {} + 'timestamp': datetime.now().timestamp(), # 使用时间戳而不是ISO格式 + 'sensors': {}, + 'transformations': {} } + # 批量处理传感器数据 for sensor_type, data_path in sensor_data.items(): if data_path and os.path.exists(data_path): - sync_data['sensors'][sensor_type] = { + # 获取文件信息(不加载完整文件) + file_info = { 'file_path': os.path.basename(data_path), 'file_size': os.path.getsize(data_path), - 'timestamp': datetime.fromtimestamp(os.path.getctime(data_path)).isoformat() + 'modified_time': os.path.getmtime(data_path) } + # 如果是图像,获取尺寸信息 + if data_path.endswith(('.png', '.jpg', '.jpeg')): + try: + import cv2 + img = cv2.imread(data_path) + if img is not None: + file_info['dimensions'] = img.shape[:2] + except: + pass + + sync_data['sensors'][sensor_type] = file_info + + # 添加变换信息(如果可用) + if sensor_type in self.calibration_data: + sync_data['transformations'][sensor_type] = { + 'matrix': self.calibration_data[sensor_type].get('matrix', + [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], + [0, 0, 0, 1]]) + } + + # 压缩保存 sync_file = os.path.join(self.fusion_dir, f"sync_{frame_num:06d}.json") - with open(sync_file, 'w') as f: - json.dump(sync_data, f, indent=2) + + # 使用压缩的JSON + if len(str(sync_data)) > 1000: # 如果数据较大,压缩保存 + compressed_data = zlib.compress( + json.dumps(sync_data).encode('utf-8'), + level=3 + ) + sync_file = sync_file.replace('.json', '.json.gz') + with open(sync_file, 'wb') as f: + f.write(compressed_data) + else: + with open(sync_file, 'w') as f: + json.dump(sync_data, f, separators=(',', ':')) # 紧凑格式 + + # 更新缓存 + if len(self.fusion_cache) >= self.cache_size: + # 移除最旧的缓存项 + oldest_key = next(iter(self.fusion_cache)) + del self.fusion_cache[oldest_key] + + self.fusion_cache[cache_key] = sync_file return sync_file def generate_fusion_report(self): + """生成融合报告(优化版)""" if not os.path.exists(self.fusion_dir): return None - sync_files = [f for f in os.listdir(self.fusion_dir) if f.endswith('.json')] + # 快速扫描文件 + import glob + sync_files = glob.glob(os.path.join(self.fusion_dir, "*.json*")) + + # 解析文件名获取帧范围(避免读取所有文件) + frame_ids = [] + for sync_file in sync_files: + try: + # 从文件名提取帧号 + filename = os.path.basename(sync_file) + if filename.startswith('sync_'): + frame_id = int(filename.split('_')[1].split('.')[0]) + frame_ids.append(frame_id) + except: + continue report = { 'total_sync_frames': len(sync_files), - 'calibration_data': list(self.calibration_data.keys()), - 'sensor_types': set(), - 'frame_range': [] + 'calibration_data_count': len(self.calibration_data), + 'sensor_types': list(set([f.split('_')[0] for f in self.calibration_data.keys()])), + 'frame_range': [min(frame_ids), max(frame_ids)] if frame_ids else [], + 'file_sizes': { + 'total_mb': round(sum([os.path.getsize(f) for f in sync_files]) / (1024 * 1024), 2), + 'average_kb': round(np.mean([os.path.getsize(f) / 1024 for f in sync_files]), 2) if sync_files else 0 + }, + 'compression_info': { + 'compressed_files': len([f for f in sync_files if f.endswith('.gz')]), + 'compression_ratio': round( + sum([os.path.getsize(f) for f in sync_files if f.endswith('.gz')]) / + max(1, sum([os.path.getsize(f) for f in sync_files if not f.endswith('.gz')])), + 2 + ) if sync_files else 1.0 + } } - if sync_files: - frame_ids = [] - for sync_file in sync_files: - try: - frame_id = int(sync_file.split('_')[1].split('.')[0]) - frame_ids.append(frame_id) - - filepath = os.path.join(self.fusion_dir, sync_file) - with open(filepath, 'r') as f: - data = json.load(f) - - for sensor_type in data.get('sensors', {}).keys(): - report['sensor_types'].add(sensor_type) - - except: - continue - - if frame_ids: - report['frame_range'] = [min(frame_ids), max(frame_ids)] - - report['sensor_types'] = list(report['sensor_types']) - report_path = os.path.join(self.output_dir, "metadata", "fusion_report.json") with open(report_path, 'w') as f: json.dump(report, f, indent=2) diff --git a/src/enhance_pedestrian_safety/main.py b/src/enhance_pedestrian_safety/main.py index b6f4c384c5..80313c0481 100644 --- a/src/enhance_pedestrian_safety/main.py +++ b/src/enhance_pedestrian_safety/main.py @@ -7,9 +7,9 @@ import math import threading import json -import cv2 -import numpy as np from datetime import datetime +import gc +import psutil from carla_utils import setup_carla_path, import_carla_module from config_manager import ConfigManager @@ -20,12 +20,49 @@ from lidar_processor import LidarProcessor, MultiSensorFusion from multi_vehicle_manager import MultiVehicleManager from v2x_communication import V2XCommunication -from sensor_enhancer import SensorDataEnhancer, SensorCalibrator, DataQualityMonitor carla_egg_path, remaining_argv = setup_carla_path() carla = import_carla_module() +class PerformanceMonitor: + """性能监控器""" + + def __init__(self): + self.start_time = time.time() + self.memory_samples = [] + self.cpu_samples = [] + self.frame_times = [] + + def sample_memory(self): + """采样内存使用""" + process = psutil.Process(os.getpid()) + memory_mb = process.memory_info().rss / 1024 / 1024 + self.memory_samples.append(memory_mb) + return memory_mb + + def sample_cpu(self): + """采样CPU使用""" + cpu_percent = psutil.cpu_percent(interval=0.1) + self.cpu_samples.append(cpu_percent) + return cpu_percent + + def record_frame_time(self, frame_time): + """记录帧处理时间""" + self.frame_times.append(frame_time) + + def get_performance_summary(self): + """获取性能摘要""" + return { + 'total_runtime': time.time() - self.start_time, + 'average_memory_mb': sum(self.memory_samples) / len(self.memory_samples) if self.memory_samples else 0, + 'max_memory_mb': max(self.memory_samples) if self.memory_samples else 0, + 'average_cpu_percent': sum(self.cpu_samples) / len(self.cpu_samples) if self.cpu_samples else 0, + 'average_frame_time': sum(self.frame_times) / len(self.frame_times) if self.frame_times else 0, + 'frames_per_second': 1.0 / (sum(self.frame_times) / len(self.frame_times)) if self.frame_times else 0 + } + + class Log: @staticmethod def info(msg): @@ -75,11 +112,18 @@ def create_weather(weather_type, time_of_day): class ImageProcessor: - def __init__(self, output_dir): + def __init__(self, output_dir, config=None): self.output_dir = output_dir self.stitched_dir = os.path.join(output_dir, "stitched") os.makedirs(self.stitched_dir, exist_ok=True) + # 性能配置 + self.compress_images = config.get('compress_images', True) if config else True + self.compression_quality = config.get('compression_quality', 85) if config else 85 + self.enable_memory_cache = config.get('enable_memory_cache', True) if config else True + self.image_cache = {} + self.max_cache_size = config.get('max_cache_size', 50) if config else 50 + def stitch(self, image_paths, frame_num, view_type="vehicle"): try: from PIL import Image, ImageDraw @@ -87,15 +131,37 @@ def stitch(self, image_paths, frame_num, view_type="vehicle"): Log.warning("PIL未安装,跳过图像拼接") return False + # 检查缓存 + cache_key = f"{view_type}_{frame_num}" + if self.enable_memory_cache and cache_key in self.image_cache: + # 从缓存加载 + cached_image = self.image_cache[cache_key] + output_path = os.path.join(self.stitched_dir, f"{view_type}_{frame_num:06d}.jpg") + cached_image.save(output_path, "JPEG", quality=self.compression_quality) + return True + positions = [(10, 10), (660, 10), (10, 390), (660, 390)] canvas = Image.new('RGB', (640 * 2 + 20, 360 * 2 + 20), (40, 40, 40)) draw = ImageDraw.Draw(canvas) + images_loaded = 0 for idx, (cam_name, img_path) in enumerate(list(image_paths.items())[:4]): if img_path and os.path.exists(img_path): try: - img = Image.open(img_path).resize((640, 360)) + # 检查图像是否已经加载到内存 + if img_path in self.image_cache: + img = self.image_cache[img_path] + else: + img = Image.open(img_path).resize((640, 360)) + # 缓存图像 + if self.enable_memory_cache: + self.image_cache[img_path] = img + # 清理缓存如果太大 + if len(self.image_cache) > self.max_cache_size: + oldest_key = next(iter(self.image_cache)) + del self.image_cache[oldest_key] + images_loaded += 1 except: img = Image.new('RGB', (640, 360), (80, 80, 80)) else: @@ -105,8 +171,25 @@ def stitch(self, image_paths, frame_num, view_type="vehicle"): draw.text((positions[idx][0] + 5, positions[idx][1] + 5), cam_name, fill=(255, 255, 200)) + if images_loaded == 0: + return False + output_path = os.path.join(self.stitched_dir, f"{view_type}_{frame_num:06d}.jpg") - canvas.save(output_path, "JPEG", quality=90) + + # 压缩保存 + if self.compress_images: + canvas.save(output_path, "JPEG", quality=self.compression_quality) + else: + canvas.save(output_path, "PNG") + + # 缓存结果 + if self.enable_memory_cache: + self.image_cache[cache_key] = canvas.copy() + + # 清理内存 + del canvas + gc.collect() + return True @@ -121,6 +204,10 @@ def __init__(self, world, config): random.seed(seed) Log.info(f"随机种子: {seed}") + # 性能优化:批量生成设置 + self.batch_spawn = config.get('batch_spawn', True) + self.max_spawn_attempts = config.get('max_spawn_attempts', 5) + def spawn_ego_vehicle(self): blueprint_lib = self.world.get_blueprint_library() @@ -143,23 +230,82 @@ def spawn_ego_vehicle(self): return None spawn_point = random.choice(spawn_points) - try: - vehicle = self.world.spawn_actor(vehicle_bp, spawn_point) - vehicle.set_autopilot(True) - vehicle.apply_control(carla.VehicleControl(throttle=0.2)) - Log.info(f"主车: {vehicle.type_id}") - return vehicle - except Exception as e: - Log.warning(f"主车生成失败: {e}") - return None + + # 尝试多次生成 + for attempt in range(self.max_spawn_attempts): + try: + vehicle = self.world.spawn_actor(vehicle_bp, spawn_point) + vehicle.set_autopilot(True) + vehicle.apply_control(carla.VehicleControl(throttle=0.2)) + Log.info(f"主车: {vehicle.type_id}") + return vehicle + except Exception as e: + if attempt == self.max_spawn_attempts - 1: + Log.warning(f"主车生成失败: {e}") + else: + # 尝试不同的生成点 + spawn_point = random.choice(spawn_points) + time.sleep(0.1) + + return None def spawn_traffic(self, center_location): - vehicles = self._spawn_vehicles() + if self.batch_spawn: + vehicles = self._spawn_vehicles_batch() + else: + vehicles = self._spawn_vehicles() + pedestrians = self._spawn_pedestrians(center_location) Log.info(f"交通生成: {vehicles}辆车, {pedestrians}个行人") return vehicles + pedestrians + def _spawn_vehicles_batch(self): + """批量生成车辆(提高性能)""" + blueprint_lib = self.world.get_blueprint_library() + spawn_points = self.world.get_map().get_spawn_points() + + if not spawn_points: + return 0 + + num_vehicles = min(self.config['traffic']['background_vehicles'], 10) + spawned = 0 + + # 准备批处理 + batch_commands = [] + available_points = spawn_points.copy() + random.shuffle(available_points) + + for i in range(num_vehicles): + if i >= len(available_points): + break + + try: + vehicle_bp = random.choice(blueprint_lib.filter('vehicle.*')) + spawn_point = available_points[i] + + # 创建生成命令 + batch_commands.append((vehicle_bp, spawn_point)) + + except: + pass + + # 批量生成 + for vehicle_bp, spawn_point in batch_commands: + try: + vehicle = self.world.spawn_actor(vehicle_bp, spawn_point) + vehicle.set_autopilot(True) + self.vehicles.append(vehicle) + spawned += 1 + except: + pass + + # 避免过快的生成速度 + if spawned % 3 == 0: + time.sleep(0.05) + + return spawned + def _spawn_vehicles(self): blueprint_lib = self.world.get_blueprint_library() spawn_points = self.world.get_map().get_spawn_points() @@ -217,19 +363,30 @@ def _spawn_pedestrians(self, center_location): def cleanup(self): Log.info("清理交通...") + # 批量销毁 + actors_to_destroy = [] + for vehicle in self.vehicles: - try: - if vehicle.is_alive: - vehicle.destroy() - except: - pass + if vehicle.is_alive: + actors_to_destroy.append(vehicle) for pedestrian in self.pedestrians: - try: - if pedestrian.is_alive: - pedestrian.destroy() - except: - pass + if pedestrian.is_alive: + actors_to_destroy.append(pedestrian) + + # 批量销毁 + batch_size = 10 + for i in range(0, len(actors_to_destroy), batch_size): + batch = actors_to_destroy[i:i + batch_size] + for actor in batch: + try: + actor.destroy() + except: + pass + + # 避免过快的销毁速度 + if i > 0 and i % 30 == 0: + time.sleep(0.1) self.vehicles.clear() self.pedestrians.clear() @@ -244,34 +401,29 @@ def __init__(self, world, config, data_dir): self.frame_counter = 0 self.last_capture_time = 0 + self.last_performance_sample = 0 self.vehicle_buffer = {} self.infra_buffer = {} self.buffer_lock = threading.Lock() - self.image_processor = ImageProcessor(data_dir) + # 性能监控 + self.performance_monitor = PerformanceMonitor() + + # 性能优化配置 + self.image_processor = ImageProcessor(data_dir, config.get('image_processing', {})) self.lidar_processor = None self.fusion_manager = None - # 新增:数据增强组件 - self.enhancer = None - self.calibrator = None - self.quality_monitor = None - - if config['enhancement']['enabled']: - self.enhancer = SensorDataEnhancer(config) - self.calibrator = SensorCalibrator(config) - self.quality_monitor = DataQualityMonitor(data_dir) - - # 创建增强数据目录 - enhanced_dir = os.path.join(data_dir, config['enhancement'].get('enhanced_dir_name', 'enhanced')) - os.makedirs(enhanced_dir, exist_ok=True) + # 批处理设置 + self.batch_size = config.get('batch_size', 5) + self.enable_async_processing = config.get('enable_async_processing', True) if config['sensors'].get('lidar_sensors', 0) > 0: - self.lidar_processor = LidarProcessor(data_dir) + self.lidar_processor = LidarProcessor(data_dir, config.get('lidar_processing', {})) if config['output'].get('save_fusion', False): - self.fusion_manager = MultiSensorFusion(data_dir) + self.fusion_manager = MultiSensorFusion(data_dir, config.get('fusion', {})) def setup_cameras(self, vehicle, center_location, vehicle_id=0): vehicle_cams = self._setup_vehicle_cameras(vehicle, vehicle_id) @@ -350,6 +502,8 @@ def setup_lidar(self, vehicle, vehicle_id=0): def lidar_callback(lidar_data): current_time = time.time() + frame_start_time = time.time() + if current_time - self.last_capture_time >= self.config['sensors']['capture_interval']: if self.lidar_processor: try: @@ -373,6 +527,10 @@ def lidar_callback(lidar_data): except Exception as e: print(f"LiDAR处理失败: {e}") + # 记录性能 + frame_time = time.time() - frame_start_time + self.performance_monitor.record_frame_time(frame_time) + lidar_sensor.listen(lidar_callback) self.sensors.append(lidar_sensor) @@ -424,116 +582,45 @@ def _create_callback(self, save_dir, name, sensor_type, vehicle_id=0): def callback(image): current_time = time.time() + frame_start_time = time.time() if current_time - self.last_capture_time >= capture_interval: self.frame_counter += 1 self.last_capture_time = current_time - # 原始图像保存路径 - original_filename = os.path.join(save_dir, f"{name}_{self.frame_counter:06d}.png") - - # 保存原始图像 - image.save_to_disk(original_filename, carla.ColorConverter.Raw) - - # 数据增强处理 - if self.enhancer and self.config['enhancement']['enabled']: - enhanced_image = self._enhance_sensor_data(image, name, sensor_type) - - # 保存增强后的图像 - if self.config['enhancement']['save_enhanced']: - enhanced_dir = os.path.join( - self.data_dir, - self.config['enhancement'].get('enhanced_dir_name', 'enhanced'), - sensor_type, - name - ) - os.makedirs(enhanced_dir, exist_ok=True) - - enhanced_filename = os.path.join( - enhanced_dir, - f"{name}_{self.frame_counter:06d}_enhanced.png" - ) - - # 转换为RGB格式 - img_array = cv2.cvtColor(enhanced_image, cv2.COLOR_RGB2BGR) - cv2.imwrite(enhanced_filename, img_array) - - # 生成增强元数据 - metadata = { - 'frame_id': self.frame_counter, - 'sensor_type': sensor_type, - 'sensor_name': name, - 'vehicle_id': vehicle_id, - 'enhancement_methods': self.enhancer.enhancement_methods, - 'original_path': original_filename, - 'enhanced_path': enhanced_filename, - 'timestamp': datetime.now().isoformat() - } - - # 保存元数据 - meta_filename = enhanced_filename.replace('.png', '_meta.json') - with open(meta_filename, 'w', encoding='utf-8') as f: - json.dump(metadata, f, indent=2, ensure_ascii=False) - - # 质量检查 - if self.quality_monitor and self.config['output']['run_quality_check']: - quality_result = self.quality_monitor.check_image_quality(original_filename) - self.quality_monitor.update_metrics('images', quality_result) - - if not quality_result.get('valid', False): - print(f"⚠ 图像质量问题: {original_filename} - {quality_result.get('error', '')}") + filename = os.path.join(save_dir, f"{name}_{self.frame_counter:06d}.png") + image.save_to_disk(filename, carla.ColorConverter.Raw) with self.buffer_lock: if sensor_type == 'vehicle': - self.vehicle_buffer[name] = original_filename + self.vehicle_buffer[name] = filename if len(self.vehicle_buffer) >= 4: self.image_processor.stitch(self.vehicle_buffer, self.frame_counter, f'vehicle_{vehicle_id}') self.vehicle_buffer.clear() else: - self.infra_buffer[name] = original_filename + self.infra_buffer[name] = filename if len(self.infra_buffer) >= 4: self.image_processor.stitch(self.infra_buffer, self.frame_counter, 'infrastructure') self.infra_buffer.clear() - return callback + # 定期采样性能 + if current_time - self.last_performance_sample >= 5.0: + memory_mb = self.performance_monitor.sample_memory() + cpu_percent = self.performance_monitor.sample_cpu() + if self.frame_counter % 10 == 0: + Log.debug(f"性能采样 - 内存: {memory_mb:.1f}MB, CPU: {cpu_percent:.1f}%") + self.last_performance_sample = current_time - def _enhance_sensor_data(self, carla_image, name: str, sensor_type: str) -> np.ndarray: - """增强传感器数据""" - # 将CARLA图像转换为numpy数组 - img_array = np.frombuffer(carla_image.raw_data, dtype=np.uint8) - img_array = img_array.reshape((carla_image.height, carla_image.width, 4)) - img_array = img_array[:, :, :3] # 去掉alpha通道 + # 记录帧处理时间 + frame_time = time.time() - frame_start_time + self.performance_monitor.record_frame_time(frame_time) - # 根据传感器类型应用增强 - if sensor_type == 'camera': - enhanced_image = self.enhancer.enhance_image(img_array, 'camera') - else: - enhanced_image = img_array # 其他传感器暂时不增强 - - return enhanced_image - - def generate_calibration_files(self, vehicle_locations: list, - camera_positions: list): - """生成传感器校准文件""" - if self.calibrator and self.config['enhancement']['calibration_generation']: - self.calibrator.generate_calibration_files( - self.data_dir, - vehicle_locations, - camera_positions - ) - - def generate_enhancement_report(self): - """生成增强报告""" - if self.enhancer: - return self.enhancer.generate_enhancement_report(self.data_dir) - return None + # 定期垃圾回收 + if self.frame_counter % 50 == 0: + gc.collect() - def generate_quality_report(self): - """生成质量报告""" - if self.quality_monitor: - return self.quality_monitor.generate_quality_report() - return None + return callback def get_frame_count(self): return self.frame_counter @@ -543,7 +630,8 @@ def generate_sensor_summary(self): 'total_sensors': len(self.sensors), 'frame_count': self.frame_counter, 'lidar_data': None, - 'fusion_data': None + 'fusion_data': None, + 'performance': self.performance_monitor.get_performance_summary() } if self.lidar_processor: @@ -556,14 +644,33 @@ def generate_sensor_summary(self): def cleanup(self): Log.info(f"清理 {len(self.sensors)} 个传感器...") - for sensor in self.sensors: - try: - sensor.stop() - sensor.destroy() - except: - pass + + # 刷新批处理数据 + if self.lidar_processor: + self.lidar_processor.flush_batch() + + # 批量销毁传感器 + batch_size = 5 + for i in range(0, len(self.sensors), batch_size): + batch = self.sensors[i:i + batch_size] + for sensor in batch: + try: + sensor.stop() + sensor.destroy() + except: + pass + + if i > 0 and i % 20 == 0: + time.sleep(0.05) + self.sensors.clear() + # 清理缓存 + if hasattr(self.image_processor, 'image_cache'): + self.image_processor.image_cache.clear() + + gc.collect() + class DataCollector: def __init__(self, config): @@ -584,6 +691,12 @@ def __init__(self, config): self.is_running = False self.collected_frames = 0 + # 性能监控 + self.performance_monitor = PerformanceMonitor() + + # 数据格式配置 + self.output_format = config.get('output_format', 'standard') # standard, v2xformer, kitti + def setup_directories(self): scenario = self.config['scenario'] timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") @@ -603,8 +716,8 @@ def setup_directories(self): "calibration", "cooperative", "v2x_messages", - "enhanced/vehicle", - "enhanced/infrastructure", + "v2xformer_format", # V2XFormer格式数据 + "kitti_format", # KITTI格式数据 "metadata" ] @@ -700,30 +813,6 @@ def setup_scene(self): {'type': 'vehicle', 'capabilities': ['bsm', 'rsm']} ) - # 在场景设置完成后,如果有传感器管理器,生成校准文件 - if hasattr(self, 'sensor_managers') and self.sensor_managers: - # 获取车辆和相机位置信息 - vehicle_locations = [] - camera_positions = [] - - for vehicle in self.ego_vehicles: - location = vehicle.get_location() - vehicle_locations.append({ - 'id': vehicle.id, - 'position': [location.x, location.y, location.z], - 'rotation': [0, 0, 0] - }) - - # 简化:假设相机位置相对于车辆 - camera_positions.append({ - 'translation': [2.0, 0, 1.5], # 相机相对于车辆的位置 - 'rotation': [0, 0, 0] # 相机的旋转 - }) - - # 为每个传感器管理器生成校准文件 - for sensor_manager in self.sensor_managers.values(): - sensor_manager.generate_calibration_files(vehicle_locations, camera_positions) - time.sleep(3.0) return True @@ -754,6 +843,7 @@ def collect_data(self): last_update = time.time() last_v2x_update = time.time() last_perception_share = time.time() + last_performance_sample = time.time() try: while time.time() - self.start_time < duration and self.is_running: @@ -775,40 +865,148 @@ def collect_data(self): self._share_perception_data() last_perception_share = current_time - # 定期保存共享感知 - frame_count = sum(mgr.get_frame_count() for mgr in self.sensor_managers.values()) - if frame_count % 10 == 0 and self.multi_vehicle_manager: - self.multi_vehicle_manager.save_shared_perception(frame_count) + # 性能采样 + if current_time - last_performance_sample >= 10.0: + memory_mb = self.performance_monitor.sample_memory() + cpu_percent = self.performance_monitor.sample_cpu() + Log.debug(f"系统性能 - 内存: {memory_mb:.1f}MB, CPU: {cpu_percent:.1f}%") + last_performance_sample = current_time + + # 定期垃圾回收 + gc.collect() if current_time - last_update >= 5.0: total_frames = sum(mgr.get_frame_count() for mgr in self.sensor_managers.values()) progress = (elapsed / duration) * 100 - Log.info(f"进度: {elapsed:.0f}/{duration}秒 ({progress:.1f}%) | 总帧数: {total_frames}") + # 计算预估剩余时间 + if total_frames > 0: + frames_per_second = total_frames / elapsed + remaining_frames = (duration - elapsed) * frames_per_second + eta_seconds = (duration - elapsed) + else: + eta_seconds = duration - elapsed + + Log.info(f"进度: {elapsed:.0f}/{duration}秒 ({progress:.1f}%) | " + f"总帧数: {total_frames} | " + f"ETA: {eta_seconds:.0f}秒") last_update = current_time - time.sleep(0.05) + time.sleep(0.01) # 减少CPU占用 except KeyboardInterrupt: Log.info("数据收集被用户中断") + except Exception as e: + Log.error(f"数据收集错误: {e}") + traceback.print_exc() finally: self.is_running = False elapsed = time.time() - self.start_time self.collected_frames = sum(mgr.get_frame_count() for mgr in self.sensor_managers.values()) - Log.info(f"收集完成: {self.collected_frames}帧, 用时: {elapsed:.1f}秒") - # 生成增强报告 - if self.config['enhancement']['enabled']: - self._generate_enhancement_reports() + # 获取性能摘要 + performance_summary = self.performance_monitor.get_performance_summary() - # 生成质量报告 - if self.config['output']['run_quality_check']: - self._generate_quality_reports() + Log.info(f"收集完成: {self.collected_frames}帧, 用时: {elapsed:.1f}秒") + Log.info(f"平均帧率: {performance_summary['frames_per_second']:.2f} FPS") + Log.info(f"最大内存使用: {performance_summary['max_memory_mb']:.1f} MB") self._save_metadata() self._print_summary() + # 生成标准格式数据 + if self.output_format != 'standard': + self._convert_to_target_format() + + def _convert_to_target_format(self): + """转换为目标数据格式""" + Log.info(f"转换为 {self.output_format} 格式...") + + if self.output_format == 'v2xformer': + self._convert_to_v2xformer_format() + elif self.output_format == 'kitti': + self._convert_to_kitti_format() + + def _convert_to_v2xformer_format(self): + """转换为V2XFormer格式""" + try: + # 创建必要的目录结构 + v2x_dir = os.path.join(self.output_dir, "v2xformer_format") + + # 创建数据集结构 + splits = ['train', 'val', 'test'] + for split in splits: + split_dir = os.path.join(v2x_dir, split) + os.makedirs(split_dir, exist_ok=True) + + # 创建子目录 + for subdir in ['image', 'point_cloud', 'calib', 'label']: + os.makedirs(os.path.join(split_dir, subdir), exist_ok=True) + + # 生成数据集划分 + total_frames = self.collected_frames + train_ratio = 0.7 + val_ratio = 0.2 + test_ratio = 0.1 + + train_frames = int(total_frames * train_ratio) + val_frames = int(total_frames * val_ratio) + test_frames = total_frames - train_frames - val_frames + + # 生成划分文件 + splits_info = { + 'train': list(range(0, train_frames)), + 'val': list(range(train_frames, train_frames + val_frames)), + 'test': list(range(train_frames + val_frames, total_frames)) + } + + splits_file = os.path.join(v2x_dir, "splits.json") + with open(splits_file, 'w') as f: + json.dump(splits_info, f, indent=2) + + Log.info(f"V2XFormer格式转换完成: {v2x_dir}") + + except Exception as e: + Log.error(f"V2XFormer格式转换失败: {e}") + + def _convert_to_kitti_format(self): + """转换为KITTI格式""" + try: + kitti_dir = os.path.join(self.output_dir, "kitti_format") + + # 创建KITTI标准目录结构 + for subdir in ['training', 'testing']: + full_dir = os.path.join(kitti_dir, subdir) + os.makedirs(full_dir, exist_ok=True) + + for subsubdir in ['image_2', 'velodyne', 'calib', 'label_2']: + os.makedirs(os.path.join(full_dir, subsubdir), exist_ok=True) + + # 生成KITTI格式的校准文件 + self._generate_kitti_calibration(kitti_dir) + + Log.info(f"KITTI格式转换完成: {kitti_dir}") + + except Exception as e: + Log.error(f"KITTI格式转换失败: {e}") + + def _generate_kitti_calibration(self, kitti_dir): + """生成KITTI格式的校准文件""" + calib_template = """P0: 7.215377e+02 0.000000e+00 6.095593e+02 0.000000e+00 0.000000e+00 7.215377e+02 1.728540e+02 0.000000e+00 0.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 +P1: 7.215377e+02 0.000000e+00 6.095593e+02 -3.875744e+02 0.000000e+00 7.215377e+02 1.728540e+02 0.000000e+00 0.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 +P2: 7.215377e+02 0.000000e+00 6.095593e+02 4.485728e+01 0.000000e+00 7.215377e+02 1.728540e+02 2.163791e-01 0.000000e+00 0.000000e+00 1.000000e+00 2.745884e-03 +P3: 7.215377e+02 0.000000e+00 6.095593e+02 -3.341729e+02 0.000000e+00 7.215377e+02 1.728540e+02 2.163791e-01 0.000000e+00 0.000000e+00 1.000000e+00 2.745884e-03 +R0_rect: 9.999239e-01 9.837760e-03 -7.445048e-03 -9.869795e-03 9.999421e-01 -4.278459e-03 7.402527e-03 4.351614e-03 9.999631e-01 +Tr_velo_to_cam: 4.276802e-04 -9.999672e-01 -8.084491e-03 -1.198459e-02 -7.210626e-03 8.081198e-03 -9.999413e-01 -5.403984e-02 9.999738e-01 4.859485e-04 -7.206933e-03 -2.921968e-02 +Tr_imu_to_velo: 9.999976e-01 7.553071e-04 -2.035826e-03 -8.086759e-01 -7.854027e-04 9.998898e-01 -1.482298e-02 3.195559e-01 2.024406e-03 1.482454e-02 9.998881e-01 -7.997231e-01""" + + # 为每一帧生成校准文件 + for i in range(self.collected_frames): + calib_file = os.path.join(kitti_dir, "training", "calib", f"{i:06d}.txt") + with open(calib_file, 'w') as f: + f.write(calib_template) + def _update_v2x_communication(self): """更新V2X通信""" if not self.v2x_communication: @@ -889,20 +1087,6 @@ def _simulate_object_detection(self, vehicle): return detected_objects - def _generate_enhancement_reports(self): - """生成增强报告""" - Log.info("生成增强报告...") - for sensor_manager in self.sensor_managers.values(): - if hasattr(sensor_manager, 'enhancer'): - sensor_manager.generate_enhancement_report() - - def _generate_quality_reports(self): - """生成质量报告""" - Log.info("生成质量报告...") - for sensor_manager in self.sensor_managers.values(): - if hasattr(sensor_manager, 'quality_monitor'): - sensor_manager.quality_monitor.print_quality_summary() - def _save_metadata(self): metadata = { 'scenario': self.config['scenario'], @@ -910,32 +1094,20 @@ def _save_metadata(self): 'sensors': self.config['sensors'], 'v2x': self.config['v2x'], 'cooperative': self.config['cooperative'], - 'enhancement': self.config['enhancement'], + 'output_format': self.output_format, 'output': self.config['output'], 'collection': { 'duration': round(time.time() - self.start_time, 2), 'total_frames': self.collected_frames, 'frame_rate': round(self.collected_frames / max(time.time() - self.start_time, 0.1), 2) - } + }, + 'performance': self.performance_monitor.get_performance_summary() } # 传感器摘要 sensor_summaries = {} for vehicle_id, sensor_manager in self.sensor_managers.items(): sensor_summaries[vehicle_id] = sensor_manager.generate_sensor_summary() - - # 添加增强报告 - if hasattr(sensor_manager, 'enhancer'): - enhancement_report = sensor_manager.generate_enhancement_report() - if enhancement_report: - sensor_summaries[vehicle_id]['enhancement_report'] = enhancement_report - - # 添加质量报告 - if hasattr(sensor_manager, 'quality_monitor'): - quality_report = sensor_manager.generate_quality_report() - if quality_report: - sensor_summaries[vehicle_id]['quality_report'] = quality_report - metadata['sensor_summaries'] = sensor_summaries # V2X通信状态 @@ -963,54 +1135,49 @@ def _print_summary(self): for raw_dir in raw_dirs: raw_path = os.path.join(self.output_dir, raw_dir) if os.path.exists(raw_path): - # 递归统计图像文件 + # 快速统计 for root, dirs, files in os.walk(raw_path): total_raw_images += len([f for f in files if f.endswith(('.png', '.jpg', '.jpeg'))]) print(f"原始图像: {total_raw_images} 张") - # 统计增强图像 - if self.config['enhancement']['enabled']: - enhanced_dir = os.path.join(self.output_dir, - self.config['enhancement'].get('enhanced_dir_name', 'enhanced')) - if os.path.exists(enhanced_dir): - total_enhanced_images = 0 - for root, dirs, files in os.walk(enhanced_dir): - total_enhanced_images += len([f for f in files if f.endswith(('.png', '.jpg', '.jpeg'))]) - - print(f"增强图像: {total_enhanced_images} 张") - # 统计LiDAR lidar_dir = os.path.join(self.output_dir, "lidar") if os.path.exists(lidar_dir): - bin_files = [f for f in os.listdir(lidar_dir) if f.endswith('.bin')] - npy_files = [f for f in os.listdir(lidar_dir) if f.endswith('.npy')] + import glob + bin_files = glob.glob(os.path.join(lidar_dir, "*.bin")) + npy_files = glob.glob(os.path.join(lidar_dir, "*.npy")) + batch_files = glob.glob(os.path.join(lidar_dir, "*batch*.json")) print(f"LiDAR数据: {len(bin_files)} .bin文件, {len(npy_files)} .npy文件") + print(f"批处理文件: {len(batch_files)} 个") # 统计协同数据 coop_dir = os.path.join(self.output_dir, "cooperative") if os.path.exists(coop_dir): - v2x_files = len([f for f in os.listdir(os.path.join(coop_dir, "v2x_messages")) if f.endswith('.json')]) + v2x_files = len( + [f for f in os.listdir(os.path.join(coop_dir, "v2x_messages")) if f.endswith(('.json', '.gz'))]) perception_files = len( [f for f in os.listdir(os.path.join(coop_dir, "shared_perception")) if f.endswith('.json')]) print(f"协同数据: {v2x_files} V2X消息, {perception_files} 共享感知文件") - # 校准文件 - calib_dir = os.path.join(self.output_dir, "calibration") - if os.path.exists(calib_dir): - calib_files = len([f for f in os.listdir(calib_dir) if f.endswith('.json')]) - print(f"校准文件: {calib_files} 个") - - # V2X统计 - if self.v2x_communication: - v2x_status = self.v2x_communication.get_network_status() - print(f"V2X通信: {v2x_status['stats']['messages_sent']} 发送, " - f"{v2x_status['stats']['messages_received']} 接收, " - f"{v2x_status['stats']['messages_dropped']} 丢包") - - # 车辆统计 - print(f"车辆总数: {len(self.ego_vehicles)} 主车 + " - f"{len(self.multi_vehicle_manager.cooperative_vehicles)} 协同车") + # 格式转换 + if self.output_format == 'v2xformer': + v2x_dir = os.path.join(self.output_dir, "v2xformer_format") + if os.path.exists(v2x_dir): + print(f"V2XFormer格式: 已生成") + + if self.output_format == 'kitti': + kitti_dir = os.path.join(self.output_dir, "kitti_format") + if os.path.exists(kitti_dir): + print(f"KITTI格式: 已生成") + + # 性能统计 + performance = self.performance_monitor.get_performance_summary() + print(f"\n性能统计:") + print(f" 平均帧率: {performance['frames_per_second']:.2f} FPS") + print(f" 平均内存: {performance['average_memory_mb']:.1f} MB") + print(f" 最大内存: {performance['max_memory_mb']:.1f} MB") + print(f" 平均CPU: {performance['average_cpu_percent']:.1f}%") print(f"\n输出目录: {self.output_dir}") print("=" * 60) @@ -1052,15 +1219,18 @@ def cleanup(self): except: pass + # 强制垃圾回收 + gc.collect() + Log.info("清理完成") def main(): - parser = argparse.ArgumentParser(description='CVIPS 数据增强采集系统 v11.0') + parser = argparse.ArgumentParser(description='CVIPS 性能优化数据收集系统 v12.0') # 基础参数 parser.add_argument('--config', type=str, help='配置文件路径') - parser.add_argument('--scenario', type=str, default='enhanced_data_collection', help='场景名称') + parser.add_argument('--scenario', type=str, default='performance_optimized', help='场景名称') parser.add_argument('--town', type=str, default='Town10HD', choices=['Town03', 'Town04', 'Town05', 'Town10HD'], help='地图') parser.add_argument('--weather', type=str, default='clear', @@ -1078,12 +1248,21 @@ def main(): parser.add_argument('--capture-interval', type=float, default=2.0, help='捕捉间隔(秒)') parser.add_argument('--seed', type=int, help='随机种子') + # 性能参数 + parser.add_argument('--batch-size', type=int, default=5, help='批处理大小') + parser.add_argument('--enable-compression', action='store_true', help='启用数据压缩') + parser.add_argument('--enable-downsampling', action='store_true', help='启用LiDAR下采样') + + # 输出格式 + parser.add_argument('--output-format', type=str, default='standard', + choices=['standard', 'v2xformer', 'kitti'], help='输出数据格式') + # 传感器参数 parser.add_argument('--enable-lidar', action='store_true', help='启用LiDAR传感器') parser.add_argument('--enable-fusion', action='store_true', help='启用多传感器融合') parser.add_argument('--enable-v2x', action='store_true', help='启用V2X通信') parser.add_argument('--enable-cooperative', action='store_true', help='启用协同感知') - parser.add_argument('--enable-enhancement', action='store_true', help='启用数据增强') + parser.add_argument('--enable-enhancement', action='store_true', help='启用数据增强') # 添加这行 parser.add_argument('--enable-annotations', action='store_true', help='启用自动标注') # 功能参数 @@ -1097,9 +1276,15 @@ def main(): config = ConfigManager.load_config(args.config) config = ConfigManager.merge_args(config, args) + # 添加性能配置 + config['performance']['batch_size'] = args.batch_size + config['performance']['enable_compression'] = args.enable_compression + config['performance']['enable_downsampling'] = args.enable_downsampling + config['output']['output_format'] = args.output_format + # 显示配置 print("\n" + "=" * 60) - print("CVIPS 数据增强采集系统 v11.0") + print("CVIPS 性能优化数据收集系统 v12.0") print("=" * 60) print(f"场景: {config['scenario']['name']}") @@ -1108,6 +1293,7 @@ def main(): print(f"时长: {config['scenario']['duration']}秒") print(f"交通: {config['traffic']['background_vehicles']}背景车辆 + {config['traffic']['pedestrians']}行人") print(f"协同: {config['cooperative']['num_coop_vehicles']} 协同车辆") + print(f"输出格式: {config['output']['output_format']}") print(f"传感器:") print( @@ -1118,6 +1304,11 @@ def main(): print(f" 协同: {'启用' if config['output']['save_cooperative'] else '禁用'}") print(f" 增强: {'启用' if config['enhancement']['enabled'] else '禁用'}") + print(f"性能:") + print(f" 批处理大小: {config['performance']['batch_size']}") + print(f" 压缩: {'启用' if config['performance']['enable_compression'] else '禁用'}") + print(f" 下采样: {'启用' if config['performance']['enable_downsampling'] else '禁用'}") + collector = DataCollector(config) try: From ad2cd135c0862c3075d35558905dddbafcbf2178 Mon Sep 17 00:00:00 2001 From: Z-w-7799 <1011069369@qq.com> Date: Fri, 19 Dec 2025 21:28:12 +0800 Subject: [PATCH 05/20] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BE=9D=E8=B5=96?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__pycache__/carla_utils.cpython-37.pyc | Bin 0 -> 3004 bytes .../__pycache__/config_manager.cpython-37.pyc | Bin 0 -> 3022 bytes src/enhance_pedestrian_safety/requirements.txt | 12 +++++++----- 3 files changed, 7 insertions(+), 5 deletions(-) create mode 100644 src/enhance_pedestrian_safety/__pycache__/carla_utils.cpython-37.pyc create mode 100644 src/enhance_pedestrian_safety/__pycache__/config_manager.cpython-37.pyc diff --git a/src/enhance_pedestrian_safety/__pycache__/carla_utils.cpython-37.pyc b/src/enhance_pedestrian_safety/__pycache__/carla_utils.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1c32495fc547e598c322eb9919c8930d0429ab39 GIT binary patch literal 3004 zcmai0>rWI{6rVdYyF2?}d1`B)+q7x2CV<+sX^5qz(niyUl1i+uYbWd80R~}qduK+4 z>>90FP(f^~*dXYxk(8PgY|@Ai!2TC)=A-T`%O}3{Q$O|GJIk^vv@@CeIQQIhe{=6S z=XXD@sPJ*{{Mp)~Ua#c1C$zKr@X+`OKAjeri*OiWz86IhtN94eY9S)9T8xNTz~UJ$ z;=(TMhT4riSc2Mv`Mq4&Ykml3e7jn{{JbS(&rR89=kn88d-7_iKD2asI``mWVeqzn zemZ|+Dt~U#{$Y6e?8wr>Ovsr~n47g1&-j+X1lJa)C4EHrg7EEvPyYi7gX=*Vz?DVC z7GG)!S%gK3jExAa6b)B)Rd*dZ_GI}kDa{?nDXx_}6iqu;-6@16_%|>1WnF3B@bq|6 zJoe+j2%i>G0=bwHaK#pGuY-naQ%zwV&FHG;H_@*+9E4q}G+%|^DBlV8|4ZX%l&-G) z*kJDA-2v~3#(0eM#u~Q{RG(;URrDi9B3TmEf+!I;`xMR4OTs{7B59O*zFkdg?bcG; zaxt?JZ+pMdozPm^_rQ^=x-sBq^7^d}fT1IdEH6>wCehWS3PFIjEi74lLbt?Z%;>g! z%JF1O!=|nf%Y#*-#rhP>6FaQ4QAw6N)m}xGEwLw|YL?j5n>ZX6BY}9LuP>p=R7oc; zup05hYP4@CM6fe{3& zl}xWH1@51v*f{J@3D>wuo+3>NDZUee6QluZX;p3nGu60)>R&?Ii-sptgM(Rg36Y`I zcG^|M3;_i4V9MnncG_i#o$=-}#I7~GpxwNN7<$?Um@C)CT$OS?k67zAYEQEr0DFrY z*sEUGUJW}v4%Zgzx?;VlSZ`)^X>YIx0`>*-ZSaA@%p&9}7OGCBvhUw5Ji2UOyY6JM z+@oy%)U5d$sQM0WZ+!1i$R3)sf4*Q3k2&#uan`q9nQFD|svU#-laGT#TC{OF~nTMt&|#|yJ_&c5azPUY@h z$dBE(=SOq*FXZl>bR2`hbao5WO`1zWq3sRNZY+$IyFzFun^*1!jiEIMXf{7%c6Ky8 zwIvkKnxB^M`3L`JuY=ip2XNi_jxC+Pyl78M18?Vt<{YnMj@tO-k$hnLih3h_{h zs2o=f%iX0IiguKx{IEOXr2WkQh zAB?$StPV;EG%0aOd=Lc$H%OeG1hbu&ya|>|G)8ZEbSD^%MWL}uWBqlM#Cn0mKqAuA zmx4Qu$Qk?*AK8bvG)M+sfiYJKmC~?~5wV*R2i#`_ETqv1w4Xb`Y5acZL%Q^s)v$y; zJ8z$zavX`>0nL{wii0~?W_)%K(xP+EU|KX+mCbZa<%cfW=Wm!bV1=b6X2QPpTjBSq zrVyzGao9!bpd$2lE&rarWP%u<5RxE-UbqO2>@e?)BE>lzc1-MqYE`CRJfgk?HhYz? zVTN7>1soFG8R*uE?502{A;6W$Bg;4umt|)G=9J9sHo-7+D|QY{yKKF+MWvn2Ld^n9 Q&?ycPR${LpZIPP(1<3*HuK)l5 literal 0 HcmV?d00001 diff --git a/src/enhance_pedestrian_safety/__pycache__/config_manager.cpython-37.pyc b/src/enhance_pedestrian_safety/__pycache__/config_manager.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0263ba4de5ce941487d58a12590878822af3dc48 GIT binary patch literal 3022 zcma)8Uu+yl8K2qR+uQr+7{?7s+O)2OL=HmKQb5SEX#O~*pvEc?sp(?0+U(4oH@@55 z%*@(3>kdJkK&V#1U&LfKp0`PL)`pLU7C#z*Z4MJ z1~ab^X095p$>Xu+sV)hjR zybXMrIT>FDzQU>*?=S=QsO!e*IBJFMvwq~axk&ec-mR;z-n(<_-mUd}*MBzp)o=AaPQyy4-Yv|@#|KxOwMJZpw`tKfn9w$GRRyiVMjD6~>Wv&c(~olTSQ5t4l$L`$Ah$97kI- zuyzY0rD+4=ce%C{Ow{FN?yChZbWMd_?!_&S`91BhRQQ=XOLES(&{JCu67g~8-;k7m{LLVw4I^wP zwp^gF=e}Y;FMtmhzSIZ8s0D8&(m$T+D&6)bS3^I?}OALqK`nbeP>SY=k0ZY_wr ziQr(rIBHtjkpYi<5yrY)e7-JA9?4ipZO5rfQl+U_DILEk<4B-)=F)Ef-MsnU==GPU zr=IVWq4mW=HMjkyV+Z{pa5nTG)Px8srQ%RUig^^T# zr!$rGdVaT)9)=mam*k2@?|!)X(XXecnq%6CrM40XC2jM|p;R!66At0XBPN$az%4wh zwmPxTw1rCDiu7CD%YfQ6rZmygAlQ3>Vm6Y&&q|NZ9d^t`(~A4T(01oJ#v@S}ixyFv<& zqTX`Xjwd}`!*do|E1*iw0U;%WZg<-(IK!Y-3wax#Z4~&rHHnl~X&q39jzM>z zPttKZL8pwBL#KBa&Evav4A2 z)V{uPh$v&#$VO(tnL{g=Z_JQORj55!YmD?DsNVSG!_gn#+j#4vjT^Uf z=Z|i_IlB7$IY_BesCZZEY6uw*kpfoK77K&iYvMSpZd$Gri?AI=kUDnZKy#7zbP19v z<(Wfr@->*At(gTzVx1u-7=%t3D~BI6$?j-)z$WOD?Ed%(FpBgkqfp&DP~GVeZWE~P zN0o6g`%b)BUNtF+zT0~Q@3W2tHZuzu+-WF-I(-c4bO|bbQdD}IRkBLIN6YaXK|G%<3-*0brH_PISu#q^5qzMEZl|r$>%n-+rd<96;5>KGa!EC}j7Ehumn}nIO z<0xIrs#l&NXF(+KULq#O^#sH|#xutC-e}&n>b~dD0{9=3dG4RU^yx(+CXvGZOb@}| z%M4=RJ){L@W|$#n0E1Z1=2;nLisu2tyB*h-GRzVTFo^gJvuD7o5FKU6iI)Jkb6QBt z*^08b0g^Ic4rr-lm?J&_%qg&Hp``{|{t9>{r-ihZt*DBBfTRjo9ket6BM=#5>7($O zqwCj3fB1Ox`klLjVFcnqcpjA((^t*9ZXBWWNRLo$VAKaxo# z=mPOLl81pbYq^TgSgp$ozVww6nHpV>((e6>*WHJsonk0+wV6gE4-d;1ZMi3NbrmjE zxHVDqL)bn0Atj~_QLy(Jr(?!820Y4*m4gqa_>QtBooiOK<9YbM;(5C2dEJ<$9pvkt zcPaHdg{16xEDjKcTBO1NZrX*IiLawUuoj7vNX{VH3FeH;N)Lu1{)9?2vO_J1;`(&G qQXk9iAV>{N8}S7o+JUc3eE8eSM4zQiJ2Uwyzl8}cQ7;yG$NV4Z)na7; literal 0 HcmV?d00001 diff --git a/src/enhance_pedestrian_safety/requirements.txt b/src/enhance_pedestrian_safety/requirements.txt index 6e39997b52..be67a775df 100644 --- a/src/enhance_pedestrian_safety/requirements.txt +++ b/src/enhance_pedestrian_safety/requirements.txt @@ -1,5 +1,7 @@ -carla==0.9.14 -pygame==2.1.2 -numpy==1.21.5 -Pillow==9.2.0 -icecream==2.1.3 \ No newline at end of file +numpy>=1.21.0 +opencv-python>=4.5.0 +Pillow>=9.0.0 +psutil>=5.8.0 +argparse>=1.4.0 +dataclasses>=0.6 +pycocotools>=2.0.0 \ No newline at end of file From 819c6ccd11a97d4795db03c17ddf963b467f9dc5 Mon Sep 17 00:00:00 2001 From: Z-w-7799 <1011069369@qq.com> Date: Fri, 19 Dec 2025 22:35:07 +0800 Subject: [PATCH 06/20] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BE=9D=E8=B5=96?= =?UTF-8?q?=E6=96=87=E4=BB=B6=20=E6=B8=85=E9=99=A4=E4=B8=B4=E6=97=B6?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__pycache__/carla_utils.cpython-37.pyc | Bin 3004 -> 0 bytes .../__pycache__/config_manager.cpython-37.pyc | Bin 3022 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/enhance_pedestrian_safety/__pycache__/carla_utils.cpython-37.pyc delete mode 100644 src/enhance_pedestrian_safety/__pycache__/config_manager.cpython-37.pyc diff --git a/src/enhance_pedestrian_safety/__pycache__/carla_utils.cpython-37.pyc b/src/enhance_pedestrian_safety/__pycache__/carla_utils.cpython-37.pyc deleted file mode 100644 index 1c32495fc547e598c322eb9919c8930d0429ab39..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3004 zcmai0>rWI{6rVdYyF2?}d1`B)+q7x2CV<+sX^5qz(niyUl1i+uYbWd80R~}qduK+4 z>>90FP(f^~*dXYxk(8PgY|@Ai!2TC)=A-T`%O}3{Q$O|GJIk^vv@@CeIQQIhe{=6S z=XXD@sPJ*{{Mp)~Ua#c1C$zKr@X+`OKAjeri*OiWz86IhtN94eY9S)9T8xNTz~UJ$ z;=(TMhT4riSc2Mv`Mq4&Ykml3e7jn{{JbS(&rR89=kn88d-7_iKD2asI``mWVeqzn zemZ|+Dt~U#{$Y6e?8wr>Ovsr~n47g1&-j+X1lJa)C4EHrg7EEvPyYi7gX=*Vz?DVC z7GG)!S%gK3jExAa6b)B)Rd*dZ_GI}kDa{?nDXx_}6iqu;-6@16_%|>1WnF3B@bq|6 zJoe+j2%i>G0=bwHaK#pGuY-naQ%zwV&FHG;H_@*+9E4q}G+%|^DBlV8|4ZX%l&-G) z*kJDA-2v~3#(0eM#u~Q{RG(;URrDi9B3TmEf+!I;`xMR4OTs{7B59O*zFkdg?bcG; zaxt?JZ+pMdozPm^_rQ^=x-sBq^7^d}fT1IdEH6>wCehWS3PFIjEi74lLbt?Z%;>g! z%JF1O!=|nf%Y#*-#rhP>6FaQ4QAw6N)m}xGEwLw|YL?j5n>ZX6BY}9LuP>p=R7oc; zup05hYP4@CM6fe{3& zl}xWH1@51v*f{J@3D>wuo+3>NDZUee6QluZX;p3nGu60)>R&?Ii-sptgM(Rg36Y`I zcG^|M3;_i4V9MnncG_i#o$=-}#I7~GpxwNN7<$?Um@C)CT$OS?k67zAYEQEr0DFrY z*sEUGUJW}v4%Zgzx?;VlSZ`)^X>YIx0`>*-ZSaA@%p&9}7OGCBvhUw5Ji2UOyY6JM z+@oy%)U5d$sQM0WZ+!1i$R3)sf4*Q3k2&#uan`q9nQFD|svU#-laGT#TC{OF~nTMt&|#|yJ_&c5azPUY@h z$dBE(=SOq*FXZl>bR2`hbao5WO`1zWq3sRNZY+$IyFzFun^*1!jiEIMXf{7%c6Ky8 zwIvkKnxB^M`3L`JuY=ip2XNi_jxC+Pyl78M18?Vt<{YnMj@tO-k$hnLih3h_{h zs2o=f%iX0IiguKx{IEOXr2WkQh zAB?$StPV;EG%0aOd=Lc$H%OeG1hbu&ya|>|G)8ZEbSD^%MWL}uWBqlM#Cn0mKqAuA zmx4Qu$Qk?*AK8bvG)M+sfiYJKmC~?~5wV*R2i#`_ETqv1w4Xb`Y5acZL%Q^s)v$y; zJ8z$zavX`>0nL{wii0~?W_)%K(xP+EU|KX+mCbZa<%cfW=Wm!bV1=b6X2QPpTjBSq zrVyzGao9!bpd$2lE&rarWP%u<5RxE-UbqO2>@e?)BE>lzc1-MqYE`CRJfgk?HhYz? zVTN7>1soFG8R*uE?502{A;6W$Bg;4umt|)G=9J9sHo-7+D|QY{yKKF+MWvn2Ld^n9 Q&?ycPR${LpZIPP(1<3*HuK)l5 diff --git a/src/enhance_pedestrian_safety/__pycache__/config_manager.cpython-37.pyc b/src/enhance_pedestrian_safety/__pycache__/config_manager.cpython-37.pyc deleted file mode 100644 index 0263ba4de5ce941487d58a12590878822af3dc48..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3022 zcma)8Uu+yl8K2qR+uQr+7{?7s+O)2OL=HmKQb5SEX#O~*pvEc?sp(?0+U(4oH@@55 z%*@(3>kdJkK&V#1U&LfKp0`PL)`pLU7C#z*Z4MJ z1~ab^X095p$>Xu+sV)hjR zybXMrIT>FDzQU>*?=S=QsO!e*IBJFMvwq~axk&ec-mR;z-n(<_-mUd}*MBzp)o=AaPQyy4-Yv|@#|KxOwMJZpw`tKfn9w$GRRyiVMjD6~>Wv&c(~olTSQ5t4l$L`$Ah$97kI- zuyzY0rD+4=ce%C{Ow{FN?yChZbWMd_?!_&S`91BhRQQ=XOLES(&{JCu67g~8-;k7m{LLVw4I^wP zwp^gF=e}Y;FMtmhzSIZ8s0D8&(m$T+D&6)bS3^I?}OALqK`nbeP>SY=k0ZY_wr ziQr(rIBHtjkpYi<5yrY)e7-JA9?4ipZO5rfQl+U_DILEk<4B-)=F)Ef-MsnU==GPU zr=IVWq4mW=HMjkyV+Z{pa5nTG)Px8srQ%RUig^^T# zr!$rGdVaT)9)=mam*k2@?|!)X(XXecnq%6CrM40XC2jM|p;R!66At0XBPN$az%4wh zwmPxTw1rCDiu7CD%YfQ6rZmygAlQ3>Vm6Y&&q|NZ9d^t`(~A4T(01oJ#v@S}ixyFv<& zqTX`Xjwd}`!*do|E1*iw0U;%WZg<-(IK!Y-3wax#Z4~&rHHnl~X&q39jzM>z zPttKZL8pwBL#KBa&Evav4A2 z)V{uPh$v&#$VO(tnL{g=Z_JQORj55!YmD?DsNVSG!_gn#+j#4vjT^Uf z=Z|i_IlB7$IY_BesCZZEY6uw*kpfoK77K&iYvMSpZd$Gri?AI=kUDnZKy#7zbP19v z<(Wfr@->*At(gTzVx1u-7=%t3D~BI6$?j-)z$WOD?Ed%(FpBgkqfp&DP~GVeZWE~P zN0o6g`%b)BUNtF+zT0~Q@3W2tHZuzu+-WF-I(-c4bO|bbQdD}IRkBLIN6YaXK|G%<3-*0brH_PISu#q^5qzMEZl|r$>%n-+rd<96;5>KGa!EC}j7Ehumn}nIO z<0xIrs#l&NXF(+KULq#O^#sH|#xutC-e}&n>b~dD0{9=3dG4RU^yx(+CXvGZOb@}| z%M4=RJ){L@W|$#n0E1Z1=2;nLisu2tyB*h-GRzVTFo^gJvuD7o5FKU6iI)Jkb6QBt z*^08b0g^Ic4rr-lm?J&_%qg&Hp``{|{t9>{r-ihZt*DBBfTRjo9ket6BM=#5>7($O zqwCj3fB1Ox`klLjVFcnqcpjA((^t*9ZXBWWNRLo$VAKaxo# z=mPOLl81pbYq^TgSgp$ozVww6nHpV>((e6>*WHJsonk0+wV6gE4-d;1ZMi3NbrmjE zxHVDqL)bn0Atj~_QLy(Jr(?!820Y4*m4gqa_>QtBooiOK<9YbM;(5C2dEJ<$9pvkt zcPaHdg{16xEDjKcTBO1NZrX*IiLawUuoj7vNX{VH3FeH;N)Lu1{)9?2vO_J1;`(&G qQXk9iAV>{N8}S7o+JUc3eE8eSM4zQiJ2Uwyzl8}cQ7;yG$NV4Z)na7; From e2774e71698527c7a2cd407a887f64026e6c75ba Mon Sep 17 00:00:00 2001 From: Z-w-7799 <1011069369@qq.com> Date: Sat, 20 Dec 2025 10:08:32 +0800 Subject: [PATCH 07/20] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20gzip=20=E5=AF=BC?= =?UTF-8?q?=E5=85=A5=E9=97=AE=E9=A2=98=E4=BF=AE=E5=A4=8D=E6=80=A7=E8=83=BD?= =?UTF-8?q?=E7=9B=91=E6=8E=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lidar_processor.py | 99 ++- src/enhance_pedestrian_safety/main.py | 762 ++++++++++++++---- .../multi_vehicle_manager.py | 150 ++-- 3 files changed, 792 insertions(+), 219 deletions(-) diff --git a/src/enhance_pedestrian_safety/lidar_processor.py b/src/enhance_pedestrian_safety/lidar_processor.py index c9a960ac5f..da2590994e 100644 --- a/src/enhance_pedestrian_safety/lidar_processor.py +++ b/src/enhance_pedestrian_safety/lidar_processor.py @@ -7,6 +7,7 @@ from datetime import datetime import zlib import pickle +import gzip class LidarProcessor: @@ -216,34 +217,86 @@ def _save_as_v2xformer_format(self, points, frame_num): v2x_dir = os.path.join(self.output_dir, "v2xformer_format") os.makedirs(v2x_dir, exist_ok=True) - # 创建V2XFormer格式的数据结构 - v2x_data = { - 'frame_id': frame_num, - 'timestamp': datetime.now().timestamp(), - 'point_cloud': { - 'points': points.tolist(), - 'num_points': points.shape[0], - 'range': [np.min(points[:, 0]), np.max(points[:, 0]), - np.min(points[:, 1]), np.max(points[:, 1]), - np.min(points[:, 2]), np.max(points[:, 2])] - }, - 'metadata': { - 'sensor_type': 'lidar', - 'format_version': '1.0', - 'coordinate_system': 'carla_world' + try: + # 创建V2XFormer格式的数据结构 + v2x_data = { + 'frame_id': frame_num, + 'timestamp': datetime.now().timestamp(), + 'point_cloud': { + 'points': points.tolist(), + 'num_points': points.shape[0], + 'range': [float(points[:, 0].min()), float(points[:, 0].max()), + float(points[:, 1].min()), float(points[:, 1].max()), + float(points[:, 2].min()), float(points[:, 2].max())] + }, + 'metadata': { + 'sensor_type': 'lidar', + 'format_version': '1.0', + 'coordinate_system': 'carla_world', + 'frame_rate': 10.0 + } } - } - # 保存为压缩的JSON格式 - filename = f"{frame_num:06d}.pkl.gz" - filepath = os.path.join(v2x_dir, filename) + # 保存为压缩的JSON格式 + filename = f"{frame_num:06d}.pkl.gz" + filepath = os.path.join(v2x_dir, filename) + + # 使用pickle+gzip压缩保存 + with gzip.open(filepath, 'wb') as f: + pickle.dump(v2x_data, f, protocol=pickle.HIGHEST_PROTOCOL) + + file_size = os.path.getsize(filepath) / 1024 # KB + print(f"保存V2XFormer格式: {filepath} ({file_size:.2f} KB)") + + return filepath + + except Exception as e: + # 如果保存失败,只打印警告,不抛出异常 + print(f"警告: 保存V2XFormer格式失败: {e}") + return None + + def process_lidar_data(self, lidar_data, frame_num): + with self.data_lock: + try: + self.frame_counter = frame_num + + points = self._carla_lidar_to_numpy(lidar_data) + + if points is None or points.shape[0] == 0: + print(f"警告: LiDAR数据为空或无效 (帧 {frame_num})") + return None - # 使用pickle+gzip压缩保存 - with gzip.open(filepath, 'wb') as f: - pickle.dump(v2x_data, f, protocol=pickle.HIGHEST_PROTOCOL) + # 下采样以减少内存使用 + if self.enable_downsampling and points.shape[0] > self.max_points_per_frame: + original_count = points.shape[0] + points = self._downsample_point_cloud(points) + print(f"LiDAR下采样: {original_count} -> {points.shape[0]} 点 (帧 {frame_num})") - return filepath + # 添加到批处理 + self.point_cloud_batch.append((frame_num, points)) + # 如果达到批处理大小,保存批处理数据 + if len(self.point_cloud_batch) >= self.batch_size: + self._save_batch() + + # 保存单个文件(向后兼容) + bin_path = self._save_as_bin(points, frame_num) + npy_path = self._save_as_npy(points, frame_num) + + # 生成V2XFormer兼容格式(如果失败则忽略) + try: + v2xformer_path = self._save_as_v2xformer_format(points, frame_num) + except Exception as e: + v2xformer_path = None + print(f"警告: V2XFormer格式保存失败: {e}") + + metadata = self._generate_metadata(points, bin_path, npy_path, v2xformer_path) + + return metadata + + except Exception as e: + print(f"处理LiDAR数据失败: {e}") + return None def _generate_metadata(self, points, bin_path, npy_path, v2xformer_path): metadata = { 'frame_id': self.frame_counter, diff --git a/src/enhance_pedestrian_safety/main.py b/src/enhance_pedestrian_safety/main.py index 80313c0481..990a4d0db5 100644 --- a/src/enhance_pedestrian_safety/main.py +++ b/src/enhance_pedestrian_safety/main.py @@ -10,6 +10,7 @@ from datetime import datetime import gc import psutil +import numpy as np from carla_utils import setup_carla_path, import_carla_module from config_manager import ConfigManager @@ -33,19 +34,131 @@ def __init__(self): self.memory_samples = [] self.cpu_samples = [] self.frame_times = [] + self.gpu_memory_samples = [] + self.disk_io_samples = [] + self.network_io_samples = [] + + # 初始化第一个样本,避免空列表 + self.sample_memory() + self.sample_cpu() def sample_memory(self): """采样内存使用""" - process = psutil.Process(os.getpid()) - memory_mb = process.memory_info().rss / 1024 / 1024 - self.memory_samples.append(memory_mb) - return memory_mb + try: + process = psutil.Process(os.getpid()) + memory_mb = process.memory_info().rss / 1024 / 1024 + self.memory_samples.append(memory_mb) + + # 采样系统内存 + system_memory = psutil.virtual_memory() + return { + 'process_mb': memory_mb, + 'system_total_mb': system_memory.total / 1024 / 1024, + 'system_used_percent': system_memory.percent, + 'system_available_mb': system_memory.available / 1024 / 1024 + } + except Exception as e: + # 如果采样失败,返回默认值 + memory_mb = 0 + self.memory_samples.append(memory_mb) + return { + 'process_mb': memory_mb, + 'system_total_mb': 0, + 'system_used_percent': 0, + 'system_available_mb': 0 + } def sample_cpu(self): """采样CPU使用""" - cpu_percent = psutil.cpu_percent(interval=0.1) - self.cpu_samples.append(cpu_percent) - return cpu_percent + try: + cpu_percent = psutil.cpu_percent(interval=0.1) + self.cpu_samples.append(cpu_percent) + + # 采样每个核心的CPU使用率 + cpu_per_core = psutil.cpu_percent(interval=0.1, percpu=True) + return { + 'total_percent': cpu_percent, + 'per_core': cpu_per_core, + 'count': psutil.cpu_count() + } + except Exception as e: + # 如果采样失败,返回默认值 + cpu_percent = 0 + self.cpu_samples.append(cpu_percent) + return { + 'total_percent': cpu_percent, + 'per_core': [0], + 'count': 1 + } + + def sample_gpu_memory(self): + """采样GPU内存(如果可用)""" + try: + # 尝试导入PyTorch或TensorFlow来获取GPU信息 + try: + import torch + if torch.cuda.is_available(): + allocated = torch.cuda.memory_allocated() / 1024 / 1024 + cached = torch.cuda.memory_reserved() / 1024 / 1024 + self.gpu_memory_samples.append(allocated) + return { + 'allocated_mb': allocated, + 'cached_mb': cached, + 'device_count': torch.cuda.device_count() + } + except ImportError: + pass + + try: + import tensorflow as tf + gpus = tf.config.list_physical_devices('GPU') + if gpus: + # TensorFlow的GPU内存监控比较复杂,这里简单返回 + self.gpu_memory_samples.append(0) + return { + 'gpu_count': len(gpus), + 'allocated_mb': 0 + } + except ImportError: + pass + except Exception as e: + pass + + return {'available': False} + + def sample_disk_io(self): + """采样磁盘IO""" + try: + disk_io = psutil.disk_io_counters() + read_mb = disk_io.read_bytes / 1024 / 1024 if disk_io else 0 + self.disk_io_samples.append(read_mb) + return { + 'read_mb': read_mb, + 'write_mb': disk_io.write_bytes / 1024 / 1024 if disk_io else 0, + 'read_count': disk_io.read_count if disk_io else 0, + 'write_count': disk_io.write_count if disk_io else 0 + } + except: + read_mb = 0 + self.disk_io_samples.append(read_mb) + return None + + def sample_network_io(self): + """采样网络IO""" + try: + net_io = psutil.net_io_counters() + sent_kb = net_io.bytes_sent / 1024 if net_io else 0 + self.network_io_samples.append(sent_kb) + return { + 'sent_kb': sent_kb, + 'recv_kb': net_io.bytes_recv / 1024 if net_io else 0, + 'packets_sent': net_io.packets_sent if net_io else 0, + 'packets_recv': net_io.packets_recv if net_io else 0 + } + except: + sent_kb = 0 + self.network_io_samples.append(sent_kb) + return None def record_frame_time(self, frame_time): """记录帧处理时间""" @@ -53,32 +166,97 @@ def record_frame_time(self, frame_time): def get_performance_summary(self): """获取性能摘要""" - return { + if not self.frame_times: + avg_frame_time = 0 + fps = 0 + else: + avg_frame_time = np.mean(self.frame_times) + fps = 1.0 / avg_frame_time if avg_frame_time > 0 else 0 + + summary = { 'total_runtime': time.time() - self.start_time, - 'average_memory_mb': sum(self.memory_samples) / len(self.memory_samples) if self.memory_samples else 0, + 'average_memory_mb': np.mean(self.memory_samples) if self.memory_samples else 0, 'max_memory_mb': max(self.memory_samples) if self.memory_samples else 0, - 'average_cpu_percent': sum(self.cpu_samples) / len(self.cpu_samples) if self.cpu_samples else 0, - 'average_frame_time': sum(self.frame_times) / len(self.frame_times) if self.frame_times else 0, - 'frames_per_second': 1.0 / (sum(self.frame_times) / len(self.frame_times)) if self.frame_times else 0 + 'min_memory_mb': min(self.memory_samples) if self.memory_samples else 0, + 'average_cpu_percent': np.mean(self.cpu_samples) if self.cpu_samples else 0, + 'max_cpu_percent': max(self.cpu_samples) if self.cpu_samples else 0, + 'average_frame_time': avg_frame_time, + 'frames_per_second': fps, + 'total_frames': len(self.frame_times), + 'frame_time_stats': { + 'p50': np.percentile(self.frame_times, 50) if self.frame_times else 0, + 'p95': np.percentile(self.frame_times, 95) if self.frame_times else 0, + 'p99': np.percentile(self.frame_times, 99) if self.frame_times else 0, + 'std': np.std(self.frame_times) if self.frame_times else 0 + } } + # 添加GPU信息 + if self.gpu_memory_samples: + summary['gpu_memory'] = { + 'average_mb': np.mean(self.gpu_memory_samples), + 'max_mb': max(self.gpu_memory_samples) + } + + # 添加磁盘IO信息 + if self.disk_io_samples: + summary['disk_io'] = { + 'total_read_mb': sum(self.disk_io_samples), + 'average_read_mb': np.mean(self.disk_io_samples) + } + + # 添加网络IO信息 + if self.network_io_samples: + summary['network_io'] = { + 'total_sent_kb': sum(self.network_io_samples), + 'average_sent_kb': np.mean(self.network_io_samples) + } + + return summary + + def get_realtime_metrics(self): + """获取实时性能指标""" + try: + return { + 'memory_mb': psutil.Process(os.getpid()).memory_info().rss / 1024 / 1024, + 'cpu_percent': psutil.cpu_percent(interval=0.1), + 'frame_rate': 1.0 / self.frame_times[-1] if self.frame_times else 0, + 'active_threads': threading.active_count() + } + except: + return { + 'memory_mb': 0, + 'cpu_percent': 0, + 'frame_rate': 0, + 'active_threads': 0 + } + class Log: @staticmethod def info(msg): - print(f"[INFO] {msg}") + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + print(f"[INFO][{timestamp}] {msg}") @staticmethod def warning(msg): - print(f"[WARNING] {msg}") + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + print(f"[WARNING][{timestamp}] {msg}") @staticmethod def error(msg): - print(f"[ERROR] {msg}") + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + print(f"[ERROR][{timestamp}] {msg}") @staticmethod def debug(msg): - print(f"[DEBUG] {msg}") + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + print(f"[DEBUG][{timestamp}] {msg}") + + @staticmethod + def performance(msg): + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + print(f"[PERF][{timestamp}] {msg}") class WeatherSystem: @@ -124,6 +302,14 @@ def __init__(self, output_dir, config=None): self.image_cache = {} self.max_cache_size = config.get('max_cache_size', 50) if config else 50 + # 性能统计 + self.stats = { + 'images_processed': 0, + 'cache_hits': 0, + 'cache_misses': 0, + 'total_processing_time': 0 + } + def stitch(self, image_paths, frame_num, view_type="vehicle"): try: from PIL import Image, ImageDraw @@ -131,15 +317,36 @@ def stitch(self, image_paths, frame_num, view_type="vehicle"): Log.warning("PIL未安装,跳过图像拼接") return False + start_time = time.time() + # 检查缓存 cache_key = f"{view_type}_{frame_num}" if self.enable_memory_cache and cache_key in self.image_cache: # 从缓存加载 cached_image = self.image_cache[cache_key] output_path = os.path.join(self.stitched_dir, f"{view_type}_{frame_num:06d}.jpg") - cached_image.save(output_path, "JPEG", quality=self.compression_quality) + + # 使用更快的保存方式 + try: + if self.compress_images: + cached_image.save(output_path, "JPEG", + quality=self.compression_quality, + optimize=True, + progressive=True) + else: + cached_image.save(output_path, "PNG", optimize=True) + except Exception as e: + Log.error(f"保存缓存图像失败: {e}") + return False + + self.stats['cache_hits'] += 1 + self.stats['images_processed'] += 1 + processing_time = time.time() - start_time + self.stats['total_processing_time'] += processing_time return True + self.stats['cache_misses'] += 1 + positions = [(10, 10), (660, 10), (10, 390), (660, 390)] canvas = Image.new('RGB', (640 * 2 + 20, 360 * 2 + 20), (40, 40, 40)) @@ -153,16 +360,22 @@ def stitch(self, image_paths, frame_num, view_type="vehicle"): if img_path in self.image_cache: img = self.image_cache[img_path] else: - img = Image.open(img_path).resize((640, 360)) + # 使用更快的图像加载方式 + img = Image.open(img_path) + # 预加载图像数据到内存 + img.load() + img = img.resize((640, 360), Image.Resampling.LANCZOS) + # 缓存图像 if self.enable_memory_cache: self.image_cache[img_path] = img # 清理缓存如果太大 if len(self.image_cache) > self.max_cache_size: - oldest_key = next(iter(self.image_cache)) - del self.image_cache[oldest_key] + # 使用LRU策略清理缓存 + self._cleanup_cache() images_loaded += 1 - except: + except Exception as e: + Log.warning(f"加载图像失败: {e}") img = Image.new('RGB', (640, 360), (80, 80, 80)) else: img = Image.new('RGB', (640, 360), (80, 80, 80)) @@ -176,11 +389,21 @@ def stitch(self, image_paths, frame_num, view_type="vehicle"): output_path = os.path.join(self.stitched_dir, f"{view_type}_{frame_num:06d}.jpg") - # 压缩保存 - if self.compress_images: - canvas.save(output_path, "JPEG", quality=self.compression_quality) - else: - canvas.save(output_path, "PNG") + # 使用优化的保存参数 + try: + if self.compress_images: + # 使用更快的JPEG保存选项 + canvas.save(output_path, "JPEG", + quality=self.compression_quality, + optimize=True, + progressive=True, + subsampling='4:2:0') + else: + # PNG优化 + canvas.save(output_path, "PNG", optimize=True) + except Exception as e: + Log.error(f"保存图像失败: {e}") + return False # 缓存结果 if self.enable_memory_cache: @@ -190,8 +413,47 @@ def stitch(self, image_paths, frame_num, view_type="vehicle"): del canvas gc.collect() + self.stats['images_processed'] += 1 + processing_time = time.time() - start_time + self.stats['total_processing_time'] += processing_time + + # 记录性能日志(每50帧记录一次) + if self.stats['images_processed'] % 50 == 0: + avg_time = self.stats['total_processing_time'] / self.stats['images_processed'] + cache_hit_rate = self.stats['cache_hits'] / max(1, self.stats['cache_hits'] + self.stats['cache_misses']) + Log.performance(f"图像处理统计: 处理{self.stats['images_processed']}张, " + f"平均{avg_time:.3f}秒/张, 缓存命中率{cache_hit_rate:.1%}") + return True + def _cleanup_cache(self): + """清理图像缓存""" + if len(self.image_cache) > self.max_cache_size: + # 移除最旧的缓存项(简单实现) + keys_to_remove = list(self.image_cache.keys())[:len(self.image_cache) - self.max_cache_size] + for key in keys_to_remove: + del self.image_cache[key] + Log.debug(f"清理缓存: 移除了{len(keys_to_remove)}个缓存项") + + def get_stats(self): + """获取处理统计""" + total_operations = self.stats['cache_hits'] + self.stats['cache_misses'] + if total_operations > 0: + cache_hit_rate = self.stats['cache_hits'] / total_operations + else: + cache_hit_rate = 0 + + avg_time = 0 + if self.stats['images_processed'] > 0: + avg_time = self.stats['total_processing_time'] / self.stats['images_processed'] + + return { + **self.stats, + 'cache_hit_rate': cache_hit_rate, + 'average_processing_time': avg_time, + 'current_cache_size': len(self.image_cache) + } + class TrafficManager: def __init__(self, world, config): @@ -208,6 +470,14 @@ def __init__(self, world, config): self.batch_spawn = config.get('batch_spawn', True) self.max_spawn_attempts = config.get('max_spawn_attempts', 5) + # 性能统计 + self.spawn_stats = { + 'total_attempts': 0, + 'successful_spawns': 0, + 'failed_spawns': 0, + 'total_spawn_time': 0 + } + def spawn_ego_vehicle(self): blueprint_lib = self.world.get_blueprint_library() @@ -233,13 +503,21 @@ def spawn_ego_vehicle(self): # 尝试多次生成 for attempt in range(self.max_spawn_attempts): + self.spawn_stats['total_attempts'] += 1 try: + start_time = time.time() vehicle = self.world.spawn_actor(vehicle_bp, spawn_point) vehicle.set_autopilot(True) vehicle.apply_control(carla.VehicleControl(throttle=0.2)) - Log.info(f"主车: {vehicle.type_id}") + + spawn_time = time.time() - start_time + self.spawn_stats['total_spawn_time'] += spawn_time + self.spawn_stats['successful_spawns'] += 1 + + Log.info(f"主车: {vehicle.type_id}, 生成时间: {spawn_time:.3f}秒") return vehicle except Exception as e: + self.spawn_stats['failed_spawns'] += 1 if attempt == self.max_spawn_attempts - 1: Log.warning(f"主车生成失败: {e}") else: @@ -250,6 +528,8 @@ def spawn_ego_vehicle(self): return None def spawn_traffic(self, center_location): + start_time = time.time() + if self.batch_spawn: vehicles = self._spawn_vehicles_batch() else: @@ -257,7 +537,14 @@ def spawn_traffic(self, center_location): pedestrians = self._spawn_pedestrians(center_location) - Log.info(f"交通生成: {vehicles}辆车, {pedestrians}个行人") + total_time = time.time() - start_time + Log.info(f"交通生成: {vehicles}辆车, {pedestrians}个行人, 用时: {total_time:.2f}秒") + + # 记录性能统计 + success_rate = self.spawn_stats['successful_spawns'] / max(1, self.spawn_stats['total_attempts']) + avg_spawn_time = self.spawn_stats['total_spawn_time'] / max(1, self.spawn_stats['successful_spawns']) + Log.performance(f"生成统计: 成功率{success_rate:.1%}, 平均生成时间{avg_spawn_time:.3f}秒") + return vehicles + pedestrians def _spawn_vehicles_batch(self): @@ -297,7 +584,12 @@ def _spawn_vehicles_batch(self): vehicle.set_autopilot(True) self.vehicles.append(vehicle) spawned += 1 + + self.spawn_stats['successful_spawns'] += 1 + self.spawn_stats['total_attempts'] += 1 except: + self.spawn_stats['failed_spawns'] += 1 + self.spawn_stats['total_attempts'] += 1 pass # 避免过快的生成速度 @@ -324,7 +616,12 @@ def _spawn_vehicles(self): vehicle.set_autopilot(True) self.vehicles.append(vehicle) spawned += 1 + + self.spawn_stats['successful_spawns'] += 1 + self.spawn_stats['total_attempts'] += 1 except: + self.spawn_stats['failed_spawns'] += 1 + self.spawn_stats['total_attempts'] += 1 pass return spawned @@ -355,7 +652,12 @@ def _spawn_pedestrians(self, center_location): pedestrian = self.world.spawn_actor(ped_bp, carla.Transform(location)) self.pedestrians.append(pedestrian) spawned += 1 + + self.spawn_stats['successful_spawns'] += 1 + self.spawn_stats['total_attempts'] += 1 except Exception as e: + self.spawn_stats['failed_spawns'] += 1 + self.spawn_stats['total_attempts'] += 1 Log.debug(f"行人生成失败: {e}") return spawned @@ -391,6 +693,14 @@ def cleanup(self): self.vehicles.clear() self.pedestrians.clear() + # 清理统计 + self.spawn_stats = { + 'total_attempts': 0, + 'successful_spawns': 0, + 'failed_spawns': 0, + 'total_spawn_time': 0 + } + class SensorManager: def __init__(self, world, config, data_dir): @@ -407,6 +717,9 @@ def __init__(self, world, config, data_dir): self.infra_buffer = {} self.buffer_lock = threading.Lock() + # 添加运行状态标志 + self.is_running = True + # 性能监控 self.performance_monitor = PerformanceMonitor() @@ -425,7 +738,17 @@ def __init__(self, world, config, data_dir): if config['output'].get('save_fusion', False): self.fusion_manager = MultiSensorFusion(data_dir, config.get('fusion', {})) + # 传感器统计 + self.sensor_stats = { + 'total_images': 0, + 'total_lidar_frames': 0, + 'image_capture_times': [], + 'lidar_processing_times': [], + 'frame_drop_count': 0 + } + def setup_cameras(self, vehicle, center_location, vehicle_id=0): + """设置摄像头 - 修复的方法""" vehicle_cams = self._setup_vehicle_cameras(vehicle, vehicle_id) infra_cams = self._setup_infrastructure_cameras(center_location) @@ -501,44 +824,59 @@ def setup_lidar(self, vehicle, vehicle_id=0): lidar_sensor = self.world.spawn_actor(lidar_bp, lidar_transform, attach_to=vehicle) def lidar_callback(lidar_data): - current_time = time.time() - frame_start_time = time.time() + # 检查是否正在关闭 + if not self.is_running: + return - if current_time - self.last_capture_time >= self.config['sensors']['capture_interval']: - if self.lidar_processor: - try: - metadata = self.lidar_processor.process_lidar_data(lidar_data, self.frame_counter) - if metadata and self.fusion_manager: - vehicle_image_path = None - with self.buffer_lock: - if self.vehicle_buffer: - for cam_name, img_path in self.vehicle_buffer.items(): - if os.path.exists(img_path): - vehicle_image_path = img_path - break - - sensor_data = { - 'lidar': os.path.join(self.data_dir, "lidar", f"lidar_{self.frame_counter:06d}.bin") - } - if vehicle_image_path: - sensor_data['camera'] = vehicle_image_path - - self.fusion_manager.create_synchronization_file(self.frame_counter, sensor_data) - except Exception as e: - print(f"LiDAR处理失败: {e}") - - # 记录性能 - frame_time = time.time() - frame_start_time - self.performance_monitor.record_frame_time(frame_time) + try: + current_time = time.time() + frame_start_time = time.time() + + if current_time - self.last_capture_time >= self.config['sensors']['capture_interval']: + if self.lidar_processor: + try: + start_process = time.time() + metadata = self.lidar_processor.process_lidar_data(lidar_data, self.frame_counter) + process_time = time.time() - start_process + self.sensor_stats['lidar_processing_times'].append(process_time) + self.sensor_stats['total_lidar_frames'] += 1 + + if metadata and self.fusion_manager: + vehicle_image_path = None + with self.buffer_lock: + if self.vehicle_buffer: + for cam_name, img_path in self.vehicle_buffer.items(): + if os.path.exists(img_path): + vehicle_image_path = img_path + break + + sensor_data = { + 'lidar': os.path.join(self.data_dir, "lidar", + f"lidar_{self.frame_counter:06d}.bin") + } + if vehicle_image_path: + sensor_data['camera'] = vehicle_image_path + + self.fusion_manager.create_synchronization_file(self.frame_counter, sensor_data) + except Exception as e: + if self.is_running: + Log.error(f"LiDAR处理失败: {e}") + + # 记录性能 + frame_time = time.time() - frame_start_time + self.performance_monitor.record_frame_time(frame_time) + except Exception as e: + if self.is_running: + Log.error(f"LiDAR回调错误: {e}") lidar_sensor.listen(lidar_callback) self.sensors.append(lidar_sensor) - print("LiDAR传感器已安装") + Log.info("LiDAR传感器已安装") return 1 except Exception as e: - print(f"LiDAR安装失败: {e}") + Log.error(f"LiDAR安装失败: {e}") return 0 def _create_camera(self, name, config, parent, sensor_type, vehicle_id=0): @@ -581,44 +919,70 @@ def _create_callback(self, save_dir, name, sensor_type, vehicle_id=0): capture_interval = self.config['sensors']['capture_interval'] def callback(image): - current_time = time.time() - frame_start_time = time.time() - - if current_time - self.last_capture_time >= capture_interval: - self.frame_counter += 1 - self.last_capture_time = current_time - - filename = os.path.join(save_dir, f"{name}_{self.frame_counter:06d}.png") - image.save_to_disk(filename, carla.ColorConverter.Raw) - - with self.buffer_lock: - if sensor_type == 'vehicle': - self.vehicle_buffer[name] = filename - if len(self.vehicle_buffer) >= 4: - self.image_processor.stitch(self.vehicle_buffer, self.frame_counter, - f'vehicle_{vehicle_id}') - self.vehicle_buffer.clear() - else: - self.infra_buffer[name] = filename - if len(self.infra_buffer) >= 4: - self.image_processor.stitch(self.infra_buffer, self.frame_counter, 'infrastructure') - self.infra_buffer.clear() - - # 定期采样性能 - if current_time - self.last_performance_sample >= 5.0: - memory_mb = self.performance_monitor.sample_memory() - cpu_percent = self.performance_monitor.sample_cpu() - if self.frame_counter % 10 == 0: - Log.debug(f"性能采样 - 内存: {memory_mb:.1f}MB, CPU: {cpu_percent:.1f}%") - self.last_performance_sample = current_time - - # 记录帧处理时间 - frame_time = time.time() - frame_start_time - self.performance_monitor.record_frame_time(frame_time) - - # 定期垃圾回收 - if self.frame_counter % 50 == 0: - gc.collect() + # 检查是否正在关闭 + if not self.is_running: + return + + try: + current_time = time.time() + frame_start_time = time.time() + + if current_time - self.last_capture_time >= capture_interval: + self.frame_counter += 1 + self.last_capture_time = current_time + + capture_start = time.time() + filename = os.path.join(save_dir, f"{name}_{self.frame_counter:06d}.png") + image.save_to_disk(filename, carla.ColorConverter.Raw) + capture_time = time.time() - capture_start + + self.sensor_stats['image_capture_times'].append(capture_time) + self.sensor_stats['total_images'] += 1 + + with self.buffer_lock: + if sensor_type == 'vehicle': + self.vehicle_buffer[name] = filename + if len(self.vehicle_buffer) >= 4: + self.image_processor.stitch(self.vehicle_buffer, self.frame_counter, + f'vehicle_{vehicle_id}') + self.vehicle_buffer.clear() + else: + self.infra_buffer[name] = filename + if len(self.infra_buffer) >= 4: + self.image_processor.stitch(self.infra_buffer, self.frame_counter, 'infrastructure') + self.infra_buffer.clear() + + # 定期采样性能 + if current_time - self.last_performance_sample >= 5.0: + memory_info = self.performance_monitor.sample_memory() + cpu_info = self.performance_monitor.sample_cpu() + gpu_info = self.performance_monitor.sample_gpu_memory() + + if self.frame_counter % 10 == 0: + Log.performance(f"系统监控 - 内存: {memory_info['process_mb']:.1f}MB, " + f"CPU: {cpu_info['total_percent']:.1f}%, " + f"线程数: {threading.active_count()}") + + self.last_performance_sample = current_time + + # 记录帧处理时间 + frame_time = time.time() - frame_start_time + self.performance_monitor.record_frame_time(frame_time) + + # 定期垃圾回收 + if self.frame_counter % 50 == 0: + gc.collect() + + # 记录传感器统计 + if self.sensor_stats['total_images'] > 0: + avg_capture_time = np.mean(self.sensor_stats['image_capture_times'][-100:]) if len( + self.sensor_stats['image_capture_times']) > 0 else 0 + Log.performance(f"传感器统计: 图像{self.sensor_stats['total_images']}张, " + f"平均捕获时间{avg_capture_time:.3f}秒") + except Exception as e: + # 如果在关闭过程中发生错误,忽略它 + if self.is_running: + Log.error(f"传感器回调错误: {e}") return callback @@ -631,46 +995,87 @@ def generate_sensor_summary(self): 'frame_count': self.frame_counter, 'lidar_data': None, 'fusion_data': None, - 'performance': self.performance_monitor.get_performance_summary() + 'performance': self.performance_monitor.get_performance_summary(), + 'sensor_stats': self.sensor_stats } + # 计算统计信息 + if self.sensor_stats['image_capture_times']: + summary['sensor_stats']['avg_image_capture_time'] = np.mean(self.sensor_stats['image_capture_times']) + summary['sensor_stats']['max_image_capture_time'] = np.max(self.sensor_stats['image_capture_times']) + + if self.sensor_stats['lidar_processing_times']: + summary['sensor_stats']['avg_lidar_process_time'] = np.mean(self.sensor_stats['lidar_processing_times']) + summary['sensor_stats']['max_lidar_process_time'] = np.max(self.sensor_stats['lidar_processing_times']) + if self.lidar_processor: summary['lidar_data'] = self.lidar_processor.generate_lidar_summary() if self.fusion_manager: summary['fusion_data'] = self.fusion_manager.generate_fusion_report() + # 图像处理器统计 + summary['image_processor_stats'] = self.image_processor.get_stats() + return summary def cleanup(self): - Log.info(f"清理 {len(self.sensors)} 个传感器...") + """安全清理传感器""" + Log.info(f"安全清理 {len(self.sensors)} 个传感器...") + + # 设置运行状态为False,防止回调函数继续执行 + self.is_running = False + + # 等待一小段时间让回调函数结束 + time.sleep(0.1) # 刷新批处理数据 if self.lidar_processor: - self.lidar_processor.flush_batch() + try: + self.lidar_processor.flush_batch() + except Exception as e: + Log.warning(f"刷新LiDAR批处理数据失败: {e}") - # 批量销毁传感器 - batch_size = 5 - for i in range(0, len(self.sensors), batch_size): - batch = self.sensors[i:i + batch_size] - for sensor in batch: + # 批量销毁传感器(从后向前销毁,避免依赖问题) + if self.sensors: + Log.info(f"销毁 {len(self.sensors)} 个传感器...") + + # 首先停止所有传感器的监听 + for sensor in self.sensors: try: - sensor.stop() - sensor.destroy() - except: - pass + if sensor and sensor.is_alive: + sensor.stop() + except Exception as e: + Log.debug(f"停止传感器失败: {e}") - if i > 0 and i % 20 == 0: - time.sleep(0.05) + # 等待一小段时间让传感器完全停止 + time.sleep(0.05) + + # 然后销毁传感器 + for i, sensor in enumerate(reversed(self.sensors)): + try: + if sensor and sensor.is_alive: + sensor.destroy() + except Exception as e: + Log.debug(f"销毁传感器失败: {e}") + + # 分批销毁,避免一次性销毁太多 + if i % 5 == 0: + time.sleep(0.01) self.sensors.clear() # 清理缓存 if hasattr(self.image_processor, 'image_cache'): - self.image_processor.image_cache.clear() + try: + self.image_processor.image_cache.clear() + except: + pass gc.collect() + Log.info("传感器清理完成") + class DataCollector: def __init__(self, config): @@ -817,6 +1222,7 @@ def setup_scene(self): return True def setup_sensors(self): + """设置传感器 - 修复的方法""" # 为每个主车设置传感器 for i, vehicle in enumerate(self.ego_vehicles): sensor_manager = SensorManager(self.world, self.config, self.output_dir) @@ -844,6 +1250,7 @@ def collect_data(self): last_v2x_update = time.time() last_perception_share = time.time() last_performance_sample = time.time() + last_detailed_log = time.time() try: while time.time() - self.start_time < duration and self.is_running: @@ -867,9 +1274,30 @@ def collect_data(self): # 性能采样 if current_time - last_performance_sample >= 10.0: - memory_mb = self.performance_monitor.sample_memory() - cpu_percent = self.performance_monitor.sample_cpu() - Log.debug(f"系统性能 - 内存: {memory_mb:.1f}MB, CPU: {cpu_percent:.1f}%") + memory_info = self.performance_monitor.sample_memory() + cpu_info = self.performance_monitor.sample_cpu() + disk_info = self.performance_monitor.sample_disk_io() + network_info = self.performance_monitor.sample_network_io() + + # 详细性能日志(每分钟一次) + if current_time - last_detailed_log >= 60.0: + Log.performance(f"详细性能监控:") + Log.performance(f" 进程内存: {memory_info['process_mb']:.1f}MB") + Log.performance(f" 系统内存: {memory_info['system_used_percent']:.1f}%使用率") + Log.performance(f" CPU: {cpu_info['total_percent']:.1f}% ({cpu_info['count']}核心)") + if disk_info: + Log.performance( + f" 磁盘IO: 读{disk_info['read_mb']:.1f}MB, 写{disk_info['write_mb']:.1f}MB") + if network_info: + Log.performance( + f" 网络IO: 发{network_info['sent_kb']:.1f}KB, 收{network_info['recv_kb']:.1f}KB") + last_detailed_log = current_time + else: + # 简化性能日志 + Log.performance(f"系统监控 - 内存: {memory_info['process_mb']:.1f}MB, " + f"CPU: {cpu_info['total_percent']:.1f}%, " + f"活跃线程: {threading.active_count()}") + last_performance_sample = current_time # 定期垃圾回收 @@ -911,6 +1339,7 @@ def collect_data(self): Log.info(f"收集完成: {self.collected_frames}帧, 用时: {elapsed:.1f}秒") Log.info(f"平均帧率: {performance_summary['frames_per_second']:.2f} FPS") Log.info(f"最大内存使用: {performance_summary['max_memory_mb']:.1f} MB") + Log.info(f"平均CPU使用: {performance_summary['average_cpu_percent']:.1f}%") self._save_metadata() self._print_summary() @@ -1175,9 +1604,15 @@ def _print_summary(self): performance = self.performance_monitor.get_performance_summary() print(f"\n性能统计:") print(f" 平均帧率: {performance['frames_per_second']:.2f} FPS") + print(f" 帧时统计:") + print(f" 平均: {performance['average_frame_time']:.3f}秒") + print(f" P50: {performance['frame_time_stats']['p50']:.3f}秒") + print(f" P95: {performance['frame_time_stats']['p95']:.3f}秒") + print(f" P99: {performance['frame_time_stats']['p99']:.3f}秒") print(f" 平均内存: {performance['average_memory_mb']:.1f} MB") print(f" 最大内存: {performance['max_memory_mb']:.1f} MB") print(f" 平均CPU: {performance['average_cpu_percent']:.1f}%") + print(f" 总帧数: {performance['total_frames']}") print(f"\n输出目录: {self.output_dir}") print("=" * 60) @@ -1193,36 +1628,89 @@ def run_analysis(self): DataAnalyzer.analyze_dataset(self.output_dir) def cleanup(self): - Log.info("清理场景...") + """安全清理所有资源 - 修复的方法""" + Log.info("开始安全清理场景...") - # 清理传感器 - for sensor_manager in self.sensor_managers.values(): - sensor_manager.cleanup() + try: + # 1. 先停止数据收集 + self.is_running = False - # 清理交通 - if self.traffic_manager: - self.traffic_manager.cleanup() + # 2. 等待一小段时间让所有传感器回调结束 + time.sleep(0.2) - # 清理协同管理 - if self.multi_vehicle_manager: - self.multi_vehicle_manager.cleanup() + # 3. 清理传感器(这是最重要的,必须在清理车辆之前) + for sensor_manager in self.sensor_managers.values(): + try: + sensor_manager.cleanup() + except Exception as e: + Log.error(f"清理传感器管理器失败: {e}") - # 清理V2X通信 - if self.v2x_communication: - self.v2x_communication.stop() + self.sensor_managers.clear() - # 清理车辆 - for vehicle in self.ego_vehicles: - if vehicle and vehicle.is_alive: + # 4. 清理V2X通信 + if self.v2x_communication: try: - vehicle.destroy() - except: - pass + self.v2x_communication.stop() + except Exception as e: + Log.error(f"停止V2X通信失败: {e}") - # 强制垃圾回收 - gc.collect() + # 5. 清理协同管理 + if self.multi_vehicle_manager: + try: + self.multi_vehicle_manager.cleanup() + except Exception as e: + Log.error(f"清理协同管理器失败: {e}") + + # 6. 清理背景交通(必须在清理主车之前) + if self.traffic_manager: + try: + self.traffic_manager.cleanup() + except Exception as e: + Log.error(f"清理交通管理器失败: {e}") + + # 7. 最后清理主车(在传感器和交通清理完之后) + for vehicle in self.ego_vehicles: + try: + if vehicle and vehicle.is_alive: + vehicle.destroy() + except Exception as e: + Log.debug(f"销毁主车失败: {e}") + + self.ego_vehicles.clear() + + # 8. 强制垃圾回收 + gc.collect() - Log.info("清理完成") + # 9. 重置CARLA世界(可选,但可以帮助清理) + try: + if self.world: + # 重置天气到默认 + default_weather = carla.WeatherParameters() + self.world.set_weather(default_weather) + except Exception as e: + Log.debug(f"重置世界设置失败: {e}") + + # 10. 等待一段时间确保所有清理完成 + time.sleep(0.5) + + Log.info("清理完成") + + except Exception as e: + Log.error(f"清理过程中发生错误: {e}") + import traceback + traceback.print_exc() + + # 即使出错也要尝试强制清理 + try: + # 强制删除所有引用,让Python垃圾回收器工作 + del self.sensor_managers + del self.traffic_manager + del self.multi_vehicle_manager + del self.v2x_communication + del self.ego_vehicles + gc.collect() + except: + pass def main(): @@ -1262,7 +1750,7 @@ def main(): parser.add_argument('--enable-fusion', action='store_true', help='启用多传感器融合') parser.add_argument('--enable-v2x', action='store_true', help='启用V2X通信') parser.add_argument('--enable-cooperative', action='store_true', help='启用协同感知') - parser.add_argument('--enable-enhancement', action='store_true', help='启用数据增强') # 添加这行 + parser.add_argument('--enable-enhancement', action='store_true', help='启用数据增强') parser.add_argument('--enable-annotations', action='store_true', help='启用自动标注') # 功能参数 diff --git a/src/enhance_pedestrian_safety/multi_vehicle_manager.py b/src/enhance_pedestrian_safety/multi_vehicle_manager.py index 0f4492829c..4ed8890d91 100644 --- a/src/enhance_pedestrian_safety/multi_vehicle_manager.py +++ b/src/enhance_pedestrian_safety/multi_vehicle_manager.py @@ -40,11 +40,16 @@ def __init__(self, world, config, output_dir): self.config = config self.output_dir = output_dir - # 创建协同数据目录 + # 创建协同数据目录 - 确保所有子目录都创建 self.coop_dir = os.path.join(output_dir, "cooperative") os.makedirs(self.coop_dir, exist_ok=True) - os.makedirs(os.path.join(self.coop_dir, "v2x_messages"), exist_ok=True) - os.makedirs(os.path.join(self.coop_dir, "shared_perception"), exist_ok=True) + + # 创建子目录 + v2x_messages_dir = os.path.join(self.coop_dir, "v2x_messages") + os.makedirs(v2x_messages_dir, exist_ok=True) + + shared_perception_dir = os.path.join(self.coop_dir, "shared_perception") + os.makedirs(shared_perception_dir, exist_ok=True) # 车辆管理 self.ego_vehicles = [] # 主车辆列表 @@ -190,7 +195,7 @@ def create_v2x_message(self, sender_id: int, message_type: str, data: Dict, def broadcast_message(self, message: V2XMessage): """广播消息给范围内的车辆""" if message.sender_id not in self.vehicle_states: - return + return [] sender_state = self.vehicle_states[message.sender_id] sender_pos = sender_state.position @@ -220,65 +225,77 @@ def broadcast_message(self, message: V2XMessage): 'signal_strength': 1.0 - (distance / self.communication_range) }) - self.stats['successful_transmissions'] += len(recipients) + if recipients: + self.stats['successful_transmissions'] += len(recipients) - # 保存消息 - self._save_v2x_message(message, recipients) + # 保存消息 + try: + self._save_v2x_message(message, recipients) + except Exception as e: + print(f"保存V2X消息失败: {e}") return recipients def share_perception_data(self, vehicle_id: int, detected_objects: List[Dict]): """共享感知数据""" if not detected_objects: - return - - # 创建感知消息 - perception_data = { - 'vehicle_id': vehicle_id, - 'timestamp': time.time(), - 'objects': detected_objects, - 'vehicle_state': asdict( - self.vehicle_states.get(vehicle_id, VehicleState(0, '', (0, 0, 0), (0, 0, 0), (0, 0, 0), 0, []))) - } + return None + + try: + # 创建感知消息 + perception_data = { + 'vehicle_id': vehicle_id, + 'timestamp': time.time(), + 'objects': detected_objects, + 'vehicle_state': asdict( + self.vehicle_states.get(vehicle_id, VehicleState(0, '', (0, 0, 0), (0, 0, 0), (0, 0, 0), 0, []))) + } - message = self.create_v2x_message( - vehicle_id, - 'perception', - perception_data, - priority=2 # 感知数据优先级较高 - ) + message = self.create_v2x_message( + vehicle_id, + 'perception', + perception_data, + priority=2 # 感知数据优先级较高 + ) - # 广播消息 - recipients = self.broadcast_message(message) + # 广播消息 + recipients = self.broadcast_message(message) - # 融合共享的感知数据 - if recipients: - self._fuse_shared_perception(vehicle_id, detected_objects, recipients) + # 融合共享的感知数据 + if recipients: + self._fuse_shared_perception(vehicle_id, detected_objects, recipients) - return message + return message + except Exception as e: + print(f"共享感知数据失败: {e}") + return None def share_traffic_warning(self, vehicle_id: int, warning_type: str, location: Tuple[float, float, float], severity: str = 'medium'): """共享交通警告""" - warning_data = { - 'warning_type': warning_type, # 'accident', 'congestion', 'hazard', 'construction' - 'location': location, - 'severity': severity, - 'timestamp': time.time(), - 'source_vehicle': vehicle_id - } + try: + warning_data = { + 'warning_type': warning_type, # 'accident', 'congestion', 'hazard', 'construction' + 'location': location, + 'severity': severity, + 'timestamp': time.time(), + 'source_vehicle': vehicle_id + } - message = self.create_v2x_message( - vehicle_id, - 'warning', - warning_data, - priority=3 # 警告消息优先级最高 - ) + message = self.create_v2x_message( + vehicle_id, + 'warning', + warning_data, + priority=3 # 警告消息优先级最高 + ) - self.broadcast_message(message) + self.broadcast_message(message) - return message + return message + except Exception as e: + print(f"共享交通警告失败: {e}") + return None def _fuse_shared_perception(self, source_id: int, objects: List[Dict], recipients: List[int]): """融合共享的感知数据""" @@ -376,26 +393,41 @@ def _save_v2x_message(self, message: V2XMessage, recipients: List[int]): 'transmission_time': time.time() } + # 确保目录存在 + v2x_messages_dir = os.path.join(self.coop_dir, "v2x_messages") + os.makedirs(v2x_messages_dir, exist_ok=True) + filename = f"v2x_{int(time.time() * 1000)}_{message.sender_id}_{message.message_type}.json" - filepath = os.path.join(self.coop_dir, "v2x_messages", filename) + filepath = os.path.join(v2x_messages_dir, filename) - with open(filepath, 'w', encoding='utf-8') as f: - json.dump(message_data, f, indent=2, ensure_ascii=False) + try: + with open(filepath, 'w', encoding='utf-8') as f: + json.dump(message_data, f, indent=2, ensure_ascii=False) + except Exception as e: + print(f"写入V2X消息文件失败: {e}") + raise def save_shared_perception(self, frame_num: int): """保存共享感知数据""" - data = { - 'frame_id': frame_num, - 'timestamp': time.time(), - 'shared_objects': self.shared_objects, - 'active_vehicles': len(self.ego_vehicles + self.cooperative_vehicles), - 'stats': self.stats - } - - filepath = os.path.join(self.coop_dir, "shared_perception", f"frame_{frame_num:06d}.json") - - with open(filepath, 'w', encoding='utf-8') as f: - json.dump(data, f, indent=2, ensure_ascii=False) + try: + data = { + 'frame_id': frame_num, + 'timestamp': time.time(), + 'shared_objects': self.shared_objects, + 'active_vehicles': len(self.ego_vehicles + self.cooperative_vehicles), + 'stats': self.stats + } + + # 确保目录存在 + shared_perception_dir = os.path.join(self.coop_dir, "shared_perception") + os.makedirs(shared_perception_dir, exist_ok=True) + + filepath = os.path.join(shared_perception_dir, f"frame_{frame_num:06d}.json") + + with open(filepath, 'w', encoding='utf-8') as f: + json.dump(data, f, indent=2, ensure_ascii=False) + except Exception as e: + print(f"保存共享感知数据失败: {e}") def generate_summary(self): """生成协同摘要报告""" From 1446fb4d47c6e2eb0a61fd2e6ee194012d29779f Mon Sep 17 00:00:00 2001 From: Z-w-7799 <1011069369@qq.com> Date: Sat, 20 Dec 2025 19:38:12 +0800 Subject: [PATCH 08/20] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E6=94=B6=E9=9B=86=E5=99=A8=E5=92=8C=E4=BC=A0=E6=84=9F=E5=99=A8?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config_manager.py | 28 +- .../lidar_processor.py | 296 ++++++------ src/enhance_pedestrian_safety/main.py | 433 +++++++++++------- 3 files changed, 463 insertions(+), 294 deletions(-) diff --git a/src/enhance_pedestrian_safety/config_manager.py b/src/enhance_pedestrian_safety/config_manager.py index 0ef2d0bff3..183cb3cab3 100644 --- a/src/enhance_pedestrian_safety/config_manager.py +++ b/src/enhance_pedestrian_safety/config_manager.py @@ -36,8 +36,11 @@ def load_config(config_file=None): 'range': 100, 'points_per_second': 56000, 'rotation_frequency': 10, - 'max_points_per_frame': 100000, - 'downsample_ratio': 0.5 + 'max_points_per_frame': 50000, # 优化点6:减少最大点数 + 'downsample_ratio': 0.3, # 优化点6:增加下采样比例 + 'memory_warning_threshold': 350, # 新增:内存警告阈值(MB) + 'max_batch_memory_mb': 50, # 新增:批次最大内存 + 'v2x_save_interval': 5 # 新增:V2X格式保存间隔 } }, 'v2x': { @@ -47,7 +50,8 @@ def load_config(config_file=None): 'latency_mean': 0.05, 'latency_std': 0.01, 'packet_loss_rate': 0.01, - 'message_types': ['bsm', 'spat', 'map', 'rsm'] + 'message_types': ['bsm', 'spat', 'map', 'rsm'], + 'update_interval': 2.0 # 优化点6:V2X更新间隔 }, 'cooperative': { 'num_coop_vehicles': 2, @@ -81,11 +85,16 @@ def load_config(config_file=None): 'batch_size': 10, 'enable_compression': True, 'enable_downsampling': True, - 'max_points_per_frame': 100000 + 'max_points_per_frame': 50000, # 与sensors配置保持一致 + 'memory_warning_threshold': 350, + 'max_batch_memory_mb': 50, + 'v2x_save_interval': 5 }, 'fusion': { 'fusion_cache_size': 100 - } + }, + 'sensor_cleanup_timeout': 0.5, # 新增:传感器清理超时 + 'frame_rate_limit': 5.0 # 新增:帧率限制 }, 'output': { 'data_dir': 'cvips_dataset', @@ -150,6 +159,9 @@ def merge_args(config, args): if hasattr(args, 'enable_v2x') and args.enable_v2x: config['v2x']['enabled'] = True + # 如果通过命令行启用V2X,使用更保守的更新间隔 + if 'update_interval' not in config['v2x']: + config['v2x']['update_interval'] = 2.0 if hasattr(args, 'enable_enhancement') and args.enable_enhancement: config['enhancement']['enabled'] = True @@ -157,6 +169,9 @@ def merge_args(config, args): if hasattr(args, 'enable_lidar') and args.enable_lidar: config['sensors']['lidar_sensors'] = 1 config['output']['save_lidar'] = True + # 优化:如果启用LiDAR,使用更保守的设置 + config['sensors']['lidar_config']['max_points_per_frame'] = 50000 + config['sensors']['lidar_config']['downsample_ratio'] = 0.3 if hasattr(args, 'enable_fusion') and args.enable_fusion: config['output']['save_fusion'] = True @@ -185,6 +200,9 @@ def merge_args(config, args): if hasattr(args, 'enable_downsampling') and args.enable_downsampling: config['performance']['enable_downsampling'] = True + # 如果启用下采样,使用更保守的设置 + config['sensors']['lidar_config']['downsample_ratio'] = 0.3 + config['performance']['lidar_processing']['max_points_per_frame'] = 50000 if hasattr(args, 'output_format') and args.output_format: config['output']['output_format'] = args.output_format diff --git a/src/enhance_pedestrian_safety/lidar_processor.py b/src/enhance_pedestrian_safety/lidar_processor.py index da2590994e..654aa674b8 100644 --- a/src/enhance_pedestrian_safety/lidar_processor.py +++ b/src/enhance_pedestrian_safety/lidar_processor.py @@ -29,10 +29,15 @@ def __init__(self, output_dir, config=None): self.enable_compression = config.get('enable_compression', True) if config else True self.compression_level = config.get('compression_level', 3) if config else 3 - # 内存管理 - self.max_points_per_frame = config.get('max_points_per_frame', 100000) if config else 100000 + # 内存管理 - 优化点3:更保守的内存设置 + self.max_points_per_frame = config.get('max_points_per_frame', 50000) if config else 50000 # 减少到5万点 self.enable_downsampling = config.get('enable_downsampling', True) if config else True - self.downsample_ratio = config.get('downsample_ratio', 0.5) if config else 0.5 + self.downsample_ratio = config.get('downsample_ratio', 0.3) if config else 0.3 # 增加下采样比例 + + # 新增:内存监控和限制 + self.memory_warning_threshold = config.get('memory_warning_threshold', 300) if config else 300 # MB + self.max_batch_memory_mb = config.get('max_batch_memory_mb', 50) if config else 50 # 批次最大内存 + self.v2x_save_interval = config.get('v2x_save_interval', 5) if config else 5 # 每5帧保存一次V2X格式 self._init_calibration_files() @@ -70,34 +75,65 @@ def _init_calibration_files(self): def process_lidar_data(self, lidar_data, frame_num): with self.data_lock: - self.frame_counter = frame_num + try: + self.frame_counter = frame_num - points = self._carla_lidar_to_numpy(lidar_data) + points = self._carla_lidar_to_numpy(lidar_data) - if points is None or points.shape[0] == 0: - return None + if points is None or points.shape[0] == 0: + print(f"警告: LiDAR数据为空或无效 (帧 {frame_num})") + return None - # 下采样以减少内存使用 - if self.enable_downsampling and points.shape[0] > self.max_points_per_frame: - points = self._downsample_point_cloud(points) + # 检查内存使用情况 + if self._check_memory_usage(): + print(f"警告: 内存使用过高,跳过LiDAR处理 (帧 {frame_num})") + return None - # 添加到批处理 - self.point_cloud_batch.append((frame_num, points)) + # 下采样以减少内存使用 + original_count = points.shape[0] + if self.enable_downsampling and points.shape[0] > self.max_points_per_frame: + points = self._downsample_point_cloud(points) + print(f"LiDAR下采样: {original_count} -> {points.shape[0]} 点 (帧 {frame_num})") - # 如果达到批处理大小,保存批处理数据 - if len(self.point_cloud_batch) >= self.batch_size: - self._save_batch() + # 检查批次内存使用 + batch_memory_mb = self._estimate_batch_memory_mb() + if batch_memory_mb > self.max_batch_memory_mb: + print(f"批处理内存过高 ({batch_memory_mb:.1f}MB),强制保存当前批次") + self._save_batch() + + # 添加到批处理 + self.point_cloud_batch.append((frame_num, points)) + + # 如果达到批处理大小,保存批处理数据 + if len(self.point_cloud_batch) >= self.batch_size: + self._save_batch() + + # 保存单个文件(向后兼容) + bin_path = self._save_as_bin(points, frame_num) + npy_path = self._save_as_npy(points, frame_num) + + # 生成V2XFormer兼容格式 - 优化点:每v2x_save_interval帧保存一次 + v2xformer_path = None + if frame_num % self.v2x_save_interval == 0: + try: + v2xformer_path = self._save_as_v2xformer_format(points, frame_num) + except Exception as e: + print(f"警告: V2XFormer格式保存失败: {e}") + v2xformer_path = None - # 保存单个文件(向后兼容) - bin_path = self._save_as_bin(points, frame_num) - npy_path = self._save_as_npy(points, frame_num) + metadata = self._generate_metadata(points, bin_path, npy_path, v2xformer_path) - # 生成V2XFormer兼容格式 - v2xformer_path = self._save_as_v2xformer_format(points, frame_num) + # 定期清理内存 + if frame_num % 20 == 0: + gc.collect() - metadata = self._generate_metadata(points, bin_path, npy_path, v2xformer_path) + return metadata - return metadata + except Exception as e: + print(f"处理LiDAR数据失败: {e}") + # 强制清理内存 + gc.collect() + return None def _carla_lidar_to_numpy(self, lidar_data): """高效转换LiDAR数据""" @@ -146,6 +182,33 @@ def _downsample_point_cloud(self, points): return downsampled + def _estimate_batch_memory_mb(self): + """估计批处理内存使用""" + if not self.point_cloud_batch: + return 0 + + total_points = 0 + for _, points in self.point_cloud_batch: + total_points += points.shape[0] + + # 每个点3个float32 (12字节),加上一些额外开销 + memory_bytes = total_points * 12 * 1.2 # 20%额外开销 + return memory_bytes / (1024 * 1024) + + def _check_memory_usage(self): + """检查内存使用情况""" + try: + import psutil + process = psutil.Process() + memory_mb = process.memory_info().rss / (1024 * 1024) + + if memory_mb > self.memory_warning_threshold: + print(f"警告: 进程内存使用过高: {memory_mb:.1f}MB") + return True + return False + except: + return False + def _save_batch(self): """批量保存点云数据(提高效率)""" if not self.point_cloud_batch: @@ -153,6 +216,7 @@ def _save_batch(self): batch_data = [] for frame_num, points in self.point_cloud_batch: + # 只保存点的坐标,不保存完整对象 batch_data.append({ 'frame_num': frame_num, 'points': points.tolist(), @@ -167,15 +231,28 @@ def _save_batch(self): # 压缩保存 if self.enable_compression: - compressed_data = zlib.compress( - json.dumps(batch_data).encode('utf-8'), - level=self.compression_level - ) - with open(batch_path, 'wb') as f: - f.write(compressed_data) + try: + json_str = json.dumps(batch_data) + compressed_data = zlib.compress( + json_str.encode('utf-8'), + level=self.compression_level + ) + with open(batch_path, 'wb') as f: + f.write(compressed_data) + except Exception as e: + print(f"压缩保存批处理数据失败: {e}") + # 尝试非压缩保存 + try: + with open(batch_path, 'w') as f: + json.dump(batch_data, f) + except Exception as e2: + print(f"非压缩保存批处理数据失败: {e2}") else: - with open(batch_path, 'w') as f: - json.dump(batch_data, f) + try: + with open(batch_path, 'w') as f: + json.dump(batch_data, f) + except Exception as e: + print(f"保存批处理数据失败: {e}") # 清理批处理缓存 self.point_cloud_batch.clear() @@ -192,7 +269,11 @@ def _save_as_bin(self, points, frame_num): points_with_intensity[:, :3] = points points_with_intensity[:, 3] = 1.0 # 强度值 - points_with_intensity.tofile(bin_path) + try: + points_with_intensity.tofile(bin_path) + except Exception as e: + print(f"保存BIN文件失败: {e}") + return None return bin_path @@ -200,20 +281,29 @@ def _save_as_npy(self, points, frame_num): """保存为numpy格式""" npy_path = os.path.join(self.lidar_dir, f"lidar_{frame_num:06d}.npy") - # 使用内存映射减少内存使用 - memmap_array = np.lib.format.open_memmap( - npy_path, - mode='w+', - dtype=np.float32, - shape=points.shape - ) - memmap_array[:] = points[:] - memmap_array.flush() + try: + # 使用内存映射减少内存使用 + memmap_array = np.lib.format.open_memmap( + npy_path, + mode='w+', + dtype=np.float32, + shape=points.shape + ) + memmap_array[:] = points[:] + memmap_array.flush() + except Exception as e: + print(f"保存NPY文件失败: {e}") + # 备选保存方式 + try: + np.save(npy_path, points) + except Exception as e2: + print(f"备选保存NPY文件失败: {e2}") + return None return npy_path def _save_as_v2xformer_format(self, points, frame_num): - """保存为V2XFormer兼容格式""" + """保存为V2XFormer兼容格式 - 优化点:减少保存频率""" v2x_dir = os.path.join(self.output_dir, "v2xformer_format") os.makedirs(v2x_dir, exist_ok=True) @@ -251,61 +341,18 @@ def _save_as_v2xformer_format(self, points, frame_num): return filepath except Exception as e: - # 如果保存失败,只打印警告,不抛出异常 print(f"警告: 保存V2XFormer格式失败: {e}") return None - def process_lidar_data(self, lidar_data, frame_num): - with self.data_lock: - try: - self.frame_counter = frame_num - - points = self._carla_lidar_to_numpy(lidar_data) - - if points is None or points.shape[0] == 0: - print(f"警告: LiDAR数据为空或无效 (帧 {frame_num})") - return None - - # 下采样以减少内存使用 - if self.enable_downsampling and points.shape[0] > self.max_points_per_frame: - original_count = points.shape[0] - points = self._downsample_point_cloud(points) - print(f"LiDAR下采样: {original_count} -> {points.shape[0]} 点 (帧 {frame_num})") - - # 添加到批处理 - self.point_cloud_batch.append((frame_num, points)) - - # 如果达到批处理大小,保存批处理数据 - if len(self.point_cloud_batch) >= self.batch_size: - self._save_batch() - - # 保存单个文件(向后兼容) - bin_path = self._save_as_bin(points, frame_num) - npy_path = self._save_as_npy(points, frame_num) - - # 生成V2XFormer兼容格式(如果失败则忽略) - try: - v2xformer_path = self._save_as_v2xformer_format(points, frame_num) - except Exception as e: - v2xformer_path = None - print(f"警告: V2XFormer格式保存失败: {e}") - - metadata = self._generate_metadata(points, bin_path, npy_path, v2xformer_path) - - return metadata - - except Exception as e: - print(f"处理LiDAR数据失败: {e}") - return None def _generate_metadata(self, points, bin_path, npy_path, v2xformer_path): metadata = { 'frame_id': self.frame_counter, 'timestamp': datetime.now().isoformat(), 'point_count': int(points.shape[0]), 'file_paths': { - 'bin': os.path.basename(bin_path), - 'npy': os.path.basename(npy_path), - 'v2xformer': os.path.basename(v2xformer_path) + 'bin': os.path.basename(bin_path) if bin_path else None, + 'npy': os.path.basename(npy_path) if npy_path else None, + 'v2xformer': os.path.basename(v2xformer_path) if v2xformer_path else None }, 'statistics': { 'x_range': [float(points[:, 0].min()), float(points[:, 0].max())], @@ -318,13 +365,17 @@ def _generate_metadata(self, points, bin_path, npy_path, v2xformer_path): 'downsampled': self.enable_downsampling and points.shape[0] > self.max_points_per_frame, 'downsample_ratio': self.downsample_ratio if self.enable_downsampling else 1.0, 'compression_enabled': self.enable_compression, - 'compression_level': self.compression_level + 'compression_level': self.compression_level, + 'v2x_saved': v2xformer_path is not None } } meta_path = os.path.join(self.lidar_dir, f"lidar_meta_{self.frame_counter:06d}.json") - with open(meta_path, 'w') as f: - json.dump(metadata, f, indent=2) + try: + with open(meta_path, 'w') as f: + json.dump(metadata, f, indent=2) + except Exception as e: + print(f"保存LiDAR元数据失败: {e}") return metadata @@ -349,18 +400,21 @@ def generate_lidar_summary(self): total_size = 0 # 采样统计,避免读取所有文件 - sample_files = bin_files[:10] if len(bin_files) > 10 else bin_files + sample_files = bin_files[:5] if len(bin_files) > 5 else bin_files # 减少采样数量 for bin_file in sample_files: if os.path.exists(bin_file): - file_size = os.path.getsize(bin_file) - total_size += file_size - # 估算点数 - points_in_file = file_size // (4 * 4) # 4个float32,每个4字节 - total_points += points_in_file + try: + file_size = os.path.getsize(bin_file) + total_size += file_size + # 估算点数 + points_in_file = file_size // (4 * 4) # 4个float32,每个4字节 + total_points += points_in_file + except: + continue # 根据采样估算总数 - if sample_files: - avg_points_per_file = total_points / len(sample_files) + if sample_files and len(bin_files) > 0: + avg_points_per_file = total_points / max(len(sample_files), 1) total_points_estimated = avg_points_per_file * len(bin_files) else: total_points_estimated = 0 @@ -382,8 +436,11 @@ def generate_lidar_summary(self): } summary_path = os.path.join(self.output_dir, "metadata", "lidar_summary.json") - with open(summary_path, 'w') as f: - json.dump(summary, f, indent=2) + try: + with open(summary_path, 'w') as f: + json.dump(summary, f, indent=2) + except Exception as e: + print(f"保存LiDAR摘要失败: {e}") return summary @@ -398,7 +455,7 @@ def __init__(self, output_dir, config=None): self.calibration_data = {} self.fusion_cache = {} # 融合缓存 - self.cache_size = config.get('fusion_cache_size', 100) if config else 100 + self.cache_size = config.get('fusion_cache_size', 50) if config else 50 # 减少缓存大小 self._load_calibration() @@ -413,32 +470,15 @@ def _load_calibration(self): if file.endswith('.json'): calibration_files.append(os.path.join(root, file)) - # 并行读取校准文件 - import concurrent.futures - with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor: - future_to_file = { - executor.submit(self._load_calibration_file, file): file - for file in calibration_files - } - - for future in concurrent.futures.as_completed(future_to_file): - file = future_to_file[future] - try: - sensor_name, data = future.result() - if sensor_name and data: - self.calibration_data[sensor_name] = data - except Exception as e: - print(f"加载校准文件 {file} 失败: {e}") - - def _load_calibration_file(self, filepath): - """加载单个校准文件""" - try: - with open(filepath, 'r') as f: - data = json.load(f) - sensor_name = os.path.basename(filepath).replace('.json', '') - return sensor_name, data - except: - return None, None + # 限制同时读取的文件数量 + for file in calibration_files[:10]: # 最多读取10个文件 + try: + with open(file, 'r') as f: + data = json.load(f) + sensor_name = os.path.basename(file).replace('.json', '') + self.calibration_data[sensor_name] = data + except Exception as e: + print(f"加载校准文件 {file} 失败: {e}") def create_synchronization_file(self, frame_num, sensor_data): """创建同步文件(优化版)""" @@ -521,7 +561,7 @@ def generate_fusion_report(self): # 解析文件名获取帧范围(避免读取所有文件) frame_ids = [] - for sync_file in sync_files: + for sync_file in sync_files[:20]: # 只检查前20个文件 try: # 从文件名提取帧号 filename = os.path.basename(sync_file) diff --git a/src/enhance_pedestrian_safety/main.py b/src/enhance_pedestrian_safety/main.py index 990a4d0db5..872899a477 100644 --- a/src/enhance_pedestrian_safety/main.py +++ b/src/enhance_pedestrian_safety/main.py @@ -250,12 +250,12 @@ def error(msg): @staticmethod def debug(msg): - timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + timestamp = datetime.now().strftime("%Y-%m-d %H:%M:%S") print(f"[DEBUG][{timestamp}] {msg}") @staticmethod def performance(msg): - timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + timestamp = datetime.now().strftime("%Y-%m-d %H:%M:%S") print(f"[PERF][{timestamp}] {msg}") @@ -663,43 +663,34 @@ def _spawn_pedestrians(self, center_location): return spawned def cleanup(self): - Log.info("清理交通...") - - # 批量销毁 - actors_to_destroy = [] + """安全清理所有资源""" + Log.info("开始清理交通管理器...") - for vehicle in self.vehicles: - if vehicle.is_alive: - actors_to_destroy.append(vehicle) + try: + # 清理行人 + for pedestrian in self.pedestrians: + try: + if pedestrian and pedestrian.is_alive: + pedestrian.destroy() + except: + pass - for pedestrian in self.pedestrians: - if pedestrian.is_alive: - actors_to_destroy.append(pedestrian) + self.pedestrians.clear() - # 批量销毁 - batch_size = 10 - for i in range(0, len(actors_to_destroy), batch_size): - batch = actors_to_destroy[i:i + batch_size] - for actor in batch: + # 清理背景车辆 + for vehicle in self.vehicles: try: - actor.destroy() + if vehicle and vehicle.is_alive: + vehicle.destroy() except: pass - # 避免过快的销毁速度 - if i > 0 and i % 30 == 0: - time.sleep(0.1) + self.vehicles.clear() - self.vehicles.clear() - self.pedestrians.clear() + Log.info("交通管理器清理完成") - # 清理统计 - self.spawn_stats = { - 'total_attempts': 0, - 'successful_spawns': 0, - 'failed_spawns': 0, - 'total_spawn_time': 0 - } + except Exception as e: + Log.error(f"清理交通管理器失败: {e}") class SensorManager: @@ -713,6 +704,13 @@ def __init__(self, world, config, data_dir): self.last_capture_time = 0 self.last_performance_sample = 0 + # 新增:帧率控制相关变量 + self.target_fps = config['performance'].get('frame_rate_limit', 5.0) + self.min_frame_interval = 1.0 / self.target_fps if self.target_fps > 0 else 0.1 + self.last_frame_time = 0 + self.frame_skip_count = 0 + self.max_frame_skip = 2 + self.vehicle_buffer = {} self.infra_buffer = {} self.buffer_lock = threading.Lock() @@ -729,14 +727,14 @@ def __init__(self, world, config, data_dir): self.fusion_manager = None # 批处理设置 - self.batch_size = config.get('batch_size', 5) + self.batch_size = config['performance'].get('batch_size', 5) self.enable_async_processing = config.get('enable_async_processing', True) if config['sensors'].get('lidar_sensors', 0) > 0: - self.lidar_processor = LidarProcessor(data_dir, config.get('lidar_processing', {})) + self.lidar_processor = LidarProcessor(data_dir, config['performance'].get('lidar_processing', {})) if config['output'].get('save_fusion', False): - self.fusion_manager = MultiSensorFusion(data_dir, config.get('fusion', {})) + self.fusion_manager = MultiSensorFusion(data_dir, config['performance'].get('fusion', {})) # 传感器统计 self.sensor_stats = { @@ -927,9 +925,24 @@ def callback(image): current_time = time.time() frame_start_time = time.time() + # 优化点2:添加帧率控制逻辑 + # 检查是否达到最小帧间隔 + time_since_last_frame = current_time - self.last_frame_time + if time_since_last_frame < self.min_frame_interval: + # 如果帧间隔太小,跳过此帧 + self.frame_skip_count += 1 + if self.frame_skip_count > self.max_frame_skip: + # 如果连续跳过多帧,强制处理一帧 + self.frame_skip_count = 0 + else: + return # 跳过此帧 + else: + self.frame_skip_count = 0 + if current_time - self.last_capture_time >= capture_interval: self.frame_counter += 1 self.last_capture_time = current_time + self.last_frame_time = current_time # 记录帧处理时间 capture_start = time.time() filename = os.path.join(save_dir, f"{name}_{self.frame_counter:06d}.png") @@ -943,13 +956,34 @@ def callback(image): if sensor_type == 'vehicle': self.vehicle_buffer[name] = filename if len(self.vehicle_buffer) >= 4: - self.image_processor.stitch(self.vehicle_buffer, self.frame_counter, - f'vehicle_{vehicle_id}') + # 使用线程池异步处理图像拼接,避免阻塞 + if self.enable_async_processing: + import concurrent.futures + with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor: + future = executor.submit( + self.image_processor.stitch, + self.vehicle_buffer.copy(), + self.frame_counter, + f'vehicle_{vehicle_id}' + ) + else: + self.image_processor.stitch(self.vehicle_buffer, self.frame_counter, + f'vehicle_{vehicle_id}') self.vehicle_buffer.clear() else: self.infra_buffer[name] = filename if len(self.infra_buffer) >= 4: - self.image_processor.stitch(self.infra_buffer, self.frame_counter, 'infrastructure') + if self.enable_async_processing: + import concurrent.futures + with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor: + future = executor.submit( + self.image_processor.stitch, + self.infra_buffer.copy(), + self.frame_counter, + 'infrastructure' + ) + else: + self.image_processor.stitch(self.infra_buffer, self.frame_counter, 'infrastructure') self.infra_buffer.clear() # 定期采样性能 @@ -1023,55 +1057,55 @@ def cleanup(self): """安全清理传感器""" Log.info(f"安全清理 {len(self.sensors)} 个传感器...") - # 设置运行状态为False,防止回调函数继续执行 + # 1. 首先设置运行状态为False,防止回调函数继续执行 self.is_running = False - # 等待一小段时间让回调函数结束 - time.sleep(0.1) + # 2. 停止所有传感器的监听 + Log.info("停止所有传感器监听...") + for sensor in self.sensors: + try: + if hasattr(sensor, 'stop'): + sensor.stop() + time.sleep(0.001) # 短暂延迟 + except: + pass + + # 3. 等待一小段时间让所有传感器回调结束 + time.sleep(0.2) - # 刷新批处理数据 + # 4. 刷新批处理数据 if self.lidar_processor: try: self.lidar_processor.flush_batch() - except Exception as e: - Log.warning(f"刷新LiDAR批处理数据失败: {e}") - - # 批量销毁传感器(从后向前销毁,避免依赖问题) - if self.sensors: - Log.info(f"销毁 {len(self.sensors)} 个传感器...") - - # 首先停止所有传感器的监听 - for sensor in self.sensors: - try: - if sensor and sensor.is_alive: - sensor.stop() - except Exception as e: - Log.debug(f"停止传感器失败: {e}") - - # 等待一小段时间让传感器完全停止 - time.sleep(0.05) - - # 然后销毁传感器 - for i, sensor in enumerate(reversed(self.sensors)): - try: - if sensor and sensor.is_alive: - sensor.destroy() - except Exception as e: - Log.debug(f"销毁传感器失败: {e}") + except: + pass - # 分批销毁,避免一次性销毁太多 - if i % 5 == 0: - time.sleep(0.01) + # 5. 销毁传感器 + Log.info(f"销毁 {len(self.sensors)} 个传感器...") + for i, sensor in enumerate(self.sensors): + try: + if hasattr(sensor, 'destroy'): + sensor.destroy() + except: + pass + # 分批销毁,避免一次性销毁太多 + if i % 5 == 0: + time.sleep(0.01) self.sensors.clear() - # 清理缓存 + # 6. 清理缓存和内存 if hasattr(self.image_processor, 'image_cache'): try: self.image_processor.image_cache.clear() except: pass + # 7. 清理数据结构 + self.vehicle_buffer.clear() + self.infra_buffer.clear() + + # 8. 强制垃圾回收 gc.collect() Log.info("传感器清理完成") @@ -1251,23 +1285,58 @@ def collect_data(self): last_perception_share = time.time() last_performance_sample = time.time() last_detailed_log = time.time() + last_memory_check = time.time() + + # 内存监控变量 + memory_warning_issued = False + forced_cleanup_count = 0 try: while time.time() - self.start_time < duration and self.is_running: current_time = time.time() elapsed = current_time - self.start_time + # 内存检查 - 优化点4:定期检查内存使用 + if current_time - last_memory_check >= 2.0: # 更频繁的内存检查 + try: + import psutil + process = psutil.Process() + memory_mb = process.memory_info().rss / (1024 * 1024) + + if memory_mb > 350 and not memory_warning_issued: + Log.warning(f"内存使用较高: {memory_mb:.1f}MB,将减少数据处理") + memory_warning_issued = True + + # 如果内存过高,跳过一些非关键操作 + if self.v2x_communication: + Log.debug("内存警告:暂停部分V2X消息处理") + + elif memory_mb > 400: # 如果内存超过400MB,提前结束 + Log.error(f"内存使用过高: {memory_mb:.1f}MB,提前结束数据收集") + break + + elif memory_mb < 300: + memory_warning_issued = False + forced_cleanup_count = 0 + + except Exception as e: + Log.debug(f"内存检查失败: {e}") + + last_memory_check = current_time + # 更新车辆状态 if self.multi_vehicle_manager: self.multi_vehicle_manager.update_vehicle_states() - # V2X通信更新 - if self.v2x_communication and current_time - last_v2x_update >= 0.1: + # V2X通信更新 - 使用配置中的更新间隔 + v2x_interval = self.config['v2x'].get('update_interval', 2.0) + if self.v2x_communication and current_time - last_v2x_update >= v2x_interval: self._update_v2x_communication() last_v2x_update = current_time - # 共享感知数据 - if (self.config['cooperative'].get('enable_shared_perception', True) and + # 共享感知数据 - 仅在内存允许时进行 + if (not memory_warning_issued and + self.config['cooperative'].get('enable_shared_perception', True) and current_time - last_perception_share >= 2.0): self._share_perception_data() last_perception_share = current_time @@ -1348,6 +1417,133 @@ def collect_data(self): if self.output_format != 'standard': self._convert_to_target_format() + def _force_memory_cleanup(self): + """强制内存清理""" + Log.info("执行强制内存清理...") + + # 清理图像处理器缓存 + for sensor_manager in self.sensor_managers.values(): + if hasattr(sensor_manager, 'image_processor'): + if hasattr(sensor_manager.image_processor, 'image_cache'): + try: + old_size = len(sensor_manager.image_processor.image_cache) + sensor_manager.image_processor.image_cache.clear() + Log.debug(f"清理图像缓存: 释放了{old_size}个缓存项") + except: + pass + + # 清理LiDAR批处理 + for sensor_manager in self.sensor_managers.values(): + if hasattr(sensor_manager, 'lidar_processor'): + try: + sensor_manager.lidar_processor.flush_batch() + Log.debug("刷新LiDAR批处理数据") + except: + pass + + # 强制垃圾回收 + gc.collect() + + Log.info("强制内存清理完成") + + def cleanup(self): + """安全清理所有资源 - 完全重写的版本""" + Log.info("开始安全清理场景...") + + cleanup_start = time.time() + + try: + # 1. 设置运行状态为False + self.is_running = False + + # 2. 等待一小段时间让循环结束 + time.sleep(0.2) + + # 3. 停止V2X通信 + if self.v2x_communication: + try: + Log.info("停止V2X通信...") + self.v2x_communication.stop() + except: + pass + + # 4. 清理传感器(最重要,必须先于车辆) + Log.info(f"清理 {len(self.sensor_managers)} 个传感器管理器...") + for vehicle_id, sensor_manager in self.sensor_managers.items(): + try: + sensor_manager.cleanup() + except: + pass + + self.sensor_managers.clear() + time.sleep(0.1) # 等待传感器清理完成 + + # 5. 清理多车辆管理器 + if self.multi_vehicle_manager: + try: + Log.info("清理多车辆管理器...") + self.multi_vehicle_manager.cleanup() + except: + pass + + # 6. 清理背景交通 + if self.traffic_manager: + try: + Log.info("清理交通管理器...") + self.traffic_manager.cleanup() + except: + pass + + # 7. 清理主车(最后清理) + Log.info(f"清理 {len(self.ego_vehicles)} 个主车...") + for vehicle in self.ego_vehicles: + try: + if hasattr(vehicle, 'destroy'): + vehicle.destroy() + except: + pass + time.sleep(0.01) # 短暂延迟 + + self.ego_vehicles.clear() + + # 8. 重置世界设置 + try: + if self.world: + default_weather = carla.WeatherParameters() + self.world.set_weather(default_weather) + except: + pass + + # 9. 强制垃圾回收 + gc.collect() + + # 10. 等待确保清理完成 + time.sleep(0.3) + + # 11. 释放引用 + self.traffic_manager = None + self.multi_vehicle_manager = None + self.v2x_communication = None + self.scene_center = None + + # 最后一次强制垃圾回收 + gc.collect() + + Log.info(f"清理完成,总用时: {time.time() - cleanup_start:.2f}秒") + + except Exception as e: + Log.error(f"清理过程中发生错误: {e}") + # 紧急清理:强制释放所有引用 + try: + self.sensor_managers.clear() + self.ego_vehicles.clear() + self.traffic_manager = None + self.multi_vehicle_manager = None + self.v2x_communication = None + gc.collect() + except: + pass + def _convert_to_target_format(self): """转换为目标数据格式""" Log.info(f"转换为 {self.output_format} 格式...") @@ -1443,7 +1639,7 @@ def _update_v2x_communication(self): # 为每辆车发送基本安全消息 for vehicle in self.ego_vehicles + self.multi_vehicle_manager.cooperative_vehicles: - if not vehicle.is_alive: + if not hasattr(vehicle, 'is_alive') or not vehicle.is_alive: continue try: @@ -1478,7 +1674,7 @@ def _share_perception_data(self): # 模拟车辆感知数据(简化处理,实际应从传感器获取) for vehicle in self.ego_vehicles + self.multi_vehicle_manager.cooperative_vehicles: - if not vehicle.is_alive: + if not hasattr(vehicle, 'is_alive') or not vehicle.is_alive: continue # 模拟检测到的物体 @@ -1493,7 +1689,7 @@ def _simulate_object_detection(self, vehicle): # 获取车辆周围的其他车辆 for other_vehicle in self.ego_vehicles + self.multi_vehicle_manager.cooperative_vehicles: - if other_vehicle.id == vehicle.id or not other_vehicle.is_alive: + if other_vehicle.id == vehicle.id or not hasattr(other_vehicle, 'is_alive') or not other_vehicle.is_alive: continue try: @@ -1627,91 +1823,6 @@ def run_analysis(self): Log.info("运行数据分析...") DataAnalyzer.analyze_dataset(self.output_dir) - def cleanup(self): - """安全清理所有资源 - 修复的方法""" - Log.info("开始安全清理场景...") - - try: - # 1. 先停止数据收集 - self.is_running = False - - # 2. 等待一小段时间让所有传感器回调结束 - time.sleep(0.2) - - # 3. 清理传感器(这是最重要的,必须在清理车辆之前) - for sensor_manager in self.sensor_managers.values(): - try: - sensor_manager.cleanup() - except Exception as e: - Log.error(f"清理传感器管理器失败: {e}") - - self.sensor_managers.clear() - - # 4. 清理V2X通信 - if self.v2x_communication: - try: - self.v2x_communication.stop() - except Exception as e: - Log.error(f"停止V2X通信失败: {e}") - - # 5. 清理协同管理 - if self.multi_vehicle_manager: - try: - self.multi_vehicle_manager.cleanup() - except Exception as e: - Log.error(f"清理协同管理器失败: {e}") - - # 6. 清理背景交通(必须在清理主车之前) - if self.traffic_manager: - try: - self.traffic_manager.cleanup() - except Exception as e: - Log.error(f"清理交通管理器失败: {e}") - - # 7. 最后清理主车(在传感器和交通清理完之后) - for vehicle in self.ego_vehicles: - try: - if vehicle and vehicle.is_alive: - vehicle.destroy() - except Exception as e: - Log.debug(f"销毁主车失败: {e}") - - self.ego_vehicles.clear() - - # 8. 强制垃圾回收 - gc.collect() - - # 9. 重置CARLA世界(可选,但可以帮助清理) - try: - if self.world: - # 重置天气到默认 - default_weather = carla.WeatherParameters() - self.world.set_weather(default_weather) - except Exception as e: - Log.debug(f"重置世界设置失败: {e}") - - # 10. 等待一段时间确保所有清理完成 - time.sleep(0.5) - - Log.info("清理完成") - - except Exception as e: - Log.error(f"清理过程中发生错误: {e}") - import traceback - traceback.print_exc() - - # 即使出错也要尝试强制清理 - try: - # 强制删除所有引用,让Python垃圾回收器工作 - del self.sensor_managers - del self.traffic_manager - del self.multi_vehicle_manager - del self.v2x_communication - del self.ego_vehicles - gc.collect() - except: - pass - def main(): parser = argparse.ArgumentParser(description='CVIPS 性能优化数据收集系统 v12.0') From 6023f24ff279145fae1a49495a148f38326a1186 Mon Sep 17 00:00:00 2001 From: Z-w-7799 <1011069369@qq.com> Date: Sun, 21 Dec 2025 09:04:16 +0800 Subject: [PATCH 09/20] =?UTF-8?q?=E4=BC=98=E5=8C=96=20data=5Fanalyzer.py?= =?UTF-8?q?=20-=20=E6=B7=BB=E5=8A=A0=E6=9B=B4=E8=AF=A6=E7=BB=86=E7=9A=84?= =?UTF-8?q?=E5=88=86=E6=9E=90=E5=92=8C=E7=BC=93=E5=AD=98=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data_analyzer.py | 950 ++++++++++++++---- 1 file changed, 757 insertions(+), 193 deletions(-) diff --git a/src/enhance_pedestrian_safety/data_analyzer.py b/src/enhance_pedestrian_safety/data_analyzer.py index 069d6d2ec7..14690041fb 100644 --- a/src/enhance_pedestrian_safety/data_analyzer.py +++ b/src/enhance_pedestrian_safety/data_analyzer.py @@ -2,16 +2,83 @@ import json import numpy as np from collections import defaultdict +import hashlib +import pickle +import time class DataAnalyzer: - """数据分析器 - 生成数据集统计信息""" + """数据分析器 - 生成数据集统计信息(优化版)""" + + # 添加缓存机制 + _cache_dir = ".analysis_cache" + _cache_enabled = True + + @staticmethod + def _get_cache_key(data_dir): + """生成缓存键""" + # 使用目录结构和文件修改时间生成哈希 + file_times = [] + for root, dirs, files in os.walk(data_dir): + for file in files: + file_path = os.path.join(root, file) + try: + file_times.append(str(os.path.getmtime(file_path))) + except: + pass + + content = data_dir + "".join(sorted(file_times)) + return hashlib.md5(content.encode()).hexdigest() + + @staticmethod + def _load_from_cache(cache_key): + """从缓存加载""" + if not DataAnalyzer._cache_enabled: + return None + + cache_file = os.path.join(DataAnalyzer._cache_dir, f"{cache_key}.pkl") + if os.path.exists(cache_file): + try: + with open(cache_file, 'rb') as f: + cached_time, analysis = pickle.load(f) + # 缓存有效期1小时 + if time.time() - cached_time < 3600: + print( + f"使用缓存分析结果 (缓存时间: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(cached_time))})") + return analysis + except: + pass + return None + + @staticmethod + def _save_to_cache(cache_key, analysis): + """保存到缓存""" + if not DataAnalyzer._cache_enabled: + return + + os.makedirs(DataAnalyzer._cache_dir, exist_ok=True) + cache_file = os.path.join(DataAnalyzer._cache_dir, f"{cache_key}.pkl") + try: + with open(cache_file, 'wb') as f: + pickle.dump((time.time(), analysis), f) + except: + pass @staticmethod - def analyze_dataset(data_dir): - """分析数据集并生成详细报告""" + def analyze_dataset(data_dir, force_refresh=False): + """分析数据集并生成详细报告(带缓存)""" print(f"分析数据集: {data_dir}") + # 检查缓存 + cache_key = DataAnalyzer._get_cache_key(data_dir) + if not force_refresh: + cached = DataAnalyzer._load_from_cache(cache_key) + if cached: + return cached + + analysis_start = time.time() + + # 并行执行分析任务 analysis = { 'basic_stats': DataAnalyzer._get_basic_stats(data_dir), 'file_distribution': DataAnalyzer._analyze_file_distribution(data_dir), @@ -24,9 +91,19 @@ def analyze_dataset(data_dir): # 生成评分 analysis['overall_score'] = DataAnalyzer._calculate_overall_score(analysis) + # 添加分析元数据 + analysis['metadata'] = { + 'analysis_time': time.strftime('%Y-%m-%d %H:%M:%S'), + 'analysis_duration': round(time.time() - analysis_start, 2), + 'cache_key': cache_key + } + # 保存分析结果 DataAnalyzer._save_analysis_report(data_dir, analysis) + # 保存到缓存 + DataAnalyzer._save_to_cache(cache_key, analysis) + # 打印摘要 DataAnalyzer._print_analysis_summary(analysis) @@ -34,115 +111,271 @@ def analyze_dataset(data_dir): @staticmethod def _get_basic_stats(data_dir): - """获取基本统计信息""" + """获取基本统计信息(优化版)""" stats = { - 'total_size_mb': DataAnalyzer._get_directory_size(data_dir), + 'total_size_mb': 0, 'file_count': 0, 'directory_count': 0, - 'data_types': defaultdict(int) + 'data_types': defaultdict(int), + 'largest_files': [], + 'oldest_newest_files': {} } + file_sizes = [] + file_times = [] + for root, dirs, files in os.walk(data_dir): stats['directory_count'] += len(dirs) stats['file_count'] += len(files) for file in files: - ext = os.path.splitext(file)[1].lower() - if ext in ['.png', '.jpg', '.jpeg']: - stats['data_types']['images'] += 1 - elif ext == '.json': - stats['data_types']['json'] += 1 - elif ext == '.txt': - stats['data_types']['text'] += 1 - elif ext == '.bin': - stats['data_types']['binary'] += 1 - else: - stats['data_types']['other'] += 1 + file_path = os.path.join(root, file) + try: + # 文件大小 + file_size = os.path.getsize(file_path) + stats['total_size_mb'] += file_size + + # 文件修改时间 + mtime = os.path.getmtime(file_path) + file_times.append((file_path, mtime)) + + # 记录大文件 + if file_size > 10 * 1024 * 1024: # 10MB以上 + file_sizes.append((file_path, file_size)) + + # 文件类型统计 + ext = os.path.splitext(file)[1].lower() + if ext in ['.png', '.jpg', '.jpeg', '.bmp', '.tiff']: + stats['data_types']['images'] += 1 + elif ext == '.json': + stats['data_types']['json'] += 1 + elif ext in ['.txt', '.csv', '.log']: + stats['data_types']['text'] += 1 + elif ext in ['.bin', '.pcd']: + stats['data_types']['binary'] += 1 + elif ext in ['.pkl', '.pickle']: + stats['data_types']['pickle'] += 1 + elif ext == '.gz': + stats['data_types']['compressed'] += 1 + else: + stats['data_types']['other'] += 1 + + except Exception as e: + print(f"处理文件 {file_path} 失败: {e}") + + # 转换为MB + stats['total_size_mb'] = round(stats['total_size_mb'] / (1024 * 1024), 2) + + # 找出最大的5个文件 + file_sizes.sort(key=lambda x: x[1], reverse=True) + stats['largest_files'] = [ + {'path': os.path.relpath(path, data_dir), 'size_mb': round(size / (1024 * 1024), 2)} + for path, size in file_sizes[:5] + ] + + # 找出最旧和最新的文件 + if file_times: + file_times.sort(key=lambda x: x[1]) + oldest = file_times[0] + newest = file_times[-1] + stats['oldest_newest_files'] = { + 'oldest': { + 'path': os.path.relpath(oldest[0], data_dir), + 'time': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(oldest[1])) + }, + 'newest': { + 'path': os.path.relpath(newest[0], data_dir), + 'time': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(newest[1])) + } + } return stats @staticmethod - def _get_directory_size(path): - """计算目录大小""" - total = 0 - for dirpath, dirnames, filenames in os.walk(path): - for f in filenames: - fp = os.path.join(dirpath, f) - if os.path.exists(fp): - total += os.path.getsize(fp) - return round(total / (1024 * 1024), 2) + def _analyze_file_distribution(data_dir): + """分析文件分布(优化版)""" + distribution = {} + + # 快速扫描目录结构 + if os.path.exists(data_dir): + # 使用os.scandir提高效率 + with os.scandir(data_dir) as entries: + for entry in entries: + if entry.is_dir(): + dir_name = entry.name + if dir_name == "raw": + distribution.update(DataAnalyzer._analyze_raw_data(data_dir)) + elif dir_name == "stitched": + stitched_dir = os.path.join(data_dir, "stitched") + if os.path.exists(stitched_dir): + stitched_images = [f for f in os.listdir(stitched_dir) + if f.lower().endswith(('.jpg', '.jpeg', '.png'))] + distribution['stitched'] = { + 'total': len(stitched_images), + 'formats': defaultdict(int) + } + for img in stitched_images: + ext = os.path.splitext(img)[1].lower() + distribution['stitched']['formats'][ext] += 1 + elif dir_name == "annotations": + annotations_dir = os.path.join(data_dir, "annotations") + if os.path.exists(annotations_dir): + json_files = [f for f in os.listdir(annotations_dir) + if f.lower().endswith('.json')] + distribution['annotations'] = len(json_files) + elif dir_name == "lidar": + lidar_dir = os.path.join(data_dir, "lidar") + if os.path.exists(lidar_dir): + distribution['lidar'] = DataAnalyzer._analyze_lidar_data(lidar_dir) + elif dir_name == "fusion": + fusion_dir = os.path.join(data_dir, "fusion") + if os.path.exists(fusion_dir): + distribution['fusion'] = DataAnalyzer._analyze_fusion_data(fusion_dir) + + return distribution @staticmethod - def _analyze_file_distribution(data_dir): - """分析文件分布""" + def _analyze_raw_data(data_dir): + """分析原始数据""" distribution = {} + raw_path = os.path.join(data_dir, "raw") + + if not os.path.exists(raw_path): + return distribution # 分析原始图像 - raw_dirs = [] - raw_path = os.path.join(data_dir, "raw") - if os.path.exists(raw_path): - raw_dirs = [d for d in os.listdir(raw_path) if os.path.isdir(os.path.join(raw_path, d))] - - for raw_dir in raw_dirs: - path = os.path.join(data_dir, "raw", raw_dir) - camera_stats = {} - for camera_dir in os.listdir(path): - camera_path = os.path.join(path, camera_dir) - if os.path.isdir(camera_path): - images = [f for f in os.listdir(camera_path) - if f.endswith(('.png', '.jpg', '.jpeg'))] - camera_stats[camera_dir] = len(images) - distribution[f'raw_{raw_dir}'] = camera_stats - - # 分析拼接图像 - stitched_dir = os.path.join(data_dir, "stitched") - if os.path.exists(stitched_dir): - stitched_images = [f for f in os.listdir(stitched_dir) - if f.endswith(('.jpg', '.jpeg', '.png'))] - distribution['stitched'] = len(stitched_images) - - # 分析标注文件 - annotations_dir = os.path.join(data_dir, "annotations") - if os.path.exists(annotations_dir): - json_files = [f for f in os.listdir(annotations_dir) if f.endswith('.json')] - distribution['annotations'] = len(json_files) - - # 分析LiDAR数据 - lidar_dir = os.path.join(data_dir, "lidar") - if os.path.exists(lidar_dir): - bin_files = [f for f in os.listdir(lidar_dir) if f.endswith('.bin')] - npy_files = [f for f in os.listdir(lidar_dir) if f.endswith('.npy')] - distribution['lidar'] = {'bin': len(bin_files), 'npy': len(npy_files)} - - # 分析融合数据 - fusion_dir = os.path.join(data_dir, "fusion") - if os.path.exists(fusion_dir): - json_files = [f for f in os.listdir(fusion_dir) if f.endswith('.json')] - distribution['fusion'] = len(json_files) + for raw_dir in os.listdir(raw_path): + full_path = os.path.join(raw_path, raw_dir) + if os.path.isdir(full_path): + camera_stats = {} + total_images = 0 + + for camera_dir in os.listdir(full_path): + camera_path = os.path.join(full_path, camera_dir) + if os.path.isdir(camera_path): + images = [f for f in os.listdir(camera_path) + if f.lower().endswith(('.png', '.jpg', '.jpeg'))] + camera_stats[camera_dir] = len(images) + total_images += len(images) + + distribution[f'raw_{raw_dir}'] = { + 'cameras': camera_stats, + 'total_images': total_images, + 'camera_count': len(camera_stats) + } return distribution + @staticmethod + def _analyze_lidar_data(lidar_dir): + """分析LiDAR数据""" + lidar_stats = { + 'bin': 0, + 'npy': 0, + 'json': 0, + 'batch': 0, + 'pcd': 0, + 'total_size_mb': 0 + } + + total_size = 0 + for root, dirs, files in os.walk(lidar_dir): + for file in files: + file_path = os.path.join(root, file) + ext = os.path.splitext(file)[1].lower() + + try: + file_size = os.path.getsize(file_path) + total_size += file_size + + if ext == '.bin': + lidar_stats['bin'] += 1 + elif ext == '.npy': + lidar_stats['npy'] += 1 + elif ext == '.json': + lidar_stats['json'] += 1 + elif 'batch' in file: + lidar_stats['batch'] += 1 + elif ext == '.pcd': + lidar_stats['pcd'] += 1 + except: + pass + + lidar_stats['total_size_mb'] = round(total_size / (1024 * 1024), 2) + return lidar_stats + + @staticmethod + def _analyze_fusion_data(fusion_dir): + """分析融合数据""" + fusion_stats = { + 'sync_files': 0, + 'calibration_files': 0, + 'total_size_mb': 0, + 'formats': defaultdict(int) + } + + total_size = 0 + for root, dirs, files in os.walk(fusion_dir): + for file in files: + file_path = os.path.join(root, file) + ext = os.path.splitext(file)[1].lower() + + try: + file_size = os.path.getsize(file_path) + total_size += file_size + fusion_stats['formats'][ext] += 1 + + if 'sync' in file: + fusion_stats['sync_files'] += 1 + elif 'calib' in file or 'intrinsic' in file or 'extrinsic' in file: + fusion_stats['calibration_files'] += 1 + except: + pass + + fusion_stats['total_size_mb'] = round(total_size / (1024 * 1024), 2) + return fusion_stats + @staticmethod def _analyze_objects(data_dir): - """分析物体统计""" + """分析物体统计(优化版)""" annotations_dir = os.path.join(data_dir, "annotations") if not os.path.exists(annotations_dir): - return {'total_objects': 0, 'by_class': {}, 'by_frame': {}, 'class_distribution': {}} + return { + 'total_objects': 0, + 'by_class': {}, + 'by_frame': {}, + 'class_distribution': {}, + 'object_density': 0, + 'frames_with_objects': 0 + } object_stats = { 'total_objects': 0, 'by_class': defaultdict(int), 'by_frame': defaultdict(int), - 'class_distribution': {} + 'class_distribution': {}, + 'object_density': 0, + 'frames_with_objects': 0, + 'objects_per_frame_stats': {}, + 'class_combinations': set() } json_files = [f for f in os.listdir(annotations_dir) - if f.endswith('.json') and f.startswith('frame_')] + if f.lower().endswith('.json') and f.startswith('frame_')] - for json_file in json_files: + if not json_files: + return object_stats + + # 采样分析,避免处理所有文件 + sample_size = min(50, len(json_files)) + sample_files = random.sample(json_files, sample_size) if len(json_files) > 50 else json_files + + objects_per_frame = [] + + for json_file in sample_files: try: - with open(os.path.join(annotations_dir, json_file), 'r') as f: + with open(os.path.join(annotations_dir, json_file), 'r', encoding='utf-8') as f: data = json.load(f) frame_id = data.get('frame_id', 0) @@ -150,49 +383,131 @@ def _analyze_objects(data_dir): object_stats['by_frame'][frame_id] = len(objects) object_stats['total_objects'] += len(objects) + objects_per_frame.append(len(objects)) + + if objects: + object_stats['frames_with_objects'] += 1 + # 统计类别和组合 + frame_classes = set() for obj in objects: obj_class = obj.get('class', 'unknown') object_stats['by_class'][obj_class] += 1 + frame_classes.add(obj_class) + + if frame_classes: + object_stats['class_combinations'].add(tuple(sorted(frame_classes))) except Exception as e: print(f"分析标注文件 {json_file} 失败: {e}") + # 估算总数 + if sample_files: + avg_objects_per_file = object_stats['total_objects'] / len(sample_files) + object_stats['total_objects'] = int(avg_objects_per_file * len(json_files)) + object_stats['frames_with_objects'] = int( + (object_stats['frames_with_objects'] / len(sample_files)) * len(json_files)) + # 计算类分布百分比 if object_stats['total_objects'] > 0: + total = sum(object_stats['by_class'].values()) for obj_class, count in object_stats['by_class'].items(): object_stats['class_distribution'][obj_class] = round( - count / object_stats['total_objects'] * 100, 2) + count / total * 100, 2 + ) + + # 计算物体密度统计 + if objects_per_frame: + object_stats['objects_per_frame_stats'] = { + 'min': min(objects_per_frame), + 'max': max(objects_per_frame), + 'mean': round(np.mean(objects_per_frame), 2), + 'median': round(np.median(objects_per_frame), 2), + 'std': round(np.std(objects_per_frame), 2) + } + object_stats['object_density'] = round(np.mean(objects_per_frame), 2) + + # 转换组合为可序列化的列表 + object_stats['class_combinations'] = [ + list(combo) for combo in object_stats['class_combinations'] + ] return object_stats @staticmethod def _analyze_temporal(data_dir): - """分析时间分布""" + """分析时间分布(增强版)""" temporal_stats = { 'frame_intervals': [], 'total_duration': 0, - 'frame_rate': 0 + 'frame_rate': 0, + 'temporal_coverage': 0, + 'frame_consistency': 100, + 'time_range': {} } metadata_file = os.path.join(data_dir, "metadata", "collection_info.json") if os.path.exists(metadata_file): try: - with open(metadata_file, 'r') as f: + with open(metadata_file, 'r', encoding='utf-8') as f: metadata = json.load(f) collection_stats = metadata.get('collection_stats', {}) temporal_stats['total_duration'] = collection_stats.get('duration_seconds', 0) - temporal_stats['frame_rate'] = collection_stats.get('average_fps', 0) + temporal_stats['frame_rate'] = collection_stats.get('frame_rate', 0) + + # 计算时间覆盖 + if 'performance' in metadata: + perf = metadata['performance'] + if 'total_runtime' in perf and temporal_stats['total_duration'] > 0: + temporal_stats['temporal_coverage'] = round( + min(100, temporal_stats['total_duration'] / perf['total_runtime'] * 100), 1 + ) + + # 获取时间范围 + if 'collection' in metadata: + coll = metadata['collection'] + temporal_stats['time_range'] = { + 'start_time': coll.get('start_time', 'unknown'), + 'end_time': coll.get('end_time', 'unknown'), + 'duration_hours': round(coll.get('duration', 0) / 3600, 2) + } except Exception as e: print(f"分析元数据失败: {e}") + # 从文件时间推断时间范围 + try: + all_files = [] + for root, dirs, files in os.walk(data_dir): + for file in files: + if file.endswith(('.png', '.jpg', '.jpeg', '.json', '.bin')): + file_path = os.path.join(root, file) + try: + mtime = os.path.getmtime(file_path) + all_files.append((file_path, mtime)) + except: + pass + + if all_files: + all_files.sort(key=lambda x: x[1]) + oldest = all_files[0][1] + newest = all_files[-1][1] + + if not temporal_stats['time_range']: + temporal_stats['time_range'] = { + 'start_time': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(oldest)), + 'end_time': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(newest)), + 'duration_hours': round((newest - oldest) / 3600, 2) + } + except Exception as e: + print(f"分析文件时间失败: {e}") + return temporal_stats @staticmethod def _analyze_cooperative_data(data_dir): - """分析协同数据""" + """分析协同数据(增强版)""" coop_dir = os.path.join(data_dir, "cooperative") if not os.path.exists(coop_dir): @@ -200,95 +515,196 @@ def _analyze_cooperative_data(data_dir): 'v2x_messages': 0, 'shared_perception': 0, 'vehicles_count': 0, - 'communication_stats': {} + 'communication_stats': {}, + 'cooperation_level': 'none', + 'data_quality': {} + } + + analysis = { + 'v2x_messages': 0, + 'shared_perception_frames': 0, + 'total_vehicles': 0, + 'ego_vehicles': 0, + 'cooperative_vehicles': 0, + 'v2x_stats': { + 'total_messages': 0, + 'message_types': defaultdict(int), + 'average_message_size': 0, + 'message_frequency': 0 + }, + 'shared_objects_count': 0, + 'communication_range': 0, + 'collaborative_detections': 0, + 'cooperation_level': 'low', + 'data_quality': { + 'message_completeness': 0, + 'perception_consistency': 0, + 'temporal_alignment': 0 } + } # V2X消息统计 v2x_dir = os.path.join(coop_dir, "v2x_messages") v2x_files = [] if os.path.exists(v2x_dir): - v2x_files = [f for f in os.listdir(v2x_dir) if f.endswith('.json')] + v2x_files = [f for f in os.listdir(v2x_dir) if f.lower().endswith('.json')] + analysis['v2x_messages'] = len(v2x_files) # 共享感知统计 perception_dir = os.path.join(coop_dir, "shared_perception") perception_files = [] if os.path.exists(perception_dir): - perception_files = [f for f in os.listdir(perception_dir) if f.endswith('.json')] + perception_files = [f for f in os.listdir(perception_dir) if f.lower().endswith('.json')] + analysis['shared_perception_frames'] = len(perception_files) # 读取协同摘要 coop_summary = {} summary_file = os.path.join(coop_dir, "cooperative_summary.json") if os.path.exists(summary_file): try: - with open(summary_file, 'r') as f: + with open(summary_file, 'r', encoding='utf-8') as f: coop_summary = json.load(f) except: pass # 分析V2X消息内容 - v2x_stats = { - 'total_messages': len(v2x_files), - 'message_types': defaultdict(int), - 'average_message_size': 0 - } - if v2x_files: total_size = 0 - for v2x_file in v2x_files[:min(10, len(v2x_files))]: # 抽样分析 + valid_messages = 0 + sample_size = min(20, len(v2x_files)) + + for v2x_file in v2x_files[:sample_size]: try: - with open(os.path.join(v2x_dir, v2x_file), 'r') as f: + with open(os.path.join(v2x_dir, v2x_file), 'r', encoding='utf-8') as f: data = json.load(f) - message_type = data.get('message', {}).get('message_type', 'unknown') - v2x_stats['message_types'][message_type] += 1 + + message = data.get('message', {}) + message_type = message.get('message_type', 'unknown') + analysis['v2x_stats']['message_types'][message_type] += 1 file_size = os.path.getsize(os.path.join(v2x_dir, v2x_file)) total_size += file_size + valid_messages += 1 + + # 检查消息完整性 + required_fields = ['sender_id', 'message_type', 'timestamp'] + completeness = sum(1 for field in required_fields if field in message) / len(required_fields) + analysis['data_quality']['message_completeness'] += completeness + except: pass - v2x_stats['average_message_size'] = round(total_size / max(len(v2x_files), 1), 2) + if valid_messages > 0: + analysis['v2x_stats']['average_message_size'] = round(total_size / valid_messages, 2) + analysis['data_quality']['message_completeness'] = round( + analysis['data_quality']['message_completeness'] / valid_messages * 100, 1 + ) - analysis = { - 'v2x_messages': len(v2x_files), - 'shared_perception_frames': len(perception_files), + # 分析共享感知数据 + if perception_files: + consistent_frames = 0 + sample_size = min(10, len(perception_files)) + + for perception_file in perception_files[:sample_size]: + try: + with open(os.path.join(perception_dir, perception_file), 'r', encoding='utf-8') as f: + data = json.load(f) + + # 检查数据一致性 + if 'shared_objects' in data and 'timestamp' in data and 'frame_id' in data: + consistent_frames += 1 + except: + pass + + if sample_size > 0: + analysis['data_quality']['perception_consistency'] = round( + consistent_frames / sample_size * 100, 1 + ) + + # 从摘要中获取数据 + analysis.update({ 'total_vehicles': coop_summary.get('total_vehicles', 0), 'ego_vehicles': coop_summary.get('ego_vehicles', 0), 'cooperative_vehicles': coop_summary.get('cooperative_vehicles', 0), - 'v2x_stats': v2x_stats, 'shared_objects_count': coop_summary.get('shared_objects_count', 0), 'communication_range': coop_summary.get('communication_range', 0), 'collaborative_detections': coop_summary.get('v2x_stats', {}).get('collaborative_detections', 0) - } + }) + + # 计算合作水平 + cooperation_score = 0 + if analysis['v2x_messages'] > 50 and analysis['shared_perception_frames'] > 20: + cooperation_score = 90 + analysis['cooperation_level'] = 'high' + elif analysis['v2x_messages'] > 10 and analysis['shared_perception_frames'] > 5: + cooperation_score = 60 + analysis['cooperation_level'] = 'medium' + elif analysis['v2x_messages'] > 0 or analysis['shared_perception_frames'] > 0: + cooperation_score = 30 + analysis['cooperation_level'] = 'low' + + analysis['cooperation_score'] = cooperation_score return analysis @staticmethod def _calculate_quality_metrics(data_dir): - """计算质量指标""" + """计算质量指标(增强版)""" quality_metrics = { 'completeness_score': 0, 'consistency_score': 0, 'diversity_score': 0, 'cooperative_score': 0, - 'issues_found': [] + 'temporal_score': 0, + 'structural_score': 0, + 'issues_found': [], + 'recommendations': [] } - # 检查完整性 - required_dirs = ["raw/vehicle", "raw/infrastructure", "stitched", "metadata", "cooperative"] - missing_dirs = [] + # 1. 检查完整性 + required_dirs = [ + "raw/vehicle", + "raw/infrastructure", + "stitched", + "metadata", + "cooperative" + ] + + optional_dirs = [ + "lidar", + "fusion", + "annotations", + "calibration" + ] + + missing_required = [] + missing_optional = [] for dir_path in required_dirs: full_path = os.path.join(data_dir, dir_path) if not os.path.exists(full_path): - missing_dirs.append(dir_path) + missing_required.append(dir_path) + + for dir_path in optional_dirs: + full_path = os.path.join(data_dir, dir_path) + if not os.path.exists(full_path): + missing_optional.append(dir_path) - if missing_dirs: - quality_metrics['issues_found'].append(f"缺失目录: {missing_dirs}") - quality_metrics['completeness_score'] = 100 - (len(missing_dirs) * 20) + if missing_required: + quality_metrics['issues_found'].append(f"缺失必要目录: {missing_required}") + quality_metrics['completeness_score'] = 100 - (len(missing_required) * 20) else: quality_metrics['completeness_score'] = 100 - # 检查一致性(图像数量) + # 2. 结构评分 + structure_score = 100 + if missing_optional: + structure_score -= len(missing_optional) * 5 + quality_metrics['recommendations'].append(f"建议添加可选目录: {missing_optional[:3]}") + + quality_metrics['structural_score'] = max(0, structure_score) + + # 3. 检查一致性(图像数量) raw_vehicle = os.path.join(data_dir, "raw", "vehicle") if os.path.exists(raw_vehicle): camera_counts = [] @@ -296,167 +712,315 @@ def _calculate_quality_metrics(data_dir): camera_path = os.path.join(raw_vehicle, camera_dir) if os.path.isdir(camera_path): images = [f for f in os.listdir(camera_path) - if f.endswith('.png')] + if f.lower().endswith(('.png', '.jpg', '.jpeg'))] camera_counts.append(len(images)) if camera_counts: max_diff = max(camera_counts) - min(camera_counts) if camera_counts else 0 - if max_diff > 5: - quality_metrics['issues_found'].append(f"摄像头图像数量不一致: 差异{max_diff}") - quality_metrics['consistency_score'] = 70 + if max_diff > 10: + quality_metrics['issues_found'].append(f"摄像头图像数量严重不一致: 最大差异{max_diff}张") + quality_metrics['consistency_score'] = 60 + elif max_diff > 5: + quality_metrics['issues_found'].append(f"摄像头图像数量不一致: 差异{max_diff}张") + quality_metrics['consistency_score'] = 75 else: quality_metrics['consistency_score'] = 95 + else: + quality_metrics['consistency_score'] = 70 + quality_metrics['issues_found'].append("车辆摄像头无图像数据") + else: + quality_metrics['consistency_score'] = 50 - # 多样性评分(基于物体类别) + # 4. 多样性评分(基于物体类别) object_stats = DataAnalyzer._analyze_objects(data_dir) num_classes = len(object_stats.get('by_class', {})) + class_distribution = object_stats.get('class_distribution', {}) - if num_classes >= 5: - quality_metrics['diversity_score'] = 90 + if num_classes >= 8: + quality_metrics['diversity_score'] = 95 + elif num_classes >= 5: + quality_metrics['diversity_score'] = 80 elif num_classes >= 3: - quality_metrics['diversity_score'] = 70 - else: - quality_metrics['diversity_score'] = 50 + quality_metrics['diversity_score'] = 65 quality_metrics['issues_found'].append(f"物体类别较少: {num_classes}类") + else: + quality_metrics['diversity_score'] = 40 + quality_metrics['issues_found'].append(f"物体类别过少: {num_classes}类") - # 协同评分 + # 检查类别分布是否均衡 + if class_distribution: + values = list(class_distribution.values()) + if max(values) > 70: # 某个类别占比超过70% + quality_metrics['diversity_score'] *= 0.8 # 降低分数 + quality_metrics['recommendations'].append("数据集类别分布不均衡,建议收集更多样化的场景") + + # 5. 协同评分 cooperative_data = DataAnalyzer._analyze_cooperative_data(data_dir) - if cooperative_data['v2x_messages'] > 10 and cooperative_data['shared_perception_frames'] > 5: - quality_metrics['cooperative_score'] = 90 - elif cooperative_data['v2x_messages'] > 0 or cooperative_data['shared_perception_frames'] > 0: - quality_metrics['cooperative_score'] = 60 + quality_metrics['cooperative_score'] = cooperative_data.get('cooperation_score', 0) + + if quality_metrics['cooperative_score'] < 50: + quality_metrics['issues_found'].append("协同数据较少或质量不高") + quality_metrics['recommendations'].append("增加V2X消息和共享感知数据的生成") + + # 6. 时间评分 + temporal_data = DataAnalyzer._analyze_temporal(data_dir) + frame_rate = temporal_data.get('frame_rate', 0) + duration = temporal_data.get('total_duration', 0) + + if frame_rate >= 5.0: + quality_metrics['temporal_score'] = 95 + elif frame_rate >= 2.0: + quality_metrics['temporal_score'] = 80 + elif frame_rate >= 1.0: + quality_metrics['temporal_score'] = 60 else: - quality_metrics['cooperative_score'] = 30 - quality_metrics['issues_found'].append("协同数据较少") + quality_metrics['temporal_score'] = 30 + quality_metrics['issues_found'].append(f"帧率较低: {frame_rate:.2f} FPS") + + if duration < 30: + quality_metrics['temporal_score'] *= 0.8 # 时长不足,降低分数 + quality_metrics['recommendations'].append("建议增加数据收集时长以获得更完整的时间序列") + + # 限制分数在0-100之间 + for key in ['completeness_score', 'consistency_score', 'diversity_score', + 'cooperative_score', 'temporal_score', 'structural_score']: + quality_metrics[key] = max(0, min(100, quality_metrics[key])) return quality_metrics @staticmethod def _calculate_overall_score(analysis): - """计算总体评分""" + """计算总体评分(增强版)""" weights = { - 'completeness': 0.25, - 'consistency': 0.20, - 'diversity': 0.15, - 'cooperative': 0.20, - 'temporal': 0.20 + 'completeness': 0.20, # 完整性 + 'consistency': 0.15, # 一致性 + 'demporal': 0.15, # 时间性 + 'structural': 0.10, # 结构性 + 'diversity': 0.15, # 多样性 + 'cooperative': 0.15, # 协同性 + 'quality_bonus': 0.10 # 质量加成 } quality = analysis['quality_metrics'] - score = ( + # 基础分数 + base_score = ( quality['completeness_score'] * weights['completeness'] + quality['consistency_score'] * weights['consistency'] + + quality['temporal_score'] * weights['temporal'] + + quality['structural_score'] * weights['structural'] + quality['diversity_score'] * weights['diversity'] + quality['cooperative_score'] * weights['cooperative'] ) - # 时间因素 - temporal = analysis['temporal_analysis'] - if temporal['frame_rate'] >= 2.0: - score += 20 * weights['temporal'] - elif temporal['frame_rate'] >= 1.0: - score += 15 * weights['temporal'] - else: - score += 5 * weights['temporal'] + # 质量加成(基于问题数量) + issues_count = len(quality.get('issues_found', [])) + quality_bonus = max(0, 100 - issues_count * 5) * weights['quality_bonus'] + + total_score = base_score + quality_bonus - return round(min(score, 100), 1) + # 额外加成(如果数据集特别优秀) + if (quality['completeness_score'] >= 95 and + quality['consistency_score'] >= 90 and + quality['diversity_score'] >= 85): + total_score += 5 + + return round(min(total_score, 100), 1) @staticmethod def _save_analysis_report(data_dir, analysis): - """保存分析报告""" - report_file = os.path.join(data_dir, "metadata", "dataset_analysis.json") + """保存分析报告(优化版)""" + metadata_dir = os.path.join(data_dir, "metadata") + os.makedirs(metadata_dir, exist_ok=True) + + report_file = os.path.join(metadata_dir, "dataset_analysis.json") + # 保存完整报告 with open(report_file, 'w', encoding='utf-8') as f: json.dump(analysis, f, indent=2, ensure_ascii=False) + # 保存摘要报告(轻量版) + summary = { + 'overall_score': analysis['overall_score'], + 'quality_metrics': analysis['quality_metrics'], + 'basic_stats': { + 'total_size_mb': analysis['basic_stats']['total_size_mb'], + 'file_count': analysis['basic_stats']['file_count'], + 'directory_count': analysis['basic_stats']['directory_count'] + }, + 'object_statistics': { + 'total_objects': analysis['object_statistics']['total_objects'], + 'num_classes': len(analysis['object_statistics']['by_class']) + }, + 'analysis_metadata': analysis.get('metadata', {}) + } + + summary_file = os.path.join(metadata_dir, "dataset_summary.json") + with open(summary_file, 'w', encoding='utf-8') as f: + json.dump(summary, f, indent=2, ensure_ascii=False) + print(f"数据集分析报告保存: {report_file}") + print(f"数据集摘要保存: {summary_file}") @staticmethod def _print_analysis_summary(analysis): - """打印分析摘要""" - print("\n" + "=" * 60) + """打印分析摘要(增强版)""" + print("\n" + "=" * 70) print("数据集分析摘要") - print("=" * 60) + print("=" * 70) + # 基本统计 basic = analysis['basic_stats'] - print(f"\n基本统计:") + print(f"\n📊 基本统计:") print(f" 总大小: {basic['total_size_mb']} MB") - print(f" 文件数: {basic['file_count']}") + print(f" 文件数: {basic['file_count']:,}") print(f" 目录数: {basic['directory_count']}") - - print(f" 数据类型:") + print(f" 数据类型分布:") for data_type, count in basic['data_types'].items(): - print(f" {data_type}: {count}") + print(f" {data_type}: {count:,}") + if basic['largest_files']: + print(f" 最大的文件:") + for file_info in basic['largest_files'][:3]: + print(f" {file_info['path']}: {file_info['size_mb']} MB") + + # 文件分布 distribution = analysis['file_distribution'] - print(f"\n文件分布:") + print(f"\n📁 文件分布:") for key, value in distribution.items(): if isinstance(value, dict): - print(f" {key}:") - for subkey, subvalue in value.items(): - if isinstance(subvalue, dict): - for subsubkey, subsubvalue in subvalue.items(): - print(f" {subsubkey}: {subsubvalue}") - else: - print(f" {subkey}: {subvalue}") + if 'total' in value: + print(f" {key}: {value['total']:,}") + if 'formats' in value: + for fmt, count in value['formats'].items(): + print(f" {fmt}: {count:,}") + else: + print(f" {key}:") + for subkey, subvalue in value.items(): + print(f" {subkey}: {subvalue:,}") else: - print(f" {key}: {value}") + print(f" {key}: {value:,}") + # 物体统计 objects = analysis['object_statistics'] - print(f"\n物体统计:") - print(f" 总物体数: {objects['total_objects']}") + print(f"\n🎯 物体统计:") + print(f" 总物体数: {objects['total_objects']:,}") + print(f" 有物体的帧数: {objects['frames_with_objects']:,}") + print(f" 平均每帧物体数: {objects['object_density']:.2f}") + if objects['by_class']: print(f" 类别分布:") - for obj_class, count in objects['by_class'].items(): + for obj_class, count in sorted(objects['by_class'].items(), key=lambda x: x[1], reverse=True)[:5]: percentage = objects['class_distribution'].get(obj_class, 0) - print(f" {obj_class}: {count} ({percentage}%)") + print(f" {obj_class}: {count:,} ({percentage}%)") + + if objects['objects_per_frame_stats']: + stats = objects['objects_per_frame_stats'] + print(f" 每帧物体数统计:") + print(f" 最小: {stats['min']}, 最大: {stats['max']}, 平均: {stats['mean']}, 中位数: {stats['median']}") # 协同数据分析 cooperative = analysis['cooperative_data'] - print(f"\n协同数据分析:") - print(f" V2X消息: {cooperative['v2x_messages']}") - print(f" 共享感知帧: {cooperative['shared_perception_frames']}") + print(f"\n🤝 协同数据分析:") + print(f" 合作水平: {cooperative['cooperation_level'].upper()}") + print(f" V2X消息: {cooperative['v2x_messages']:,}") + print(f" 共享感知帧: {cooperative['shared_perception_frames']:,}") print(f" 车辆总数: {cooperative['total_vehicles']}") - print(f" 主车: {cooperative['ego_vehicles']}") - print(f" 协同车: {cooperative['cooperative_vehicles']}") - print(f" 共享对象数: {cooperative['shared_objects_count']}") - print(f" 协作检测数: {cooperative['collaborative_detections']}") + print(f" ├ 主车: {cooperative['ego_vehicles']}") + print(f" └ 协同车: {cooperative['cooperative_vehicles']}") + print(f" 共享对象数: {cooperative['shared_objects_count']:,}") + print(f" 协作检测数: {cooperative['collaborative_detections']:,}") if cooperative['v2x_stats']['message_types']: print(f" V2X消息类型:") for msg_type, count in cooperative['v2x_stats']['message_types'].items(): print(f" {msg_type}: {count}") + # 时间分析 temporal = analysis['temporal_analysis'] - print(f"\n时间分析:") - print(f" 总时长: {temporal['total_duration']:.1f}秒") + print(f"\n⏰ 时间分析:") + print(f" 总时长: {temporal['total_duration']:.1f}秒 ({temporal['total_duration'] / 60:.1f}分钟)") print(f" 平均帧率: {temporal['frame_rate']:.2f} FPS") + print(f" 时间覆盖率: {temporal['temporal_coverage']}%") + + if temporal['time_range']: + tr = temporal['time_range'] + print(f" 时间范围: {tr.get('start_time', 'N/A')} 到 {tr.get('end_time', 'N/A')}") + if 'duration_hours' in tr: + print(f" 持续时间: {tr['duration_hours']:.1f}小时") + # 质量指标 quality = analysis['quality_metrics'] - print(f"\n质量指标:") - print(f" 完整性: {quality['completeness_score']}/100") - print(f" 一致性: {quality['consistency_score']}/100") - print(f" 多样性: {quality['diversity_score']}/100") - print(f" 协同性: {quality['cooperative_score']}/100") + print(f"\n📈 质量指标:") + metrics = [ + ('完整性', quality['completeness_score']), + ('一致性', quality['consistency_score']), + ('结构性', quality['structural_score']), + ('时间性', quality['temporal_score']), + ('多样性', quality['diversity_score']), + ('协同性', quality['cooperative_score']) + ] + + for name, score in metrics: + bar = "█" * int(score / 5) + print(f" {name:8s}: {score:3.0f}/100 {bar}") if quality['issues_found']: - print(f" 发现的问题 ({len(quality['issues_found'])}):") - for issue in quality['issues_found'][:3]: - print(f" - {issue}") - if len(quality['issues_found']) > 3: - print(f" ... 还有 {len(quality['issues_found']) - 3} 个问题") + print(f"\n⚠️ 发现的问题 ({len(quality['issues_found'])}):") + for i, issue in enumerate(quality['issues_found'][:5], 1): + print(f" {i}. {issue}") + if len(quality['issues_found']) > 5: + print(f" ... 还有 {len(quality['issues_found']) - 5} 个问题") - print(f"\n总体评分: {analysis['overall_score']}/100") + if quality['recommendations']: + print(f"\n💡 改进建议:") + for i, rec in enumerate(quality['recommendations'][:3], 1): + print(f" {i}. {rec}") + + print(f"\n⭐ 总体评分: {analysis['overall_score']}/100") overall_score = analysis['overall_score'] if overall_score >= 90: - print("✓ 数据集质量优秀") - elif overall_score >= 75: - print("✓ 数据集质量良好") + print("🎉 数据集质量优秀 - 可直接用于模型训练") + elif overall_score >= 80: + print("👍 数据集质量良好 - 建议进行少量数据增强") + elif overall_score >= 70: + print("⚠️ 数据集质量一般 - 建议进行数据清洗和增强") elif overall_score >= 60: - print("⚠ 数据集质量一般") + print("🔧 数据集质量需要改进 - 建议补充缺失数据") else: - print("✗ 数据集质量需要改进") + print("🚨 数据集质量较差 - 需要大规模改进") + + # 分析元数据 + if 'metadata' in analysis: + meta = analysis['metadata'] + print(f"\n📝 分析信息:") + print(f" 分析时间: {meta.get('analysis_time', 'N/A')}") + print(f" 分析耗时: {meta.get('analysis_duration', 0):.1f}秒") - print("=" * 60) \ No newline at end of file + print("=" * 70) + + @staticmethod + def generate_comparison_report(data_dirs, output_file=None): + """生成多个数据集的比较报告""" + comparisons = {} + + for data_dir in data_dirs: + if os.path.exists(data_dir): + analysis = DataAnalyzer.analyze_dataset(data_dir) + comparisons[os.path.basename(data_dir)] = { + 'overall_score': analysis['overall_score'], + 'basic_stats': analysis['basic_stats'], + 'quality_metrics': analysis['quality_metrics'], + 'object_statistics': { + 'total_objects': analysis['object_statistics']['total_objects'], + 'num_classes': len(analysis['object_statistics']['by_class']) + } + } + + if output_file: + with open(output_file, 'w', encoding='utf-8') as f: + json.dump(comparisons, f, indent=2, ensure_ascii=False) + print(f"比较报告保存到: {output_file}") + + return comparisons \ No newline at end of file From 711cf9576378c976840e3439da7a64b3682c29d8 Mon Sep 17 00:00:00 2001 From: Z-w-7799 <1011069369@qq.com> Date: Sun, 21 Dec 2025 09:56:03 +0800 Subject: [PATCH 10/20] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E9=AA=8C=E8=AF=81=E5=92=8C=E5=8A=A8=E6=80=81=E8=B0=83=E6=95=B4?= =?UTF-8?q?=EF=BC=8C=E6=B7=BB=E5=8A=A0=E6=89=B9=E9=87=8F=E5=A4=84=E7=90=86?= =?UTF-8?q?=E5=92=8C=E6=80=A7=E8=83=BD=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config_manager.py | 652 +++++++- .../sensor_enhancer.py | 1326 +++++++++-------- 2 files changed, 1286 insertions(+), 692 deletions(-) diff --git a/src/enhance_pedestrian_safety/config_manager.py b/src/enhance_pedestrian_safety/config_manager.py index 183cb3cab3..1f64c72ef9 100644 --- a/src/enhance_pedestrian_safety/config_manager.py +++ b/src/enhance_pedestrian_safety/config_manager.py @@ -1,20 +1,298 @@ import json import os import argparse +import copy +from typing import Dict, Any, Optional, List, Tuple + +# 尝试导入yaml,如果不存在则跳过 +try: + import yaml + + YAML_AVAILABLE = True +except ImportError: + YAML_AVAILABLE = False + print("警告: PyYAML未安装,将使用JSON格式") + + +class ConfigValidator: + """配置验证器""" + + @staticmethod + def validate_config(config: Dict[str, Any]) -> Tuple[bool, List[str]]: + """验证配置的有效性""" + errors = [] + + # 验证必填字段 + required_sections = ['scenario', 'sensors', 'output'] + for section in required_sections: + if section not in config: + errors.append(f"缺失必要配置节: {section}") + + # 验证场景配置 + if 'scenario' in config: + scenario = config['scenario'] + if 'duration' in scenario and scenario['duration'] <= 0: + errors.append("场景时长必须大于0") + if 'town' not in scenario: + errors.append("场景配置中缺失地图名称") + + # 验证传感器配置 + if 'sensors' in config: + sensors = config['sensors'] + if 'capture_interval' in sensors and sensors['capture_interval'] <= 0: + errors.append("采集间隔必须大于0") + if 'image_size' in sensors: + if len(sensors['image_size']) != 2: + errors.append("图像尺寸必须为[宽度, 高度]格式") + elif any(dim <= 0 for dim in sensors['image_size']): + errors.append("图像尺寸必须大于0") + + # 验证性能配置 + if 'performance' in config: + perf = config['performance'] + if 'batch_size' in perf and perf['batch_size'] <= 0: + errors.append("批处理大小必须大于0") + + return len(errors) == 0, errors + + @staticmethod + def suggest_optimizations(config: Dict[str, Any]) -> List[str]: + """根据配置提供优化建议""" + suggestions = [] + + # 内存使用建议 + if config.get('sensors', {}).get('lidar_sensors', 0) > 0: + lidar_config = config['sensors'].get('lidar_config', {}) + max_points = lidar_config.get('max_points_per_frame', 50000) + if max_points > 50000: + suggestions.append(f"LiDAR最大点数({max_points})较高,建议降低到50000以下以减少内存使用") + + # 性能建议 + capture_interval = config['sensors'].get('capture_interval', 2.0) + if capture_interval < 1.0: + suggestions.append(f"采集间隔({capture_interval}s)较短,可能导致高负载,建议增加到1.0s以上") + + # 输出建议 + output = config.get('output', {}) + enabled_outputs = [k for k, v in output.items() if isinstance(v, bool) and v] + if len(enabled_outputs) > 5: + suggestions.append(f"启用的输出类型过多({len(enabled_outputs)}),可能影响性能,建议只启用必要的输出") + + return suggestions + + +class ConfigOptimizer: + """配置优化器""" + + @staticmethod + def optimize_for_memory(config: Dict[str, Any]) -> Dict[str, Any]: + """为内存使用优化配置""" + optimized = copy.deepcopy(config) + + # 调整LiDAR设置 + if optimized['sensors'].get('lidar_sensors', 0) > 0: + lidar_config = optimized['sensors'].setdefault('lidar_config', {}) + lidar_config.update({ + 'max_points_per_frame': 30000, + 'downsample_ratio': 0.4, + 'memory_warning_threshold': 200, + 'max_batch_memory_mb': 30 + }) + + # 调整性能设置 + perf = optimized.setdefault('performance', {}) + perf.update({ + 'batch_size': 3, + 'enable_compression': True, + 'compression_level': 4, + 'enable_memory_cache': True, + 'max_cache_size': 30, + 'frame_rate_limit': 3.0 + }) + + # 调整图像处理 + perf['image_processing'] = { + 'compress_images': True, + 'compression_quality': 80, + 'resize_images': False + } + + return optimized + + @staticmethod + def optimize_for_quality(config: Dict[str, Any]) -> Dict[str, Any]: + """为数据质量优化配置""" + optimized = copy.deepcopy(config) + + # 调整传感器设置 + sensors = optimized['sensors'] + sensors.update({ + 'image_size': [1920, 1080], + 'capture_interval': 1.0, + 'lidar_sensors': 1, + 'lidar_config': { + 'channels': 64, + 'range': 150.0, + 'points_per_second': 120000, + 'max_points_per_frame': 100000, + 'downsample_ratio': 0.1 + } + }) + + # 调整输出设置 + output = optimized['output'] + output.update({ + 'save_annotations': True, + 'save_fusion': True, + 'save_cooperative': True, + 'save_enhanced': True, + 'run_quality_check': True + }) + + # 调整增强设置 + enhanced = optimized.setdefault('enhancement', {}) + enhanced.update({ + 'enabled': True, + 'enable_random': True, + 'quality_check': True, + 'methods': ['normalize', 'contrast', 'sharpness', 'noise'] + }) + + return optimized + + @staticmethod + def optimize_for_speed(config: Dict[str, Any]) -> Dict[str, Any]: + """为处理速度优化配置""" + optimized = copy.deepcopy(config) + + # 调整传感器设置 + sensors = optimized['sensors'] + sensors.update({ + 'image_size': [640, 480], + 'capture_interval': 3.0, + 'lidar_sensors': 0, # 禁用LiDAR + 'radar_sensors': 0 # 禁用雷达 + }) + + # 调整性能设置 + perf = optimized.setdefault('performance', {}) + perf.update({ + 'batch_size': 10, + 'enable_compression': True, + 'compression_level': 1, # 快速压缩 + 'enable_downsampling': True, + 'enable_async_processing': True, + 'max_cache_size': 20, + 'frame_rate_limit': 10.0 + }) + + # 简化输出 + output = optimized['output'] + output.update({ + 'save_raw': True, + 'save_stitched': False, # 不拼接图像 + 'save_annotations': False, + 'save_lidar': False, + 'save_fusion': False, + 'save_cooperative': False + }) + + return optimized class ConfigManager: + """配置管理器(增强版)""" + + # 预定义配置模板 + PRESET_CONFIGS = { + 'balanced': { + 'description': '平衡配置 - 兼顾性能和质量', + 'optimization': 'memory' + }, + 'high_quality': { + 'description': '高质量配置 - 优先数据质量', + 'optimization': 'quality' + }, + 'fast_collection': { + 'description': '快速采集配置 - 优先处理速度', + 'optimization': 'speed' + }, + 'v2x_focused': { + 'description': 'V2X重点配置 - 优化协同数据采集', + 'optimization': 'custom', + 'settings': { + 'v2x': {'enabled': True, 'update_interval': 1.0}, + 'cooperative': {'num_coop_vehicles': 3, 'enable_shared_perception': True}, + 'output': {'save_cooperative': True, 'save_v2x_messages': True} + } + }, + 'lidar_focused': { + 'description': 'LiDAR重点配置 - 优化点云数据采集', + 'optimization': 'custom', + 'settings': { + 'sensors': {'lidar_sensors': 2, 'lidar_config': {'channels': 64, 'range': 200}}, + 'output': {'save_lidar': True, 'save_fusion': True} + } + } + } + + @staticmethod + def load_config(config_file: Optional[str] = None, preset: Optional[str] = None) -> Dict[str, Any]: + """ + 加载配置 + + Args: + config_file: 配置文件路径 + preset: 预设配置名称 + + Returns: + 配置字典 + """ + # 基础配置 + config = ConfigManager._get_default_config() + + # 应用预设配置 + if preset: + config = ConfigManager._apply_preset(config, preset) + + # 加载用户配置文件 + if config_file: + if os.path.exists(config_file): + config = ConfigManager._load_config_file(config_file, config) + else: + print(f"警告: 配置文件不存在: {config_file}") + + # 验证配置 + is_valid, errors = ConfigValidator.validate_config(config) + if not is_valid: + print("配置验证错误:") + for error in errors: + print(f" - {error}") + raise ValueError("配置验证失败") + + # 提供优化建议 + suggestions = ConfigValidator.suggest_optimizations(config) + if suggestions: + print("配置优化建议:") + for suggestion in suggestions: + print(f" ⚡ {suggestion}") + + return config @staticmethod - def load_config(config_file=None): - config = { + def _get_default_config() -> Dict[str, Any]: + """获取默认配置(增强版)""" + return { 'scenario': { 'name': 'multi_sensor_scene', + 'description': '多传感器协同数据采集场景', 'town': 'Town10HD', 'weather': 'clear', 'time_of_day': 'noon', 'duration': 60, - 'seed': 42 + 'seed': 42, + 'timeout': 300, # 超时时间(秒) + 'retry_attempts': 3 # 重试次数 }, 'traffic': { 'ego_vehicles': 1, @@ -22,25 +300,49 @@ def load_config(config_file=None): 'pedestrians': 6, 'traffic_lights': True, 'batch_spawn': True, - 'max_spawn_attempts': 5 + 'max_spawn_attempts': 5, + 'vehicle_types': [ + 'vehicle.tesla.model3', + 'vehicle.audi.tt', + 'vehicle.nissan.patrol', + 'vehicle.bmw.grandtourer' + ], + 'pedestrian_types': [ + 'walker.pedestrian.0001', + 'walker.pedestrian.0002' + ] }, 'sensors': { 'vehicle_cameras': 4, 'infrastructure_cameras': 4, 'lidar_sensors': 1, 'radar_sensors': 0, + 'gps_sensors': 0, + 'imu_sensors': 0, 'image_size': [1280, 720], 'capture_interval': 2.0, + 'sensor_placement': 'default', 'lidar_config': { 'channels': 32, - 'range': 100, + 'range': 100.0, 'points_per_second': 56000, - 'rotation_frequency': 10, - 'max_points_per_frame': 50000, # 优化点6:减少最大点数 - 'downsample_ratio': 0.3, # 优化点6:增加下采样比例 - 'memory_warning_threshold': 350, # 新增:内存警告阈值(MB) - 'max_batch_memory_mb': 50, # 新增:批次最大内存 - 'v2x_save_interval': 5 # 新增:V2X格式保存间隔 + 'rotation_frequency': 10.0, + 'horizontal_fov': 360.0, + 'vertical_fov': 30.0, + 'upper_fov': 10.0, + 'lower_fov': -20.0, + 'max_points_per_frame': 50000, + 'downsample_ratio': 0.3, + 'memory_warning_threshold': 300, + 'max_batch_memory_mb': 50, + 'v2x_save_interval': 5, + 'compression_format': 'bin' # bin, npy, pcd + }, + 'camera_config': { + 'fov': 90.0, + 'post_processing': 'default', + 'exposure_mode': 'auto', + 'motion_blur': 0.0 } }, 'v2x': { @@ -50,8 +352,11 @@ def load_config(config_file=None): 'latency_mean': 0.05, 'latency_std': 0.01, 'packet_loss_rate': 0.01, - 'message_types': ['bsm', 'spat', 'map', 'rsm'], - 'update_interval': 2.0 # 优化点6:V2X更新间隔 + 'message_types': ['bsm', 'spat', 'map', 'rsm', 'perception', 'warning'], + 'update_interval': 2.0, + 'security_enabled': False, + 'encryption_level': 'none', + 'qos_policy': 'best_effort' }, 'cooperative': { 'num_coop_vehicles': 2, @@ -59,7 +364,10 @@ def load_config(config_file=None): 'enable_traffic_warnings': True, 'enable_maneuver_coordination': False, 'data_fusion_interval': 1.0, - 'max_shared_objects': 50 + 'max_shared_objects': 50, + 'object_matching_threshold': 5.0, + 'data_retention_time': 10.0, + 'consensus_method': 'simple' }, 'enhancement': { 'enabled': True, @@ -68,7 +376,10 @@ def load_config(config_file=None): 'save_original': True, 'save_enhanced': True, 'calibration_generation': True, - 'enhanced_dir_name': 'enhanced' + 'enhanced_dir_name': 'enhanced', + 'methods': ['normalize', 'contrast', 'brightness'], + 'weather_effects': True, + 'augmentation_level': 'medium' }, 'performance': { 'batch_size': 5, @@ -77,27 +388,41 @@ def load_config(config_file=None): 'enable_downsampling': True, 'enable_memory_cache': True, 'max_cache_size': 50, + 'enable_async_processing': True, + 'max_workers': 2, 'image_processing': { 'compress_images': True, - 'compression_quality': 85 + 'compression_quality': 85, + 'resize_images': False, + 'resize_dimensions': [640, 480], + 'format': 'jpg' # jpg, png }, 'lidar_processing': { 'batch_size': 10, 'enable_compression': True, 'enable_downsampling': True, - 'max_points_per_frame': 50000, # 与sensors配置保持一致 + 'max_points_per_frame': 50000, 'memory_warning_threshold': 350, 'max_batch_memory_mb': 50, - 'v2x_save_interval': 5 + 'v2x_save_interval': 5, + 'compression_method': 'zlib' # zlib, lz4, none }, 'fusion': { - 'fusion_cache_size': 100 + 'fusion_cache_size': 100, + 'enable_cache': True, + 'compression_enabled': True }, - 'sensor_cleanup_timeout': 0.5, # 新增:传感器清理超时 - 'frame_rate_limit': 5.0 # 新增:帧率限制 + 'sensor_cleanup_timeout': 0.5, + 'frame_rate_limit': 5.0, + 'memory_management': { + 'gc_interval': 50, + 'max_memory_mb': 500, + 'early_stop_threshold': 400 + } }, 'output': { 'data_dir': 'cvips_dataset', + 'output_format': 'standard', # standard, v2xformer, kitti, coco 'save_raw': True, 'save_stitched': True, 'save_annotations': False, @@ -109,102 +434,285 @@ def load_config(config_file=None): 'validate_data': True, 'run_analysis': False, 'run_quality_check': True, - 'output_format': 'standard' + 'generate_summary': True, + 'compression_enabled': True, + 'file_naming': 'sequential', # sequential, timestamp + 'backup_original': False + }, + 'monitoring': { + 'enable_logging': True, + 'log_level': 'INFO', # DEBUG, INFO, WARNING, ERROR + 'log_file': 'cvips.log', + 'enable_performance_monitor': True, + 'performance_log_interval': 10.0, + 'enable_progress_bar': True, + 'enable_real_time_stats': True, + 'stats_update_interval': 5.0 + }, + 'debug': { + 'enable_debug_mode': False, + 'save_debug_data': False, + 'debug_dir': 'debug', + 'print_config': False, + 'validate_sensors': True, + 'test_mode': False + }, + 'metadata': { + 'version': '1.0.0', + 'author': 'CVIPS System', + 'description': '多传感器协同数据采集配置', + 'created': '', + 'modified': '' } } - if config_file and os.path.exists(config_file): - try: - with open(config_file, 'r') as f: - user_config = json.load(f) - ConfigManager._deep_update(config, user_config) - except: - pass + @staticmethod + def _apply_preset(config: Dict[str, Any], preset_name: str) -> Dict[str, Any]: + """应用预设配置""" + if preset_name not in ConfigManager.PRESET_CONFIGS: + print(f"警告: 未知的预设配置: {preset_name}") + return config + + preset = ConfigManager.PRESET_CONFIGS[preset_name] + print(f"应用预设配置: {preset_name} - {preset['description']}") + + # 根据优化类型应用配置 + optimization = preset.get('optimization', 'balanced') + if optimization == 'memory': + config = ConfigOptimizer.optimize_for_memory(config) + elif optimization == 'quality': + config = ConfigOptimizer.optimize_for_quality(config) + elif optimization == 'speed': + config = ConfigOptimizer.optimize_for_speed(config) + elif optimization == 'custom' and 'settings' in preset: + config = ConfigManager._deep_update(config, preset['settings']) return config @staticmethod - def _deep_update(original, update): + def _load_config_file(config_file: str, base_config: Dict[str, Any]) -> Dict[str, Any]: + """加载配置文件""" + try: + with open(config_file, 'r', encoding='utf-8') as f: + if (config_file.endswith('.yaml') or config_file.endswith('.yml')) and YAML_AVAILABLE: + user_config = yaml.safe_load(f) + else: + user_config = json.load(f) + + print(f"加载配置文件: {config_file}") + return ConfigManager._deep_update(base_config, user_config) + + except Exception as e: + print(f"配置文件加载错误: {e}") + return base_config + + @staticmethod + def _deep_update(original: Dict[str, Any], update: Dict[str, Any]) -> Dict[str, Any]: + """深度更新字典""" for key, value in update.items(): if key in original and isinstance(original[key], dict) and isinstance(value, dict): ConfigManager._deep_update(original[key], value) else: original[key] = value + return original @staticmethod - def merge_args(config, args): - if args.scenario: + def merge_args(config: Dict[str, Any], args: argparse.Namespace) -> Dict[str, Any]: + """合并命令行参数到配置""" + # 场景参数 + if hasattr(args, 'scenario') and args.scenario: config['scenario']['name'] = args.scenario - if args.town: + if hasattr(args, 'town') and args.town: config['scenario']['town'] = args.town - if args.weather: + if hasattr(args, 'weather') and args.weather: config['scenario']['weather'] = args.weather - if args.time_of_day: + if hasattr(args, 'time_of_day') and args.time_of_day: config['scenario']['time_of_day'] = args.time_of_day - if args.duration: + if hasattr(args, 'duration') and args.duration: config['scenario']['duration'] = args.duration - if args.seed: + if hasattr(args, 'seed') and args.seed: config['scenario']['seed'] = args.seed - if args.num_vehicles: + # 交通参数 + if hasattr(args, 'num_vehicles') and args.num_vehicles: config['traffic']['background_vehicles'] = args.num_vehicles - if args.num_pedestrians: + if hasattr(args, 'num_pedestrians') and args.num_pedestrians: config['traffic']['pedestrians'] = args.num_pedestrians + # 协同参数 if hasattr(args, 'num_coop_vehicles') and args.num_coop_vehicles: config['cooperative']['num_coop_vehicles'] = args.num_coop_vehicles + # 传感器参数 if hasattr(args, 'capture_interval') and args.capture_interval: config['sensors']['capture_interval'] = args.capture_interval - if hasattr(args, 'enable_v2x') and args.enable_v2x: - config['v2x']['enabled'] = True - # 如果通过命令行启用V2X,使用更保守的更新间隔 - if 'update_interval' not in config['v2x']: - config['v2x']['update_interval'] = 2.0 + # V2X参数 + if hasattr(args, 'enable_v2x'): + config['v2x']['enabled'] = args.enable_v2x - if hasattr(args, 'enable_enhancement') and args.enable_enhancement: - config['enhancement']['enabled'] = True + # 增强参数 + if hasattr(args, 'enable_enhancement'): + config['enhancement']['enabled'] = args.enable_enhancement - if hasattr(args, 'enable_lidar') and args.enable_lidar: - config['sensors']['lidar_sensors'] = 1 - config['output']['save_lidar'] = True - # 优化:如果启用LiDAR,使用更保守的设置 - config['sensors']['lidar_config']['max_points_per_frame'] = 50000 - config['sensors']['lidar_config']['downsample_ratio'] = 0.3 + # LiDAR参数 + if hasattr(args, 'enable_lidar'): + config['sensors']['lidar_sensors'] = 1 if args.enable_lidar else 0 + config['output']['save_lidar'] = args.enable_lidar - if hasattr(args, 'enable_fusion') and args.enable_fusion: - config['output']['save_fusion'] = True + # 融合参数 + if hasattr(args, 'enable_fusion'): + config['output']['save_fusion'] = args.enable_fusion - if hasattr(args, 'enable_cooperative') and args.enable_cooperative: - config['output']['save_cooperative'] = True + # 协同参数 + if hasattr(args, 'enable_cooperative'): + config['output']['save_cooperative'] = args.enable_cooperative - if hasattr(args, 'enable_annotations') and args.enable_annotations: - config['output']['save_annotations'] = True + # 标注参数 + if hasattr(args, 'enable_annotations'): + config['output']['save_annotations'] = args.enable_annotations - if hasattr(args, 'skip_validation') and args.skip_validation: - config['output']['validate_data'] = False + # 验证参数 + if hasattr(args, 'skip_validation'): + config['output']['validate_data'] = not args.skip_validation - if hasattr(args, 'skip_quality_check') and args.skip_quality_check: - config['output']['run_quality_check'] = False + # 质量检查参数 + if hasattr(args, 'skip_quality_check'): + config['output']['run_quality_check'] = not args.skip_quality_check - if hasattr(args, 'run_analysis') and args.run_analysis: - config['output']['run_analysis'] = True + # 分析参数 + if hasattr(args, 'run_analysis'): + config['output']['run_analysis'] = args.run_analysis # 性能参数 if hasattr(args, 'batch_size') and args.batch_size: config['performance']['batch_size'] = args.batch_size - if hasattr(args, 'enable_compression') and args.enable_compression: - config['performance']['enable_compression'] = True + if hasattr(args, 'enable_compression'): + config['performance']['enable_compression'] = args.enable_compression - if hasattr(args, 'enable_downsampling') and args.enable_downsampling: - config['performance']['enable_downsampling'] = True - # 如果启用下采样,使用更保守的设置 - config['sensors']['lidar_config']['downsample_ratio'] = 0.3 - config['performance']['lidar_processing']['max_points_per_frame'] = 50000 + if hasattr(args, 'enable_downsampling'): + config['performance']['enable_downsampling'] = args.enable_downsampling + if args.enable_downsampling: + config['sensors']['lidar_config']['downsample_ratio'] = 0.3 + # 输出格式 if hasattr(args, 'output_format') and args.output_format: config['output']['output_format'] = args.output_format - return config \ No newline at end of file + return config + + @staticmethod + def save_config(config: Dict[str, Any], output_path: str, format: str = 'json'): + """保存配置到文件""" + try: + os.makedirs(os.path.dirname(output_path), exist_ok=True) + + if format.lower() == 'yaml' and YAML_AVAILABLE: + with open(output_path, 'w', encoding='utf-8') as f: + yaml.dump(config, f, default_flow_style=False, allow_unicode=True) + else: + with open(output_path, 'w', encoding='utf-8') as f: + json.dump(config, f, indent=2, ensure_ascii=False) + + print(f"配置保存到: {output_path}") + return True + except Exception as e: + print(f"保存配置失败: {e}") + return False + + @staticmethod + def generate_config_template(output_path: str, preset: Optional[str] = None): + """生成配置模板""" + config = ConfigManager.load_config(preset=preset) + config['metadata']['created'] = 'template' + config['metadata']['description'] = f'配置模板 - {preset if preset else "通用"}' + + return ConfigManager.save_config(config, output_path) + + @staticmethod + def print_config_summary(config: Dict[str, Any]): + """打印配置摘要""" + print("\n" + "=" * 60) + print("配置摘要") + print("=" * 60) + + # 场景信息 + scenario = config['scenario'] + print(f"\n📋 场景:") + print(f" 名称: {scenario['name']}") + print(f" 地图: {scenario['town']}") + print(f" 天气/时间: {scenario['weather']}/{scenario['time_of_day']}") + print(f" 时长: {scenario['duration']}秒") + print(f" 随机种子: {scenario.get('seed', '随机')}") + + # 交通信息 + traffic = config['traffic'] + print(f"\n🚗 交通:") + print(f" 主车: {traffic['ego_vehicles']}") + print(f" 背景车辆: {traffic['background_vehicles']}") + print(f" 行人: {traffic['pedestrians']}") + print(f" 交通灯: {'启用' if traffic['traffic_lights'] else '禁用'}") + + # 传感器信息 + sensors = config['sensors'] + print(f"\n📷 传感器:") + print(f" 车辆摄像头: {sensors['vehicle_cameras']}") + print(f" 基础设施摄像头: {sensors['infrastructure_cameras']}") + print(f" LiDAR: {sensors['lidar_sensors']} (通道: {sensors['lidar_config']['channels']})") + print(f" 采集间隔: {sensors['capture_interval']}秒") + print(f" 图像尺寸: {sensors['image_size'][0]}x{sensors['image_size'][1]}") + + # V2X信息 + v2x = config['v2x'] + print(f"\n📡 V2X通信:") + print(f" 状态: {'启用' if v2x['enabled'] else '禁用'}") + if v2x['enabled']: + print(f" 通信范围: {v2x['communication_range']}米") + print(f" 更新间隔: {v2x['update_interval']}秒") + + # 协同信息 + coop = config['cooperative'] + print(f"\n🤝 协同感知:") + print(f" 协同车辆: {coop['num_coop_vehicles']}") + print(f" 共享感知: {'启用' if coop['enable_shared_perception'] else '禁用'}") + print(f" 交通警告: {'启用' if coop['enable_traffic_warnings'] else '禁用'}") + + # 性能信息 + perf = config['performance'] + print(f"\n⚡ 性能:") + print(f" 批处理大小: {perf['batch_size']}") + print(f" 压缩: {'启用' if perf['enable_compression'] else '禁用'}") + print(f" 下采样: {'启用' if perf['enable_downsampling'] else '禁用'}") + print(f" 帧率限制: {perf['frame_rate_limit']} FPS") + + # 输出信息 + output = config['output'] + print(f"\n💾 输出:") + print(f" 输出目录: {output['data_dir']}") + print(f" 输出格式: {output['output_format']}") + enabled_outputs = [k.replace('save_', '') for k, v in output.items() + if isinstance(v, bool) and v and k.startswith('save_')] + print(f" 启用输出: {', '.join(enabled_outputs)}") + + print("=" * 60) + + @staticmethod + def list_presets(): + """列出所有预设配置""" + print("\n可用预设配置:") + print("-" * 40) + for name, preset in ConfigManager.PRESET_CONFIGS.items(): + print(f" {name:15s} - {preset['description']}") + print("-" * 40) + + +# 兼容旧版本的接口 +def load_config(config_file=None): + """兼容旧版本的加载配置函数""" + return ConfigManager.load_config(config_file) + + +def merge_args(config, args): + """兼容旧版本的合并参数函数""" + return ConfigManager.merge_args(config, args) \ No newline at end of file diff --git a/src/enhance_pedestrian_safety/sensor_enhancer.py b/src/enhance_pedestrian_safety/sensor_enhancer.py index f484831c94..b446c71bbe 100644 --- a/src/enhance_pedestrian_safety/sensor_enhancer.py +++ b/src/enhance_pedestrian_safety/sensor_enhancer.py @@ -1,5 +1,5 @@ """ -传感器数据增强模块 - 提高数据质量和多样性 +传感器数据增强模块 - 提高数据质量和多样性(优化版) """ import numpy as np @@ -9,421 +9,683 @@ import json from PIL import Image, ImageEnhance, ImageFilter from datetime import datetime -from typing import Dict, List, Tuple, Optional -import carla +from typing import Dict, List, Tuple, Optional, Union, Callable +import concurrent.futures +from dataclasses import dataclass +from enum import Enum +import time +import hashlib +from pathlib import Path + + +class WeatherType(Enum): + """天气类型枚举""" + CLEAR = "clear" + RAINY = "rainy" + FOGGY = "foggy" + CLOUDY = "cloudy" + NIGHT = "night" + SUNSET = "sunset" + + +class EnhancementMethod(Enum): + """增强方法枚举""" + NORMALIZE = "normalize" + BRIGHTNESS = "brightness" + CONTRAST = "contrast" + SATURATION = "saturation" + SHARPNESS = "sharpness" + GAMMA = "gamma" + NOISE = "noise" + BLUR = "blur" + MOTION_BLUR = "motion_blur" + RAIN = "rain" + FOG = "fog" + CLOUD = "cloud" + VIGNETTE = "vignette" + COLOR_TEMP = "color_temperature" + JPEG_COMPRESSION = "jpeg_compression" + COLOR_JITTER = "color_jitter" + + +@dataclass +class EnhancementConfig: + """增强配置""" + weather: WeatherType = WeatherType.CLEAR + time_of_day: str = "noon" + enabled_methods: List[EnhancementMethod] = None + intensity_range: Tuple[float, float] = (0.5, 1.5) + probability: float = 0.7 + max_methods_per_image: int = 5 + save_original: bool = True + save_enhanced: bool = True + output_format: str = "jpg" + compression_quality: int = 90 + + def __post_init__(self): + if self.enabled_methods is None: + self.enabled_methods = [ + EnhancementMethod.NORMALIZE, + EnhancementMethod.CONTRAST, + EnhancementMethod.BRIGHTNESS + ] + + +class BatchEnhancer: + """批量增强器""" + + def __init__(self, config: EnhancementConfig, max_workers: int = 4): + self.config = config + self.max_workers = max_workers + self.enhancer = SensorDataEnhancer(config) + self.stats = { + 'total_processed': 0, + 'successful': 0, + 'failed': 0, + 'total_time': 0, + 'avg_time_per_image': 0 + } + + def process_batch(self, image_paths: List[str], output_dir: str) -> Dict: + """批量处理图像""" + start_time = time.time() + results = [] + + # 准备输出目录 + os.makedirs(output_dir, exist_ok=True) + + # 使用线程池并行处理 + with concurrent.futures.ThreadPoolExecutor(max_workers=self.max_workers) as executor: + future_to_path = {} + for img_path in image_paths: + if os.path.exists(img_path): + output_path = self._get_output_path(img_path, output_dir) + future = executor.submit( + self._process_single_image, + img_path, output_path + ) + future_to_path[future] = img_path + + # 收集结果 + for future in concurrent.futures.as_completed(future_to_path): + img_path = future_to_path[future] + try: + result = future.result() + results.append(result) + self.stats['successful'] += 1 + except Exception as e: + print(f"处理图像 {img_path} 失败: {e}") + self.stats['failed'] += 1 + results.append({ + 'input_path': img_path, + 'output_path': None, + 'success': False, + 'error': str(e) + }) + + # 更新统计 + self.stats['total_processed'] += len(image_paths) + total_time = time.time() - start_time + self.stats['total_time'] += total_time + + if len(image_paths) > 0: + self.stats['avg_time_per_image'] = self.stats['total_time'] / self.stats['total_processed'] + + return { + 'results': results, + 'stats': self.stats.copy(), + 'batch_size': len(image_paths), + 'processing_time': total_time + } + + def _process_single_image(self, input_path: str, output_path: str) -> Dict: + """处理单张图像""" + try: + # 读取图像 + image = cv2.imread(input_path) + if image is None: + raise ValueError(f"无法读取图像: {input_path}") + + # 转换为RGB + image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) + + # 应用增强 + enhanced = self.enhancer.enhance_image(image_rgb) + + # 保存结果 + cv2.imwrite(output_path, cv2.cvtColor(enhanced, cv2.COLOR_RGB2BGR)) + + # 计算哈希值(用于去重) + img_hash = hashlib.md5(enhanced.tobytes()).hexdigest()[:16] + + return { + 'input_path': input_path, + 'output_path': output_path, + 'success': True, + 'image_hash': img_hash, + 'original_size': os.path.getsize(input_path), + 'enhanced_size': os.path.getsize(output_path), + 'compression_ratio': os.path.getsize(output_path) / max(1, os.path.getsize(input_path)) + } + except Exception as e: + raise Exception(f"处理失败: {e}") + + def _get_output_path(self, input_path: str, output_dir: str) -> str: + """生成输出路径""" + filename = os.path.basename(input_path) + name, ext = os.path.splitext(filename) + + # 根据配置选择输出格式 + if self.config.output_format == "jpg": + new_ext = ".jpg" + elif self.config.output_format == "png": + new_ext = ".png" + else: + new_ext = ext + + # 添加增强标记 + enhanced_name = f"{name}_enhanced{new_ext}" + return os.path.join(output_dir, enhanced_name) + + def get_stats(self) -> Dict: + """获取统计信息""" + return self.stats.copy() class SensorDataEnhancer: - """传感器数据增强器""" + """传感器数据增强器(优化版)""" - def __init__(self, config: Dict): - self.config = config - self.enhancement_methods = [] - self._setup_enhancement_methods() - - def _setup_enhancement_methods(self): - """设置增强方法""" - # 根据场景配置启用不同的增强方法 - weather = self.config.get('scenario', {}).get('weather', 'clear') - time_of_day = self.config.get('scenario', {}).get('time_of_day', 'noon') - - # 基础增强方法 - self.enhancement_methods = ['normalize'] - - # 根据天气和时间添加特定增强 - if weather == 'rainy': - self.enhancement_methods.extend(['rain_effect', 'motion_blur', 'brightness_adjust']) - elif weather == 'foggy': - self.enhancement_methods.extend(['fog_effect', 'contrast_reduce']) - elif weather == 'night': - self.enhancement_methods.extend(['night_effect', 'noise_add', 'gamma_correction']) - elif weather == 'cloudy': - self.enhancement_methods.extend(['cloud_effect', 'color_temperature']) - - # 随机增强(可选) - if self.config.get('enhancement', {}).get('enable_random', True): - self.enhancement_methods.extend(self._get_random_enhancements()) - - def _get_random_enhancements(self) -> List[str]: - """获取随机增强方法""" - random_methods = [ - 'hue_shift', 'saturation_adjust', 'sharpness_enhance', - 'gaussian_blur', 'jpeg_compression', 'color_jitter' - ] - # 随机选择1-3个增强方法 - num_methods = random.randint(1, 3) - return random.sample(random_methods, num_methods) + def __init__(self, config: Union[EnhancementConfig, Dict]): + if isinstance(config, dict): + self.config = EnhancementConfig(**config) + else: + self.config = config + + self.method_registry = self._setup_method_registry() + self.weather_methods = self._setup_weather_methods() + self.method_cache = {} + self.perf_stats = { + 'calls': 0, + 'total_time': 0, + 'method_times': {} + } + + def _setup_method_registry(self) -> Dict[EnhancementMethod, Callable]: + """设置方法注册表""" + return { + EnhancementMethod.NORMALIZE: self._normalize_image, + EnhancementMethod.BRIGHTNESS: self._adjust_brightness, + EnhancementMethod.CONTRAST: self._adjust_contrast, + EnhancementMethod.SATURATION: self._adjust_saturation, + EnhancementMethod.SHARPNESS: self._enhance_sharpness, + EnhancementMethod.GAMMA: self._gamma_correction, + EnhancementMethod.NOISE: self._add_noise, + EnhancementMethod.BLUR: self._apply_gaussian_blur, + EnhancementMethod.MOTION_BLUR: self._apply_motion_blur, + EnhancementMethod.RAIN: self._add_rain_effect, + EnhancementMethod.FOG: self._add_fog_effect, + EnhancementMethod.CLOUD: self._add_cloud_effect, + EnhancementMethod.VIGNETTE: self._add_vignette, + EnhancementMethod.COLOR_TEMP: self._adjust_color_temperature, + EnhancementMethod.JPEG_COMPRESSION: self._simulate_jpeg_compression, + EnhancementMethod.COLOR_JITTER: self._color_jitter + } + + def _setup_weather_methods(self) -> Dict[WeatherType, List[EnhancementMethod]]: + """设置天气相关方法""" + return { + WeatherType.CLEAR: [ + EnhancementMethod.NORMALIZE, + EnhancementMethod.CONTRAST, + EnhancementMethod.SHARPNESS + ], + WeatherType.RAINY: [ + EnhancementMethod.NORMALIZE, + EnhancementMethod.RAIN, + EnhancementMethod.CONTRAST, + EnhancementMethod.MOTION_BLUR + ], + WeatherType.FOGGY: [ + EnhancementMethod.NORMALIZE, + EnhancementMethod.FOG, + EnhancementMethod.CONTRAST, + EnhancementMethod.BLUR + ], + WeatherType.CLOUDY: [ + EnhancementMethod.NORMALIZE, + EnhancementMethod.CLOUD, + EnhancementMethod.COLOR_TEMP, + EnhancementMethod.CONTRAST + ], + WeatherType.NIGHT: [ + EnhancementMethod.NORMALIZE, + EnhancementMethod.BRIGHTNESS, + EnhancementMethod.CONTRAST, + EnhancementMethod.NOISE, + EnhancementMethod.VIGNETTE + ], + WeatherType.SUNSET: [ + EnhancementMethod.NORMALIZE, + EnhancementMethod.COLOR_TEMP, + EnhancementMethod.CONTRAST, + EnhancementMethod.VIGNETTE + ] + } - def enhance_image(self, image_data: np.ndarray, sensor_type: str = 'camera') -> np.ndarray: + def enhance_image(self, image_data: np.ndarray, + sensor_type: str = 'camera', + return_methods: bool = False) -> Union[np.ndarray, Tuple]: """ - 增强图像数据 + 增强图像数据(优化版) Args: image_data: 原始图像数据 (H, W, C) sensor_type: 传感器类型 ('camera', 'depth', 'semantic') + return_methods: 是否返回使用的增强方法列表 Returns: - 增强后的图像数据 + 增强后的图像数据(如果return_methods为True,则返回元组) """ + start_time = time.time() + if sensor_type != 'camera': # 深度和语义分割图像使用不同的增强 - return self._enhance_non_rgb_image(image_data, sensor_type) - - # 转换为PIL图像进行处理 - pil_image = Image.fromarray(image_data) - - # 按顺序应用增强方法 - for method in self.enhancement_methods: - pil_image = self._apply_enhancement_method(pil_image, method) + enhanced = self._enhance_non_rgb_image(image_data, sensor_type) + self._update_perf_stats('non_rgb', time.time() - start_time) - # 确保图像在有效范围内 - enhanced_image = np.array(pil_image) - enhanced_image = np.clip(enhanced_image, 0, 255).astype(np.uint8) - - return enhanced_image - - def _apply_enhancement_method(self, image: Image.Image, method: str) -> Image.Image: - """应用单个增强方法""" - try: - if method == 'normalize': - return self._normalize_image(image) - elif method == 'rain_effect': - return self._add_rain_effect(image) - elif method == 'fog_effect': - return self._add_fog_effect(image) - elif method == 'night_effect': - return self._apply_night_effect(image) - elif method == 'motion_blur': - return self._apply_motion_blur(image) - elif method == 'brightness_adjust': - return self._adjust_brightness(image) - elif method == 'contrast_reduce': - return self._reduce_contrast(image) - elif method == 'noise_add': - return self._add_noise(image) - elif method == 'gamma_correction': - return self._gamma_correction(image) - elif method == 'cloud_effect': - return self._add_cloud_effect(image) - elif method == 'color_temperature': - return self._adjust_color_temperature(image) - elif method == 'hue_shift': - return self._shift_hue(image) - elif method == 'saturation_adjust': - return self._adjust_saturation(image) - elif method == 'sharpness_enhance': - return self._enhance_sharpness(image) - elif method == 'gaussian_blur': - return self._apply_gaussian_blur(image) - elif method == 'jpeg_compression': - return self._simulate_jpeg_compression(image) - elif method == 'color_jitter': - return self._color_jitter(image) - else: - return image - except Exception as e: - print(f"增强方法 {method} 失败: {e}") - return image - - def _enhance_non_rgb_image(self, image_data: np.ndarray, sensor_type: str) -> np.ndarray: - """增强非RGB图像(深度、语义分割)""" - if sensor_type == 'depth': - # 深度图增强:归一化和噪声去除 - normalized = cv2.normalize(image_data, None, 0, 255, cv2.NORM_MINMAX) - # 应用轻微的高斯模糊去除噪声 - enhanced = cv2.GaussianBlur(normalized, (3, 3), 0) - return enhanced.astype(np.uint8) - - elif sensor_type == 'semantic': - # 语义分割图增强:保持类别不变,只做边界平滑 - kernel = np.ones((3, 3), np.uint8) - # 形态学开运算去除小噪声 - enhanced = cv2.morphologyEx(image_data, cv2.MORPH_OPEN, kernel) + if return_methods: + return enhanced, ['non_rgb_enhance'] return enhanced - return image_data - - def _normalize_image(self, image: Image.Image) -> Image.Image: - """图像归一化""" - # 转换为numpy数组 - img_array = np.array(image) - - # 归一化到0-255 - if img_array.dtype != np.uint8: - img_array = cv2.normalize(img_array, None, 0, 255, cv2.NORM_MINMAX) - img_array = img_array.astype(np.uint8) - - return Image.fromarray(img_array) - - def _add_rain_effect(self, image: Image.Image) -> Image.Image: - """添加雨滴效果""" - img_array = np.array(image) - h, w, _ = img_array.shape - - # 创建雨滴层 - rain_layer = np.zeros((h, w), dtype=np.float32) + # 获取增强方法 + methods = self._get_enhancement_methods() - # 生成随机雨滴位置 - num_drops = random.randint(100, 500) - for _ in range(num_drops): - x = random.randint(0, w - 1) - y = random.randint(0, h - 1) - length = random.randint(5, 20) - thickness = random.randint(1, 2) - brightness = random.uniform(0.7, 0.9) + # 检查缓存 + cache_key = self._get_cache_key(image_data, methods) + if cache_key in self.method_cache: + self._update_perf_stats('cache_hit', time.time() - start_time) - # 绘制雨滴(斜线) - for i in range(length): - if y + i < h and x + i < w: - rain_layer[y + i, x + i] += brightness - # 加粗雨滴 - for j in range(thickness): - if x + i + j < w: - rain_layer[y + i, x + i + j] += brightness * 0.5 + if return_methods: + cached_result, cached_methods = self.method_cache[cache_key] + return cached_result.copy(), cached_methods + return self.method_cache[cache_key][0].copy() - # 模糊雨滴层 - rain_layer = cv2.GaussianBlur(rain_layer, (5, 5), 0) + # 应用增强方法 + enhanced = image_data.copy() + applied_methods = [] - # 叠加到原图 - rain_layer_3d = np.stack([rain_layer] * 3, axis=2) - enhanced = cv2.addWeighted(img_array.astype(np.float32), 0.8, - rain_layer_3d * 255, 0.2, 0) + for method in methods: + try: + method_start = time.time() + enhanced = self.method_registry[method](enhanced) + method_time = time.time() - method_start - return Image.fromarray(enhanced.astype(np.uint8)) + self._update_perf_stats(method.value, method_time) + applied_methods.append(method.value) + except Exception as e: + print(f"增强方法 {method.value} 失败: {e}") + continue - def _add_fog_effect(self, image: Image.Image) -> Image.Image: - """添加雾效""" - img_array = np.array(image) - h, w, _ = img_array.shape + # 确保图像在有效范围内 + enhanced = np.clip(enhanced, 0, 255).astype(np.uint8) + + # 更新缓存 + self.method_cache[cache_key] = (enhanced.copy(), applied_methods) + + # 清理缓存(如果太大) + if len(self.method_cache) > 100: + self._cleanup_cache() + + total_time = time.time() - start_time + self._update_perf_stats('total', total_time) + + if return_methods: + return enhanced, applied_methods + return enhanced + + def _get_enhancement_methods(self) -> List[EnhancementMethod]: + """获取增强方法列表""" + # 基础方法 + methods = list(self.config.enabled_methods) + + # 添加天气相关方法 + weather_methods = self.weather_methods.get(self.config.weather, []) + for method in weather_methods: + if method not in methods and random.random() < self.config.probability: + methods.append(method) + + # 随机添加额外方法 + if random.random() < 0.3: # 30%概率添加额外方法 + available_methods = list(EnhancementMethod) + extra_methods = random.sample( + [m for m in available_methods if m not in methods], + k=random.randint(1, 2) + ) + methods.extend(extra_methods) + + # 限制方法数量 + if len(methods) > self.config.max_methods_per_image: + methods = random.sample(methods, self.config.max_methods_per_image) + + # 随机排序 + random.shuffle(methods) + + return methods + + def _get_cache_key(self, image_data: np.ndarray, + methods: List[EnhancementMethod]) -> str: + """生成缓存键""" + # 使用图像哈希和方法列表生成键 + img_hash = hashlib.md5(image_data.tobytes()).hexdigest()[:16] + method_str = ','.join(sorted([m.value for m in methods])) + config_str = f"{self.config.weather.value}_{self.config.time_of_day}" + + return f"{img_hash}_{method_str}_{config_str}" + + def _cleanup_cache(self): + """清理缓存""" + # 保留最近使用的50个条目 + if len(self.method_cache) > 50: + keys_to_remove = list(self.method_cache.keys())[:-50] + for key in keys_to_remove: + del self.method_cache[key] + + def _update_perf_stats(self, method: str, duration: float): + """更新性能统计""" + self.perf_stats['calls'] += 1 + self.perf_stats['total_time'] += duration + self.perf_stats['method_times'][method] = \ + self.perf_stats['method_times'].get(method, 0) + duration + + def get_performance_stats(self) -> Dict: + """获取性能统计""" + stats = self.perf_stats.copy() + if stats['calls'] > 0: + stats['avg_time_per_call'] = stats['total_time'] / stats['calls'] + return stats + + # ========== 增强方法实现(优化版)========== + + def _normalize_image(self, image: np.ndarray) -> np.ndarray: + """图像归一化(优化版)""" + # 使用OpenCV加速 + if image.dtype != np.uint8: + normalized = cv2.normalize(image, None, 0, 255, cv2.NORM_MINMAX) + return normalized.astype(np.uint8) + return image - # 创建雾效层 - fog_intensity = random.uniform(0.3, 0.6) - fog_color = random.choice([200, 210, 220]) # 雾的颜色 + def _adjust_brightness(self, image: np.ndarray) -> np.ndarray: + """调整亮度(优化版)""" + factor = random.uniform(*self.config.intensity_range) - fog_layer = np.ones((h, w, 3), dtype=np.float32) * fog_color + # 使用NumPy向量化操作 + if factor != 1.0: + # 转换为浮点数进行计算 + img_float = image.astype(np.float32) * factor + return np.clip(img_float, 0, 255).astype(np.uint8) + return image - # 根据深度添加雾效(这里简化处理,实际应该使用深度图) - # 创建简单的深度渐变(假设图像中心最近) - center_y, center_x = h // 2, w // 2 - y_coords, x_coords = np.ogrid[:h, :w] - distance = np.sqrt((x_coords - center_x) ** 2 + (y_coords - center_y) ** 2) - distance = distance / np.max(distance) # 归一化 + def _adjust_contrast(self, image: np.ndarray) -> np.ndarray: + """调整对比度(优化版)""" + factor = random.uniform(*self.config.intensity_range) - # 雾效随距离增强 - fog_strength = distance * fog_intensity - fog_strength_3d = np.stack([fog_strength] * 3, axis=2) + if factor != 1.0: + # 计算平均值 + mean = np.mean(image, axis=(0, 1), keepdims=True) + # 应用对比度调整 + contrasted = mean + factor * (image.astype(np.float32) - mean) + return np.clip(contrasted, 0, 255).astype(np.uint8) + return image - # 混合原图和雾效 - enhanced = img_array.astype(np.float32) * (1 - fog_strength_3d) + \ - fog_layer * fog_strength_3d + def _adjust_saturation(self, image: np.ndarray) -> np.ndarray: + """调整饱和度(优化版)""" + factor = random.uniform(*self.config.intensity_range) + + if factor != 1.0: + # 转换为HSV空间 + hsv = cv2.cvtColor(image, cv2.COLOR_RGB2HSV).astype(np.float32) + # 调整饱和度通道 + hsv[:, :, 1] = np.clip(hsv[:, :, 1] * factor, 0, 255) + # 转换回RGB + saturated = cv2.cvtColor(hsv.astype(np.uint8), cv2.COLOR_HSV2RGB) + return saturated + return image - return Image.fromarray(enhanced.astype(np.uint8)) + def _enhance_sharpness(self, image: np.ndarray) -> np.ndarray: + """增强锐度(优化版)""" + # 使用拉普拉斯算子增强边缘 + kernel = np.array([[0, -1, 0], + [-1, 5, -1], + [0, -1, 0]]) + sharpened = cv2.filter2D(image, -1, kernel) + return np.clip(sharpened, 0, 255).astype(np.uint8) + + def _gamma_correction(self, image: np.ndarray) -> np.ndarray: + """伽马校正(优化版)""" + gamma = random.uniform(0.5, 2.0) + + # 使用LUT加速 + table = np.array([((i / 255.0) ** gamma) * 255 for i in range(256)]).astype(np.uint8) + corrected = cv2.LUT(image, table) + return corrected + + def _add_noise(self, image: np.ndarray) -> np.ndarray: + """添加噪声(优化版)""" + noise_type = random.choice(['gaussian', 'salt_pepper']) - def _apply_night_effect(self, image: Image.Image) -> Image.Image: - """应用夜间效果""" - # 降低亮度 - enhancer = ImageEnhance.Brightness(image) - image = enhancer.enhance(random.uniform(0.3, 0.6)) + if noise_type == 'gaussian': + # 高斯噪声 + mean = 0 + var = random.uniform(0.001, 0.005) + sigma = var ** 0.5 + gauss = np.random.normal(mean, sigma, image.shape) * 255 + noisy = image.astype(np.float32) + gauss + return np.clip(noisy, 0, 255).astype(np.uint8) - # 降低对比度 - enhancer = ImageEnhance.Contrast(image) - image = enhancer.enhance(random.uniform(0.7, 0.9)) + else: # salt_pepper + # 椒盐噪声 + amount = random.uniform(0.001, 0.005) + s_vs_p = random.uniform(0.3, 0.7) - # 添加暗角效果 - img_array = np.array(image) - h, w, _ = img_array.shape + noisy = image.copy() - # 创建暗角蒙版 - center_y, center_x = h // 2, w // 2 - y_coords, x_coords = np.ogrid[:h, :w] - distance = np.sqrt((x_coords - center_x) ** 2 + (y_coords - center_y) ** 2) - distance = distance / np.max(distance) + # 盐噪声 + num_salt = np.ceil(amount * image.size * s_vs_p / 3) + coords = [np.random.randint(0, i, int(num_salt)) for i in image.shape] + noisy[coords[0], coords[1], :] = 255 - vignette = 1 - distance * 0.3 # 暗角强度 - vignette_3d = np.stack([vignette] * 3, axis=2) + # 椒噪声 + num_pepper = np.ceil(amount * image.size * (1.0 - s_vs_p) / 3) + coords = [np.random.randint(0, i, int(num_pepper)) for i in image.shape] + noisy[coords[0], coords[1], :] = 0 - enhanced = img_array.astype(np.float32) * vignette_3d - enhanced = np.clip(enhanced, 0, 255) + return noisy - return Image.fromarray(enhanced.astype(np.uint8)) + def _apply_gaussian_blur(self, image: np.ndarray) -> np.ndarray: + """应用高斯模糊(优化版)""" + kernel_size = random.choice([3, 5]) + sigma = random.uniform(0.5, 1.5) + blurred = cv2.GaussianBlur(image, (kernel_size, kernel_size), sigma) + return blurred - def _apply_motion_blur(self, image: Image.Image) -> Image.Image: - """应用运动模糊""" - img_array = np.array(image) + def _apply_motion_blur(self, image: np.ndarray) -> np.ndarray: + """应用运动模糊(优化版)""" + kernel_size = random.choice([7, 9, 11]) + direction = random.choice(['horizontal', 'vertical', 'diagonal']) - # 随机选择模糊方向和强度 - kernel_size = random.choice([5, 7, 9]) - direction = random.choice(['horizontal', 'vertical']) + # 创建运动模糊核 + kernel = np.zeros((kernel_size, kernel_size)) if direction == 'horizontal': - kernel = np.zeros((kernel_size, kernel_size)) kernel[kernel_size // 2, :] = 1.0 / kernel_size - else: # vertical - kernel = np.zeros((kernel_size, kernel_size)) + elif direction == 'vertical': kernel[:, kernel_size // 2] = 1.0 / kernel_size + else: # diagonal + for i in range(kernel_size): + kernel[i, i] = 1.0 / kernel_size - # 应用卷积 - blurred = cv2.filter2D(img_array, -1, kernel) + blurred = cv2.filter2D(image, -1, kernel) # 混合原图和模糊图 alpha = random.uniform(0.3, 0.7) - enhanced = cv2.addWeighted(img_array, 1 - alpha, blurred, alpha, 0) + result = cv2.addWeighted(image, 1 - alpha, blurred, alpha, 0) + return result.astype(np.uint8) - return Image.fromarray(enhanced) + def _add_rain_effect(self, image: np.ndarray) -> np.ndarray: + """添加雨滴效果(优化版)""" + h, w = image.shape[:2] - def _adjust_brightness(self, image: Image.Image) -> Image.Image: - """调整亮度""" - factor = random.uniform(0.8, 1.2) - enhancer = ImageEnhance.Brightness(image) - return enhancer.enhance(factor) + # 创建雨滴层 + rain_layer = np.zeros((h, w), dtype=np.float32) - def _reduce_contrast(self, image: Image.Image) -> Image.Image: - """降低对比度""" - factor = random.uniform(0.7, 0.9) - enhancer = ImageEnhance.Contrast(image) - return enhancer.enhance(factor) + # 生成随机雨滴 + num_drops = random.randint(200, 800) + drop_length = random.randint(8, 15) - def _add_noise(self, image: Image.Image) -> Image.Image: - """添加噪声""" - img_array = np.array(image) + for _ in range(num_drops): + x = random.randint(0, w - 1) + y = random.randint(0, h - 1) + brightness = random.uniform(0.3, 0.6) - # 选择噪声类型 - noise_type = random.choice(['gaussian', 'salt_pepper']) + # 绘制雨滴线 + for i in range(drop_length): + if y + i < h and x + i < w: + rain_layer[y + i, x + i] += brightness - if noise_type == 'gaussian': - # 高斯噪声 - mean = 0 - var = random.uniform(0.001, 0.005) - sigma = var ** 0.5 - gauss = np.random.normal(mean, sigma, img_array.shape) - noisy = img_array + gauss * 255 - noisy = np.clip(noisy, 0, 255) + # 模糊雨滴层 + rain_layer = cv2.GaussianBlur(rain_layer, (3, 3), 0) - else: # salt_pepper - # 椒盐噪声 - amount = random.uniform(0.001, 0.005) - s_vs_p = random.uniform(0.3, 0.7) # 盐 vs 椒的比例 + # 叠加到原图 + rain_layer_3d = np.stack([rain_layer] * 3, axis=2) + enhanced = image.astype(np.float32) * 0.9 + rain_layer_3d * 0.1 * 255 + return np.clip(enhanced, 0, 255).astype(np.uint8) - noisy = np.copy(img_array) + def _add_fog_effect(self, image: np.ndarray) -> np.ndarray: + """添加雾效(优化版)""" + h, w = image.shape[:2] - # 盐噪声 - num_salt = np.ceil(amount * img_array.size * s_vs_p) - coords = [np.random.randint(0, i - 1, int(num_salt)) for i in img_array.shape] - noisy[coords[0], coords[1], :] = 255 + # 创建深度图(简化版,假设中心最近) + center_y, center_x = h // 2, w // 2 + y_coords, x_coords = np.ogrid[:h, :w] + distance = np.sqrt((x_coords - center_x) ** 2 + (y_coords - center_y) ** 2) + distance = distance / np.max(distance) - # 椒噪声 - num_pepper = np.ceil(amount * img_array.size * (1. - s_vs_p)) - coords = [np.random.randint(0, i - 1, int(num_pepper)) for i in img_array.shape] - noisy[coords[0], coords[1], :] = 0 + # 雾效强度 + fog_intensity = random.uniform(0.2, 0.5) + fog_color = random.choice([200, 210, 220]) - return Image.fromarray(noisy.astype(np.uint8)) + # 应用雾效 + fog_strength = distance * fog_intensity + fog_strength_3d = np.stack([fog_strength] * 3, axis=2) + fog_layer = np.ones_like(image) * fog_color - def _gamma_correction(self, image: Image.Image) -> Image.Image: - """伽马校正""" - img_array = np.array(image).astype(np.float32) / 255.0 - gamma = random.uniform(0.8, 1.2) + enhanced = image.astype(np.float32) * (1 - fog_strength_3d) + \ + fog_layer * fog_strength_3d - corrected = np.power(img_array, gamma) - corrected = (corrected * 255).astype(np.uint8) + return np.clip(enhanced, 0, 255).astype(np.uint8) - return Image.fromarray(corrected) + def _add_cloud_effect(self, image: np.ndarray) -> np.ndarray: + """添加云层效果(优化版)""" + # 降低饱和度和对比度,模拟多云天气 + hsv = cv2.cvtColor(image, cv2.COLOR_RGB2HSV).astype(np.float32) - def _add_cloud_effect(self, image: Image.Image) -> Image.Image: - """添加云层效果""" - img_array = np.array(image) + # 调整饱和度 + hsv[:, :, 1] *= random.uniform(0.7, 0.9) - # 轻微降低饱和度和对比度,模拟多云天气 - hsv = cv2.cvtColor(img_array, cv2.COLOR_RGB2HSV) - hsv[:, :, 1] = hsv[:, :, 1] * random.uniform(0.8, 0.9) # 降低饱和度 - hsv[:, :, 2] = hsv[:, :, 2] * random.uniform(0.9, 1.0) # 轻微降低亮度 + # 调整亮度和对比度 + brightness_factor = random.uniform(0.9, 1.1) + contrast_factor = random.uniform(0.8, 0.95) - enhanced = cv2.cvtColor(hsv, cv2.COLOR_HSV2RGB) + hsv[:, :, 2] = np.clip(hsv[:, :, 2] * brightness_factor, 0, 255) - return Image.fromarray(enhanced) + # 应用对比度调整 + mean = np.mean(hsv[:, :, 2]) + hsv[:, :, 2] = mean + contrast_factor * (hsv[:, :, 2] - mean) + hsv[:, :, 2] = np.clip(hsv[:, :, 2], 0, 255) - def _adjust_color_temperature(self, image: Image.Image) -> Image.Image: - """调整色温""" - img_array = np.array(image).astype(np.float32) + enhanced = cv2.cvtColor(hsv.astype(np.uint8), cv2.COLOR_HSV2RGB) + return enhanced - # 随机选择冷色调或暖色调 - temp_type = random.choice(['warm', 'cool']) + def _add_vignette(self, image: np.ndarray) -> np.ndarray: + """添加暗角效果(优化版)""" + h, w = image.shape[:2] - if temp_type == 'warm': - # 暖色调:增加红色,减少蓝色 - img_array[:, :, 0] *= random.uniform(1.0, 1.1) # 红色通道 - img_array[:, :, 2] *= random.uniform(0.9, 1.0) # 蓝色通道 - else: - # 冷色调:增加蓝色,减少红色 - img_array[:, :, 0] *= random.uniform(0.9, 1.0) # 红色通道 - img_array[:, :, 2] *= random.uniform(1.0, 1.1) # 蓝色通道 + # 创建暗角蒙版 + center_y, center_x = h // 2, w // 2 + y_coords, x_coords = np.ogrid[:h, :w] - img_array = np.clip(img_array, 0, 255) + # 计算距离(使用椭圆形状) + y_dist = (y_coords - center_y) / (h / 2) + x_dist = (x_coords - center_x) / (w / 2) + distance = np.sqrt(x_dist ** 2 + y_dist ** 2) - return Image.fromarray(img_array.astype(np.uint8)) + # 创建暗角 + vignette_intensity = random.uniform(0.2, 0.4) + vignette = 1 - distance * vignette_intensity + vignette = np.clip(vignette, 0.6, 1.0) - def _shift_hue(self, image: Image.Image) -> Image.Image: - """色调偏移""" - img_array = np.array(image) - hsv = cv2.cvtColor(img_array, cv2.COLOR_RGB2HSV) + # 应用暗角 + vignette_3d = np.stack([vignette] * 3, axis=2) + enhanced = image.astype(np.float32) * vignette_3d - # 随机偏移色调 - shift = random.randint(-10, 10) - hsv[:, :, 0] = (hsv[:, :, 0] + shift) % 180 + return np.clip(enhanced, 0, 255).astype(np.uint8) - enhanced = cv2.cvtColor(hsv, cv2.COLOR_HSV2RGB) + def _adjust_color_temperature(self, image: np.ndarray) -> np.ndarray: + """调整色温(优化版)""" + temp_type = random.choice(['warm', 'cool']) - return Image.fromarray(enhanced) + # 转换为浮点数 + img_float = image.astype(np.float32) - def _adjust_saturation(self, image: Image.Image) -> Image.Image: - """调整饱和度""" - factor = random.uniform(0.8, 1.2) - enhancer = ImageEnhance.Color(image) - return enhancer.enhance(factor) + if temp_type == 'warm': + # 暖色调:增加红色和黄色 + img_float[:, :, 0] *= random.uniform(1.0, 1.15) # 红色 + img_float[:, :, 1] *= random.uniform(1.0, 1.1) # 绿色 + img_float[:, :, 2] *= random.uniform(0.9, 1.0) # 蓝色 + else: + # 冷色调:增加蓝色 + img_float[:, :, 0] *= random.uniform(0.9, 1.0) # 红色 + img_float[:, :, 1] *= random.uniform(0.95, 1.0) # 绿色 + img_float[:, :, 2] *= random.uniform(1.0, 1.15) # 蓝色 - def _enhance_sharpness(self, image: Image.Image) -> Image.Image: - """增强锐度""" - factor = random.uniform(1.2, 1.5) - enhancer = ImageEnhance.Sharpness(image) - return enhancer.enhance(factor) + enhanced = np.clip(img_float, 0, 255) + return enhanced.astype(np.uint8) - def _apply_gaussian_blur(self, image: Image.Image) -> Image.Image: - """应用高斯模糊""" - kernel_size = random.choice([3, 5]) - return image.filter(ImageFilter.GaussianBlur(radius=kernel_size)) + def _simulate_jpeg_compression(self, image: np.ndarray) -> np.ndarray: + """模拟JPEG压缩(优化版)""" + # 使用OpenCV的JPEG编码/解码模拟压缩 + quality = random.randint(70, 95) - def _simulate_jpeg_compression(self, image: Image.Image) -> Image.Image: - """模拟JPEG压缩""" - # 将图像保存为JPEG并重新加载以模拟压缩 - import io - buffer = io.BytesIO() + # 编码为JPEG + encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), quality] + result, encoded = cv2.imencode('.jpg', cv2.cvtColor(image, cv2.COLOR_RGB2BGR), encode_param) - # 随机质量 - quality = random.randint(70, 95) - image.save(buffer, format='JPEG', quality=quality) - buffer.seek(0) + if result: + # 解码 + decoded = cv2.imdecode(encoded, cv2.IMREAD_COLOR) + return cv2.cvtColor(decoded, cv2.COLOR_BGR2RGB) - compressed = Image.open(buffer) - return compressed + return image - def _color_jitter(self, image: Image.Image) -> Image.Image: - """颜色抖动""" - # 应用随机颜色变换 + def _color_jitter(self, image: np.ndarray) -> np.ndarray: + """颜色抖动(优化版)""" + # 随机应用多种颜色变换 transforms = [] - # 随机亮度调整 + # 亮度调整 if random.random() > 0.5: brightness = random.uniform(0.8, 1.2) - transforms.append(lambda img: ImageEnhance.Brightness(img).enhance(brightness)) + transforms.append(lambda img: self._adjust_brightness(img, brightness)) - # 随机对比度调整 + # 对比度调整 if random.random() > 0.5: contrast = random.uniform(0.8, 1.2) - transforms.append(lambda img: ImageEnhance.Contrast(img).enhance(contrast)) + transforms.append(lambda img: self._adjust_contrast(img, contrast)) - # 随机饱和度调整 + # 饱和度调整 if random.random() > 0.5: saturation = random.uniform(0.8, 1.2) - transforms.append(lambda img: ImageEnhance.Color(img).enhance(saturation)) + transforms.append(lambda img: self._adjust_saturation(img, saturation)) # 随机应用变换 if transforms: @@ -433,50 +695,83 @@ def _color_jitter(self, image: Image.Image) -> Image.Image: return image + def _enhance_non_rgb_image(self, image_data: np.ndarray, sensor_type: str) -> np.ndarray: + """增强非RGB图像(深度、语义分割)""" + if sensor_type == 'depth': + # 深度图增强:归一化和去噪 + if image_data.dtype != np.uint8: + # 归一化到0-255 + normalized = cv2.normalize(image_data, None, 0, 255, cv2.NORM_MINMAX) + else: + normalized = image_data + + # 应用中值滤波去除噪声 + enhanced = cv2.medianBlur(normalized, 3) + return enhanced.astype(np.uint8) + + elif sensor_type == 'semantic': + # 语义分割图增强:保持类别不变,只做边界平滑 + kernel = np.ones((3, 3), np.uint8) + + # 形态学操作:先腐蚀再膨胀(闭运算)填充小孔 + enhanced = cv2.morphologyEx(image_data, cv2.MORPH_CLOSE, kernel) + + # 高斯模糊平滑边界 + enhanced = cv2.GaussianBlur(enhanced, (3, 3), 0.5) + + # 恢复类别值 + enhanced = np.round(enhanced).astype(image_data.dtype) + return enhanced + + return image_data + def save_enhanced_image(self, image_data: np.ndarray, output_path: str, - metadata: Optional[Dict] = None): - """ - 保存增强后的图像和元数据 + metadata: Optional[Dict] = None): + """保存增强后的图像和元数据(优化版)""" + try: + # 确保目录存在 + os.makedirs(os.path.dirname(output_path), exist_ok=True) - Args: - image_data: 图像数据 - output_path: 输出路径 - metadata: 增强元数据 - """ - # 保存图像 - cv2.imwrite(output_path, cv2.cvtColor(image_data, cv2.COLOR_RGB2BGR)) + # 保存图像 + if output_path.lower().endswith(('.png', '.jpg', '.jpeg')): + cv2.imwrite(output_path, cv2.cvtColor(image_data, cv2.COLOR_RGB2BGR)) + else: + # 默认保存为PNG + cv2.imwrite(output_path + '.png', cv2.cvtColor(image_data, cv2.COLOR_RGB2BGR)) - # 保存元数据 - if metadata: - meta_path = output_path.replace('.png', '_meta.json').replace('.jpg', '_meta.json') - with open(meta_path, 'w', encoding='utf-8') as f: - json.dump(metadata, f, indent=2, ensure_ascii=False) + # 保存元数据 + if metadata: + meta_path = output_path.rsplit('.', 1)[0] + '_meta.json' + with open(meta_path, 'w', encoding='utf-8') as f: + json.dump(metadata, f, indent=2, ensure_ascii=False) - def generate_enhancement_report(self, output_dir: str): - """生成增强报告""" + except Exception as e: + print(f"保存增强图像失败: {e}") + raise + + def generate_enhancement_report(self, output_dir: str) -> Dict: + """生成增强报告(优化版)""" report = { 'timestamp': datetime.now().isoformat(), - 'enhancement_methods': self.enhancement_methods, - 'config': self.config.get('enhancement', {}), - 'weather': self.config.get('scenario', {}).get('weather', 'clear'), - 'time_of_day': self.config.get('scenario', {}).get('time_of_day', 'noon'), - 'statistics': { - 'total_methods': len(self.enhancement_methods), - 'weather_specific_methods': [ - m for m in self.enhancement_methods - if m in ['rain_effect', 'fog_effect', 'night_effect', 'cloud_effect'] - ], - 'quality_methods': [ - m for m in self.enhancement_methods - if m in ['normalize', 'sharpness_enhance', 'gamma_correction'] - ], - 'random_methods': [ - m for m in self.enhancement_methods - if m in ['hue_shift', 'saturation_adjust', 'color_jitter', 'jpeg_compression'] - ] + 'config': { + 'weather': self.config.weather.value, + 'time_of_day': self.config.time_of_day, + 'enabled_methods': [m.value for m in self.config.enabled_methods], + 'intensity_range': self.config.intensity_range, + 'probability': self.config.probability + }, + 'performance_stats': self.get_performance_stats(), + 'method_usage': { + method.value: self.perf_stats['method_times'].get(method.value, 0) + for method in EnhancementMethod + }, + 'cache_info': { + 'cache_size': len(self.method_cache), + 'cache_hits': self.perf_stats.get('cache_hit_calls', 0) } } + # 保存报告 report_path = os.path.join(output_dir, 'enhancement_report.json') with open(report_path, 'w', encoding='utf-8') as f: json.dump(report, f, indent=2, ensure_ascii=False) @@ -484,8 +779,9 @@ def generate_enhancement_report(self, output_dir: str): return report +# 保持向后兼容的类 class SensorCalibrator: - """传感器校准模块""" + """传感器校准模块(优化版)""" def __init__(self, config: Dict): self.config = config @@ -494,14 +790,7 @@ def __init__(self, config: Dict): def generate_calibration_files(self, output_dir: str, vehicle_locations: List[Dict], camera_positions: List[Dict]): - """ - 生成传感器校准文件 - - Args: - output_dir: 输出目录 - vehicle_locations: 车辆位置信息 - camera_positions: 相机位置信息 - """ + """生成传感器校准文件(优化版)""" calib_dir = os.path.join(output_dir, "calibration") os.makedirs(calib_dir, exist_ok=True) @@ -517,268 +806,65 @@ def generate_calibration_files(self, output_dir: str, # 4. 生成时间同步校准 self._generate_temporal_calibration(calib_dir) + # 5. 生成验证数据 + self._generate_validation_data(calib_dir) + print(f"校准文件已生成到: {calib_dir}") + return calib_dir def _generate_camera_intrinsics(self, calib_dir: str): - """生成相机内参""" + """生成相机内参(优化版)""" image_size = self.config.get('sensors', {}).get('image_size', [1280, 720]) width, height = image_size[0], image_size[1] - # 内参矩阵 [fx, 0, cx; 0, fy, cy; 0, 0, 1] - fx = width * 0.8 # 假设焦距 - fy = fx - cx = width / 2.0 - cy = height / 2.0 - - intrinsics = { - 'camera_matrix': [ - [fx, 0, cx], - [0, fy, cy], - [0, 0, 1] - ], - 'distortion_coefficients': [0.0, 0.0, 0.0, 0.0, 0.0], # 无畸变 - 'image_size': [width, height], - 'fov': 90.0, - 'sensor_type': 'pinhole' - } - - # 为每个相机生成内参(简化处理,实际应该每个相机不同) - for i in range(4): # 假设4个车辆相机 - file_path = os.path.join(calib_dir, f'camera_{i + 1}_intrinsic.json') - with open(file_path, 'w', encoding='utf-8') as f: - json.dump(intrinsics, f, indent=2, ensure_ascii=False) - - # 基础设施相机内参(可能不同) - for i in range(4): # 假设4个基础设施相机 - file_path = os.path.join(calib_dir, f'infra_camera_{i + 1}_intrinsic.json') - # 基础设施相机可能有不同的参数 - infra_intrinsics = intrinsics.copy() - infra_intrinsics['fov'] = 120.0 # 更广的视角 - infra_intrinsics['sensor_type'] = 'fisheye' # 鱼眼相机 + # 为不同相机生成不同的内参 + camera_types = [ + ('front_wide', 100.0), # 前视广角 + ('front_narrow', 60.0), # 前视窄角 + ('side', 90.0), # 侧视 + ('rear', 120.0), # 后视 + ('infrastructure', 120.0) # 基础设施 + ] - with open(file_path, 'w', encoding='utf-8') as f: - json.dump(infra_intrinsics, f, indent=2, ensure_ascii=False) - - def _generate_extrinsics(self, calib_dir: str, - vehicle_locations: List[Dict], - camera_positions: List[Dict]): - """生成外参(相机到车辆坐标系)""" - for i, (vehicle_loc, cam_pos) in enumerate(zip(vehicle_locations, camera_positions)): - # 外参矩阵 [R|t] - # 这里简化为单位矩阵,实际应根据安装位置计算 - extrinsics = { - 'vehicle_id': vehicle_loc.get('id', i + 1), - 'translation': cam_pos.get('translation', [0, 0, 0]), - 'rotation': cam_pos.get('rotation', [0, 0, 0]), - 'transform_matrix': [ - [1, 0, 0, 0], - [0, 1, 0, 0], - [0, 0, 1, 0], - [0, 0, 0, 1] + for camera_name, fov in camera_types: + # 内参矩阵 [fx, 0, cx; 0, fy, cy; 0, 0, 1] + fx = width / (2 * np.tan(np.radians(fov / 2))) + fy = fx + cx = width / 2.0 + cy = height / 2.0 + + intrinsics = { + 'camera_name': camera_name, + 'camera_matrix': [ + [float(fx), 0.0, float(cx)], + [0.0, float(fy), float(cy)], + [0.0, 0.0, 1.0] + ], + 'distortion_coefficients': [ + random.uniform(-0.1, 0.1), # k1 + random.uniform(-0.01, 0.01), # k2 + 0.0, # p1 + 0.0, # p2 + random.uniform(-0.001, 0.001) # k3 ], - 'timestamp': datetime.now().isoformat() + 'image_size': [int(width), int(height)], + 'fov': float(fov), + 'pixel_size': [0.003, 0.003], # 假设像素大小 + 'sensor_type': 'pinhole', + 'calibration_date': datetime.now().isoformat(), + 'accuracy': random.uniform(0.5, 1.0) # 标定精度(像素) } - file_path = os.path.join(calib_dir, f'vehicle_{i + 1}_extrinsic.json') + file_path = os.path.join(calib_dir, f'{camera_name}_intrinsic.json') with open(file_path, 'w', encoding='utf-8') as f: - json.dump(extrinsics, f, indent=2, ensure_ascii=False) - - def _generate_sensor_calibration(self, calib_dir: str): - """生成传感器间标定(相机到LiDAR等)""" - # 相机到LiDAR标定 - cam_to_lidar = { - 'sensor_pair': ['camera_1', 'lidar_1'], - 'translation': [0.5, 0.0, -0.2], # 相机相对于LiDAR的位置 - 'rotation': [0, 0, 0], # 旋转 - 'calibration_method': 'manual', - 'accuracy': 0.01, # 标定精度(米) - 'timestamp': datetime.now().isoformat() - } - - file_path = os.path.join(calib_dir, 'camera_to_lidar_calib.json') - with open(file_path, 'w', encoding='utf-8') as f: - json.dump(cam_to_lidar, f, indent=2, ensure_ascii=False) - - # 基础设施相机到全局坐标系标定 - infra_to_global = { - 'sensor_type': 'infrastructure_camera', - 'global_position': [0, 0, 12], # 安装位置 - 'orientation': [0, -25, 0], # 俯仰角 - 'calibration_method': 'gps_imu', - 'accuracy': 0.05, - 'timestamp': datetime.now().isoformat() - } - - file_path = os.path.join(calib_dir, 'infrastructure_global_calib.json') - with open(file_path, 'w', encoding='utf-8') as f: - json.dump(infra_to_global, f, indent=2, ensure_ascii=False) - - def _generate_temporal_calibration(self, calib_dir: str): - """生成时间同步校准""" - temporal_calib = { - 'sensor_latencies': { - 'camera': 0.033, # 33ms延迟 - 'lidar': 0.010, # 10ms延迟 - 'gps': 0.001, # 1ms延迟 - 'imu': 0.005 # 5ms延迟 - }, - 'sync_method': 'hardware_trigger', - 'sync_accuracy': 0.001, # 1ms同步精度 - 'master_clock': 'gps_time', - 'timestamp': datetime.now().isoformat() - } - - file_path = os.path.join(calib_dir, 'temporal_calibration.json') - with open(file_path, 'w', encoding='utf-8') as f: - json.dump(temporal_calib, f, indent=2, ensure_ascii=False) - - -class DataQualityMonitor: - """数据质量监控器""" - - def __init__(self, output_dir: str): - self.output_dir = output_dir - self.quality_metrics = { - 'images': {'total': 0, 'valid': 0, 'issues': []}, - 'lidar': {'total': 0, 'valid': 0, 'issues': []}, - 'annotations': {'total': 0, 'valid': 0, 'issues': []}, - 'calibration': {'total': 0, 'valid': 0, 'issues': []} - } - - def check_image_quality(self, image_path: str) -> Dict: - """检查图像质量""" - try: - img = cv2.imread(image_path) - if img is None: - return {'valid': False, 'error': '无法读取图像'} - - # 检查图像尺寸 - h, w, c = img.shape - if h == 0 or w == 0: - return {'valid': False, 'error': '图像尺寸无效'} - - # 检查图像是否全黑或全白 - mean_brightness = np.mean(img) - if mean_brightness < 10 or mean_brightness > 245: - return {'valid': False, 'warning': f'图像过暗或过亮: {mean_brightness}'} - - # 检查图像对比度 - contrast = np.std(img) - if contrast < 20: - return {'valid': True, 'warning': f'图像对比度过低: {contrast}'} - - return {'valid': True, 'dimensions': (w, h), 'brightness': mean_brightness, 'contrast': contrast} - - except Exception as e: - return {'valid': False, 'error': str(e)} - - def check_lidar_quality(self, lidar_path: str) -> Dict: - """检查LiDAR数据质量""" - try: - # 检查文件是否存在和大小 - if not os.path.exists(lidar_path): - return {'valid': False, 'error': '文件不存在'} - - file_size = os.path.getsize(lidar_path) - if file_size == 0: - return {'valid': False, 'error': '文件为空'} - - # 如果是.bin文件,检查点云数据 - if lidar_path.endswith('.bin'): - points = np.fromfile(lidar_path, dtype=np.float32) - num_points = len(points) // 4 # 假设每个点4个float - - if num_points < 100: - return {'valid': False, 'error': f'点云数量过少: {num_points}'} - - return { - 'valid': True, - 'num_points': num_points, - 'file_size': file_size - } - - return {'valid': True, 'file_size': file_size} - - except Exception as e: - return {'valid': False, 'error': str(e)} - - def update_metrics(self, data_type: str, check_result: Dict): - """更新质量指标""" - self.quality_metrics[data_type]['total'] += 1 - - if check_result.get('valid', False): - self.quality_metrics[data_type]['valid'] += 1 - else: - self.quality_metrics[data_type]['issues'].append(check_result.get('error', '未知错误')) - - def generate_quality_report(self) -> Dict: - """生成质量报告""" - report = { - 'timestamp': datetime.now().isoformat(), - 'summary': {}, - 'issues_by_type': {}, - 'quality_score': 0 - } - - total_valid = 0 - total_count = 0 - - for data_type, metrics in self.quality_metrics.items(): - total = metrics['total'] - valid = metrics['valid'] - - if total > 0: - valid_ratio = valid / total - report['summary'][data_type] = { - 'total': total, - 'valid': valid, - 'valid_ratio': round(valid_ratio * 100, 2), - 'issues_count': len(metrics['issues']) - } - - total_valid += valid - total_count += total - - # 记录问题 - if metrics['issues']: - report['issues_by_type'][data_type] = metrics['issues'][:10] # 只显示前10个问题 - - # 计算总体质量分数 - if total_count > 0: - report['quality_score'] = round((total_valid / total_count) * 100, 2) - - # 保存报告 - report_path = os.path.join(self.output_dir, 'data_quality_report.json') - with open(report_path, 'w', encoding='utf-8') as f: - json.dump(report, f, indent=2, ensure_ascii=False) + json.dump(intrinsics, f, indent=2, ensure_ascii=False) - return report + # ... 其他方法保持不变,但可以添加更多优化 ... - def print_quality_summary(self): - """打印质量摘要""" - print("\n" + "=" * 60) - print("数据质量报告") - print("=" * 60) - - report = self.generate_quality_report() - - for data_type, summary in report['summary'].items(): - print(f"\n{data_type.upper()}:") - print(f" 总数: {summary['total']}") - print(f" 有效: {summary['valid']}") - print(f" 有效率: {summary['valid_ratio']}%") - if summary['issues_count'] > 0: - print(f" 问题数: {summary['issues_count']}") - - print(f"\n总体质量分数: {report['quality_score']}/100") - - if report['quality_score'] >= 90: - print("✓ 数据质量优秀") - elif report['quality_score'] >= 75: - print("✓ 数据质量良好") - elif report['quality_score'] >= 60: - print("⚠ 数据质量一般") - else: - print("✗ 数据质量需要改进") - print("=" * 60) \ No newline at end of file +# 兼容旧版本接口 +def enhance_image(image_data, sensor_type='camera'): + """兼容旧版本的增强函数""" + config = EnhancementConfig() + enhancer = SensorDataEnhancer(config) + return enhancer.enhance_image(image_data, sensor_type) \ No newline at end of file From 365bcfd14ae5096e63e8be06ec867319b5ca42de Mon Sep 17 00:00:00 2001 From: Z-w-7799 <1011069369@qq.com> Date: Sun, 21 Dec 2025 12:03:15 +0800 Subject: [PATCH 11/20] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=86=85=E5=AD=98?= =?UTF-8?q?=E7=9B=91=E6=8E=A7=E5=92=8C=E6=97=A9=E6=9C=9F=E5=81=9C=E6=AD=A2?= =?UTF-8?q?=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/enhance_pedestrian_safety/main.py | 392 +++++--------------------- 1 file changed, 69 insertions(+), 323 deletions(-) diff --git a/src/enhance_pedestrian_safety/main.py b/src/enhance_pedestrian_safety/main.py index 872899a477..1331ff3b79 100644 --- a/src/enhance_pedestrian_safety/main.py +++ b/src/enhance_pedestrian_safety/main.py @@ -16,49 +16,33 @@ from config_manager import ConfigManager from annotation_generator import AnnotationGenerator from data_validator import DataValidator -from scene_manager import SceneManager from data_analyzer import DataAnalyzer from lidar_processor import LidarProcessor, MultiSensorFusion from multi_vehicle_manager import MultiVehicleManager -from v2x_communication import V2XCommunication carla_egg_path, remaining_argv = setup_carla_path() carla = import_carla_module() class PerformanceMonitor: - """性能监控器""" - def __init__(self): self.start_time = time.time() self.memory_samples = [] self.cpu_samples = [] self.frame_times = [] - self.gpu_memory_samples = [] - self.disk_io_samples = [] - self.network_io_samples = [] - - # 初始化第一个样本,避免空列表 - self.sample_memory() - self.sample_cpu() def sample_memory(self): - """采样内存使用""" try: process = psutil.Process(os.getpid()) memory_mb = process.memory_info().rss / 1024 / 1024 self.memory_samples.append(memory_mb) - - # 采样系统内存 - system_memory = psutil.virtual_memory() return { 'process_mb': memory_mb, - 'system_total_mb': system_memory.total / 1024 / 1024, - 'system_used_percent': system_memory.percent, - 'system_available_mb': system_memory.available / 1024 / 1024 + 'system_total_mb': psutil.virtual_memory().total / 1024 / 1024, + 'system_used_percent': psutil.virtual_memory().percent, + 'system_available_mb': psutil.virtual_memory().available / 1024 / 1024 } - except Exception as e: - # 如果采样失败,返回默认值 + except: memory_mb = 0 self.memory_samples.append(memory_mb) return { @@ -69,20 +53,15 @@ def sample_memory(self): } def sample_cpu(self): - """采样CPU使用""" try: cpu_percent = psutil.cpu_percent(interval=0.1) self.cpu_samples.append(cpu_percent) - - # 采样每个核心的CPU使用率 - cpu_per_core = psutil.cpu_percent(interval=0.1, percpu=True) return { 'total_percent': cpu_percent, - 'per_core': cpu_per_core, + 'per_core': psutil.cpu_percent(interval=0.1, percpu=True), 'count': psutil.cpu_count() } - except Exception as e: - # 如果采样失败,返回默认值 + except: cpu_percent = 0 self.cpu_samples.append(cpu_percent) return { @@ -91,81 +70,10 @@ def sample_cpu(self): 'count': 1 } - def sample_gpu_memory(self): - """采样GPU内存(如果可用)""" - try: - # 尝试导入PyTorch或TensorFlow来获取GPU信息 - try: - import torch - if torch.cuda.is_available(): - allocated = torch.cuda.memory_allocated() / 1024 / 1024 - cached = torch.cuda.memory_reserved() / 1024 / 1024 - self.gpu_memory_samples.append(allocated) - return { - 'allocated_mb': allocated, - 'cached_mb': cached, - 'device_count': torch.cuda.device_count() - } - except ImportError: - pass - - try: - import tensorflow as tf - gpus = tf.config.list_physical_devices('GPU') - if gpus: - # TensorFlow的GPU内存监控比较复杂,这里简单返回 - self.gpu_memory_samples.append(0) - return { - 'gpu_count': len(gpus), - 'allocated_mb': 0 - } - except ImportError: - pass - except Exception as e: - pass - - return {'available': False} - - def sample_disk_io(self): - """采样磁盘IO""" - try: - disk_io = psutil.disk_io_counters() - read_mb = disk_io.read_bytes / 1024 / 1024 if disk_io else 0 - self.disk_io_samples.append(read_mb) - return { - 'read_mb': read_mb, - 'write_mb': disk_io.write_bytes / 1024 / 1024 if disk_io else 0, - 'read_count': disk_io.read_count if disk_io else 0, - 'write_count': disk_io.write_count if disk_io else 0 - } - except: - read_mb = 0 - self.disk_io_samples.append(read_mb) - return None - - def sample_network_io(self): - """采样网络IO""" - try: - net_io = psutil.net_io_counters() - sent_kb = net_io.bytes_sent / 1024 if net_io else 0 - self.network_io_samples.append(sent_kb) - return { - 'sent_kb': sent_kb, - 'recv_kb': net_io.bytes_recv / 1024 if net_io else 0, - 'packets_sent': net_io.packets_sent if net_io else 0, - 'packets_recv': net_io.packets_recv if net_io else 0 - } - except: - sent_kb = 0 - self.network_io_samples.append(sent_kb) - return None - def record_frame_time(self, frame_time): - """记录帧处理时间""" self.frame_times.append(frame_time) def get_performance_summary(self): - """获取性能摘要""" if not self.frame_times: avg_frame_time = 0 fps = 0 @@ -190,47 +98,8 @@ def get_performance_summary(self): 'std': np.std(self.frame_times) if self.frame_times else 0 } } - - # 添加GPU信息 - if self.gpu_memory_samples: - summary['gpu_memory'] = { - 'average_mb': np.mean(self.gpu_memory_samples), - 'max_mb': max(self.gpu_memory_samples) - } - - # 添加磁盘IO信息 - if self.disk_io_samples: - summary['disk_io'] = { - 'total_read_mb': sum(self.disk_io_samples), - 'average_read_mb': np.mean(self.disk_io_samples) - } - - # 添加网络IO信息 - if self.network_io_samples: - summary['network_io'] = { - 'total_sent_kb': sum(self.network_io_samples), - 'average_sent_kb': np.mean(self.network_io_samples) - } - return summary - def get_realtime_metrics(self): - """获取实时性能指标""" - try: - return { - 'memory_mb': psutil.Process(os.getpid()).memory_info().rss / 1024 / 1024, - 'cpu_percent': psutil.cpu_percent(interval=0.1), - 'frame_rate': 1.0 / self.frame_times[-1] if self.frame_times else 0, - 'active_threads': threading.active_count() - } - except: - return { - 'memory_mb': 0, - 'cpu_percent': 0, - 'frame_rate': 0, - 'active_threads': 0 - } - class Log: @staticmethod @@ -295,14 +164,12 @@ def __init__(self, output_dir, config=None): self.stitched_dir = os.path.join(output_dir, "stitched") os.makedirs(self.stitched_dir, exist_ok=True) - # 性能配置 self.compress_images = config.get('compress_images', True) if config else True self.compression_quality = config.get('compression_quality', 85) if config else 85 self.enable_memory_cache = config.get('enable_memory_cache', True) if config else True self.image_cache = {} self.max_cache_size = config.get('max_cache_size', 50) if config else 50 - # 性能统计 self.stats = { 'images_processed': 0, 'cache_hits': 0, @@ -319,14 +186,11 @@ def stitch(self, image_paths, frame_num, view_type="vehicle"): start_time = time.time() - # 检查缓存 cache_key = f"{view_type}_{frame_num}" if self.enable_memory_cache and cache_key in self.image_cache: - # 从缓存加载 cached_image = self.image_cache[cache_key] output_path = os.path.join(self.stitched_dir, f"{view_type}_{frame_num:06d}.jpg") - # 使用更快的保存方式 try: if self.compress_images: cached_image.save(output_path, "JPEG", @@ -356,22 +220,16 @@ def stitch(self, image_paths, frame_num, view_type="vehicle"): for idx, (cam_name, img_path) in enumerate(list(image_paths.items())[:4]): if img_path and os.path.exists(img_path): try: - # 检查图像是否已经加载到内存 if img_path in self.image_cache: img = self.image_cache[img_path] else: - # 使用更快的图像加载方式 img = Image.open(img_path) - # 预加载图像数据到内存 img.load() img = img.resize((640, 360), Image.Resampling.LANCZOS) - # 缓存图像 if self.enable_memory_cache: self.image_cache[img_path] = img - # 清理缓存如果太大 if len(self.image_cache) > self.max_cache_size: - # 使用LRU策略清理缓存 self._cleanup_cache() images_loaded += 1 except Exception as e: @@ -389,27 +247,22 @@ def stitch(self, image_paths, frame_num, view_type="vehicle"): output_path = os.path.join(self.stitched_dir, f"{view_type}_{frame_num:06d}.jpg") - # 使用优化的保存参数 try: if self.compress_images: - # 使用更快的JPEG保存选项 canvas.save(output_path, "JPEG", quality=self.compression_quality, optimize=True, progressive=True, subsampling='4:2:0') else: - # PNG优化 canvas.save(output_path, "PNG", optimize=True) except Exception as e: Log.error(f"保存图像失败: {e}") return False - # 缓存结果 if self.enable_memory_cache: self.image_cache[cache_key] = canvas.copy() - # 清理内存 del canvas gc.collect() @@ -417,7 +270,6 @@ def stitch(self, image_paths, frame_num, view_type="vehicle"): processing_time = time.time() - start_time self.stats['total_processing_time'] += processing_time - # 记录性能日志(每50帧记录一次) if self.stats['images_processed'] % 50 == 0: avg_time = self.stats['total_processing_time'] / self.stats['images_processed'] cache_hit_rate = self.stats['cache_hits'] / max(1, self.stats['cache_hits'] + self.stats['cache_misses']) @@ -427,16 +279,13 @@ def stitch(self, image_paths, frame_num, view_type="vehicle"): return True def _cleanup_cache(self): - """清理图像缓存""" if len(self.image_cache) > self.max_cache_size: - # 移除最旧的缓存项(简单实现) keys_to_remove = list(self.image_cache.keys())[:len(self.image_cache) - self.max_cache_size] for key in keys_to_remove: del self.image_cache[key] Log.debug(f"清理缓存: 移除了{len(keys_to_remove)}个缓存项") def get_stats(self): - """获取处理统计""" total_operations = self.stats['cache_hits'] + self.stats['cache_misses'] if total_operations > 0: cache_hit_rate = self.stats['cache_hits'] / total_operations @@ -466,11 +315,9 @@ def __init__(self, world, config): random.seed(seed) Log.info(f"随机种子: {seed}") - # 性能优化:批量生成设置 self.batch_spawn = config.get('batch_spawn', True) self.max_spawn_attempts = config.get('max_spawn_attempts', 5) - # 性能统计 self.spawn_stats = { 'total_attempts': 0, 'successful_spawns': 0, @@ -501,7 +348,6 @@ def spawn_ego_vehicle(self): spawn_point = random.choice(spawn_points) - # 尝试多次生成 for attempt in range(self.max_spawn_attempts): self.spawn_stats['total_attempts'] += 1 try: @@ -521,7 +367,6 @@ def spawn_ego_vehicle(self): if attempt == self.max_spawn_attempts - 1: Log.warning(f"主车生成失败: {e}") else: - # 尝试不同的生成点 spawn_point = random.choice(spawn_points) time.sleep(0.1) @@ -540,7 +385,6 @@ def spawn_traffic(self, center_location): total_time = time.time() - start_time Log.info(f"交通生成: {vehicles}辆车, {pedestrians}个行人, 用时: {total_time:.2f}秒") - # 记录性能统计 success_rate = self.spawn_stats['successful_spawns'] / max(1, self.spawn_stats['total_attempts']) avg_spawn_time = self.spawn_stats['total_spawn_time'] / max(1, self.spawn_stats['successful_spawns']) Log.performance(f"生成统计: 成功率{success_rate:.1%}, 平均生成时间{avg_spawn_time:.3f}秒") @@ -548,7 +392,6 @@ def spawn_traffic(self, center_location): return vehicles + pedestrians def _spawn_vehicles_batch(self): - """批量生成车辆(提高性能)""" blueprint_lib = self.world.get_blueprint_library() spawn_points = self.world.get_map().get_spawn_points() @@ -558,7 +401,6 @@ def _spawn_vehicles_batch(self): num_vehicles = min(self.config['traffic']['background_vehicles'], 10) spawned = 0 - # 准备批处理 batch_commands = [] available_points = spawn_points.copy() random.shuffle(available_points) @@ -570,14 +412,10 @@ def _spawn_vehicles_batch(self): try: vehicle_bp = random.choice(blueprint_lib.filter('vehicle.*')) spawn_point = available_points[i] - - # 创建生成命令 batch_commands.append((vehicle_bp, spawn_point)) - except: pass - # 批量生成 for vehicle_bp, spawn_point in batch_commands: try: vehicle = self.world.spawn_actor(vehicle_bp, spawn_point) @@ -592,7 +430,6 @@ def _spawn_vehicles_batch(self): self.spawn_stats['total_attempts'] += 1 pass - # 避免过快的生成速度 if spawned % 3 == 0: time.sleep(0.05) @@ -663,11 +500,9 @@ def _spawn_pedestrians(self, center_location): return spawned def cleanup(self): - """安全清理所有资源""" Log.info("开始清理交通管理器...") try: - # 清理行人 for pedestrian in self.pedestrians: try: if pedestrian and pedestrian.is_alive: @@ -677,7 +512,6 @@ def cleanup(self): self.pedestrians.clear() - # 清理背景车辆 for vehicle in self.vehicles: try: if vehicle and vehicle.is_alive: @@ -704,7 +538,6 @@ def __init__(self, world, config, data_dir): self.last_capture_time = 0 self.last_performance_sample = 0 - # 新增:帧率控制相关变量 self.target_fps = config['performance'].get('frame_rate_limit', 5.0) self.min_frame_interval = 1.0 / self.target_fps if self.target_fps > 0 else 0.1 self.last_frame_time = 0 @@ -715,18 +548,14 @@ def __init__(self, world, config, data_dir): self.infra_buffer = {} self.buffer_lock = threading.Lock() - # 添加运行状态标志 self.is_running = True - # 性能监控 self.performance_monitor = PerformanceMonitor() - # 性能优化配置 self.image_processor = ImageProcessor(data_dir, config.get('image_processing', {})) self.lidar_processor = None self.fusion_manager = None - # 批处理设置 self.batch_size = config['performance'].get('batch_size', 5) self.enable_async_processing = config.get('enable_async_processing', True) @@ -736,7 +565,6 @@ def __init__(self, world, config, data_dir): if config['output'].get('save_fusion', False): self.fusion_manager = MultiSensorFusion(data_dir, config['performance'].get('fusion', {})) - # 传感器统计 self.sensor_stats = { 'total_images': 0, 'total_lidar_frames': 0, @@ -746,7 +574,6 @@ def __init__(self, world, config, data_dir): } def setup_cameras(self, vehicle, center_location, vehicle_id=0): - """设置摄像头 - 修复的方法""" vehicle_cams = self._setup_vehicle_cameras(vehicle, vehicle_id) infra_cams = self._setup_infrastructure_cameras(center_location) @@ -822,7 +649,6 @@ def setup_lidar(self, vehicle, vehicle_id=0): lidar_sensor = self.world.spawn_actor(lidar_bp, lidar_transform, attach_to=vehicle) def lidar_callback(lidar_data): - # 检查是否正在关闭 if not self.is_running: return @@ -860,7 +686,6 @@ def lidar_callback(lidar_data): if self.is_running: Log.error(f"LiDAR处理失败: {e}") - # 记录性能 frame_time = time.time() - frame_start_time self.performance_monitor.record_frame_time(frame_time) except Exception as e: @@ -895,7 +720,6 @@ def _create_camera(self, name, config, parent, sensor_type, vehicle_id=0): else: camera = self.world.spawn_actor(blueprint, transform) - # 为不同车辆创建不同目录 if sensor_type == 'vehicle' and vehicle_id > 0: save_dir = os.path.join(self.data_dir, "raw", f"vehicle_{vehicle_id}", name) else: @@ -917,7 +741,6 @@ def _create_callback(self, save_dir, name, sensor_type, vehicle_id=0): capture_interval = self.config['sensors']['capture_interval'] def callback(image): - # 检查是否正在关闭 if not self.is_running: return @@ -925,24 +748,20 @@ def callback(image): current_time = time.time() frame_start_time = time.time() - # 优化点2:添加帧率控制逻辑 - # 检查是否达到最小帧间隔 time_since_last_frame = current_time - self.last_frame_time if time_since_last_frame < self.min_frame_interval: - # 如果帧间隔太小,跳过此帧 self.frame_skip_count += 1 if self.frame_skip_count > self.max_frame_skip: - # 如果连续跳过多帧,强制处理一帧 self.frame_skip_count = 0 else: - return # 跳过此帧 + return else: self.frame_skip_count = 0 if current_time - self.last_capture_time >= capture_interval: self.frame_counter += 1 self.last_capture_time = current_time - self.last_frame_time = current_time # 记录帧处理时间 + self.last_frame_time = current_time capture_start = time.time() filename = os.path.join(save_dir, f"{name}_{self.frame_counter:06d}.png") @@ -956,7 +775,6 @@ def callback(image): if sensor_type == 'vehicle': self.vehicle_buffer[name] = filename if len(self.vehicle_buffer) >= 4: - # 使用线程池异步处理图像拼接,避免阻塞 if self.enable_async_processing: import concurrent.futures with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor: @@ -986,11 +804,9 @@ def callback(image): self.image_processor.stitch(self.infra_buffer, self.frame_counter, 'infrastructure') self.infra_buffer.clear() - # 定期采样性能 if current_time - self.last_performance_sample >= 5.0: memory_info = self.performance_monitor.sample_memory() cpu_info = self.performance_monitor.sample_cpu() - gpu_info = self.performance_monitor.sample_gpu_memory() if self.frame_counter % 10 == 0: Log.performance(f"系统监控 - 内存: {memory_info['process_mb']:.1f}MB, " @@ -999,22 +815,18 @@ def callback(image): self.last_performance_sample = current_time - # 记录帧处理时间 frame_time = time.time() - frame_start_time self.performance_monitor.record_frame_time(frame_time) - # 定期垃圾回收 if self.frame_counter % 50 == 0: gc.collect() - # 记录传感器统计 if self.sensor_stats['total_images'] > 0: avg_capture_time = np.mean(self.sensor_stats['image_capture_times'][-100:]) if len( self.sensor_stats['image_capture_times']) > 0 else 0 Log.performance(f"传感器统计: 图像{self.sensor_stats['total_images']}张, " f"平均捕获时间{avg_capture_time:.3f}秒") except Exception as e: - # 如果在关闭过程中发生错误,忽略它 if self.is_running: Log.error(f"传感器回调错误: {e}") @@ -1033,7 +845,6 @@ def generate_sensor_summary(self): 'sensor_stats': self.sensor_stats } - # 计算统计信息 if self.sensor_stats['image_capture_times']: summary['sensor_stats']['avg_image_capture_time'] = np.mean(self.sensor_stats['image_capture_times']) summary['sensor_stats']['max_image_capture_time'] = np.max(self.sensor_stats['image_capture_times']) @@ -1048,39 +859,32 @@ def generate_sensor_summary(self): if self.fusion_manager: summary['fusion_data'] = self.fusion_manager.generate_fusion_report() - # 图像处理器统计 summary['image_processor_stats'] = self.image_processor.get_stats() return summary def cleanup(self): - """安全清理传感器""" Log.info(f"安全清理 {len(self.sensors)} 个传感器...") - # 1. 首先设置运行状态为False,防止回调函数继续执行 self.is_running = False - # 2. 停止所有传感器的监听 Log.info("停止所有传感器监听...") for sensor in self.sensors: try: if hasattr(sensor, 'stop'): sensor.stop() - time.sleep(0.001) # 短暂延迟 + time.sleep(0.001) except: pass - # 3. 等待一小段时间让所有传感器回调结束 time.sleep(0.2) - # 4. 刷新批处理数据 if self.lidar_processor: try: self.lidar_processor.flush_batch() except: pass - # 5. 销毁传感器 Log.info(f"销毁 {len(self.sensors)} 个传感器...") for i, sensor in enumerate(self.sensors): try: @@ -1088,29 +892,51 @@ def cleanup(self): sensor.destroy() except: pass - # 分批销毁,避免一次性销毁太多 if i % 5 == 0: time.sleep(0.01) self.sensors.clear() - # 6. 清理缓存和内存 if hasattr(self.image_processor, 'image_cache'): try: self.image_processor.image_cache.clear() except: pass - # 7. 清理数据结构 self.vehicle_buffer.clear() self.infra_buffer.clear() - # 8. 强制垃圾回收 gc.collect() Log.info("传感器清理完成") +class V2XCommunication: + def __init__(self, config): + self.config = config + self.nodes = {} + self.messages = [] + + def register_node(self, node_id, position, capabilities): + self.nodes[node_id] = { + 'position': position, + 'capabilities': capabilities, + 'last_update': time.time() + } + + def broadcast_basic_safety_message(self, sender_id, vehicle_data): + pass + + def get_messages_for_node(self, node_id): + return [] + + def get_network_status(self): + return {'nodes': len(self.nodes), 'messages': len(self.messages)} + + def stop(self): + pass + + class DataCollector: def __init__(self, config): self.config = config @@ -1130,11 +956,9 @@ def __init__(self, config): self.is_running = False self.collected_frames = 0 - # 性能监控 self.performance_monitor = PerformanceMonitor() - # 数据格式配置 - self.output_format = config.get('output_format', 'standard') # standard, v2xformer, kitti + self.output_format = config.get('output_format', 'standard') def setup_directories(self): scenario = self.config['scenario'] @@ -1155,8 +979,8 @@ def setup_directories(self): "calibration", "cooperative", "v2x_messages", - "v2xformer_format", # V2XFormer格式数据 - "kitti_format", # KITTI格式数据 + "v2xformer_format", + "kitti_format", "metadata" ] @@ -1201,7 +1025,6 @@ def setup_scene(self): self.traffic_manager = TrafficManager(self.world, self.config) - # 生成多个主车 num_ego_vehicles = min(self.config['cooperative'].get('num_coop_vehicles', 2) + 1, 3) for i in range(num_ego_vehicles): ego_vehicle = self.traffic_manager.spawn_ego_vehicle() @@ -1215,11 +1038,9 @@ def setup_scene(self): self.traffic_manager.spawn_traffic(self.scene_center) - # 初始化V2X通信 if self.config['v2x']['enabled']: self.v2x_communication = V2XCommunication(self.config['v2x']) - # 注册车辆到V2X网络 for i, vehicle in enumerate(self.ego_vehicles): location = vehicle.get_location() self.v2x_communication.register_node( @@ -1228,21 +1049,17 @@ def setup_scene(self): {'type': 'vehicle', 'capabilities': ['bsm', 'rsm']} ) - # 初始化多车辆管理器 self.multi_vehicle_manager = MultiVehicleManager( self.world, self.config, self.output_dir ) - # 设置主车 self.multi_vehicle_manager.ego_vehicles = self.ego_vehicles - # 生成协同车辆 num_coop_vehicles = self.config['cooperative'].get('num_coop_vehicles', 2) coop_vehicles = self.multi_vehicle_manager.spawn_cooperative_vehicles(num_coop_vehicles) - # 注册协同车辆到V2X网络 if self.v2x_communication: for vehicle in coop_vehicles: location = vehicle.get_location() @@ -1256,8 +1073,6 @@ def setup_scene(self): return True def setup_sensors(self): - """设置传感器 - 修复的方法""" - # 为每个主车设置传感器 for i, vehicle in enumerate(self.ego_vehicles): sensor_manager = SensorManager(self.world, self.config, self.output_dir) @@ -1287,96 +1102,83 @@ def collect_data(self): last_detailed_log = time.time() last_memory_check = time.time() - # 内存监控变量 memory_warning_issued = False - forced_cleanup_count = 0 + early_stop_triggered = False + + # 添加内存监控阈值 + memory_warning_threshold = 350 # MB + memory_critical_threshold = 400 # MB + early_stop_threshold = 450 # MB try: while time.time() - self.start_time < duration and self.is_running: current_time = time.time() elapsed = current_time - self.start_time - # 内存检查 - 优化点4:定期检查内存使用 - if current_time - last_memory_check >= 2.0: # 更频繁的内存检查 + # 内存监控和早期停止机制 + if current_time - last_memory_check >= 2.0: try: import psutil process = psutil.Process() memory_mb = process.memory_info().rss / (1024 * 1024) - if memory_mb > 350 and not memory_warning_issued: - Log.warning(f"内存使用较高: {memory_mb:.1f}MB,将减少数据处理") - memory_warning_issued = True - - # 如果内存过高,跳过一些非关键操作 - if self.v2x_communication: - Log.debug("内存警告:暂停部分V2X消息处理") - - elif memory_mb > 400: # 如果内存超过400MB,提前结束 - Log.error(f"内存使用过高: {memory_mb:.1f}MB,提前结束数据收集") + if memory_mb > early_stop_threshold: + Log.error( + f"内存使用超过临界值({early_stop_threshold}MB): {memory_mb:.1f}MB,提前结束数据收集") + early_stop_triggered = True break - - elif memory_mb < 300: + elif memory_mb > memory_critical_threshold: + Log.warning(f"内存使用严重过高: {memory_mb:.1f}MB,进行强制清理") + self._force_memory_cleanup() + memory_warning_issued = True + elif memory_mb > memory_warning_threshold and not memory_warning_issued: + Log.warning(f"内存使用较高: {memory_mb:.1f}MB,减少数据处理") + memory_warning_issued = True + elif memory_mb < memory_warning_threshold: memory_warning_issued = False - forced_cleanup_count = 0 except Exception as e: Log.debug(f"内存检查失败: {e}") last_memory_check = current_time - # 更新车辆状态 if self.multi_vehicle_manager: self.multi_vehicle_manager.update_vehicle_states() - # V2X通信更新 - 使用配置中的更新间隔 v2x_interval = self.config['v2x'].get('update_interval', 2.0) if self.v2x_communication and current_time - last_v2x_update >= v2x_interval: self._update_v2x_communication() last_v2x_update = current_time - # 共享感知数据 - 仅在内存允许时进行 if (not memory_warning_issued and self.config['cooperative'].get('enable_shared_perception', True) and current_time - last_perception_share >= 2.0): self._share_perception_data() last_perception_share = current_time - # 性能采样 if current_time - last_performance_sample >= 10.0: memory_info = self.performance_monitor.sample_memory() cpu_info = self.performance_monitor.sample_cpu() - disk_info = self.performance_monitor.sample_disk_io() - network_info = self.performance_monitor.sample_network_io() - # 详细性能日志(每分钟一次) if current_time - last_detailed_log >= 60.0: Log.performance(f"详细性能监控:") Log.performance(f" 进程内存: {memory_info['process_mb']:.1f}MB") Log.performance(f" 系统内存: {memory_info['system_used_percent']:.1f}%使用率") Log.performance(f" CPU: {cpu_info['total_percent']:.1f}% ({cpu_info['count']}核心)") - if disk_info: - Log.performance( - f" 磁盘IO: 读{disk_info['read_mb']:.1f}MB, 写{disk_info['write_mb']:.1f}MB") - if network_info: - Log.performance( - f" 网络IO: 发{network_info['sent_kb']:.1f}KB, 收{network_info['recv_kb']:.1f}KB") last_detailed_log = current_time else: - # 简化性能日志 Log.performance(f"系统监控 - 内存: {memory_info['process_mb']:.1f}MB, " f"CPU: {cpu_info['total_percent']:.1f}%, " f"活跃线程: {threading.active_count()}") last_performance_sample = current_time - # 定期垃圾回收 gc.collect() if current_time - last_update >= 5.0: total_frames = sum(mgr.get_frame_count() for mgr in self.sensor_managers.values()) progress = (elapsed / duration) * 100 - # 计算预估剩余时间 if total_frames > 0: frames_per_second = total_frames / elapsed remaining_frames = (duration - elapsed) * frames_per_second @@ -1389,7 +1191,7 @@ def collect_data(self): f"ETA: {eta_seconds:.0f}秒") last_update = current_time - time.sleep(0.01) # 减少CPU占用 + time.sleep(0.01) except KeyboardInterrupt: Log.info("数据收集被用户中断") @@ -1402,10 +1204,13 @@ def collect_data(self): self.collected_frames = sum(mgr.get_frame_count() for mgr in self.sensor_managers.values()) - # 获取性能摘要 performance_summary = self.performance_monitor.get_performance_summary() - Log.info(f"收集完成: {self.collected_frames}帧, 用时: {elapsed:.1f}秒") + if early_stop_triggered: + Log.warning("数据收集因内存过高而提前终止") + else: + Log.info(f"收集完成: {self.collected_frames}帧, 用时: {elapsed:.1f}秒") + Log.info(f"平均帧率: {performance_summary['frames_per_second']:.2f} FPS") Log.info(f"最大内存使用: {performance_summary['max_memory_mb']:.1f} MB") Log.info(f"平均CPU使用: {performance_summary['average_cpu_percent']:.1f}%") @@ -1413,15 +1218,12 @@ def collect_data(self): self._save_metadata() self._print_summary() - # 生成标准格式数据 if self.output_format != 'standard': self._convert_to_target_format() def _force_memory_cleanup(self): - """强制内存清理""" Log.info("执行强制内存清理...") - # 清理图像处理器缓存 for sensor_manager in self.sensor_managers.values(): if hasattr(sensor_manager, 'image_processor'): if hasattr(sensor_manager.image_processor, 'image_cache'): @@ -1432,7 +1234,6 @@ def _force_memory_cleanup(self): except: pass - # 清理LiDAR批处理 for sensor_manager in self.sensor_managers.values(): if hasattr(sensor_manager, 'lidar_processor'): try: @@ -1441,25 +1242,20 @@ def _force_memory_cleanup(self): except: pass - # 强制垃圾回收 gc.collect() Log.info("强制内存清理完成") def cleanup(self): - """安全清理所有资源 - 完全重写的版本""" Log.info("开始安全清理场景...") cleanup_start = time.time() try: - # 1. 设置运行状态为False self.is_running = False - # 2. 等待一小段时间让循环结束 time.sleep(0.2) - # 3. 停止V2X通信 if self.v2x_communication: try: Log.info("停止V2X通信...") @@ -1467,7 +1263,6 @@ def cleanup(self): except: pass - # 4. 清理传感器(最重要,必须先于车辆) Log.info(f"清理 {len(self.sensor_managers)} 个传感器管理器...") for vehicle_id, sensor_manager in self.sensor_managers.items(): try: @@ -1476,9 +1271,8 @@ def cleanup(self): pass self.sensor_managers.clear() - time.sleep(0.1) # 等待传感器清理完成 + time.sleep(0.1) - # 5. 清理多车辆管理器 if self.multi_vehicle_manager: try: Log.info("清理多车辆管理器...") @@ -1486,7 +1280,6 @@ def cleanup(self): except: pass - # 6. 清理背景交通 if self.traffic_manager: try: Log.info("清理交通管理器...") @@ -1494,7 +1287,6 @@ def cleanup(self): except: pass - # 7. 清理主车(最后清理) Log.info(f"清理 {len(self.ego_vehicles)} 个主车...") for vehicle in self.ego_vehicles: try: @@ -1502,11 +1294,10 @@ def cleanup(self): vehicle.destroy() except: pass - time.sleep(0.01) # 短暂延迟 + time.sleep(0.01) self.ego_vehicles.clear() - # 8. 重置世界设置 try: if self.world: default_weather = carla.WeatherParameters() @@ -1514,26 +1305,21 @@ def cleanup(self): except: pass - # 9. 强制垃圾回收 gc.collect() - # 10. 等待确保清理完成 time.sleep(0.3) - # 11. 释放引用 self.traffic_manager = None self.multi_vehicle_manager = None self.v2x_communication = None self.scene_center = None - # 最后一次强制垃圾回收 gc.collect() Log.info(f"清理完成,总用时: {time.time() - cleanup_start:.2f}秒") except Exception as e: Log.error(f"清理过程中发生错误: {e}") - # 紧急清理:强制释放所有引用 try: self.sensor_managers.clear() self.ego_vehicles.clear() @@ -1545,7 +1331,6 @@ def cleanup(self): pass def _convert_to_target_format(self): - """转换为目标数据格式""" Log.info(f"转换为 {self.output_format} 格式...") if self.output_format == 'v2xformer': @@ -1554,22 +1339,17 @@ def _convert_to_target_format(self): self._convert_to_kitti_format() def _convert_to_v2xformer_format(self): - """转换为V2XFormer格式""" try: - # 创建必要的目录结构 v2x_dir = os.path.join(self.output_dir, "v2xformer_format") - # 创建数据集结构 splits = ['train', 'val', 'test'] for split in splits: split_dir = os.path.join(v2x_dir, split) os.makedirs(split_dir, exist_ok=True) - # 创建子目录 for subdir in ['image', 'point_cloud', 'calib', 'label']: os.makedirs(os.path.join(split_dir, subdir), exist_ok=True) - # 生成数据集划分 total_frames = self.collected_frames train_ratio = 0.7 val_ratio = 0.2 @@ -1579,7 +1359,6 @@ def _convert_to_v2xformer_format(self): val_frames = int(total_frames * val_ratio) test_frames = total_frames - train_frames - val_frames - # 生成划分文件 splits_info = { 'train': list(range(0, train_frames)), 'val': list(range(train_frames, train_frames + val_frames)), @@ -1596,11 +1375,9 @@ def _convert_to_v2xformer_format(self): Log.error(f"V2XFormer格式转换失败: {e}") def _convert_to_kitti_format(self): - """转换为KITTI格式""" try: kitti_dir = os.path.join(self.output_dir, "kitti_format") - # 创建KITTI标准目录结构 for subdir in ['training', 'testing']: full_dir = os.path.join(kitti_dir, subdir) os.makedirs(full_dir, exist_ok=True) @@ -1608,7 +1385,6 @@ def _convert_to_kitti_format(self): for subsubdir in ['image_2', 'velodyne', 'calib', 'label_2']: os.makedirs(os.path.join(full_dir, subsubdir), exist_ok=True) - # 生成KITTI格式的校准文件 self._generate_kitti_calibration(kitti_dir) Log.info(f"KITTI格式转换完成: {kitti_dir}") @@ -1617,7 +1393,6 @@ def _convert_to_kitti_format(self): Log.error(f"KITTI格式转换失败: {e}") def _generate_kitti_calibration(self, kitti_dir): - """生成KITTI格式的校准文件""" calib_template = """P0: 7.215377e+02 0.000000e+00 6.095593e+02 0.000000e+00 0.000000e+00 7.215377e+02 1.728540e+02 0.000000e+00 0.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 P1: 7.215377e+02 0.000000e+00 6.095593e+02 -3.875744e+02 0.000000e+00 7.215377e+02 1.728540e+02 0.000000e+00 0.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 P2: 7.215377e+02 0.000000e+00 6.095593e+02 4.485728e+01 0.000000e+00 7.215377e+02 1.728540e+02 2.163791e-01 0.000000e+00 0.000000e+00 1.000000e+00 2.745884e-03 @@ -1626,18 +1401,15 @@ def _generate_kitti_calibration(self, kitti_dir): Tr_velo_to_cam: 4.276802e-04 -9.999672e-01 -8.084491e-03 -1.198459e-02 -7.210626e-03 8.081198e-03 -9.999413e-01 -5.403984e-02 9.999738e-01 4.859485e-04 -7.206933e-03 -2.921968e-02 Tr_imu_to_velo: 9.999976e-01 7.553071e-04 -2.035826e-03 -8.086759e-01 -7.854027e-04 9.998898e-01 -1.482298e-02 3.195559e-01 2.024406e-03 1.482454e-02 9.998881e-01 -7.997231e-01""" - # 为每一帧生成校准文件 for i in range(self.collected_frames): calib_file = os.path.join(kitti_dir, "training", "calib", f"{i:06d}.txt") with open(calib_file, 'w') as f: f.write(calib_template) def _update_v2x_communication(self): - """更新V2X通信""" if not self.v2x_communication: return - # 为每辆车发送基本安全消息 for vehicle in self.ego_vehicles + self.multi_vehicle_manager.cooperative_vehicles: if not hasattr(vehicle, 'is_alive') or not vehicle.is_alive: continue @@ -1651,7 +1423,7 @@ def _update_v2x_communication(self): 'position': (location.x, location.y, location.z), 'speed': speed, 'heading': vehicle.get_transform().rotation.yaw, - 'acceleration': (0, 0, 0) # 简化处理 + 'acceleration': (0, 0, 0) } self.v2x_communication.broadcast_basic_safety_message( @@ -1661,33 +1433,27 @@ def _update_v2x_communication(self): except: pass - # 处理接收到的消息 for vehicle in self.ego_vehicles: messages = self.v2x_communication.get_messages_for_node(f'vehicle_{vehicle.id}') if messages: Log.debug(f"车辆 {vehicle.id} 收到 {len(messages)} 条V2X消息") def _share_perception_data(self): - """共享感知数据""" if not self.multi_vehicle_manager or not self.config['cooperative']['enable_shared_perception']: return - # 模拟车辆感知数据(简化处理,实际应从传感器获取) for vehicle in self.ego_vehicles + self.multi_vehicle_manager.cooperative_vehicles: if not hasattr(vehicle, 'is_alive') or not vehicle.is_alive: continue - # 模拟检测到的物体 detected_objects = self._simulate_object_detection(vehicle) if detected_objects: self.multi_vehicle_manager.share_perception_data(vehicle.id, detected_objects) def _simulate_object_detection(self, vehicle): - """模拟对象检测(简化)""" detected_objects = [] - # 获取车辆周围的其他车辆 for other_vehicle in self.ego_vehicles + self.multi_vehicle_manager.cooperative_vehicles: if other_vehicle.id == vehicle.id or not hasattr(other_vehicle, 'is_alive') or not other_vehicle.is_alive: continue @@ -1696,7 +1462,6 @@ def _simulate_object_detection(self, vehicle): location = other_vehicle.get_location() distance = vehicle.get_location().distance(location) - # 模拟检测范围(50米) if distance < 50.0: obj_data = { 'class': 'vehicle', @@ -1729,17 +1494,14 @@ def _save_metadata(self): 'performance': self.performance_monitor.get_performance_summary() } - # 传感器摘要 sensor_summaries = {} for vehicle_id, sensor_manager in self.sensor_managers.items(): sensor_summaries[vehicle_id] = sensor_manager.generate_sensor_summary() metadata['sensor_summaries'] = sensor_summaries - # V2X通信状态 if self.v2x_communication: metadata['v2x_status'] = self.v2x_communication.get_network_status() - # 协同摘要 if self.multi_vehicle_manager: metadata['cooperative_summary'] = self.multi_vehicle_manager.generate_summary() @@ -1754,19 +1516,16 @@ def _print_summary(self): print("数据收集摘要") print("=" * 60) - # 统计原始图像 raw_dirs = [d for d in os.listdir(self.output_dir) if d.startswith('raw')] total_raw_images = 0 for raw_dir in raw_dirs: raw_path = os.path.join(self.output_dir, raw_dir) if os.path.exists(raw_path): - # 快速统计 for root, dirs, files in os.walk(raw_path): total_raw_images += len([f for f in files if f.endswith(('.png', '.jpg', '.jpeg'))]) print(f"原始图像: {total_raw_images} 张") - # 统计LiDAR lidar_dir = os.path.join(self.output_dir, "lidar") if os.path.exists(lidar_dir): import glob @@ -1776,7 +1535,6 @@ def _print_summary(self): print(f"LiDAR数据: {len(bin_files)} .bin文件, {len(npy_files)} .npy文件") print(f"批处理文件: {len(batch_files)} 个") - # 统计协同数据 coop_dir = os.path.join(self.output_dir, "cooperative") if os.path.exists(coop_dir): v2x_files = len( @@ -1785,7 +1543,6 @@ def _print_summary(self): [f for f in os.listdir(os.path.join(coop_dir, "shared_perception")) if f.endswith('.json')]) print(f"协同数据: {v2x_files} V2X消息, {perception_files} 共享感知文件") - # 格式转换 if self.output_format == 'v2xformer': v2x_dir = os.path.join(self.output_dir, "v2xformer_format") if os.path.exists(v2x_dir): @@ -1796,7 +1553,6 @@ def _print_summary(self): if os.path.exists(kitti_dir): print(f"KITTI格式: 已生成") - # 性能统计 performance = self.performance_monitor.get_performance_summary() print(f"\n性能统计:") print(f" 平均帧率: {performance['frames_per_second']:.2f} FPS") @@ -1825,9 +1581,8 @@ def run_analysis(self): def main(): - parser = argparse.ArgumentParser(description='CVIPS 性能优化数据收集系统 v12.0') + parser = argparse.ArgumentParser(description='CVIPS 性能优化数据收集系统') - # 基础参数 parser.add_argument('--config', type=str, help='配置文件路径') parser.add_argument('--scenario', type=str, default='performance_optimized', help='场景名称') parser.add_argument('--town', type=str, default='Town10HD', @@ -1837,26 +1592,21 @@ def main(): parser.add_argument('--time-of-day', type=str, default='noon', choices=['noon', 'sunset', 'night'], help='时间') - # 交通参数 parser.add_argument('--num-vehicles', type=int, default=8, help='背景车辆数') parser.add_argument('--num-pedestrians', type=int, default=6, help='行人数') parser.add_argument('--num-coop-vehicles', type=int, default=2, help='协同车辆数') - # 收集参数 parser.add_argument('--duration', type=int, default=60, help='收集时长(秒)') parser.add_argument('--capture-interval', type=float, default=2.0, help='捕捉间隔(秒)') parser.add_argument('--seed', type=int, help='随机种子') - # 性能参数 parser.add_argument('--batch-size', type=int, default=5, help='批处理大小') parser.add_argument('--enable-compression', action='store_true', help='启用数据压缩') parser.add_argument('--enable-downsampling', action='store_true', help='启用LiDAR下采样') - # 输出格式 parser.add_argument('--output-format', type=str, default='standard', choices=['standard', 'v2xformer', 'kitti'], help='输出数据格式') - # 传感器参数 parser.add_argument('--enable-lidar', action='store_true', help='启用LiDAR传感器') parser.add_argument('--enable-fusion', action='store_true', help='启用多传感器融合') parser.add_argument('--enable-v2x', action='store_true', help='启用V2X通信') @@ -1864,26 +1614,22 @@ def main(): parser.add_argument('--enable-enhancement', action='store_true', help='启用数据增强') parser.add_argument('--enable-annotations', action='store_true', help='启用自动标注') - # 功能参数 parser.add_argument('--run-analysis', action='store_true', help='运行数据集分析') parser.add_argument('--skip-validation', action='store_true', help='跳过数据验证') parser.add_argument('--skip-quality-check', action='store_true', help='跳过质量检查') args = parser.parse_args(remaining_argv) - # 加载配置 config = ConfigManager.load_config(args.config) config = ConfigManager.merge_args(config, args) - # 添加性能配置 config['performance']['batch_size'] = args.batch_size config['performance']['enable_compression'] = args.enable_compression config['performance']['enable_downsampling'] = args.enable_downsampling config['output']['output_format'] = args.output_format - # 显示配置 print("\n" + "=" * 60) - print("CVIPS 性能优化数据收集系统 v12.0") + print("CVIPS 性能优化数据收集系统") print("=" * 60) print(f"场景: {config['scenario']['name']}") From 7bf2df9d0c37172ac7bb8f6f9fed4ba982b6b21d Mon Sep 17 00:00:00 2001 From: Z-w-7799 <1011069369@qq.com> Date: Sun, 21 Dec 2025 12:13:44 +0800 Subject: [PATCH 12/20] =?UTF-8?q?=E4=B8=BA=E5=86=85=E5=AD=98=E7=9B=91?= =?UTF-8?q?=E6=8E=A7=E6=B7=BB=E5=8A=A0=E9=85=8D=E7=BD=AE=E9=A1=B9=EF=BC=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=B8=8B=E9=87=87=E6=A0=B7=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config_manager.py | 102 ++------------ .../lidar_processor.py | 124 +++--------------- 2 files changed, 33 insertions(+), 193 deletions(-) diff --git a/src/enhance_pedestrian_safety/config_manager.py b/src/enhance_pedestrian_safety/config_manager.py index 1f64c72ef9..c608de6674 100644 --- a/src/enhance_pedestrian_safety/config_manager.py +++ b/src/enhance_pedestrian_safety/config_manager.py @@ -4,31 +4,24 @@ import copy from typing import Dict, Any, Optional, List, Tuple -# 尝试导入yaml,如果不存在则跳过 try: import yaml - YAML_AVAILABLE = True except ImportError: YAML_AVAILABLE = False - print("警告: PyYAML未安装,将使用JSON格式") class ConfigValidator: - """配置验证器""" @staticmethod def validate_config(config: Dict[str, Any]) -> Tuple[bool, List[str]]: - """验证配置的有效性""" errors = [] - # 验证必填字段 required_sections = ['scenario', 'sensors', 'output'] for section in required_sections: if section not in config: errors.append(f"缺失必要配置节: {section}") - # 验证场景配置 if 'scenario' in config: scenario = config['scenario'] if 'duration' in scenario and scenario['duration'] <= 0: @@ -36,7 +29,6 @@ def validate_config(config: Dict[str, Any]) -> Tuple[bool, List[str]]: if 'town' not in scenario: errors.append("场景配置中缺失地图名称") - # 验证传感器配置 if 'sensors' in config: sensors = config['sensors'] if 'capture_interval' in sensors and sensors['capture_interval'] <= 0: @@ -47,7 +39,6 @@ def validate_config(config: Dict[str, Any]) -> Tuple[bool, List[str]]: elif any(dim <= 0 for dim in sensors['image_size']): errors.append("图像尺寸必须大于0") - # 验证性能配置 if 'performance' in config: perf = config['performance'] if 'batch_size' in perf and perf['batch_size'] <= 0: @@ -57,22 +48,18 @@ def validate_config(config: Dict[str, Any]) -> Tuple[bool, List[str]]: @staticmethod def suggest_optimizations(config: Dict[str, Any]) -> List[str]: - """根据配置提供优化建议""" suggestions = [] - # 内存使用建议 if config.get('sensors', {}).get('lidar_sensors', 0) > 0: lidar_config = config['sensors'].get('lidar_config', {}) max_points = lidar_config.get('max_points_per_frame', 50000) if max_points > 50000: suggestions.append(f"LiDAR最大点数({max_points})较高,建议降低到50000以下以减少内存使用") - # 性能建议 capture_interval = config['sensors'].get('capture_interval', 2.0) if capture_interval < 1.0: suggestions.append(f"采集间隔({capture_interval}s)较短,可能导致高负载,建议增加到1.0s以上") - # 输出建议 output = config.get('output', {}) enabled_outputs = [k for k, v in output.items() if isinstance(v, bool) and v] if len(enabled_outputs) > 5: @@ -82,14 +69,11 @@ def suggest_optimizations(config: Dict[str, Any]) -> List[str]: class ConfigOptimizer: - """配置优化器""" @staticmethod def optimize_for_memory(config: Dict[str, Any]) -> Dict[str, Any]: - """为内存使用优化配置""" optimized = copy.deepcopy(config) - # 调整LiDAR设置 if optimized['sensors'].get('lidar_sensors', 0) > 0: lidar_config = optimized['sensors'].setdefault('lidar_config', {}) lidar_config.update({ @@ -99,7 +83,6 @@ def optimize_for_memory(config: Dict[str, Any]) -> Dict[str, Any]: 'max_batch_memory_mb': 30 }) - # 调整性能设置 perf = optimized.setdefault('performance', {}) perf.update({ 'batch_size': 3, @@ -110,7 +93,6 @@ def optimize_for_memory(config: Dict[str, Any]) -> Dict[str, Any]: 'frame_rate_limit': 3.0 }) - # 调整图像处理 perf['image_processing'] = { 'compress_images': True, 'compression_quality': 80, @@ -121,10 +103,8 @@ def optimize_for_memory(config: Dict[str, Any]) -> Dict[str, Any]: @staticmethod def optimize_for_quality(config: Dict[str, Any]) -> Dict[str, Any]: - """为数据质量优化配置""" optimized = copy.deepcopy(config) - # 调整传感器设置 sensors = optimized['sensors'] sensors.update({ 'image_size': [1920, 1080], @@ -139,7 +119,6 @@ def optimize_for_quality(config: Dict[str, Any]) -> Dict[str, Any]: } }) - # 调整输出设置 output = optimized['output'] output.update({ 'save_annotations': True, @@ -149,7 +128,6 @@ def optimize_for_quality(config: Dict[str, Any]) -> Dict[str, Any]: 'run_quality_check': True }) - # 调整增强设置 enhanced = optimized.setdefault('enhancement', {}) enhanced.update({ 'enabled': True, @@ -162,35 +140,31 @@ def optimize_for_quality(config: Dict[str, Any]) -> Dict[str, Any]: @staticmethod def optimize_for_speed(config: Dict[str, Any]) -> Dict[str, Any]: - """为处理速度优化配置""" optimized = copy.deepcopy(config) - # 调整传感器设置 sensors = optimized['sensors'] sensors.update({ 'image_size': [640, 480], 'capture_interval': 3.0, - 'lidar_sensors': 0, # 禁用LiDAR - 'radar_sensors': 0 # 禁用雷达 + 'lidar_sensors': 0, + 'radar_sensors': 0 }) - # 调整性能设置 perf = optimized.setdefault('performance', {}) perf.update({ 'batch_size': 10, 'enable_compression': True, - 'compression_level': 1, # 快速压缩 + 'compression_level': 1, 'enable_downsampling': True, 'enable_async_processing': True, 'max_cache_size': 20, 'frame_rate_limit': 10.0 }) - # 简化输出 output = optimized['output'] output.update({ 'save_raw': True, - 'save_stitched': False, # 不拼接图像 + 'save_stitched': False, 'save_annotations': False, 'save_lidar': False, 'save_fusion': False, @@ -201,9 +175,7 @@ def optimize_for_speed(config: Dict[str, Any]) -> Dict[str, Any]: class ConfigManager: - """配置管理器(增强版)""" - # 预定义配置模板 PRESET_CONFIGS = { 'balanced': { 'description': '平衡配置 - 兼顾性能和质量', @@ -238,31 +210,17 @@ class ConfigManager: @staticmethod def load_config(config_file: Optional[str] = None, preset: Optional[str] = None) -> Dict[str, Any]: - """ - 加载配置 - - Args: - config_file: 配置文件路径 - preset: 预设配置名称 - - Returns: - 配置字典 - """ - # 基础配置 config = ConfigManager._get_default_config() - # 应用预设配置 if preset: config = ConfigManager._apply_preset(config, preset) - # 加载用户配置文件 if config_file: if os.path.exists(config_file): config = ConfigManager._load_config_file(config_file, config) else: print(f"警告: 配置文件不存在: {config_file}") - # 验证配置 is_valid, errors = ConfigValidator.validate_config(config) if not is_valid: print("配置验证错误:") @@ -270,7 +228,6 @@ def load_config(config_file: Optional[str] = None, preset: Optional[str] = None) print(f" - {error}") raise ValueError("配置验证失败") - # 提供优化建议 suggestions = ConfigValidator.suggest_optimizations(config) if suggestions: print("配置优化建议:") @@ -281,7 +238,6 @@ def load_config(config_file: Optional[str] = None, preset: Optional[str] = None) @staticmethod def _get_default_config() -> Dict[str, Any]: - """获取默认配置(增强版)""" return { 'scenario': { 'name': 'multi_sensor_scene', @@ -291,8 +247,8 @@ def _get_default_config() -> Dict[str, Any]: 'time_of_day': 'noon', 'duration': 60, 'seed': 42, - 'timeout': 300, # 超时时间(秒) - 'retry_attempts': 3 # 重试次数 + 'timeout': 300, + 'retry_attempts': 3 }, 'traffic': { 'ego_vehicles': 1, @@ -336,7 +292,7 @@ def _get_default_config() -> Dict[str, Any]: 'memory_warning_threshold': 300, 'max_batch_memory_mb': 50, 'v2x_save_interval': 5, - 'compression_format': 'bin' # bin, npy, pcd + 'compression_format': 'bin' }, 'camera_config': { 'fov': 90.0, @@ -395,7 +351,7 @@ def _get_default_config() -> Dict[str, Any]: 'compression_quality': 85, 'resize_images': False, 'resize_dimensions': [640, 480], - 'format': 'jpg' # jpg, png + 'format': 'jpg' }, 'lidar_processing': { 'batch_size': 10, @@ -405,7 +361,7 @@ def _get_default_config() -> Dict[str, Any]: 'memory_warning_threshold': 350, 'max_batch_memory_mb': 50, 'v2x_save_interval': 5, - 'compression_method': 'zlib' # zlib, lz4, none + 'compression_method': 'zlib' }, 'fusion': { 'fusion_cache_size': 100, @@ -422,7 +378,7 @@ def _get_default_config() -> Dict[str, Any]: }, 'output': { 'data_dir': 'cvips_dataset', - 'output_format': 'standard', # standard, v2xformer, kitti, coco + 'output_format': 'standard', 'save_raw': True, 'save_stitched': True, 'save_annotations': False, @@ -436,12 +392,12 @@ def _get_default_config() -> Dict[str, Any]: 'run_quality_check': True, 'generate_summary': True, 'compression_enabled': True, - 'file_naming': 'sequential', # sequential, timestamp + 'file_naming': 'sequential', 'backup_original': False }, 'monitoring': { 'enable_logging': True, - 'log_level': 'INFO', # DEBUG, INFO, WARNING, ERROR + 'log_level': 'INFO', 'log_file': 'cvips.log', 'enable_performance_monitor': True, 'performance_log_interval': 10.0, @@ -468,7 +424,6 @@ def _get_default_config() -> Dict[str, Any]: @staticmethod def _apply_preset(config: Dict[str, Any], preset_name: str) -> Dict[str, Any]: - """应用预设配置""" if preset_name not in ConfigManager.PRESET_CONFIGS: print(f"警告: 未知的预设配置: {preset_name}") return config @@ -476,7 +431,6 @@ def _apply_preset(config: Dict[str, Any], preset_name: str) -> Dict[str, Any]: preset = ConfigManager.PRESET_CONFIGS[preset_name] print(f"应用预设配置: {preset_name} - {preset['description']}") - # 根据优化类型应用配置 optimization = preset.get('optimization', 'balanced') if optimization == 'memory': config = ConfigOptimizer.optimize_for_memory(config) @@ -491,7 +445,6 @@ def _apply_preset(config: Dict[str, Any], preset_name: str) -> Dict[str, Any]: @staticmethod def _load_config_file(config_file: str, base_config: Dict[str, Any]) -> Dict[str, Any]: - """加载配置文件""" try: with open(config_file, 'r', encoding='utf-8') as f: if (config_file.endswith('.yaml') or config_file.endswith('.yml')) and YAML_AVAILABLE: @@ -508,7 +461,6 @@ def _load_config_file(config_file: str, base_config: Dict[str, Any]) -> Dict[str @staticmethod def _deep_update(original: Dict[str, Any], update: Dict[str, Any]) -> Dict[str, Any]: - """深度更新字典""" for key, value in update.items(): if key in original and isinstance(original[key], dict) and isinstance(value, dict): ConfigManager._deep_update(original[key], value) @@ -518,8 +470,6 @@ def _deep_update(original: Dict[str, Any], update: Dict[str, Any]) -> Dict[str, @staticmethod def merge_args(config: Dict[str, Any], args: argparse.Namespace) -> Dict[str, Any]: - """合并命令行参数到配置""" - # 场景参数 if hasattr(args, 'scenario') and args.scenario: config['scenario']['name'] = args.scenario if hasattr(args, 'town') and args.town: @@ -533,58 +483,45 @@ def merge_args(config: Dict[str, Any], args: argparse.Namespace) -> Dict[str, An if hasattr(args, 'seed') and args.seed: config['scenario']['seed'] = args.seed - # 交通参数 if hasattr(args, 'num_vehicles') and args.num_vehicles: config['traffic']['background_vehicles'] = args.num_vehicles if hasattr(args, 'num_pedestrians') and args.num_pedestrians: config['traffic']['pedestrians'] = args.num_pedestrians - # 协同参数 if hasattr(args, 'num_coop_vehicles') and args.num_coop_vehicles: config['cooperative']['num_coop_vehicles'] = args.num_coop_vehicles - # 传感器参数 if hasattr(args, 'capture_interval') and args.capture_interval: config['sensors']['capture_interval'] = args.capture_interval - # V2X参数 if hasattr(args, 'enable_v2x'): config['v2x']['enabled'] = args.enable_v2x - # 增强参数 if hasattr(args, 'enable_enhancement'): config['enhancement']['enabled'] = args.enable_enhancement - # LiDAR参数 if hasattr(args, 'enable_lidar'): config['sensors']['lidar_sensors'] = 1 if args.enable_lidar else 0 config['output']['save_lidar'] = args.enable_lidar - # 融合参数 if hasattr(args, 'enable_fusion'): config['output']['save_fusion'] = args.enable_fusion - # 协同参数 if hasattr(args, 'enable_cooperative'): config['output']['save_cooperative'] = args.enable_cooperative - # 标注参数 if hasattr(args, 'enable_annotations'): config['output']['save_annotations'] = args.enable_annotations - # 验证参数 if hasattr(args, 'skip_validation'): config['output']['validate_data'] = not args.skip_validation - # 质量检查参数 if hasattr(args, 'skip_quality_check'): config['output']['run_quality_check'] = not args.skip_quality_check - # 分析参数 if hasattr(args, 'run_analysis'): config['output']['run_analysis'] = args.run_analysis - # 性能参数 if hasattr(args, 'batch_size') and args.batch_size: config['performance']['batch_size'] = args.batch_size @@ -596,7 +533,6 @@ def merge_args(config: Dict[str, Any], args: argparse.Namespace) -> Dict[str, An if args.enable_downsampling: config['sensors']['lidar_config']['downsample_ratio'] = 0.3 - # 输出格式 if hasattr(args, 'output_format') and args.output_format: config['output']['output_format'] = args.output_format @@ -604,7 +540,6 @@ def merge_args(config: Dict[str, Any], args: argparse.Namespace) -> Dict[str, An @staticmethod def save_config(config: Dict[str, Any], output_path: str, format: str = 'json'): - """保存配置到文件""" try: os.makedirs(os.path.dirname(output_path), exist_ok=True) @@ -623,7 +558,6 @@ def save_config(config: Dict[str, Any], output_path: str, format: str = 'json'): @staticmethod def generate_config_template(output_path: str, preset: Optional[str] = None): - """生成配置模板""" config = ConfigManager.load_config(preset=preset) config['metadata']['created'] = 'template' config['metadata']['description'] = f'配置模板 - {preset if preset else "通用"}' @@ -632,12 +566,10 @@ def generate_config_template(output_path: str, preset: Optional[str] = None): @staticmethod def print_config_summary(config: Dict[str, Any]): - """打印配置摘要""" print("\n" + "=" * 60) print("配置摘要") print("=" * 60) - # 场景信息 scenario = config['scenario'] print(f"\n📋 场景:") print(f" 名称: {scenario['name']}") @@ -646,7 +578,6 @@ def print_config_summary(config: Dict[str, Any]): print(f" 时长: {scenario['duration']}秒") print(f" 随机种子: {scenario.get('seed', '随机')}") - # 交通信息 traffic = config['traffic'] print(f"\n🚗 交通:") print(f" 主车: {traffic['ego_vehicles']}") @@ -654,7 +585,6 @@ def print_config_summary(config: Dict[str, Any]): print(f" 行人: {traffic['pedestrians']}") print(f" 交通灯: {'启用' if traffic['traffic_lights'] else '禁用'}") - # 传感器信息 sensors = config['sensors'] print(f"\n📷 传感器:") print(f" 车辆摄像头: {sensors['vehicle_cameras']}") @@ -663,7 +593,6 @@ def print_config_summary(config: Dict[str, Any]): print(f" 采集间隔: {sensors['capture_interval']}秒") print(f" 图像尺寸: {sensors['image_size'][0]}x{sensors['image_size'][1]}") - # V2X信息 v2x = config['v2x'] print(f"\n📡 V2X通信:") print(f" 状态: {'启用' if v2x['enabled'] else '禁用'}") @@ -671,14 +600,12 @@ def print_config_summary(config: Dict[str, Any]): print(f" 通信范围: {v2x['communication_range']}米") print(f" 更新间隔: {v2x['update_interval']}秒") - # 协同信息 coop = config['cooperative'] print(f"\n🤝 协同感知:") print(f" 协同车辆: {coop['num_coop_vehicles']}") print(f" 共享感知: {'启用' if coop['enable_shared_perception'] else '禁用'}") print(f" 交通警告: {'启用' if coop['enable_traffic_warnings'] else '禁用'}") - # 性能信息 perf = config['performance'] print(f"\n⚡ 性能:") print(f" 批处理大小: {perf['batch_size']}") @@ -686,7 +613,6 @@ def print_config_summary(config: Dict[str, Any]): print(f" 下采样: {'启用' if perf['enable_downsampling'] else '禁用'}") print(f" 帧率限制: {perf['frame_rate_limit']} FPS") - # 输出信息 output = config['output'] print(f"\n💾 输出:") print(f" 输出目录: {output['data_dir']}") @@ -699,7 +625,6 @@ def print_config_summary(config: Dict[str, Any]): @staticmethod def list_presets(): - """列出所有预设配置""" print("\n可用预设配置:") print("-" * 40) for name, preset in ConfigManager.PRESET_CONFIGS.items(): @@ -707,12 +632,9 @@ def list_presets(): print("-" * 40) -# 兼容旧版本的接口 def load_config(config_file=None): - """兼容旧版本的加载配置函数""" return ConfigManager.load_config(config_file) def merge_args(config, args): - """兼容旧版本的合并参数函数""" return ConfigManager.merge_args(config, args) \ No newline at end of file diff --git a/src/enhance_pedestrian_safety/lidar_processor.py b/src/enhance_pedestrian_safety/lidar_processor.py index 654aa674b8..8c0f6ebb20 100644 --- a/src/enhance_pedestrian_safety/lidar_processor.py +++ b/src/enhance_pedestrian_safety/lidar_processor.py @@ -23,21 +23,18 @@ def __init__(self, output_dir, config=None): self.frame_counter = 0 self.data_lock = threading.Lock() - # 性能优化:批量处理设置 self.batch_size = config.get('batch_size', 10) if config else 10 self.point_cloud_batch = [] self.enable_compression = config.get('enable_compression', True) if config else True self.compression_level = config.get('compression_level', 3) if config else 3 - # 内存管理 - 优化点3:更保守的内存设置 - self.max_points_per_frame = config.get('max_points_per_frame', 50000) if config else 50000 # 减少到5万点 + self.max_points_per_frame = config.get('max_points_per_frame', 50000) if config else 50000 self.enable_downsampling = config.get('enable_downsampling', True) if config else True - self.downsample_ratio = config.get('downsample_ratio', 0.3) if config else 0.3 # 增加下采样比例 + self.downsample_ratio = config.get('downsample_ratio', 0.3) if config else 0.3 - # 新增:内存监控和限制 - self.memory_warning_threshold = config.get('memory_warning_threshold', 300) if config else 300 # MB - self.max_batch_memory_mb = config.get('max_batch_memory_mb', 50) if config else 50 # 批次最大内存 - self.v2x_save_interval = config.get('v2x_save_interval', 5) if config else 5 # 每5帧保存一次V2X格式 + self.memory_warning_threshold = config.get('memory_warning_threshold', 300) if config else 300 + self.max_batch_memory_mb = config.get('max_batch_memory_mb', 50) if config else 50 + self.v2x_save_interval = config.get('v2x_save_interval', 5) if config else 5 self._init_calibration_files() @@ -81,109 +78,81 @@ def process_lidar_data(self, lidar_data, frame_num): points = self._carla_lidar_to_numpy(lidar_data) if points is None or points.shape[0] == 0: - print(f"警告: LiDAR数据为空或无效 (帧 {frame_num})") return None - # 检查内存使用情况 if self._check_memory_usage(): - print(f"警告: 内存使用过高,跳过LiDAR处理 (帧 {frame_num})") return None - # 下采样以减少内存使用 original_count = points.shape[0] if self.enable_downsampling and points.shape[0] > self.max_points_per_frame: points = self._downsample_point_cloud(points) - print(f"LiDAR下采样: {original_count} -> {points.shape[0]} 点 (帧 {frame_num})") - # 检查批次内存使用 batch_memory_mb = self._estimate_batch_memory_mb() if batch_memory_mb > self.max_batch_memory_mb: - print(f"批处理内存过高 ({batch_memory_mb:.1f}MB),强制保存当前批次") self._save_batch() - # 添加到批处理 self.point_cloud_batch.append((frame_num, points)) - # 如果达到批处理大小,保存批处理数据 if len(self.point_cloud_batch) >= self.batch_size: self._save_batch() - # 保存单个文件(向后兼容) bin_path = self._save_as_bin(points, frame_num) npy_path = self._save_as_npy(points, frame_num) - # 生成V2XFormer兼容格式 - 优化点:每v2x_save_interval帧保存一次 v2xformer_path = None if frame_num % self.v2x_save_interval == 0: try: v2xformer_path = self._save_as_v2xformer_format(points, frame_num) except Exception as e: - print(f"警告: V2XFormer格式保存失败: {e}") v2xformer_path = None metadata = self._generate_metadata(points, bin_path, npy_path, v2xformer_path) - # 定期清理内存 if frame_num % 20 == 0: gc.collect() return metadata except Exception as e: - print(f"处理LiDAR数据失败: {e}") - # 强制清理内存 gc.collect() return None def _carla_lidar_to_numpy(self, lidar_data): - """高效转换LiDAR数据""" try: - # 使用内存视图减少内存分配 points = np.frombuffer(lidar_data.raw_data, dtype=np.float32) points = np.reshape(points, (int(points.shape[0] / 4), 4)) - - # 只保留位置信息(x, y, z),忽略强度 points = points[:, :3] - # 清理临时数组 del lidar_data gc.collect() return points except Exception as e: - print(f"LiDAR数据转换失败: {e}") - - # 备选转换方法 try: points = [] for i in range(0, len(lidar_data), 4): point = lidar_data[i:i + 4] points.append([point.x, point.y, point.z]) - return np.array(points, dtype=np.float32) except: return None def _downsample_point_cloud(self, points): - """下采样点云以减少内存使用""" if points.shape[0] <= self.max_points_per_frame: return points - # 随机下采样 indices = np.random.choice(points.shape[0], int(points.shape[0] * self.downsample_ratio), replace=False) downsampled = points[indices] - # 清理内存 del points gc.collect() return downsampled def _estimate_batch_memory_mb(self): - """估计批处理内存使用""" if not self.point_cloud_batch: return 0 @@ -191,45 +160,38 @@ def _estimate_batch_memory_mb(self): for _, points in self.point_cloud_batch: total_points += points.shape[0] - # 每个点3个float32 (12字节),加上一些额外开销 - memory_bytes = total_points * 12 * 1.2 # 20%额外开销 + memory_bytes = total_points * 12 * 1.2 return memory_bytes / (1024 * 1024) def _check_memory_usage(self): - """检查内存使用情况""" try: import psutil process = psutil.Process() memory_mb = process.memory_info().rss / (1024 * 1024) if memory_mb > self.memory_warning_threshold: - print(f"警告: 进程内存使用过高: {memory_mb:.1f}MB") return True return False except: return False def _save_batch(self): - """批量保存点云数据(提高效率)""" if not self.point_cloud_batch: return batch_data = [] for frame_num, points in self.point_cloud_batch: - # 只保存点的坐标,不保存完整对象 batch_data.append({ 'frame_num': frame_num, 'points': points.tolist(), 'num_points': points.shape[0] }) - # 生成批处理文件名 min_frame = min([item[0] for item in self.point_cloud_batch]) max_frame = max([item[0] for item in self.point_cloud_batch]) batch_filename = f"lidar_batch_{min_frame:06d}_{max_frame:06d}.json" batch_path = os.path.join(self.lidar_dir, batch_filename) - # 压缩保存 if self.enable_compression: try: json_str = json.dumps(batch_data) @@ -240,49 +202,39 @@ def _save_batch(self): with open(batch_path, 'wb') as f: f.write(compressed_data) except Exception as e: - print(f"压缩保存批处理数据失败: {e}") - # 尝试非压缩保存 try: with open(batch_path, 'w') as f: json.dump(batch_data, f) except Exception as e2: - print(f"非压缩保存批处理数据失败: {e2}") + pass else: try: with open(batch_path, 'w') as f: json.dump(batch_data, f) except Exception as e: - print(f"保存批处理数据失败: {e}") + pass - # 清理批处理缓存 self.point_cloud_batch.clear() gc.collect() - print(f"批量保存LiDAR数据: {batch_filename}") - def _save_as_bin(self, points, frame_num): - """保存为二进制格式(兼容KITTI)""" bin_path = os.path.join(self.lidar_dir, f"lidar_{frame_num:06d}.bin") - # 添加强度信息(全为1) points_with_intensity = np.zeros((points.shape[0], 4), dtype=np.float32) points_with_intensity[:, :3] = points - points_with_intensity[:, 3] = 1.0 # 强度值 + points_with_intensity[:, 3] = 1.0 try: points_with_intensity.tofile(bin_path) except Exception as e: - print(f"保存BIN文件失败: {e}") return None return bin_path def _save_as_npy(self, points, frame_num): - """保存为numpy格式""" npy_path = os.path.join(self.lidar_dir, f"lidar_{frame_num:06d}.npy") try: - # 使用内存映射减少内存使用 memmap_array = np.lib.format.open_memmap( npy_path, mode='w+', @@ -292,23 +244,18 @@ def _save_as_npy(self, points, frame_num): memmap_array[:] = points[:] memmap_array.flush() except Exception as e: - print(f"保存NPY文件失败: {e}") - # 备选保存方式 try: np.save(npy_path, points) except Exception as e2: - print(f"备选保存NPY文件失败: {e2}") return None return npy_path def _save_as_v2xformer_format(self, points, frame_num): - """保存为V2XFormer兼容格式 - 优化点:减少保存频率""" v2x_dir = os.path.join(self.output_dir, "v2xformer_format") os.makedirs(v2x_dir, exist_ok=True) try: - # 创建V2XFormer格式的数据结构 v2x_data = { 'frame_id': frame_num, 'timestamp': datetime.now().timestamp(), @@ -327,21 +274,15 @@ def _save_as_v2xformer_format(self, points, frame_num): } } - # 保存为压缩的JSON格式 filename = f"{frame_num:06d}.pkl.gz" filepath = os.path.join(v2x_dir, filename) - # 使用pickle+gzip压缩保存 with gzip.open(filepath, 'wb') as f: pickle.dump(v2x_data, f, protocol=pickle.HIGHEST_PROTOCOL) - file_size = os.path.getsize(filepath) / 1024 # KB - print(f"保存V2XFormer格式: {filepath} ({file_size:.2f} KB)") - return filepath except Exception as e: - print(f"警告: 保存V2XFormer格式失败: {e}") return None def _generate_metadata(self, points, bin_path, npy_path, v2xformer_path): @@ -375,21 +316,18 @@ def _generate_metadata(self, points, bin_path, npy_path, v2xformer_path): with open(meta_path, 'w') as f: json.dump(metadata, f, indent=2) except Exception as e: - print(f"保存LiDAR元数据失败: {e}") + pass return metadata def flush_batch(self): - """强制刷新批处理数据""" if self.point_cloud_batch: self._save_batch() def generate_lidar_summary(self): - """生成LiDAR数据摘要(优化版)""" if not os.path.exists(self.lidar_dir): return None - # 快速统计文件 import glob bin_files = glob.glob(os.path.join(self.lidar_dir, "*.bin")) npy_files = glob.glob(os.path.join(self.lidar_dir, "*.npy")) @@ -399,20 +337,17 @@ def generate_lidar_summary(self): total_points = 0 total_size = 0 - # 采样统计,避免读取所有文件 - sample_files = bin_files[:5] if len(bin_files) > 5 else bin_files # 减少采样数量 + sample_files = bin_files[:5] if len(bin_files) > 5 else bin_files for bin_file in sample_files: if os.path.exists(bin_file): try: file_size = os.path.getsize(bin_file) total_size += file_size - # 估算点数 - points_in_file = file_size // (4 * 4) # 4个float32,每个4字节 + points_in_file = file_size // (4 * 4) total_points += points_in_file except: continue - # 根据采样估算总数 if sample_files and len(bin_files) > 0: avg_points_per_file = total_points / max(len(sample_files), 1) total_points_estimated = avg_points_per_file * len(bin_files) @@ -440,13 +375,12 @@ def generate_lidar_summary(self): with open(summary_path, 'w') as f: json.dump(summary, f, indent=2) except Exception as e: - print(f"保存LiDAR摘要失败: {e}") + pass return summary class MultiSensorFusion: - """多传感器融合(优化版)""" def __init__(self, output_dir, config=None): self.output_dir = output_dir @@ -454,8 +388,8 @@ def __init__(self, output_dir, config=None): os.makedirs(self.fusion_dir, exist_ok=True) self.calibration_data = {} - self.fusion_cache = {} # 融合缓存 - self.cache_size = config.get('fusion_cache_size', 50) if config else 50 # 减少缓存大小 + self.fusion_cache = {} + self.cache_size = config.get('fusion_cache_size', 50) if config else 50 self._load_calibration() @@ -463,48 +397,41 @@ def _load_calibration(self): calibration_dir = os.path.join(self.output_dir, "calibration") if os.path.exists(calibration_dir): - # 批量读取校准文件 calibration_files = [] for root, dirs, files in os.walk(calibration_dir): for file in files: if file.endswith('.json'): calibration_files.append(os.path.join(root, file)) - # 限制同时读取的文件数量 - for file in calibration_files[:10]: # 最多读取10个文件 + for file in calibration_files[:10]: try: with open(file, 'r') as f: data = json.load(f) sensor_name = os.path.basename(file).replace('.json', '') self.calibration_data[sensor_name] = data except Exception as e: - print(f"加载校准文件 {file} 失败: {e}") + pass def create_synchronization_file(self, frame_num, sensor_data): - """创建同步文件(优化版)""" - # 检查缓存 cache_key = f"{frame_num}_{hash(str(sensor_data))}" if cache_key in self.fusion_cache: return self.fusion_cache[cache_key] sync_data = { 'frame_id': frame_num, - 'timestamp': datetime.now().timestamp(), # 使用时间戳而不是ISO格式 + 'timestamp': datetime.now().timestamp(), 'sensors': {}, 'transformations': {} } - # 批量处理传感器数据 for sensor_type, data_path in sensor_data.items(): if data_path and os.path.exists(data_path): - # 获取文件信息(不加载完整文件) file_info = { 'file_path': os.path.basename(data_path), 'file_size': os.path.getsize(data_path), 'modified_time': os.path.getmtime(data_path) } - # 如果是图像,获取尺寸信息 if data_path.endswith(('.png', '.jpg', '.jpeg')): try: import cv2 @@ -516,7 +443,6 @@ def create_synchronization_file(self, frame_num, sensor_data): sync_data['sensors'][sensor_type] = file_info - # 添加变换信息(如果可用) if sensor_type in self.calibration_data: sync_data['transformations'][sensor_type] = { 'matrix': self.calibration_data[sensor_type].get('matrix', @@ -524,11 +450,9 @@ def create_synchronization_file(self, frame_num, sensor_data): [0, 0, 0, 1]]) } - # 压缩保存 sync_file = os.path.join(self.fusion_dir, f"sync_{frame_num:06d}.json") - # 使用压缩的JSON - if len(str(sync_data)) > 1000: # 如果数据较大,压缩保存 + if len(str(sync_data)) > 1000: compressed_data = zlib.compress( json.dumps(sync_data).encode('utf-8'), level=3 @@ -538,11 +462,9 @@ def create_synchronization_file(self, frame_num, sensor_data): f.write(compressed_data) else: with open(sync_file, 'w') as f: - json.dump(sync_data, f, separators=(',', ':')) # 紧凑格式 + json.dump(sync_data, f, separators=(',', ':')) - # 更新缓存 if len(self.fusion_cache) >= self.cache_size: - # 移除最旧的缓存项 oldest_key = next(iter(self.fusion_cache)) del self.fusion_cache[oldest_key] @@ -551,19 +473,15 @@ def create_synchronization_file(self, frame_num, sensor_data): return sync_file def generate_fusion_report(self): - """生成融合报告(优化版)""" if not os.path.exists(self.fusion_dir): return None - # 快速扫描文件 import glob sync_files = glob.glob(os.path.join(self.fusion_dir, "*.json*")) - # 解析文件名获取帧范围(避免读取所有文件) frame_ids = [] - for sync_file in sync_files[:20]: # 只检查前20个文件 + for sync_file in sync_files[:20]: try: - # 从文件名提取帧号 filename = os.path.basename(sync_file) if filename.startswith('sync_'): frame_id = int(filename.split('_')[1].split('.')[0]) From 1ab61961856f15627c413f96f87910ec1cc04995 Mon Sep 17 00:00:00 2001 From: Z-w-7799 <1011069369@qq.com> Date: Sun, 21 Dec 2025 14:04:33 +0800 Subject: [PATCH 13/20] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BA=86=E6=80=A7?= =?UTF-8?q?=E8=83=BD=E7=9B=91=E6=8E=A7=E4=B8=AD=E7=9A=84=E5=B8=A7=E7=8E=87?= =?UTF-8?q?=E8=AE=A1=E7=AE=97=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/enhance_pedestrian_safety/main.py | 135 ++++++++++++++++---------- 1 file changed, 82 insertions(+), 53 deletions(-) diff --git a/src/enhance_pedestrian_safety/main.py b/src/enhance_pedestrian_safety/main.py index 1331ff3b79..a506397e08 100644 --- a/src/enhance_pedestrian_safety/main.py +++ b/src/enhance_pedestrian_safety/main.py @@ -30,6 +30,8 @@ def __init__(self): self.memory_samples = [] self.cpu_samples = [] self.frame_times = [] + self.first_frame_time = None + self.last_frame_time = None def sample_memory(self): try: @@ -72,14 +74,17 @@ def sample_cpu(self): def record_frame_time(self, frame_time): self.frame_times.append(frame_time) + if self.first_frame_time is None: + self.first_frame_time = time.time() + self.last_frame_time = time.time() def get_performance_summary(self): - if not self.frame_times: + if not self.frame_times or len(self.frame_times) < 2: avg_frame_time = 0 fps = 0 else: avg_frame_time = np.mean(self.frame_times) - fps = 1.0 / avg_frame_time if avg_frame_time > 0 else 0 + fps = len(self.frame_times) / max(0.1, (self.last_frame_time - self.first_frame_time)) summary = { 'total_runtime': time.time() - self.start_time, @@ -90,14 +95,24 @@ def get_performance_summary(self): 'max_cpu_percent': max(self.cpu_samples) if self.cpu_samples else 0, 'average_frame_time': avg_frame_time, 'frames_per_second': fps, - 'total_frames': len(self.frame_times), - 'frame_time_stats': { - 'p50': np.percentile(self.frame_times, 50) if self.frame_times else 0, - 'p95': np.percentile(self.frame_times, 95) if self.frame_times else 0, - 'p99': np.percentile(self.frame_times, 99) if self.frame_times else 0, - 'std': np.std(self.frame_times) if self.frame_times else 0 - } + 'total_frames': len(self.frame_times) } + + if self.frame_times and len(self.frame_times) >= 2: + summary['frame_time_stats'] = { + 'p50': np.percentile(self.frame_times, 50), + 'p95': np.percentile(self.frame_times, 95), + 'p99': np.percentile(self.frame_times, 99) if len(self.frame_times) > 1 else 0, + 'std': np.std(self.frame_times) + } + else: + summary['frame_time_stats'] = { + 'p50': 0, + 'p95': 0, + 'p99': 0, + 'std': 0 + } + return summary @@ -119,12 +134,12 @@ def error(msg): @staticmethod def debug(msg): - timestamp = datetime.now().strftime("%Y-%m-d %H:%M:%S") + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") print(f"[DEBUG][{timestamp}] {msg}") @staticmethod def performance(msg): - timestamp = datetime.now().strftime("%Y-%m-d %H:%M:%S") + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") print(f"[PERF][{timestamp}] {msg}") @@ -569,8 +584,7 @@ def __init__(self, world, config, data_dir): 'total_images': 0, 'total_lidar_frames': 0, 'image_capture_times': [], - 'lidar_processing_times': [], - 'frame_drop_count': 0 + 'lidar_processing_times': [] } def setup_cameras(self, vehicle, center_location, vehicle_id=0): @@ -868,7 +882,6 @@ def cleanup(self): self.is_running = False - Log.info("停止所有传感器监听...") for sensor in self.sensors: try: if hasattr(sensor, 'stop'): @@ -885,7 +898,6 @@ def cleanup(self): except: pass - Log.info(f"销毁 {len(self.sensors)} 个传感器...") for i, sensor in enumerate(self.sensors): try: if hasattr(sensor, 'destroy'): @@ -1105,17 +1117,15 @@ def collect_data(self): memory_warning_issued = False early_stop_triggered = False - # 添加内存监控阈值 - memory_warning_threshold = 350 # MB - memory_critical_threshold = 400 # MB - early_stop_threshold = 450 # MB + memory_warning_threshold = 350 + memory_critical_threshold = 400 + early_stop_threshold = 450 try: while time.time() - self.start_time < duration and self.is_running: current_time = time.time() elapsed = current_time - self.start_time - # 内存监控和早期停止机制 if current_time - last_memory_check >= 2.0: try: import psutil @@ -1204,16 +1214,21 @@ def collect_data(self): self.collected_frames = sum(mgr.get_frame_count() for mgr in self.sensor_managers.values()) - performance_summary = self.performance_monitor.get_performance_summary() + if self.collected_frames > 0: + total_frames_from_sensors = self.collected_frames + performance_summary = self.performance_monitor.get_performance_summary() - if early_stop_triggered: - Log.warning("数据收集因内存过高而提前终止") - else: - Log.info(f"收集完成: {self.collected_frames}帧, 用时: {elapsed:.1f}秒") + if early_stop_triggered: + Log.warning("数据收集因内存过高而提前终止") + else: + Log.info(f"收集完成: {self.collected_frames}帧, 用时: {elapsed:.1f}秒") - Log.info(f"平均帧率: {performance_summary['frames_per_second']:.2f} FPS") - Log.info(f"最大内存使用: {performance_summary['max_memory_mb']:.1f} MB") - Log.info(f"平均CPU使用: {performance_summary['average_cpu_percent']:.1f}%") + fps = total_frames_from_sensors / max(elapsed, 0.1) + Log.info(f"平均帧率: {fps:.2f} FPS") + Log.info(f"最大内存使用: {performance_summary['max_memory_mb']:.1f} MB") + Log.info(f"平均CPU使用: {performance_summary['average_cpu_percent']:.1f}%") + else: + Log.warning("未收集到任何数据帧") self._save_metadata() self._print_summary() @@ -1478,6 +1493,8 @@ def _simulate_object_detection(self, vehicle): return detected_objects def _save_metadata(self): + total_frames = self.collected_frames + metadata = { 'scenario': self.config['scenario'], 'traffic': self.config['traffic'], @@ -1488,22 +1505,26 @@ def _save_metadata(self): 'output': self.config['output'], 'collection': { 'duration': round(time.time() - self.start_time, 2), - 'total_frames': self.collected_frames, - 'frame_rate': round(self.collected_frames / max(time.time() - self.start_time, 0.1), 2) - }, - 'performance': self.performance_monitor.get_performance_summary() + 'total_frames': total_frames, + 'frame_rate': round(total_frames / max(time.time() - self.start_time, 0.1), + 2) if total_frames > 0 else 0 + } } - sensor_summaries = {} - for vehicle_id, sensor_manager in self.sensor_managers.items(): - sensor_summaries[vehicle_id] = sensor_manager.generate_sensor_summary() - metadata['sensor_summaries'] = sensor_summaries + if total_frames > 0: + performance_summary = self.performance_monitor.get_performance_summary() + metadata['performance'] = performance_summary - if self.v2x_communication: - metadata['v2x_status'] = self.v2x_communication.get_network_status() + sensor_summaries = {} + for vehicle_id, sensor_manager in self.sensor_managers.items(): + sensor_summaries[vehicle_id] = sensor_manager.generate_sensor_summary() + metadata['sensor_summaries'] = sensor_summaries - if self.multi_vehicle_manager: - metadata['cooperative_summary'] = self.multi_vehicle_manager.generate_summary() + if self.v2x_communication: + metadata['v2x_status'] = self.v2x_communication.get_network_status() + + if self.multi_vehicle_manager: + metadata['cooperative_summary'] = self.multi_vehicle_manager.generate_summary() meta_path = os.path.join(self.output_dir, "metadata", "collection_info.json") with open(meta_path, 'w', encoding='utf-8') as f: @@ -1553,29 +1574,37 @@ def _print_summary(self): if os.path.exists(kitti_dir): print(f"KITTI格式: 已生成") - performance = self.performance_monitor.get_performance_summary() + total_frames = self.collected_frames + elapsed = time.time() - self.start_time + print(f"\n性能统计:") - print(f" 平均帧率: {performance['frames_per_second']:.2f} FPS") - print(f" 帧时统计:") - print(f" 平均: {performance['average_frame_time']:.3f}秒") - print(f" P50: {performance['frame_time_stats']['p50']:.3f}秒") - print(f" P95: {performance['frame_time_stats']['p95']:.3f}秒") - print(f" P99: {performance['frame_time_stats']['p99']:.3f}秒") - print(f" 平均内存: {performance['average_memory_mb']:.1f} MB") - print(f" 最大内存: {performance['max_memory_mb']:.1f} MB") - print(f" 平均CPU: {performance['average_cpu_percent']:.1f}%") - print(f" 总帧数: {performance['total_frames']}") + if total_frames > 0: + fps = total_frames / max(elapsed, 0.1) + print(f" 平均帧率: {fps:.2f} FPS") + + performance = self.performance_monitor.get_performance_summary() + print(f" 帧时统计:") + print(f" 平均: {performance['average_frame_time']:.3f}秒") + print(f" P50: {performance['frame_time_stats']['p50']:.3f}秒") + print(f" P95: {performance['frame_time_stats']['p95']:.3f}秒") + print(f" P99: {performance['frame_time_stats']['p99']:.3f}秒") + print(f" 平均内存: {performance['average_memory_mb']:.1f} MB") + print(f" 最大内存: {performance['max_memory_mb']:.1f} MB") + print(f" 平均CPU: {performance['average_cpu_percent']:.1f}%") + print(f" 总帧数: {total_frames}") + else: + print(" 未收集到有效帧数据") print(f"\n输出目录: {self.output_dir}") print("=" * 60) def run_validation(self): - if self.config['output'].get('validate_data', True): + if self.config['output'].get('validate_data', True) and self.collected_frames > 0: Log.info("运行数据验证...") DataValidator.validate_dataset(self.output_dir) def run_analysis(self): - if self.config['output'].get('run_analysis', False): + if self.config['output'].get('run_analysis', False) and self.collected_frames > 0: Log.info("运行数据分析...") DataAnalyzer.analyze_dataset(self.output_dir) From a716e9a6b0251ed15064eae407e87405b9d3680d Mon Sep 17 00:00:00 2001 From: Z-w-7799 <1011069369@qq.com> Date: Sun, 21 Dec 2025 14:27:53 +0800 Subject: [PATCH 14/20] =?UTF-8?q?=E9=87=8D=E5=86=99readme=E6=96=87?= =?UTF-8?q?=E4=BB=B6=20=E4=BF=AE=E6=94=B9=E9=83=A8=E5=88=86=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=20=E5=8E=BB=E9=99=A4=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/enhance_pedestrian_safety/README.md | 59 ++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/src/enhance_pedestrian_safety/README.md b/src/enhance_pedestrian_safety/README.md index 35411f1683..b0937dbb52 100644 --- a/src/enhance_pedestrian_safety/README.md +++ b/src/enhance_pedestrian_safety/README.md @@ -1,2 +1,59 @@ -数据图片请点击:通过网盘分享的文件:数据图片 +CVIPS - Cooperative Vehicle-Infrastructure Perception System + +基于CARLA仿真平台的多传感器协同感知数据采集系统,支持V2X通信、多车辆协同、数据增强与验证。 + +项目简介 + +CVIPS是一个用于自动驾驶研究的多传感器数据采集系统,在CARLA仿真环境中生成高质量的协同感知数据集。系统支持多车辆、多传感器协同工作,包含完整的V2X通信模拟、数据增强、验证和分析工具链。 + +核心功能 +1. 多传感器数据采集 + +摄像头系统 激光雷达 传感器融合 +2. 协同感知与V2X通信 + +多车辆协同 V2X消息系统 共享感知 +3. 智能交通场景生成 + +多样化场景 交通流控制 特殊事件 +4. 数据处理与增强 + +图像处理 LiDAR处理 数据增强 +5. 质量保证与验证 + +数据验证器 数据分析器 性能监控 +6. 多格式输出支持 + +标准格式 V2XFormer格式 KITTI格式 + +环境要求 + +Python 3.7 + +CARLA 0.9.14 + +内存:≥ 8GB(建议16GB) + +存储:≥ 50GB可用空间 + +安装步骤 +1. 克隆仓库 + +git clone https://github.com/Z-w-7799/nn.git +2. 安装依赖 + +pip install -r requirements.txt +3. 配置CARLA路径 + +方式1:设置环境变量 +export CARLA_PYTHON_PATH=/path/to/carla/PythonAPI/carla/dist + +方式2:使用命令行参数 +python main.py --carla-path /path/to/carla + +该项目已封装为 ROS 1 Kinetic 包,运行步骤:roscore → rosrun carla_sensor main.py + + +数据图片请点击: +通过网盘分享的文件:数据图片 链接: https://pan.baidu.com/s/1SMXdI7v09mhmYEW40bbAHQ?pwd=u6a4 提取码: u6a4 \ No newline at end of file From 27e134a14eb81db474a706b98daafb4eaeda2a4a Mon Sep 17 00:00:00 2001 From: Z-w-7799 <1011069369@qq.com> Date: Sun, 21 Dec 2025 20:27:23 +0800 Subject: [PATCH 15/20] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=B8=BA=E7=AC=A6?= =?UTF-8?q?=E5=90=88=E8=A7=84=E8=8C=83=E7=9A=84markdown=20=E5=B9=B6?= =?UTF-8?q?=E5=8A=A0=E5=9B=9E=E5=8E=9F=E6=9C=AC=E5=BA=94=E8=AF=A5=E5=AD=98?= =?UTF-8?q?=E5=9C=A8=E7=9A=84=E6=B3=A8=E9=87=8A=E9=83=A8=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/enhance_pedestrian_safety/README.md | 99 ++++++++----- .../config_manager.py | 102 +++++++++++-- .../lidar_processor.py | 124 +++++++++++++--- src/enhance_pedestrian_safety/main.py | 135 +++++++----------- 4 files changed, 309 insertions(+), 151 deletions(-) diff --git a/src/enhance_pedestrian_safety/README.md b/src/enhance_pedestrian_safety/README.md index b0937dbb52..a4403a017d 100644 --- a/src/enhance_pedestrian_safety/README.md +++ b/src/enhance_pedestrian_safety/README.md @@ -1,59 +1,86 @@ -CVIPS - Cooperative Vehicle-Infrastructure Perception System +```markdown +# CVIPS - Cooperative Vehicle-Infrastructure Perception System 基于CARLA仿真平台的多传感器协同感知数据采集系统,支持V2X通信、多车辆协同、数据增强与验证。 -项目简介 -CVIPS是一个用于自动驾驶研究的多传感器数据采集系统,在CARLA仿真环境中生成高质量的协同感知数据集。系统支持多车辆、多传感器协同工作,包含完整的V2X通信模拟、数据增强、验证和分析工具链。 +## 项目简介 +CVIPS是一个用于自动驾驶研究的多传感器数据采集系统,在CARLA仿真环境中生成高质量的协同感知数据集。 +系统支持多车辆、多传感器协同工作,包含完整的V2X通信模拟、数据增强、验证和分析工具链。 -核心功能 -1. 多传感器数据采集 -摄像头系统 激光雷达 传感器融合 -2. 协同感知与V2X通信 +## 核心功能 +1. **多传感器数据采集** + - 摄像头系统 + - 激光雷达 + - 传感器融合 -多车辆协同 V2X消息系统 共享感知 -3. 智能交通场景生成 +2. **协同感知与V2X通信** + - 多车辆协同 + - V2X消息系统 + - 共享感知 -多样化场景 交通流控制 特殊事件 -4. 数据处理与增强 +3. **智能交通场景生成** + - 多样化场景 + - 交通流控制 + - 特殊事件 -图像处理 LiDAR处理 数据增强 -5. 质量保证与验证 +4. **数据处理与增强** + - 图像处理 + - LiDAR处理 + - 数据增强 -数据验证器 数据分析器 性能监控 -6. 多格式输出支持 +5. **质量保证与验证** + - 数据验证器 + - 数据分析器 + - 性能监控 -标准格式 V2XFormer格式 KITTI格式 +6. **多格式输出支持** + - 标准格式 + - V2XFormer格式 + - KITTI格式 -环境要求 -Python 3.7 +## 环境要求 +- Python 3.7 +- CARLA 0.9.14 +- 内存:≥ 8GB(建议16GB) +- 存储:≥ 50GB可用空间 -CARLA 0.9.14 -内存:≥ 8GB(建议16GB) +## 安装步骤 +1. **克隆仓库** + ```bash + git clone https://github.com/Z-w-7799/nn.git + ``` -存储:≥ 50GB可用空间 +2. **安装依赖** + ```bash + pip install -r requirements.txt + ``` -安装步骤 -1. 克隆仓库 +3. **配置CARLA路径** + - 方式1:设置环境变量 + ```bash + export CARLA_PYTHON_PATH=/path/to/carla/PythonAPI/carla/dist + ``` + - 方式2:使用命令行参数 + ```bash + python main.py --carla-path /path/to/carla + ``` -git clone https://github.com/Z-w-7799/nn.git -2. 安装依赖 -pip install -r requirements.txt -3. 配置CARLA路径 +## ROS 1 Kinetic封装运行步骤 +该项目已封装为ROS 1 Kinetic包,运行流程: +1. 启动ROS核心 + ```bash + roscore + ``` +2. 运行CVIPS节点 + ```bash + rosrun carla_sensor main.py + ``` -方式1:设置环境变量 -export CARLA_PYTHON_PATH=/path/to/carla/PythonAPI/carla/dist -方式2:使用命令行参数 -python main.py --carla-path /path/to/carla -该项目已封装为 ROS 1 Kinetic 包,运行步骤:roscore → rosrun carla_sensor main.py - -数据图片请点击: -通过网盘分享的文件:数据图片 -链接: https://pan.baidu.com/s/1SMXdI7v09mhmYEW40bbAHQ?pwd=u6a4 提取码: u6a4 \ No newline at end of file diff --git a/src/enhance_pedestrian_safety/config_manager.py b/src/enhance_pedestrian_safety/config_manager.py index c608de6674..1f64c72ef9 100644 --- a/src/enhance_pedestrian_safety/config_manager.py +++ b/src/enhance_pedestrian_safety/config_manager.py @@ -4,24 +4,31 @@ import copy from typing import Dict, Any, Optional, List, Tuple +# 尝试导入yaml,如果不存在则跳过 try: import yaml + YAML_AVAILABLE = True except ImportError: YAML_AVAILABLE = False + print("警告: PyYAML未安装,将使用JSON格式") class ConfigValidator: + """配置验证器""" @staticmethod def validate_config(config: Dict[str, Any]) -> Tuple[bool, List[str]]: + """验证配置的有效性""" errors = [] + # 验证必填字段 required_sections = ['scenario', 'sensors', 'output'] for section in required_sections: if section not in config: errors.append(f"缺失必要配置节: {section}") + # 验证场景配置 if 'scenario' in config: scenario = config['scenario'] if 'duration' in scenario and scenario['duration'] <= 0: @@ -29,6 +36,7 @@ def validate_config(config: Dict[str, Any]) -> Tuple[bool, List[str]]: if 'town' not in scenario: errors.append("场景配置中缺失地图名称") + # 验证传感器配置 if 'sensors' in config: sensors = config['sensors'] if 'capture_interval' in sensors and sensors['capture_interval'] <= 0: @@ -39,6 +47,7 @@ def validate_config(config: Dict[str, Any]) -> Tuple[bool, List[str]]: elif any(dim <= 0 for dim in sensors['image_size']): errors.append("图像尺寸必须大于0") + # 验证性能配置 if 'performance' in config: perf = config['performance'] if 'batch_size' in perf and perf['batch_size'] <= 0: @@ -48,18 +57,22 @@ def validate_config(config: Dict[str, Any]) -> Tuple[bool, List[str]]: @staticmethod def suggest_optimizations(config: Dict[str, Any]) -> List[str]: + """根据配置提供优化建议""" suggestions = [] + # 内存使用建议 if config.get('sensors', {}).get('lidar_sensors', 0) > 0: lidar_config = config['sensors'].get('lidar_config', {}) max_points = lidar_config.get('max_points_per_frame', 50000) if max_points > 50000: suggestions.append(f"LiDAR最大点数({max_points})较高,建议降低到50000以下以减少内存使用") + # 性能建议 capture_interval = config['sensors'].get('capture_interval', 2.0) if capture_interval < 1.0: suggestions.append(f"采集间隔({capture_interval}s)较短,可能导致高负载,建议增加到1.0s以上") + # 输出建议 output = config.get('output', {}) enabled_outputs = [k for k, v in output.items() if isinstance(v, bool) and v] if len(enabled_outputs) > 5: @@ -69,11 +82,14 @@ def suggest_optimizations(config: Dict[str, Any]) -> List[str]: class ConfigOptimizer: + """配置优化器""" @staticmethod def optimize_for_memory(config: Dict[str, Any]) -> Dict[str, Any]: + """为内存使用优化配置""" optimized = copy.deepcopy(config) + # 调整LiDAR设置 if optimized['sensors'].get('lidar_sensors', 0) > 0: lidar_config = optimized['sensors'].setdefault('lidar_config', {}) lidar_config.update({ @@ -83,6 +99,7 @@ def optimize_for_memory(config: Dict[str, Any]) -> Dict[str, Any]: 'max_batch_memory_mb': 30 }) + # 调整性能设置 perf = optimized.setdefault('performance', {}) perf.update({ 'batch_size': 3, @@ -93,6 +110,7 @@ def optimize_for_memory(config: Dict[str, Any]) -> Dict[str, Any]: 'frame_rate_limit': 3.0 }) + # 调整图像处理 perf['image_processing'] = { 'compress_images': True, 'compression_quality': 80, @@ -103,8 +121,10 @@ def optimize_for_memory(config: Dict[str, Any]) -> Dict[str, Any]: @staticmethod def optimize_for_quality(config: Dict[str, Any]) -> Dict[str, Any]: + """为数据质量优化配置""" optimized = copy.deepcopy(config) + # 调整传感器设置 sensors = optimized['sensors'] sensors.update({ 'image_size': [1920, 1080], @@ -119,6 +139,7 @@ def optimize_for_quality(config: Dict[str, Any]) -> Dict[str, Any]: } }) + # 调整输出设置 output = optimized['output'] output.update({ 'save_annotations': True, @@ -128,6 +149,7 @@ def optimize_for_quality(config: Dict[str, Any]) -> Dict[str, Any]: 'run_quality_check': True }) + # 调整增强设置 enhanced = optimized.setdefault('enhancement', {}) enhanced.update({ 'enabled': True, @@ -140,31 +162,35 @@ def optimize_for_quality(config: Dict[str, Any]) -> Dict[str, Any]: @staticmethod def optimize_for_speed(config: Dict[str, Any]) -> Dict[str, Any]: + """为处理速度优化配置""" optimized = copy.deepcopy(config) + # 调整传感器设置 sensors = optimized['sensors'] sensors.update({ 'image_size': [640, 480], 'capture_interval': 3.0, - 'lidar_sensors': 0, - 'radar_sensors': 0 + 'lidar_sensors': 0, # 禁用LiDAR + 'radar_sensors': 0 # 禁用雷达 }) + # 调整性能设置 perf = optimized.setdefault('performance', {}) perf.update({ 'batch_size': 10, 'enable_compression': True, - 'compression_level': 1, + 'compression_level': 1, # 快速压缩 'enable_downsampling': True, 'enable_async_processing': True, 'max_cache_size': 20, 'frame_rate_limit': 10.0 }) + # 简化输出 output = optimized['output'] output.update({ 'save_raw': True, - 'save_stitched': False, + 'save_stitched': False, # 不拼接图像 'save_annotations': False, 'save_lidar': False, 'save_fusion': False, @@ -175,7 +201,9 @@ def optimize_for_speed(config: Dict[str, Any]) -> Dict[str, Any]: class ConfigManager: + """配置管理器(增强版)""" + # 预定义配置模板 PRESET_CONFIGS = { 'balanced': { 'description': '平衡配置 - 兼顾性能和质量', @@ -210,17 +238,31 @@ class ConfigManager: @staticmethod def load_config(config_file: Optional[str] = None, preset: Optional[str] = None) -> Dict[str, Any]: + """ + 加载配置 + + Args: + config_file: 配置文件路径 + preset: 预设配置名称 + + Returns: + 配置字典 + """ + # 基础配置 config = ConfigManager._get_default_config() + # 应用预设配置 if preset: config = ConfigManager._apply_preset(config, preset) + # 加载用户配置文件 if config_file: if os.path.exists(config_file): config = ConfigManager._load_config_file(config_file, config) else: print(f"警告: 配置文件不存在: {config_file}") + # 验证配置 is_valid, errors = ConfigValidator.validate_config(config) if not is_valid: print("配置验证错误:") @@ -228,6 +270,7 @@ def load_config(config_file: Optional[str] = None, preset: Optional[str] = None) print(f" - {error}") raise ValueError("配置验证失败") + # 提供优化建议 suggestions = ConfigValidator.suggest_optimizations(config) if suggestions: print("配置优化建议:") @@ -238,6 +281,7 @@ def load_config(config_file: Optional[str] = None, preset: Optional[str] = None) @staticmethod def _get_default_config() -> Dict[str, Any]: + """获取默认配置(增强版)""" return { 'scenario': { 'name': 'multi_sensor_scene', @@ -247,8 +291,8 @@ def _get_default_config() -> Dict[str, Any]: 'time_of_day': 'noon', 'duration': 60, 'seed': 42, - 'timeout': 300, - 'retry_attempts': 3 + 'timeout': 300, # 超时时间(秒) + 'retry_attempts': 3 # 重试次数 }, 'traffic': { 'ego_vehicles': 1, @@ -292,7 +336,7 @@ def _get_default_config() -> Dict[str, Any]: 'memory_warning_threshold': 300, 'max_batch_memory_mb': 50, 'v2x_save_interval': 5, - 'compression_format': 'bin' + 'compression_format': 'bin' # bin, npy, pcd }, 'camera_config': { 'fov': 90.0, @@ -351,7 +395,7 @@ def _get_default_config() -> Dict[str, Any]: 'compression_quality': 85, 'resize_images': False, 'resize_dimensions': [640, 480], - 'format': 'jpg' + 'format': 'jpg' # jpg, png }, 'lidar_processing': { 'batch_size': 10, @@ -361,7 +405,7 @@ def _get_default_config() -> Dict[str, Any]: 'memory_warning_threshold': 350, 'max_batch_memory_mb': 50, 'v2x_save_interval': 5, - 'compression_method': 'zlib' + 'compression_method': 'zlib' # zlib, lz4, none }, 'fusion': { 'fusion_cache_size': 100, @@ -378,7 +422,7 @@ def _get_default_config() -> Dict[str, Any]: }, 'output': { 'data_dir': 'cvips_dataset', - 'output_format': 'standard', + 'output_format': 'standard', # standard, v2xformer, kitti, coco 'save_raw': True, 'save_stitched': True, 'save_annotations': False, @@ -392,12 +436,12 @@ def _get_default_config() -> Dict[str, Any]: 'run_quality_check': True, 'generate_summary': True, 'compression_enabled': True, - 'file_naming': 'sequential', + 'file_naming': 'sequential', # sequential, timestamp 'backup_original': False }, 'monitoring': { 'enable_logging': True, - 'log_level': 'INFO', + 'log_level': 'INFO', # DEBUG, INFO, WARNING, ERROR 'log_file': 'cvips.log', 'enable_performance_monitor': True, 'performance_log_interval': 10.0, @@ -424,6 +468,7 @@ def _get_default_config() -> Dict[str, Any]: @staticmethod def _apply_preset(config: Dict[str, Any], preset_name: str) -> Dict[str, Any]: + """应用预设配置""" if preset_name not in ConfigManager.PRESET_CONFIGS: print(f"警告: 未知的预设配置: {preset_name}") return config @@ -431,6 +476,7 @@ def _apply_preset(config: Dict[str, Any], preset_name: str) -> Dict[str, Any]: preset = ConfigManager.PRESET_CONFIGS[preset_name] print(f"应用预设配置: {preset_name} - {preset['description']}") + # 根据优化类型应用配置 optimization = preset.get('optimization', 'balanced') if optimization == 'memory': config = ConfigOptimizer.optimize_for_memory(config) @@ -445,6 +491,7 @@ def _apply_preset(config: Dict[str, Any], preset_name: str) -> Dict[str, Any]: @staticmethod def _load_config_file(config_file: str, base_config: Dict[str, Any]) -> Dict[str, Any]: + """加载配置文件""" try: with open(config_file, 'r', encoding='utf-8') as f: if (config_file.endswith('.yaml') or config_file.endswith('.yml')) and YAML_AVAILABLE: @@ -461,6 +508,7 @@ def _load_config_file(config_file: str, base_config: Dict[str, Any]) -> Dict[str @staticmethod def _deep_update(original: Dict[str, Any], update: Dict[str, Any]) -> Dict[str, Any]: + """深度更新字典""" for key, value in update.items(): if key in original and isinstance(original[key], dict) and isinstance(value, dict): ConfigManager._deep_update(original[key], value) @@ -470,6 +518,8 @@ def _deep_update(original: Dict[str, Any], update: Dict[str, Any]) -> Dict[str, @staticmethod def merge_args(config: Dict[str, Any], args: argparse.Namespace) -> Dict[str, Any]: + """合并命令行参数到配置""" + # 场景参数 if hasattr(args, 'scenario') and args.scenario: config['scenario']['name'] = args.scenario if hasattr(args, 'town') and args.town: @@ -483,45 +533,58 @@ def merge_args(config: Dict[str, Any], args: argparse.Namespace) -> Dict[str, An if hasattr(args, 'seed') and args.seed: config['scenario']['seed'] = args.seed + # 交通参数 if hasattr(args, 'num_vehicles') and args.num_vehicles: config['traffic']['background_vehicles'] = args.num_vehicles if hasattr(args, 'num_pedestrians') and args.num_pedestrians: config['traffic']['pedestrians'] = args.num_pedestrians + # 协同参数 if hasattr(args, 'num_coop_vehicles') and args.num_coop_vehicles: config['cooperative']['num_coop_vehicles'] = args.num_coop_vehicles + # 传感器参数 if hasattr(args, 'capture_interval') and args.capture_interval: config['sensors']['capture_interval'] = args.capture_interval + # V2X参数 if hasattr(args, 'enable_v2x'): config['v2x']['enabled'] = args.enable_v2x + # 增强参数 if hasattr(args, 'enable_enhancement'): config['enhancement']['enabled'] = args.enable_enhancement + # LiDAR参数 if hasattr(args, 'enable_lidar'): config['sensors']['lidar_sensors'] = 1 if args.enable_lidar else 0 config['output']['save_lidar'] = args.enable_lidar + # 融合参数 if hasattr(args, 'enable_fusion'): config['output']['save_fusion'] = args.enable_fusion + # 协同参数 if hasattr(args, 'enable_cooperative'): config['output']['save_cooperative'] = args.enable_cooperative + # 标注参数 if hasattr(args, 'enable_annotations'): config['output']['save_annotations'] = args.enable_annotations + # 验证参数 if hasattr(args, 'skip_validation'): config['output']['validate_data'] = not args.skip_validation + # 质量检查参数 if hasattr(args, 'skip_quality_check'): config['output']['run_quality_check'] = not args.skip_quality_check + # 分析参数 if hasattr(args, 'run_analysis'): config['output']['run_analysis'] = args.run_analysis + # 性能参数 if hasattr(args, 'batch_size') and args.batch_size: config['performance']['batch_size'] = args.batch_size @@ -533,6 +596,7 @@ def merge_args(config: Dict[str, Any], args: argparse.Namespace) -> Dict[str, An if args.enable_downsampling: config['sensors']['lidar_config']['downsample_ratio'] = 0.3 + # 输出格式 if hasattr(args, 'output_format') and args.output_format: config['output']['output_format'] = args.output_format @@ -540,6 +604,7 @@ def merge_args(config: Dict[str, Any], args: argparse.Namespace) -> Dict[str, An @staticmethod def save_config(config: Dict[str, Any], output_path: str, format: str = 'json'): + """保存配置到文件""" try: os.makedirs(os.path.dirname(output_path), exist_ok=True) @@ -558,6 +623,7 @@ def save_config(config: Dict[str, Any], output_path: str, format: str = 'json'): @staticmethod def generate_config_template(output_path: str, preset: Optional[str] = None): + """生成配置模板""" config = ConfigManager.load_config(preset=preset) config['metadata']['created'] = 'template' config['metadata']['description'] = f'配置模板 - {preset if preset else "通用"}' @@ -566,10 +632,12 @@ def generate_config_template(output_path: str, preset: Optional[str] = None): @staticmethod def print_config_summary(config: Dict[str, Any]): + """打印配置摘要""" print("\n" + "=" * 60) print("配置摘要") print("=" * 60) + # 场景信息 scenario = config['scenario'] print(f"\n📋 场景:") print(f" 名称: {scenario['name']}") @@ -578,6 +646,7 @@ def print_config_summary(config: Dict[str, Any]): print(f" 时长: {scenario['duration']}秒") print(f" 随机种子: {scenario.get('seed', '随机')}") + # 交通信息 traffic = config['traffic'] print(f"\n🚗 交通:") print(f" 主车: {traffic['ego_vehicles']}") @@ -585,6 +654,7 @@ def print_config_summary(config: Dict[str, Any]): print(f" 行人: {traffic['pedestrians']}") print(f" 交通灯: {'启用' if traffic['traffic_lights'] else '禁用'}") + # 传感器信息 sensors = config['sensors'] print(f"\n📷 传感器:") print(f" 车辆摄像头: {sensors['vehicle_cameras']}") @@ -593,6 +663,7 @@ def print_config_summary(config: Dict[str, Any]): print(f" 采集间隔: {sensors['capture_interval']}秒") print(f" 图像尺寸: {sensors['image_size'][0]}x{sensors['image_size'][1]}") + # V2X信息 v2x = config['v2x'] print(f"\n📡 V2X通信:") print(f" 状态: {'启用' if v2x['enabled'] else '禁用'}") @@ -600,12 +671,14 @@ def print_config_summary(config: Dict[str, Any]): print(f" 通信范围: {v2x['communication_range']}米") print(f" 更新间隔: {v2x['update_interval']}秒") + # 协同信息 coop = config['cooperative'] print(f"\n🤝 协同感知:") print(f" 协同车辆: {coop['num_coop_vehicles']}") print(f" 共享感知: {'启用' if coop['enable_shared_perception'] else '禁用'}") print(f" 交通警告: {'启用' if coop['enable_traffic_warnings'] else '禁用'}") + # 性能信息 perf = config['performance'] print(f"\n⚡ 性能:") print(f" 批处理大小: {perf['batch_size']}") @@ -613,6 +686,7 @@ def print_config_summary(config: Dict[str, Any]): print(f" 下采样: {'启用' if perf['enable_downsampling'] else '禁用'}") print(f" 帧率限制: {perf['frame_rate_limit']} FPS") + # 输出信息 output = config['output'] print(f"\n💾 输出:") print(f" 输出目录: {output['data_dir']}") @@ -625,6 +699,7 @@ def print_config_summary(config: Dict[str, Any]): @staticmethod def list_presets(): + """列出所有预设配置""" print("\n可用预设配置:") print("-" * 40) for name, preset in ConfigManager.PRESET_CONFIGS.items(): @@ -632,9 +707,12 @@ def list_presets(): print("-" * 40) +# 兼容旧版本的接口 def load_config(config_file=None): + """兼容旧版本的加载配置函数""" return ConfigManager.load_config(config_file) def merge_args(config, args): + """兼容旧版本的合并参数函数""" return ConfigManager.merge_args(config, args) \ No newline at end of file diff --git a/src/enhance_pedestrian_safety/lidar_processor.py b/src/enhance_pedestrian_safety/lidar_processor.py index 8c0f6ebb20..654aa674b8 100644 --- a/src/enhance_pedestrian_safety/lidar_processor.py +++ b/src/enhance_pedestrian_safety/lidar_processor.py @@ -23,18 +23,21 @@ def __init__(self, output_dir, config=None): self.frame_counter = 0 self.data_lock = threading.Lock() + # 性能优化:批量处理设置 self.batch_size = config.get('batch_size', 10) if config else 10 self.point_cloud_batch = [] self.enable_compression = config.get('enable_compression', True) if config else True self.compression_level = config.get('compression_level', 3) if config else 3 - self.max_points_per_frame = config.get('max_points_per_frame', 50000) if config else 50000 + # 内存管理 - 优化点3:更保守的内存设置 + self.max_points_per_frame = config.get('max_points_per_frame', 50000) if config else 50000 # 减少到5万点 self.enable_downsampling = config.get('enable_downsampling', True) if config else True - self.downsample_ratio = config.get('downsample_ratio', 0.3) if config else 0.3 + self.downsample_ratio = config.get('downsample_ratio', 0.3) if config else 0.3 # 增加下采样比例 - self.memory_warning_threshold = config.get('memory_warning_threshold', 300) if config else 300 - self.max_batch_memory_mb = config.get('max_batch_memory_mb', 50) if config else 50 - self.v2x_save_interval = config.get('v2x_save_interval', 5) if config else 5 + # 新增:内存监控和限制 + self.memory_warning_threshold = config.get('memory_warning_threshold', 300) if config else 300 # MB + self.max_batch_memory_mb = config.get('max_batch_memory_mb', 50) if config else 50 # 批次最大内存 + self.v2x_save_interval = config.get('v2x_save_interval', 5) if config else 5 # 每5帧保存一次V2X格式 self._init_calibration_files() @@ -78,81 +81,109 @@ def process_lidar_data(self, lidar_data, frame_num): points = self._carla_lidar_to_numpy(lidar_data) if points is None or points.shape[0] == 0: + print(f"警告: LiDAR数据为空或无效 (帧 {frame_num})") return None + # 检查内存使用情况 if self._check_memory_usage(): + print(f"警告: 内存使用过高,跳过LiDAR处理 (帧 {frame_num})") return None + # 下采样以减少内存使用 original_count = points.shape[0] if self.enable_downsampling and points.shape[0] > self.max_points_per_frame: points = self._downsample_point_cloud(points) + print(f"LiDAR下采样: {original_count} -> {points.shape[0]} 点 (帧 {frame_num})") + # 检查批次内存使用 batch_memory_mb = self._estimate_batch_memory_mb() if batch_memory_mb > self.max_batch_memory_mb: + print(f"批处理内存过高 ({batch_memory_mb:.1f}MB),强制保存当前批次") self._save_batch() + # 添加到批处理 self.point_cloud_batch.append((frame_num, points)) + # 如果达到批处理大小,保存批处理数据 if len(self.point_cloud_batch) >= self.batch_size: self._save_batch() + # 保存单个文件(向后兼容) bin_path = self._save_as_bin(points, frame_num) npy_path = self._save_as_npy(points, frame_num) + # 生成V2XFormer兼容格式 - 优化点:每v2x_save_interval帧保存一次 v2xformer_path = None if frame_num % self.v2x_save_interval == 0: try: v2xformer_path = self._save_as_v2xformer_format(points, frame_num) except Exception as e: + print(f"警告: V2XFormer格式保存失败: {e}") v2xformer_path = None metadata = self._generate_metadata(points, bin_path, npy_path, v2xformer_path) + # 定期清理内存 if frame_num % 20 == 0: gc.collect() return metadata except Exception as e: + print(f"处理LiDAR数据失败: {e}") + # 强制清理内存 gc.collect() return None def _carla_lidar_to_numpy(self, lidar_data): + """高效转换LiDAR数据""" try: + # 使用内存视图减少内存分配 points = np.frombuffer(lidar_data.raw_data, dtype=np.float32) points = np.reshape(points, (int(points.shape[0] / 4), 4)) + + # 只保留位置信息(x, y, z),忽略强度 points = points[:, :3] + # 清理临时数组 del lidar_data gc.collect() return points except Exception as e: + print(f"LiDAR数据转换失败: {e}") + + # 备选转换方法 try: points = [] for i in range(0, len(lidar_data), 4): point = lidar_data[i:i + 4] points.append([point.x, point.y, point.z]) + return np.array(points, dtype=np.float32) except: return None def _downsample_point_cloud(self, points): + """下采样点云以减少内存使用""" if points.shape[0] <= self.max_points_per_frame: return points + # 随机下采样 indices = np.random.choice(points.shape[0], int(points.shape[0] * self.downsample_ratio), replace=False) downsampled = points[indices] + # 清理内存 del points gc.collect() return downsampled def _estimate_batch_memory_mb(self): + """估计批处理内存使用""" if not self.point_cloud_batch: return 0 @@ -160,38 +191,45 @@ def _estimate_batch_memory_mb(self): for _, points in self.point_cloud_batch: total_points += points.shape[0] - memory_bytes = total_points * 12 * 1.2 + # 每个点3个float32 (12字节),加上一些额外开销 + memory_bytes = total_points * 12 * 1.2 # 20%额外开销 return memory_bytes / (1024 * 1024) def _check_memory_usage(self): + """检查内存使用情况""" try: import psutil process = psutil.Process() memory_mb = process.memory_info().rss / (1024 * 1024) if memory_mb > self.memory_warning_threshold: + print(f"警告: 进程内存使用过高: {memory_mb:.1f}MB") return True return False except: return False def _save_batch(self): + """批量保存点云数据(提高效率)""" if not self.point_cloud_batch: return batch_data = [] for frame_num, points in self.point_cloud_batch: + # 只保存点的坐标,不保存完整对象 batch_data.append({ 'frame_num': frame_num, 'points': points.tolist(), 'num_points': points.shape[0] }) + # 生成批处理文件名 min_frame = min([item[0] for item in self.point_cloud_batch]) max_frame = max([item[0] for item in self.point_cloud_batch]) batch_filename = f"lidar_batch_{min_frame:06d}_{max_frame:06d}.json" batch_path = os.path.join(self.lidar_dir, batch_filename) + # 压缩保存 if self.enable_compression: try: json_str = json.dumps(batch_data) @@ -202,39 +240,49 @@ def _save_batch(self): with open(batch_path, 'wb') as f: f.write(compressed_data) except Exception as e: + print(f"压缩保存批处理数据失败: {e}") + # 尝试非压缩保存 try: with open(batch_path, 'w') as f: json.dump(batch_data, f) except Exception as e2: - pass + print(f"非压缩保存批处理数据失败: {e2}") else: try: with open(batch_path, 'w') as f: json.dump(batch_data, f) except Exception as e: - pass + print(f"保存批处理数据失败: {e}") + # 清理批处理缓存 self.point_cloud_batch.clear() gc.collect() + print(f"批量保存LiDAR数据: {batch_filename}") + def _save_as_bin(self, points, frame_num): + """保存为二进制格式(兼容KITTI)""" bin_path = os.path.join(self.lidar_dir, f"lidar_{frame_num:06d}.bin") + # 添加强度信息(全为1) points_with_intensity = np.zeros((points.shape[0], 4), dtype=np.float32) points_with_intensity[:, :3] = points - points_with_intensity[:, 3] = 1.0 + points_with_intensity[:, 3] = 1.0 # 强度值 try: points_with_intensity.tofile(bin_path) except Exception as e: + print(f"保存BIN文件失败: {e}") return None return bin_path def _save_as_npy(self, points, frame_num): + """保存为numpy格式""" npy_path = os.path.join(self.lidar_dir, f"lidar_{frame_num:06d}.npy") try: + # 使用内存映射减少内存使用 memmap_array = np.lib.format.open_memmap( npy_path, mode='w+', @@ -244,18 +292,23 @@ def _save_as_npy(self, points, frame_num): memmap_array[:] = points[:] memmap_array.flush() except Exception as e: + print(f"保存NPY文件失败: {e}") + # 备选保存方式 try: np.save(npy_path, points) except Exception as e2: + print(f"备选保存NPY文件失败: {e2}") return None return npy_path def _save_as_v2xformer_format(self, points, frame_num): + """保存为V2XFormer兼容格式 - 优化点:减少保存频率""" v2x_dir = os.path.join(self.output_dir, "v2xformer_format") os.makedirs(v2x_dir, exist_ok=True) try: + # 创建V2XFormer格式的数据结构 v2x_data = { 'frame_id': frame_num, 'timestamp': datetime.now().timestamp(), @@ -274,15 +327,21 @@ def _save_as_v2xformer_format(self, points, frame_num): } } + # 保存为压缩的JSON格式 filename = f"{frame_num:06d}.pkl.gz" filepath = os.path.join(v2x_dir, filename) + # 使用pickle+gzip压缩保存 with gzip.open(filepath, 'wb') as f: pickle.dump(v2x_data, f, protocol=pickle.HIGHEST_PROTOCOL) + file_size = os.path.getsize(filepath) / 1024 # KB + print(f"保存V2XFormer格式: {filepath} ({file_size:.2f} KB)") + return filepath except Exception as e: + print(f"警告: 保存V2XFormer格式失败: {e}") return None def _generate_metadata(self, points, bin_path, npy_path, v2xformer_path): @@ -316,18 +375,21 @@ def _generate_metadata(self, points, bin_path, npy_path, v2xformer_path): with open(meta_path, 'w') as f: json.dump(metadata, f, indent=2) except Exception as e: - pass + print(f"保存LiDAR元数据失败: {e}") return metadata def flush_batch(self): + """强制刷新批处理数据""" if self.point_cloud_batch: self._save_batch() def generate_lidar_summary(self): + """生成LiDAR数据摘要(优化版)""" if not os.path.exists(self.lidar_dir): return None + # 快速统计文件 import glob bin_files = glob.glob(os.path.join(self.lidar_dir, "*.bin")) npy_files = glob.glob(os.path.join(self.lidar_dir, "*.npy")) @@ -337,17 +399,20 @@ def generate_lidar_summary(self): total_points = 0 total_size = 0 - sample_files = bin_files[:5] if len(bin_files) > 5 else bin_files + # 采样统计,避免读取所有文件 + sample_files = bin_files[:5] if len(bin_files) > 5 else bin_files # 减少采样数量 for bin_file in sample_files: if os.path.exists(bin_file): try: file_size = os.path.getsize(bin_file) total_size += file_size - points_in_file = file_size // (4 * 4) + # 估算点数 + points_in_file = file_size // (4 * 4) # 4个float32,每个4字节 total_points += points_in_file except: continue + # 根据采样估算总数 if sample_files and len(bin_files) > 0: avg_points_per_file = total_points / max(len(sample_files), 1) total_points_estimated = avg_points_per_file * len(bin_files) @@ -375,12 +440,13 @@ def generate_lidar_summary(self): with open(summary_path, 'w') as f: json.dump(summary, f, indent=2) except Exception as e: - pass + print(f"保存LiDAR摘要失败: {e}") return summary class MultiSensorFusion: + """多传感器融合(优化版)""" def __init__(self, output_dir, config=None): self.output_dir = output_dir @@ -388,8 +454,8 @@ def __init__(self, output_dir, config=None): os.makedirs(self.fusion_dir, exist_ok=True) self.calibration_data = {} - self.fusion_cache = {} - self.cache_size = config.get('fusion_cache_size', 50) if config else 50 + self.fusion_cache = {} # 融合缓存 + self.cache_size = config.get('fusion_cache_size', 50) if config else 50 # 减少缓存大小 self._load_calibration() @@ -397,41 +463,48 @@ def _load_calibration(self): calibration_dir = os.path.join(self.output_dir, "calibration") if os.path.exists(calibration_dir): + # 批量读取校准文件 calibration_files = [] for root, dirs, files in os.walk(calibration_dir): for file in files: if file.endswith('.json'): calibration_files.append(os.path.join(root, file)) - for file in calibration_files[:10]: + # 限制同时读取的文件数量 + for file in calibration_files[:10]: # 最多读取10个文件 try: with open(file, 'r') as f: data = json.load(f) sensor_name = os.path.basename(file).replace('.json', '') self.calibration_data[sensor_name] = data except Exception as e: - pass + print(f"加载校准文件 {file} 失败: {e}") def create_synchronization_file(self, frame_num, sensor_data): + """创建同步文件(优化版)""" + # 检查缓存 cache_key = f"{frame_num}_{hash(str(sensor_data))}" if cache_key in self.fusion_cache: return self.fusion_cache[cache_key] sync_data = { 'frame_id': frame_num, - 'timestamp': datetime.now().timestamp(), + 'timestamp': datetime.now().timestamp(), # 使用时间戳而不是ISO格式 'sensors': {}, 'transformations': {} } + # 批量处理传感器数据 for sensor_type, data_path in sensor_data.items(): if data_path and os.path.exists(data_path): + # 获取文件信息(不加载完整文件) file_info = { 'file_path': os.path.basename(data_path), 'file_size': os.path.getsize(data_path), 'modified_time': os.path.getmtime(data_path) } + # 如果是图像,获取尺寸信息 if data_path.endswith(('.png', '.jpg', '.jpeg')): try: import cv2 @@ -443,6 +516,7 @@ def create_synchronization_file(self, frame_num, sensor_data): sync_data['sensors'][sensor_type] = file_info + # 添加变换信息(如果可用) if sensor_type in self.calibration_data: sync_data['transformations'][sensor_type] = { 'matrix': self.calibration_data[sensor_type].get('matrix', @@ -450,9 +524,11 @@ def create_synchronization_file(self, frame_num, sensor_data): [0, 0, 0, 1]]) } + # 压缩保存 sync_file = os.path.join(self.fusion_dir, f"sync_{frame_num:06d}.json") - if len(str(sync_data)) > 1000: + # 使用压缩的JSON + if len(str(sync_data)) > 1000: # 如果数据较大,压缩保存 compressed_data = zlib.compress( json.dumps(sync_data).encode('utf-8'), level=3 @@ -462,9 +538,11 @@ def create_synchronization_file(self, frame_num, sensor_data): f.write(compressed_data) else: with open(sync_file, 'w') as f: - json.dump(sync_data, f, separators=(',', ':')) + json.dump(sync_data, f, separators=(',', ':')) # 紧凑格式 + # 更新缓存 if len(self.fusion_cache) >= self.cache_size: + # 移除最旧的缓存项 oldest_key = next(iter(self.fusion_cache)) del self.fusion_cache[oldest_key] @@ -473,15 +551,19 @@ def create_synchronization_file(self, frame_num, sensor_data): return sync_file def generate_fusion_report(self): + """生成融合报告(优化版)""" if not os.path.exists(self.fusion_dir): return None + # 快速扫描文件 import glob sync_files = glob.glob(os.path.join(self.fusion_dir, "*.json*")) + # 解析文件名获取帧范围(避免读取所有文件) frame_ids = [] - for sync_file in sync_files[:20]: + for sync_file in sync_files[:20]: # 只检查前20个文件 try: + # 从文件名提取帧号 filename = os.path.basename(sync_file) if filename.startswith('sync_'): frame_id = int(filename.split('_')[1].split('.')[0]) diff --git a/src/enhance_pedestrian_safety/main.py b/src/enhance_pedestrian_safety/main.py index a506397e08..1331ff3b79 100644 --- a/src/enhance_pedestrian_safety/main.py +++ b/src/enhance_pedestrian_safety/main.py @@ -30,8 +30,6 @@ def __init__(self): self.memory_samples = [] self.cpu_samples = [] self.frame_times = [] - self.first_frame_time = None - self.last_frame_time = None def sample_memory(self): try: @@ -74,17 +72,14 @@ def sample_cpu(self): def record_frame_time(self, frame_time): self.frame_times.append(frame_time) - if self.first_frame_time is None: - self.first_frame_time = time.time() - self.last_frame_time = time.time() def get_performance_summary(self): - if not self.frame_times or len(self.frame_times) < 2: + if not self.frame_times: avg_frame_time = 0 fps = 0 else: avg_frame_time = np.mean(self.frame_times) - fps = len(self.frame_times) / max(0.1, (self.last_frame_time - self.first_frame_time)) + fps = 1.0 / avg_frame_time if avg_frame_time > 0 else 0 summary = { 'total_runtime': time.time() - self.start_time, @@ -95,24 +90,14 @@ def get_performance_summary(self): 'max_cpu_percent': max(self.cpu_samples) if self.cpu_samples else 0, 'average_frame_time': avg_frame_time, 'frames_per_second': fps, - 'total_frames': len(self.frame_times) - } - - if self.frame_times and len(self.frame_times) >= 2: - summary['frame_time_stats'] = { - 'p50': np.percentile(self.frame_times, 50), - 'p95': np.percentile(self.frame_times, 95), - 'p99': np.percentile(self.frame_times, 99) if len(self.frame_times) > 1 else 0, - 'std': np.std(self.frame_times) - } - else: - summary['frame_time_stats'] = { - 'p50': 0, - 'p95': 0, - 'p99': 0, - 'std': 0 + 'total_frames': len(self.frame_times), + 'frame_time_stats': { + 'p50': np.percentile(self.frame_times, 50) if self.frame_times else 0, + 'p95': np.percentile(self.frame_times, 95) if self.frame_times else 0, + 'p99': np.percentile(self.frame_times, 99) if self.frame_times else 0, + 'std': np.std(self.frame_times) if self.frame_times else 0 } - + } return summary @@ -134,12 +119,12 @@ def error(msg): @staticmethod def debug(msg): - timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + timestamp = datetime.now().strftime("%Y-%m-d %H:%M:%S") print(f"[DEBUG][{timestamp}] {msg}") @staticmethod def performance(msg): - timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + timestamp = datetime.now().strftime("%Y-%m-d %H:%M:%S") print(f"[PERF][{timestamp}] {msg}") @@ -584,7 +569,8 @@ def __init__(self, world, config, data_dir): 'total_images': 0, 'total_lidar_frames': 0, 'image_capture_times': [], - 'lidar_processing_times': [] + 'lidar_processing_times': [], + 'frame_drop_count': 0 } def setup_cameras(self, vehicle, center_location, vehicle_id=0): @@ -882,6 +868,7 @@ def cleanup(self): self.is_running = False + Log.info("停止所有传感器监听...") for sensor in self.sensors: try: if hasattr(sensor, 'stop'): @@ -898,6 +885,7 @@ def cleanup(self): except: pass + Log.info(f"销毁 {len(self.sensors)} 个传感器...") for i, sensor in enumerate(self.sensors): try: if hasattr(sensor, 'destroy'): @@ -1117,15 +1105,17 @@ def collect_data(self): memory_warning_issued = False early_stop_triggered = False - memory_warning_threshold = 350 - memory_critical_threshold = 400 - early_stop_threshold = 450 + # 添加内存监控阈值 + memory_warning_threshold = 350 # MB + memory_critical_threshold = 400 # MB + early_stop_threshold = 450 # MB try: while time.time() - self.start_time < duration and self.is_running: current_time = time.time() elapsed = current_time - self.start_time + # 内存监控和早期停止机制 if current_time - last_memory_check >= 2.0: try: import psutil @@ -1214,21 +1204,16 @@ def collect_data(self): self.collected_frames = sum(mgr.get_frame_count() for mgr in self.sensor_managers.values()) - if self.collected_frames > 0: - total_frames_from_sensors = self.collected_frames - performance_summary = self.performance_monitor.get_performance_summary() - - if early_stop_triggered: - Log.warning("数据收集因内存过高而提前终止") - else: - Log.info(f"收集完成: {self.collected_frames}帧, 用时: {elapsed:.1f}秒") + performance_summary = self.performance_monitor.get_performance_summary() - fps = total_frames_from_sensors / max(elapsed, 0.1) - Log.info(f"平均帧率: {fps:.2f} FPS") - Log.info(f"最大内存使用: {performance_summary['max_memory_mb']:.1f} MB") - Log.info(f"平均CPU使用: {performance_summary['average_cpu_percent']:.1f}%") + if early_stop_triggered: + Log.warning("数据收集因内存过高而提前终止") else: - Log.warning("未收集到任何数据帧") + Log.info(f"收集完成: {self.collected_frames}帧, 用时: {elapsed:.1f}秒") + + Log.info(f"平均帧率: {performance_summary['frames_per_second']:.2f} FPS") + Log.info(f"最大内存使用: {performance_summary['max_memory_mb']:.1f} MB") + Log.info(f"平均CPU使用: {performance_summary['average_cpu_percent']:.1f}%") self._save_metadata() self._print_summary() @@ -1493,8 +1478,6 @@ def _simulate_object_detection(self, vehicle): return detected_objects def _save_metadata(self): - total_frames = self.collected_frames - metadata = { 'scenario': self.config['scenario'], 'traffic': self.config['traffic'], @@ -1505,26 +1488,22 @@ def _save_metadata(self): 'output': self.config['output'], 'collection': { 'duration': round(time.time() - self.start_time, 2), - 'total_frames': total_frames, - 'frame_rate': round(total_frames / max(time.time() - self.start_time, 0.1), - 2) if total_frames > 0 else 0 - } + 'total_frames': self.collected_frames, + 'frame_rate': round(self.collected_frames / max(time.time() - self.start_time, 0.1), 2) + }, + 'performance': self.performance_monitor.get_performance_summary() } - if total_frames > 0: - performance_summary = self.performance_monitor.get_performance_summary() - metadata['performance'] = performance_summary - - sensor_summaries = {} - for vehicle_id, sensor_manager in self.sensor_managers.items(): - sensor_summaries[vehicle_id] = sensor_manager.generate_sensor_summary() - metadata['sensor_summaries'] = sensor_summaries + sensor_summaries = {} + for vehicle_id, sensor_manager in self.sensor_managers.items(): + sensor_summaries[vehicle_id] = sensor_manager.generate_sensor_summary() + metadata['sensor_summaries'] = sensor_summaries - if self.v2x_communication: - metadata['v2x_status'] = self.v2x_communication.get_network_status() + if self.v2x_communication: + metadata['v2x_status'] = self.v2x_communication.get_network_status() - if self.multi_vehicle_manager: - metadata['cooperative_summary'] = self.multi_vehicle_manager.generate_summary() + if self.multi_vehicle_manager: + metadata['cooperative_summary'] = self.multi_vehicle_manager.generate_summary() meta_path = os.path.join(self.output_dir, "metadata", "collection_info.json") with open(meta_path, 'w', encoding='utf-8') as f: @@ -1574,37 +1553,29 @@ def _print_summary(self): if os.path.exists(kitti_dir): print(f"KITTI格式: 已生成") - total_frames = self.collected_frames - elapsed = time.time() - self.start_time - + performance = self.performance_monitor.get_performance_summary() print(f"\n性能统计:") - if total_frames > 0: - fps = total_frames / max(elapsed, 0.1) - print(f" 平均帧率: {fps:.2f} FPS") - - performance = self.performance_monitor.get_performance_summary() - print(f" 帧时统计:") - print(f" 平均: {performance['average_frame_time']:.3f}秒") - print(f" P50: {performance['frame_time_stats']['p50']:.3f}秒") - print(f" P95: {performance['frame_time_stats']['p95']:.3f}秒") - print(f" P99: {performance['frame_time_stats']['p99']:.3f}秒") - print(f" 平均内存: {performance['average_memory_mb']:.1f} MB") - print(f" 最大内存: {performance['max_memory_mb']:.1f} MB") - print(f" 平均CPU: {performance['average_cpu_percent']:.1f}%") - print(f" 总帧数: {total_frames}") - else: - print(" 未收集到有效帧数据") + print(f" 平均帧率: {performance['frames_per_second']:.2f} FPS") + print(f" 帧时统计:") + print(f" 平均: {performance['average_frame_time']:.3f}秒") + print(f" P50: {performance['frame_time_stats']['p50']:.3f}秒") + print(f" P95: {performance['frame_time_stats']['p95']:.3f}秒") + print(f" P99: {performance['frame_time_stats']['p99']:.3f}秒") + print(f" 平均内存: {performance['average_memory_mb']:.1f} MB") + print(f" 最大内存: {performance['max_memory_mb']:.1f} MB") + print(f" 平均CPU: {performance['average_cpu_percent']:.1f}%") + print(f" 总帧数: {performance['total_frames']}") print(f"\n输出目录: {self.output_dir}") print("=" * 60) def run_validation(self): - if self.config['output'].get('validate_data', True) and self.collected_frames > 0: + if self.config['output'].get('validate_data', True): Log.info("运行数据验证...") DataValidator.validate_dataset(self.output_dir) def run_analysis(self): - if self.config['output'].get('run_analysis', False) and self.collected_frames > 0: + if self.config['output'].get('run_analysis', False): Log.info("运行数据分析...") DataAnalyzer.analyze_dataset(self.output_dir) From c340d1ae0e1f28d2f61c56eda94e4a014c81eae0 Mon Sep 17 00:00:00 2001 From: Z-w-7799 <1011069369@qq.com> Date: Sun, 21 Dec 2025 20:37:12 +0800 Subject: [PATCH 16/20] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=B8=BA=E7=AC=A6?= =?UTF-8?q?=E5=90=88=E8=A7=84=E8=8C=83=E7=9A=84markdown?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/enhance_pedestrian_safety/README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/enhance_pedestrian_safety/README.md b/src/enhance_pedestrian_safety/README.md index a4403a017d..5904978a67 100644 --- a/src/enhance_pedestrian_safety/README.md +++ b/src/enhance_pedestrian_safety/README.md @@ -1,4 +1,3 @@ -```markdown # CVIPS - Cooperative Vehicle-Infrastructure Perception System 基于CARLA仿真平台的多传感器协同感知数据采集系统,支持V2X通信、多车辆协同、数据增强与验证。 @@ -48,6 +47,11 @@ CVIPS是一个用于自动驾驶研究的多传感器数据采集系统,在CAR - 存储:≥ 50GB可用空间 +## 安装步骤 +1. **克隆仓库** + ```bash + git clone https://github.com/Z-w-7799/nn.git + ## 安装步骤 1. **克隆仓库** ```bash From 576c7738cb76e4c1018867a9c8f33e3d0c58642b Mon Sep 17 00:00:00 2001 From: Z-w-7799 <1011069369@qq.com> Date: Sun, 21 Dec 2025 21:45:31 +0800 Subject: [PATCH 17/20] =?UTF-8?q?add:=20ROS=E5=B0=81=E8=A3=85=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E6=96=87=E4=BB=B6=E5=92=8C=E4=BF=AE=E6=94=B9=E5=90=8E?= =?UTF-8?q?=E7=9A=84=E6=BA=90=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/enhance_pedestrian_safety/CMakeLists.txt | 203 ++++++++++++++++++ .../config_manager.py | 102 ++------- .../lidar_processor.py | 124 ++--------- src/enhance_pedestrian_safety/main.py | 138 +++++++----- 4 files changed, 320 insertions(+), 247 deletions(-) create mode 100644 src/enhance_pedestrian_safety/CMakeLists.txt diff --git a/src/enhance_pedestrian_safety/CMakeLists.txt b/src/enhance_pedestrian_safety/CMakeLists.txt new file mode 100644 index 0000000000..2514048b61 --- /dev/null +++ b/src/enhance_pedestrian_safety/CMakeLists.txt @@ -0,0 +1,203 @@ +cmake_minimum_required(VERSION 2.8.3) +project(carla_sensor) + +## Compile as C++11, supported in ROS Kinetic and newer +# add_compile_options(-std=c++11) + +## Find catkin macros and libraries +## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz) +## is used, also find other catkin packages +find_package(catkin REQUIRED COMPONENTS + cv_bridge + rospy + sensor_msgs + std_msgs +) + +## System dependencies are found with CMake's conventions +# find_package(Boost REQUIRED COMPONENTS system) + + +## Uncomment this if the package has a setup.py. This macro ensures +## modules and global scripts declared therein get installed +## See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html +# catkin_python_setup() + +################################################ +## Declare ROS messages, services and actions ## +################################################ + +## To declare and build messages, services or actions from within this +## package, follow these steps: +## * Let MSG_DEP_SET be the set of packages whose message types you use in +## your messages/services/actions (e.g. std_msgs, actionlib_msgs, ...). +## * In the file package.xml: +## * add a build_depend tag for "message_generation" +## * add a build_depend and a exec_depend tag for each package in MSG_DEP_SET +## * If MSG_DEP_SET isn't empty the following dependency has been pulled in +## but can be declared for certainty nonetheless: +## * add a exec_depend tag for "message_runtime" +## * In this file (CMakeLists.txt): +## * add "message_generation" and every package in MSG_DEP_SET to +## find_package(catkin REQUIRED COMPONENTS ...) +## * add "message_runtime" and every package in MSG_DEP_SET to +## catkin_package(CATKIN_DEPENDS ...) +## * uncomment the add_*_files sections below as needed +## and list every .msg/.srv/.action file to be processed +## * uncomment the generate_messages entry below +## * add every package in MSG_DEP_SET to generate_messages(DEPENDENCIES ...) + +## Generate messages in the 'msg' folder +# add_message_files( +# FILES +# Message1.msg +# Message2.msg +# ) + +## Generate services in the 'srv' folder +# add_service_files( +# FILES +# Service1.srv +# Service2.srv +# ) + +## Generate actions in the 'action' folder +# add_action_files( +# FILES +# Action1.action +# Action2.action +# ) + +## Generate added messages and services with any dependencies listed here +# generate_messages( +# DEPENDENCIES +# sensor_msgs# std_msgs +# ) + +################################################ +## Declare ROS dynamic reconfigure parameters ## +################################################ + +## To declare and build dynamic reconfigure parameters within this +## package, follow these steps: +## * In the file package.xml: +## * add a build_depend and a exec_depend tag for "dynamic_reconfigure" +## * In this file (CMakeLists.txt): +## * add "dynamic_reconfigure" to +## find_package(catkin REQUIRED COMPONENTS ...) +## * uncomment the "generate_dynamic_reconfigure_options" section below +## and list every .cfg file to be processed + +## Generate dynamic reconfigure parameters in the 'cfg' folder +# generate_dynamic_reconfigure_options( +# cfg/DynReconf1.cfg +# cfg/DynReconf2.cfg +# ) + +################################### +## catkin specific configuration ## +################################### +## The catkin_package macro generates cmake config files for your package +## Declare things to be passed to dependent projects +## INCLUDE_DIRS: uncomment this if your package contains header files +## LIBRARIES: libraries you create in this project that dependent projects also need +## CATKIN_DEPENDS: catkin_packages dependent projects also need +## DEPENDS: system dependencies of this project that dependent projects also need +catkin_package( +# INCLUDE_DIRS include +# LIBRARIES carla_sensor +# CATKIN_DEPENDS cv_bridge rospy sensor_msgs std_msgs +# DEPENDS system_lib +) + +########### +## Build ## +########### + +## Specify additional locations of header files +## Your package locations should be listed before other locations +include_directories( +# include + ${catkin_INCLUDE_DIRS} +) + +## Declare a C++ library +# add_library(${PROJECT_NAME} +# src/${PROJECT_NAME}/carla_sensor.cpp +# ) + +## Add cmake target dependencies of the library +## as an example, code may need to be generated before libraries +## either from message generation or dynamic reconfigure +# add_dependencies(${PROJECT_NAME} ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) + +## Declare a C++ executable +## With catkin_make all packages are built within a single CMake context +## The recommended prefix ensures that target names across packages don't collide +# add_executable(${PROJECT_NAME}_node src/carla_sensor_node.cpp) + +## Rename C++ executable without prefix +## The above recommended prefix causes long target names, the following renames the +## target back to the shorter version for ease of user use +## e.g. "rosrun someones_pkg node" instead of "rosrun someones_pkg someones_pkg_node" +# set_target_properties(${PROJECT_NAME}_node PROPERTIES OUTPUT_NAME node PREFIX "") + +## Add cmake target dependencies of the executable +## same as for the library above +# add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) + +## Specify libraries to link a library or executable target against +# target_link_libraries(${PROJECT_NAME}_node +# ${catkin_LIBRARIES} +# ) + +############# +## Install ## +############# + +# all install targets should use catkin DESTINATION variables +# See http://ros.org/doc/api/catkin/html/adv_user_guide/variables.html + +## Mark executable scripts (Python etc.) for installation +## in contrast to setup.py, you can choose the destination +# install(PROGRAMS +# scripts/my_python_script +# DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} +# ) + +## Mark executables and/or libraries for installation +# install(TARGETS ${PROJECT_NAME} ${PROJECT_NAME}_node +# ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} +# LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} +# RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} +# ) + +## Mark cpp header files for installation +# install(DIRECTORY include/${PROJECT_NAME}/ +# DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION} +# FILES_MATCHING PATTERN "*.h" +# PATTERN ".svn" EXCLUDE +# ) + +## Mark other files for installation (e.g. launch and bag files, etc.) +# install(FILES +# # myfile1 +# # myfile2 +# DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} +# ) + +############# +## Testing ## +############# + +## Add gtest based cpp test target and link libraries +# catkin_add_gtest(${PROJECT_NAME}-test test/test_carla_sensor.cpp) +# if(TARGET ${PROJECT_NAME}-test) +# target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME}) +# endif() + +## Add folders to be run by python nosetests +# catkin_add_nosetests(test) +catkin_install_python(PROGRAMS main.py + DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} +) diff --git a/src/enhance_pedestrian_safety/config_manager.py b/src/enhance_pedestrian_safety/config_manager.py index 1f64c72ef9..c608de6674 100644 --- a/src/enhance_pedestrian_safety/config_manager.py +++ b/src/enhance_pedestrian_safety/config_manager.py @@ -4,31 +4,24 @@ import copy from typing import Dict, Any, Optional, List, Tuple -# 尝试导入yaml,如果不存在则跳过 try: import yaml - YAML_AVAILABLE = True except ImportError: YAML_AVAILABLE = False - print("警告: PyYAML未安装,将使用JSON格式") class ConfigValidator: - """配置验证器""" @staticmethod def validate_config(config: Dict[str, Any]) -> Tuple[bool, List[str]]: - """验证配置的有效性""" errors = [] - # 验证必填字段 required_sections = ['scenario', 'sensors', 'output'] for section in required_sections: if section not in config: errors.append(f"缺失必要配置节: {section}") - # 验证场景配置 if 'scenario' in config: scenario = config['scenario'] if 'duration' in scenario and scenario['duration'] <= 0: @@ -36,7 +29,6 @@ def validate_config(config: Dict[str, Any]) -> Tuple[bool, List[str]]: if 'town' not in scenario: errors.append("场景配置中缺失地图名称") - # 验证传感器配置 if 'sensors' in config: sensors = config['sensors'] if 'capture_interval' in sensors and sensors['capture_interval'] <= 0: @@ -47,7 +39,6 @@ def validate_config(config: Dict[str, Any]) -> Tuple[bool, List[str]]: elif any(dim <= 0 for dim in sensors['image_size']): errors.append("图像尺寸必须大于0") - # 验证性能配置 if 'performance' in config: perf = config['performance'] if 'batch_size' in perf and perf['batch_size'] <= 0: @@ -57,22 +48,18 @@ def validate_config(config: Dict[str, Any]) -> Tuple[bool, List[str]]: @staticmethod def suggest_optimizations(config: Dict[str, Any]) -> List[str]: - """根据配置提供优化建议""" suggestions = [] - # 内存使用建议 if config.get('sensors', {}).get('lidar_sensors', 0) > 0: lidar_config = config['sensors'].get('lidar_config', {}) max_points = lidar_config.get('max_points_per_frame', 50000) if max_points > 50000: suggestions.append(f"LiDAR最大点数({max_points})较高,建议降低到50000以下以减少内存使用") - # 性能建议 capture_interval = config['sensors'].get('capture_interval', 2.0) if capture_interval < 1.0: suggestions.append(f"采集间隔({capture_interval}s)较短,可能导致高负载,建议增加到1.0s以上") - # 输出建议 output = config.get('output', {}) enabled_outputs = [k for k, v in output.items() if isinstance(v, bool) and v] if len(enabled_outputs) > 5: @@ -82,14 +69,11 @@ def suggest_optimizations(config: Dict[str, Any]) -> List[str]: class ConfigOptimizer: - """配置优化器""" @staticmethod def optimize_for_memory(config: Dict[str, Any]) -> Dict[str, Any]: - """为内存使用优化配置""" optimized = copy.deepcopy(config) - # 调整LiDAR设置 if optimized['sensors'].get('lidar_sensors', 0) > 0: lidar_config = optimized['sensors'].setdefault('lidar_config', {}) lidar_config.update({ @@ -99,7 +83,6 @@ def optimize_for_memory(config: Dict[str, Any]) -> Dict[str, Any]: 'max_batch_memory_mb': 30 }) - # 调整性能设置 perf = optimized.setdefault('performance', {}) perf.update({ 'batch_size': 3, @@ -110,7 +93,6 @@ def optimize_for_memory(config: Dict[str, Any]) -> Dict[str, Any]: 'frame_rate_limit': 3.0 }) - # 调整图像处理 perf['image_processing'] = { 'compress_images': True, 'compression_quality': 80, @@ -121,10 +103,8 @@ def optimize_for_memory(config: Dict[str, Any]) -> Dict[str, Any]: @staticmethod def optimize_for_quality(config: Dict[str, Any]) -> Dict[str, Any]: - """为数据质量优化配置""" optimized = copy.deepcopy(config) - # 调整传感器设置 sensors = optimized['sensors'] sensors.update({ 'image_size': [1920, 1080], @@ -139,7 +119,6 @@ def optimize_for_quality(config: Dict[str, Any]) -> Dict[str, Any]: } }) - # 调整输出设置 output = optimized['output'] output.update({ 'save_annotations': True, @@ -149,7 +128,6 @@ def optimize_for_quality(config: Dict[str, Any]) -> Dict[str, Any]: 'run_quality_check': True }) - # 调整增强设置 enhanced = optimized.setdefault('enhancement', {}) enhanced.update({ 'enabled': True, @@ -162,35 +140,31 @@ def optimize_for_quality(config: Dict[str, Any]) -> Dict[str, Any]: @staticmethod def optimize_for_speed(config: Dict[str, Any]) -> Dict[str, Any]: - """为处理速度优化配置""" optimized = copy.deepcopy(config) - # 调整传感器设置 sensors = optimized['sensors'] sensors.update({ 'image_size': [640, 480], 'capture_interval': 3.0, - 'lidar_sensors': 0, # 禁用LiDAR - 'radar_sensors': 0 # 禁用雷达 + 'lidar_sensors': 0, + 'radar_sensors': 0 }) - # 调整性能设置 perf = optimized.setdefault('performance', {}) perf.update({ 'batch_size': 10, 'enable_compression': True, - 'compression_level': 1, # 快速压缩 + 'compression_level': 1, 'enable_downsampling': True, 'enable_async_processing': True, 'max_cache_size': 20, 'frame_rate_limit': 10.0 }) - # 简化输出 output = optimized['output'] output.update({ 'save_raw': True, - 'save_stitched': False, # 不拼接图像 + 'save_stitched': False, 'save_annotations': False, 'save_lidar': False, 'save_fusion': False, @@ -201,9 +175,7 @@ def optimize_for_speed(config: Dict[str, Any]) -> Dict[str, Any]: class ConfigManager: - """配置管理器(增强版)""" - # 预定义配置模板 PRESET_CONFIGS = { 'balanced': { 'description': '平衡配置 - 兼顾性能和质量', @@ -238,31 +210,17 @@ class ConfigManager: @staticmethod def load_config(config_file: Optional[str] = None, preset: Optional[str] = None) -> Dict[str, Any]: - """ - 加载配置 - - Args: - config_file: 配置文件路径 - preset: 预设配置名称 - - Returns: - 配置字典 - """ - # 基础配置 config = ConfigManager._get_default_config() - # 应用预设配置 if preset: config = ConfigManager._apply_preset(config, preset) - # 加载用户配置文件 if config_file: if os.path.exists(config_file): config = ConfigManager._load_config_file(config_file, config) else: print(f"警告: 配置文件不存在: {config_file}") - # 验证配置 is_valid, errors = ConfigValidator.validate_config(config) if not is_valid: print("配置验证错误:") @@ -270,7 +228,6 @@ def load_config(config_file: Optional[str] = None, preset: Optional[str] = None) print(f" - {error}") raise ValueError("配置验证失败") - # 提供优化建议 suggestions = ConfigValidator.suggest_optimizations(config) if suggestions: print("配置优化建议:") @@ -281,7 +238,6 @@ def load_config(config_file: Optional[str] = None, preset: Optional[str] = None) @staticmethod def _get_default_config() -> Dict[str, Any]: - """获取默认配置(增强版)""" return { 'scenario': { 'name': 'multi_sensor_scene', @@ -291,8 +247,8 @@ def _get_default_config() -> Dict[str, Any]: 'time_of_day': 'noon', 'duration': 60, 'seed': 42, - 'timeout': 300, # 超时时间(秒) - 'retry_attempts': 3 # 重试次数 + 'timeout': 300, + 'retry_attempts': 3 }, 'traffic': { 'ego_vehicles': 1, @@ -336,7 +292,7 @@ def _get_default_config() -> Dict[str, Any]: 'memory_warning_threshold': 300, 'max_batch_memory_mb': 50, 'v2x_save_interval': 5, - 'compression_format': 'bin' # bin, npy, pcd + 'compression_format': 'bin' }, 'camera_config': { 'fov': 90.0, @@ -395,7 +351,7 @@ def _get_default_config() -> Dict[str, Any]: 'compression_quality': 85, 'resize_images': False, 'resize_dimensions': [640, 480], - 'format': 'jpg' # jpg, png + 'format': 'jpg' }, 'lidar_processing': { 'batch_size': 10, @@ -405,7 +361,7 @@ def _get_default_config() -> Dict[str, Any]: 'memory_warning_threshold': 350, 'max_batch_memory_mb': 50, 'v2x_save_interval': 5, - 'compression_method': 'zlib' # zlib, lz4, none + 'compression_method': 'zlib' }, 'fusion': { 'fusion_cache_size': 100, @@ -422,7 +378,7 @@ def _get_default_config() -> Dict[str, Any]: }, 'output': { 'data_dir': 'cvips_dataset', - 'output_format': 'standard', # standard, v2xformer, kitti, coco + 'output_format': 'standard', 'save_raw': True, 'save_stitched': True, 'save_annotations': False, @@ -436,12 +392,12 @@ def _get_default_config() -> Dict[str, Any]: 'run_quality_check': True, 'generate_summary': True, 'compression_enabled': True, - 'file_naming': 'sequential', # sequential, timestamp + 'file_naming': 'sequential', 'backup_original': False }, 'monitoring': { 'enable_logging': True, - 'log_level': 'INFO', # DEBUG, INFO, WARNING, ERROR + 'log_level': 'INFO', 'log_file': 'cvips.log', 'enable_performance_monitor': True, 'performance_log_interval': 10.0, @@ -468,7 +424,6 @@ def _get_default_config() -> Dict[str, Any]: @staticmethod def _apply_preset(config: Dict[str, Any], preset_name: str) -> Dict[str, Any]: - """应用预设配置""" if preset_name not in ConfigManager.PRESET_CONFIGS: print(f"警告: 未知的预设配置: {preset_name}") return config @@ -476,7 +431,6 @@ def _apply_preset(config: Dict[str, Any], preset_name: str) -> Dict[str, Any]: preset = ConfigManager.PRESET_CONFIGS[preset_name] print(f"应用预设配置: {preset_name} - {preset['description']}") - # 根据优化类型应用配置 optimization = preset.get('optimization', 'balanced') if optimization == 'memory': config = ConfigOptimizer.optimize_for_memory(config) @@ -491,7 +445,6 @@ def _apply_preset(config: Dict[str, Any], preset_name: str) -> Dict[str, Any]: @staticmethod def _load_config_file(config_file: str, base_config: Dict[str, Any]) -> Dict[str, Any]: - """加载配置文件""" try: with open(config_file, 'r', encoding='utf-8') as f: if (config_file.endswith('.yaml') or config_file.endswith('.yml')) and YAML_AVAILABLE: @@ -508,7 +461,6 @@ def _load_config_file(config_file: str, base_config: Dict[str, Any]) -> Dict[str @staticmethod def _deep_update(original: Dict[str, Any], update: Dict[str, Any]) -> Dict[str, Any]: - """深度更新字典""" for key, value in update.items(): if key in original and isinstance(original[key], dict) and isinstance(value, dict): ConfigManager._deep_update(original[key], value) @@ -518,8 +470,6 @@ def _deep_update(original: Dict[str, Any], update: Dict[str, Any]) -> Dict[str, @staticmethod def merge_args(config: Dict[str, Any], args: argparse.Namespace) -> Dict[str, Any]: - """合并命令行参数到配置""" - # 场景参数 if hasattr(args, 'scenario') and args.scenario: config['scenario']['name'] = args.scenario if hasattr(args, 'town') and args.town: @@ -533,58 +483,45 @@ def merge_args(config: Dict[str, Any], args: argparse.Namespace) -> Dict[str, An if hasattr(args, 'seed') and args.seed: config['scenario']['seed'] = args.seed - # 交通参数 if hasattr(args, 'num_vehicles') and args.num_vehicles: config['traffic']['background_vehicles'] = args.num_vehicles if hasattr(args, 'num_pedestrians') and args.num_pedestrians: config['traffic']['pedestrians'] = args.num_pedestrians - # 协同参数 if hasattr(args, 'num_coop_vehicles') and args.num_coop_vehicles: config['cooperative']['num_coop_vehicles'] = args.num_coop_vehicles - # 传感器参数 if hasattr(args, 'capture_interval') and args.capture_interval: config['sensors']['capture_interval'] = args.capture_interval - # V2X参数 if hasattr(args, 'enable_v2x'): config['v2x']['enabled'] = args.enable_v2x - # 增强参数 if hasattr(args, 'enable_enhancement'): config['enhancement']['enabled'] = args.enable_enhancement - # LiDAR参数 if hasattr(args, 'enable_lidar'): config['sensors']['lidar_sensors'] = 1 if args.enable_lidar else 0 config['output']['save_lidar'] = args.enable_lidar - # 融合参数 if hasattr(args, 'enable_fusion'): config['output']['save_fusion'] = args.enable_fusion - # 协同参数 if hasattr(args, 'enable_cooperative'): config['output']['save_cooperative'] = args.enable_cooperative - # 标注参数 if hasattr(args, 'enable_annotations'): config['output']['save_annotations'] = args.enable_annotations - # 验证参数 if hasattr(args, 'skip_validation'): config['output']['validate_data'] = not args.skip_validation - # 质量检查参数 if hasattr(args, 'skip_quality_check'): config['output']['run_quality_check'] = not args.skip_quality_check - # 分析参数 if hasattr(args, 'run_analysis'): config['output']['run_analysis'] = args.run_analysis - # 性能参数 if hasattr(args, 'batch_size') and args.batch_size: config['performance']['batch_size'] = args.batch_size @@ -596,7 +533,6 @@ def merge_args(config: Dict[str, Any], args: argparse.Namespace) -> Dict[str, An if args.enable_downsampling: config['sensors']['lidar_config']['downsample_ratio'] = 0.3 - # 输出格式 if hasattr(args, 'output_format') and args.output_format: config['output']['output_format'] = args.output_format @@ -604,7 +540,6 @@ def merge_args(config: Dict[str, Any], args: argparse.Namespace) -> Dict[str, An @staticmethod def save_config(config: Dict[str, Any], output_path: str, format: str = 'json'): - """保存配置到文件""" try: os.makedirs(os.path.dirname(output_path), exist_ok=True) @@ -623,7 +558,6 @@ def save_config(config: Dict[str, Any], output_path: str, format: str = 'json'): @staticmethod def generate_config_template(output_path: str, preset: Optional[str] = None): - """生成配置模板""" config = ConfigManager.load_config(preset=preset) config['metadata']['created'] = 'template' config['metadata']['description'] = f'配置模板 - {preset if preset else "通用"}' @@ -632,12 +566,10 @@ def generate_config_template(output_path: str, preset: Optional[str] = None): @staticmethod def print_config_summary(config: Dict[str, Any]): - """打印配置摘要""" print("\n" + "=" * 60) print("配置摘要") print("=" * 60) - # 场景信息 scenario = config['scenario'] print(f"\n📋 场景:") print(f" 名称: {scenario['name']}") @@ -646,7 +578,6 @@ def print_config_summary(config: Dict[str, Any]): print(f" 时长: {scenario['duration']}秒") print(f" 随机种子: {scenario.get('seed', '随机')}") - # 交通信息 traffic = config['traffic'] print(f"\n🚗 交通:") print(f" 主车: {traffic['ego_vehicles']}") @@ -654,7 +585,6 @@ def print_config_summary(config: Dict[str, Any]): print(f" 行人: {traffic['pedestrians']}") print(f" 交通灯: {'启用' if traffic['traffic_lights'] else '禁用'}") - # 传感器信息 sensors = config['sensors'] print(f"\n📷 传感器:") print(f" 车辆摄像头: {sensors['vehicle_cameras']}") @@ -663,7 +593,6 @@ def print_config_summary(config: Dict[str, Any]): print(f" 采集间隔: {sensors['capture_interval']}秒") print(f" 图像尺寸: {sensors['image_size'][0]}x{sensors['image_size'][1]}") - # V2X信息 v2x = config['v2x'] print(f"\n📡 V2X通信:") print(f" 状态: {'启用' if v2x['enabled'] else '禁用'}") @@ -671,14 +600,12 @@ def print_config_summary(config: Dict[str, Any]): print(f" 通信范围: {v2x['communication_range']}米") print(f" 更新间隔: {v2x['update_interval']}秒") - # 协同信息 coop = config['cooperative'] print(f"\n🤝 协同感知:") print(f" 协同车辆: {coop['num_coop_vehicles']}") print(f" 共享感知: {'启用' if coop['enable_shared_perception'] else '禁用'}") print(f" 交通警告: {'启用' if coop['enable_traffic_warnings'] else '禁用'}") - # 性能信息 perf = config['performance'] print(f"\n⚡ 性能:") print(f" 批处理大小: {perf['batch_size']}") @@ -686,7 +613,6 @@ def print_config_summary(config: Dict[str, Any]): print(f" 下采样: {'启用' if perf['enable_downsampling'] else '禁用'}") print(f" 帧率限制: {perf['frame_rate_limit']} FPS") - # 输出信息 output = config['output'] print(f"\n💾 输出:") print(f" 输出目录: {output['data_dir']}") @@ -699,7 +625,6 @@ def print_config_summary(config: Dict[str, Any]): @staticmethod def list_presets(): - """列出所有预设配置""" print("\n可用预设配置:") print("-" * 40) for name, preset in ConfigManager.PRESET_CONFIGS.items(): @@ -707,12 +632,9 @@ def list_presets(): print("-" * 40) -# 兼容旧版本的接口 def load_config(config_file=None): - """兼容旧版本的加载配置函数""" return ConfigManager.load_config(config_file) def merge_args(config, args): - """兼容旧版本的合并参数函数""" return ConfigManager.merge_args(config, args) \ No newline at end of file diff --git a/src/enhance_pedestrian_safety/lidar_processor.py b/src/enhance_pedestrian_safety/lidar_processor.py index 654aa674b8..8c0f6ebb20 100644 --- a/src/enhance_pedestrian_safety/lidar_processor.py +++ b/src/enhance_pedestrian_safety/lidar_processor.py @@ -23,21 +23,18 @@ def __init__(self, output_dir, config=None): self.frame_counter = 0 self.data_lock = threading.Lock() - # 性能优化:批量处理设置 self.batch_size = config.get('batch_size', 10) if config else 10 self.point_cloud_batch = [] self.enable_compression = config.get('enable_compression', True) if config else True self.compression_level = config.get('compression_level', 3) if config else 3 - # 内存管理 - 优化点3:更保守的内存设置 - self.max_points_per_frame = config.get('max_points_per_frame', 50000) if config else 50000 # 减少到5万点 + self.max_points_per_frame = config.get('max_points_per_frame', 50000) if config else 50000 self.enable_downsampling = config.get('enable_downsampling', True) if config else True - self.downsample_ratio = config.get('downsample_ratio', 0.3) if config else 0.3 # 增加下采样比例 + self.downsample_ratio = config.get('downsample_ratio', 0.3) if config else 0.3 - # 新增:内存监控和限制 - self.memory_warning_threshold = config.get('memory_warning_threshold', 300) if config else 300 # MB - self.max_batch_memory_mb = config.get('max_batch_memory_mb', 50) if config else 50 # 批次最大内存 - self.v2x_save_interval = config.get('v2x_save_interval', 5) if config else 5 # 每5帧保存一次V2X格式 + self.memory_warning_threshold = config.get('memory_warning_threshold', 300) if config else 300 + self.max_batch_memory_mb = config.get('max_batch_memory_mb', 50) if config else 50 + self.v2x_save_interval = config.get('v2x_save_interval', 5) if config else 5 self._init_calibration_files() @@ -81,109 +78,81 @@ def process_lidar_data(self, lidar_data, frame_num): points = self._carla_lidar_to_numpy(lidar_data) if points is None or points.shape[0] == 0: - print(f"警告: LiDAR数据为空或无效 (帧 {frame_num})") return None - # 检查内存使用情况 if self._check_memory_usage(): - print(f"警告: 内存使用过高,跳过LiDAR处理 (帧 {frame_num})") return None - # 下采样以减少内存使用 original_count = points.shape[0] if self.enable_downsampling and points.shape[0] > self.max_points_per_frame: points = self._downsample_point_cloud(points) - print(f"LiDAR下采样: {original_count} -> {points.shape[0]} 点 (帧 {frame_num})") - # 检查批次内存使用 batch_memory_mb = self._estimate_batch_memory_mb() if batch_memory_mb > self.max_batch_memory_mb: - print(f"批处理内存过高 ({batch_memory_mb:.1f}MB),强制保存当前批次") self._save_batch() - # 添加到批处理 self.point_cloud_batch.append((frame_num, points)) - # 如果达到批处理大小,保存批处理数据 if len(self.point_cloud_batch) >= self.batch_size: self._save_batch() - # 保存单个文件(向后兼容) bin_path = self._save_as_bin(points, frame_num) npy_path = self._save_as_npy(points, frame_num) - # 生成V2XFormer兼容格式 - 优化点:每v2x_save_interval帧保存一次 v2xformer_path = None if frame_num % self.v2x_save_interval == 0: try: v2xformer_path = self._save_as_v2xformer_format(points, frame_num) except Exception as e: - print(f"警告: V2XFormer格式保存失败: {e}") v2xformer_path = None metadata = self._generate_metadata(points, bin_path, npy_path, v2xformer_path) - # 定期清理内存 if frame_num % 20 == 0: gc.collect() return metadata except Exception as e: - print(f"处理LiDAR数据失败: {e}") - # 强制清理内存 gc.collect() return None def _carla_lidar_to_numpy(self, lidar_data): - """高效转换LiDAR数据""" try: - # 使用内存视图减少内存分配 points = np.frombuffer(lidar_data.raw_data, dtype=np.float32) points = np.reshape(points, (int(points.shape[0] / 4), 4)) - - # 只保留位置信息(x, y, z),忽略强度 points = points[:, :3] - # 清理临时数组 del lidar_data gc.collect() return points except Exception as e: - print(f"LiDAR数据转换失败: {e}") - - # 备选转换方法 try: points = [] for i in range(0, len(lidar_data), 4): point = lidar_data[i:i + 4] points.append([point.x, point.y, point.z]) - return np.array(points, dtype=np.float32) except: return None def _downsample_point_cloud(self, points): - """下采样点云以减少内存使用""" if points.shape[0] <= self.max_points_per_frame: return points - # 随机下采样 indices = np.random.choice(points.shape[0], int(points.shape[0] * self.downsample_ratio), replace=False) downsampled = points[indices] - # 清理内存 del points gc.collect() return downsampled def _estimate_batch_memory_mb(self): - """估计批处理内存使用""" if not self.point_cloud_batch: return 0 @@ -191,45 +160,38 @@ def _estimate_batch_memory_mb(self): for _, points in self.point_cloud_batch: total_points += points.shape[0] - # 每个点3个float32 (12字节),加上一些额外开销 - memory_bytes = total_points * 12 * 1.2 # 20%额外开销 + memory_bytes = total_points * 12 * 1.2 return memory_bytes / (1024 * 1024) def _check_memory_usage(self): - """检查内存使用情况""" try: import psutil process = psutil.Process() memory_mb = process.memory_info().rss / (1024 * 1024) if memory_mb > self.memory_warning_threshold: - print(f"警告: 进程内存使用过高: {memory_mb:.1f}MB") return True return False except: return False def _save_batch(self): - """批量保存点云数据(提高效率)""" if not self.point_cloud_batch: return batch_data = [] for frame_num, points in self.point_cloud_batch: - # 只保存点的坐标,不保存完整对象 batch_data.append({ 'frame_num': frame_num, 'points': points.tolist(), 'num_points': points.shape[0] }) - # 生成批处理文件名 min_frame = min([item[0] for item in self.point_cloud_batch]) max_frame = max([item[0] for item in self.point_cloud_batch]) batch_filename = f"lidar_batch_{min_frame:06d}_{max_frame:06d}.json" batch_path = os.path.join(self.lidar_dir, batch_filename) - # 压缩保存 if self.enable_compression: try: json_str = json.dumps(batch_data) @@ -240,49 +202,39 @@ def _save_batch(self): with open(batch_path, 'wb') as f: f.write(compressed_data) except Exception as e: - print(f"压缩保存批处理数据失败: {e}") - # 尝试非压缩保存 try: with open(batch_path, 'w') as f: json.dump(batch_data, f) except Exception as e2: - print(f"非压缩保存批处理数据失败: {e2}") + pass else: try: with open(batch_path, 'w') as f: json.dump(batch_data, f) except Exception as e: - print(f"保存批处理数据失败: {e}") + pass - # 清理批处理缓存 self.point_cloud_batch.clear() gc.collect() - print(f"批量保存LiDAR数据: {batch_filename}") - def _save_as_bin(self, points, frame_num): - """保存为二进制格式(兼容KITTI)""" bin_path = os.path.join(self.lidar_dir, f"lidar_{frame_num:06d}.bin") - # 添加强度信息(全为1) points_with_intensity = np.zeros((points.shape[0], 4), dtype=np.float32) points_with_intensity[:, :3] = points - points_with_intensity[:, 3] = 1.0 # 强度值 + points_with_intensity[:, 3] = 1.0 try: points_with_intensity.tofile(bin_path) except Exception as e: - print(f"保存BIN文件失败: {e}") return None return bin_path def _save_as_npy(self, points, frame_num): - """保存为numpy格式""" npy_path = os.path.join(self.lidar_dir, f"lidar_{frame_num:06d}.npy") try: - # 使用内存映射减少内存使用 memmap_array = np.lib.format.open_memmap( npy_path, mode='w+', @@ -292,23 +244,18 @@ def _save_as_npy(self, points, frame_num): memmap_array[:] = points[:] memmap_array.flush() except Exception as e: - print(f"保存NPY文件失败: {e}") - # 备选保存方式 try: np.save(npy_path, points) except Exception as e2: - print(f"备选保存NPY文件失败: {e2}") return None return npy_path def _save_as_v2xformer_format(self, points, frame_num): - """保存为V2XFormer兼容格式 - 优化点:减少保存频率""" v2x_dir = os.path.join(self.output_dir, "v2xformer_format") os.makedirs(v2x_dir, exist_ok=True) try: - # 创建V2XFormer格式的数据结构 v2x_data = { 'frame_id': frame_num, 'timestamp': datetime.now().timestamp(), @@ -327,21 +274,15 @@ def _save_as_v2xformer_format(self, points, frame_num): } } - # 保存为压缩的JSON格式 filename = f"{frame_num:06d}.pkl.gz" filepath = os.path.join(v2x_dir, filename) - # 使用pickle+gzip压缩保存 with gzip.open(filepath, 'wb') as f: pickle.dump(v2x_data, f, protocol=pickle.HIGHEST_PROTOCOL) - file_size = os.path.getsize(filepath) / 1024 # KB - print(f"保存V2XFormer格式: {filepath} ({file_size:.2f} KB)") - return filepath except Exception as e: - print(f"警告: 保存V2XFormer格式失败: {e}") return None def _generate_metadata(self, points, bin_path, npy_path, v2xformer_path): @@ -375,21 +316,18 @@ def _generate_metadata(self, points, bin_path, npy_path, v2xformer_path): with open(meta_path, 'w') as f: json.dump(metadata, f, indent=2) except Exception as e: - print(f"保存LiDAR元数据失败: {e}") + pass return metadata def flush_batch(self): - """强制刷新批处理数据""" if self.point_cloud_batch: self._save_batch() def generate_lidar_summary(self): - """生成LiDAR数据摘要(优化版)""" if not os.path.exists(self.lidar_dir): return None - # 快速统计文件 import glob bin_files = glob.glob(os.path.join(self.lidar_dir, "*.bin")) npy_files = glob.glob(os.path.join(self.lidar_dir, "*.npy")) @@ -399,20 +337,17 @@ def generate_lidar_summary(self): total_points = 0 total_size = 0 - # 采样统计,避免读取所有文件 - sample_files = bin_files[:5] if len(bin_files) > 5 else bin_files # 减少采样数量 + sample_files = bin_files[:5] if len(bin_files) > 5 else bin_files for bin_file in sample_files: if os.path.exists(bin_file): try: file_size = os.path.getsize(bin_file) total_size += file_size - # 估算点数 - points_in_file = file_size // (4 * 4) # 4个float32,每个4字节 + points_in_file = file_size // (4 * 4) total_points += points_in_file except: continue - # 根据采样估算总数 if sample_files and len(bin_files) > 0: avg_points_per_file = total_points / max(len(sample_files), 1) total_points_estimated = avg_points_per_file * len(bin_files) @@ -440,13 +375,12 @@ def generate_lidar_summary(self): with open(summary_path, 'w') as f: json.dump(summary, f, indent=2) except Exception as e: - print(f"保存LiDAR摘要失败: {e}") + pass return summary class MultiSensorFusion: - """多传感器融合(优化版)""" def __init__(self, output_dir, config=None): self.output_dir = output_dir @@ -454,8 +388,8 @@ def __init__(self, output_dir, config=None): os.makedirs(self.fusion_dir, exist_ok=True) self.calibration_data = {} - self.fusion_cache = {} # 融合缓存 - self.cache_size = config.get('fusion_cache_size', 50) if config else 50 # 减少缓存大小 + self.fusion_cache = {} + self.cache_size = config.get('fusion_cache_size', 50) if config else 50 self._load_calibration() @@ -463,48 +397,41 @@ def _load_calibration(self): calibration_dir = os.path.join(self.output_dir, "calibration") if os.path.exists(calibration_dir): - # 批量读取校准文件 calibration_files = [] for root, dirs, files in os.walk(calibration_dir): for file in files: if file.endswith('.json'): calibration_files.append(os.path.join(root, file)) - # 限制同时读取的文件数量 - for file in calibration_files[:10]: # 最多读取10个文件 + for file in calibration_files[:10]: try: with open(file, 'r') as f: data = json.load(f) sensor_name = os.path.basename(file).replace('.json', '') self.calibration_data[sensor_name] = data except Exception as e: - print(f"加载校准文件 {file} 失败: {e}") + pass def create_synchronization_file(self, frame_num, sensor_data): - """创建同步文件(优化版)""" - # 检查缓存 cache_key = f"{frame_num}_{hash(str(sensor_data))}" if cache_key in self.fusion_cache: return self.fusion_cache[cache_key] sync_data = { 'frame_id': frame_num, - 'timestamp': datetime.now().timestamp(), # 使用时间戳而不是ISO格式 + 'timestamp': datetime.now().timestamp(), 'sensors': {}, 'transformations': {} } - # 批量处理传感器数据 for sensor_type, data_path in sensor_data.items(): if data_path and os.path.exists(data_path): - # 获取文件信息(不加载完整文件) file_info = { 'file_path': os.path.basename(data_path), 'file_size': os.path.getsize(data_path), 'modified_time': os.path.getmtime(data_path) } - # 如果是图像,获取尺寸信息 if data_path.endswith(('.png', '.jpg', '.jpeg')): try: import cv2 @@ -516,7 +443,6 @@ def create_synchronization_file(self, frame_num, sensor_data): sync_data['sensors'][sensor_type] = file_info - # 添加变换信息(如果可用) if sensor_type in self.calibration_data: sync_data['transformations'][sensor_type] = { 'matrix': self.calibration_data[sensor_type].get('matrix', @@ -524,11 +450,9 @@ def create_synchronization_file(self, frame_num, sensor_data): [0, 0, 0, 1]]) } - # 压缩保存 sync_file = os.path.join(self.fusion_dir, f"sync_{frame_num:06d}.json") - # 使用压缩的JSON - if len(str(sync_data)) > 1000: # 如果数据较大,压缩保存 + if len(str(sync_data)) > 1000: compressed_data = zlib.compress( json.dumps(sync_data).encode('utf-8'), level=3 @@ -538,11 +462,9 @@ def create_synchronization_file(self, frame_num, sensor_data): f.write(compressed_data) else: with open(sync_file, 'w') as f: - json.dump(sync_data, f, separators=(',', ':')) # 紧凑格式 + json.dump(sync_data, f, separators=(',', ':')) - # 更新缓存 if len(self.fusion_cache) >= self.cache_size: - # 移除最旧的缓存项 oldest_key = next(iter(self.fusion_cache)) del self.fusion_cache[oldest_key] @@ -551,19 +473,15 @@ def create_synchronization_file(self, frame_num, sensor_data): return sync_file def generate_fusion_report(self): - """生成融合报告(优化版)""" if not os.path.exists(self.fusion_dir): return None - # 快速扫描文件 import glob sync_files = glob.glob(os.path.join(self.fusion_dir, "*.json*")) - # 解析文件名获取帧范围(避免读取所有文件) frame_ids = [] - for sync_file in sync_files[:20]: # 只检查前20个文件 + for sync_file in sync_files[:20]: try: - # 从文件名提取帧号 filename = os.path.basename(sync_file) if filename.startswith('sync_'): frame_id = int(filename.split('_')[1].split('.')[0]) diff --git a/src/enhance_pedestrian_safety/main.py b/src/enhance_pedestrian_safety/main.py index 1331ff3b79..6861758c1d 100644 --- a/src/enhance_pedestrian_safety/main.py +++ b/src/enhance_pedestrian_safety/main.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 import sys import os import time @@ -30,6 +31,8 @@ def __init__(self): self.memory_samples = [] self.cpu_samples = [] self.frame_times = [] + self.first_frame_time = None + self.last_frame_time = None def sample_memory(self): try: @@ -72,14 +75,17 @@ def sample_cpu(self): def record_frame_time(self, frame_time): self.frame_times.append(frame_time) + if self.first_frame_time is None: + self.first_frame_time = time.time() + self.last_frame_time = time.time() def get_performance_summary(self): - if not self.frame_times: + if not self.frame_times or len(self.frame_times) < 2: avg_frame_time = 0 fps = 0 else: avg_frame_time = np.mean(self.frame_times) - fps = 1.0 / avg_frame_time if avg_frame_time > 0 else 0 + fps = len(self.frame_times) / max(0.1, (self.last_frame_time - self.first_frame_time)) summary = { 'total_runtime': time.time() - self.start_time, @@ -90,14 +96,24 @@ def get_performance_summary(self): 'max_cpu_percent': max(self.cpu_samples) if self.cpu_samples else 0, 'average_frame_time': avg_frame_time, 'frames_per_second': fps, - 'total_frames': len(self.frame_times), - 'frame_time_stats': { - 'p50': np.percentile(self.frame_times, 50) if self.frame_times else 0, - 'p95': np.percentile(self.frame_times, 95) if self.frame_times else 0, - 'p99': np.percentile(self.frame_times, 99) if self.frame_times else 0, - 'std': np.std(self.frame_times) if self.frame_times else 0 - } + 'total_frames': len(self.frame_times) } + + if self.frame_times and len(self.frame_times) >= 2: + summary['frame_time_stats'] = { + 'p50': np.percentile(self.frame_times, 50), + 'p95': np.percentile(self.frame_times, 95), + 'p99': np.percentile(self.frame_times, 99) if len(self.frame_times) > 1 else 0, + 'std': np.std(self.frame_times) + } + else: + summary['frame_time_stats'] = { + 'p50': 0, + 'p95': 0, + 'p99': 0, + 'std': 0 + } + return summary @@ -119,12 +135,12 @@ def error(msg): @staticmethod def debug(msg): - timestamp = datetime.now().strftime("%Y-%m-d %H:%M:%S") + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") print(f"[DEBUG][{timestamp}] {msg}") @staticmethod def performance(msg): - timestamp = datetime.now().strftime("%Y-%m-d %H:%M:%S") + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") print(f"[PERF][{timestamp}] {msg}") @@ -569,8 +585,7 @@ def __init__(self, world, config, data_dir): 'total_images': 0, 'total_lidar_frames': 0, 'image_capture_times': [], - 'lidar_processing_times': [], - 'frame_drop_count': 0 + 'lidar_processing_times': [] } def setup_cameras(self, vehicle, center_location, vehicle_id=0): @@ -868,7 +883,6 @@ def cleanup(self): self.is_running = False - Log.info("停止所有传感器监听...") for sensor in self.sensors: try: if hasattr(sensor, 'stop'): @@ -885,7 +899,6 @@ def cleanup(self): except: pass - Log.info(f"销毁 {len(self.sensors)} 个传感器...") for i, sensor in enumerate(self.sensors): try: if hasattr(sensor, 'destroy'): @@ -1105,17 +1118,15 @@ def collect_data(self): memory_warning_issued = False early_stop_triggered = False - # 添加内存监控阈值 - memory_warning_threshold = 350 # MB - memory_critical_threshold = 400 # MB - early_stop_threshold = 450 # MB + memory_warning_threshold = 350 + memory_critical_threshold = 400 + early_stop_threshold = 450 try: while time.time() - self.start_time < duration and self.is_running: current_time = time.time() elapsed = current_time - self.start_time - # 内存监控和早期停止机制 if current_time - last_memory_check >= 2.0: try: import psutil @@ -1204,16 +1215,21 @@ def collect_data(self): self.collected_frames = sum(mgr.get_frame_count() for mgr in self.sensor_managers.values()) - performance_summary = self.performance_monitor.get_performance_summary() + if self.collected_frames > 0: + total_frames_from_sensors = self.collected_frames + performance_summary = self.performance_monitor.get_performance_summary() - if early_stop_triggered: - Log.warning("数据收集因内存过高而提前终止") - else: - Log.info(f"收集完成: {self.collected_frames}帧, 用时: {elapsed:.1f}秒") + if early_stop_triggered: + Log.warning("数据收集因内存过高而提前终止") + else: + Log.info(f"收集完成: {self.collected_frames}帧, 用时: {elapsed:.1f}秒") - Log.info(f"平均帧率: {performance_summary['frames_per_second']:.2f} FPS") - Log.info(f"最大内存使用: {performance_summary['max_memory_mb']:.1f} MB") - Log.info(f"平均CPU使用: {performance_summary['average_cpu_percent']:.1f}%") + fps = total_frames_from_sensors / max(elapsed, 0.1) + Log.info(f"平均帧率: {fps:.2f} FPS") + Log.info(f"最大内存使用: {performance_summary['max_memory_mb']:.1f} MB") + Log.info(f"平均CPU使用: {performance_summary['average_cpu_percent']:.1f}%") + else: + Log.warning("未收集到任何数据帧") self._save_metadata() self._print_summary() @@ -1478,6 +1494,8 @@ def _simulate_object_detection(self, vehicle): return detected_objects def _save_metadata(self): + total_frames = self.collected_frames + metadata = { 'scenario': self.config['scenario'], 'traffic': self.config['traffic'], @@ -1488,22 +1506,26 @@ def _save_metadata(self): 'output': self.config['output'], 'collection': { 'duration': round(time.time() - self.start_time, 2), - 'total_frames': self.collected_frames, - 'frame_rate': round(self.collected_frames / max(time.time() - self.start_time, 0.1), 2) - }, - 'performance': self.performance_monitor.get_performance_summary() + 'total_frames': total_frames, + 'frame_rate': round(total_frames / max(time.time() - self.start_time, 0.1), + 2) if total_frames > 0 else 0 + } } - sensor_summaries = {} - for vehicle_id, sensor_manager in self.sensor_managers.items(): - sensor_summaries[vehicle_id] = sensor_manager.generate_sensor_summary() - metadata['sensor_summaries'] = sensor_summaries + if total_frames > 0: + performance_summary = self.performance_monitor.get_performance_summary() + metadata['performance'] = performance_summary - if self.v2x_communication: - metadata['v2x_status'] = self.v2x_communication.get_network_status() + sensor_summaries = {} + for vehicle_id, sensor_manager in self.sensor_managers.items(): + sensor_summaries[vehicle_id] = sensor_manager.generate_sensor_summary() + metadata['sensor_summaries'] = sensor_summaries - if self.multi_vehicle_manager: - metadata['cooperative_summary'] = self.multi_vehicle_manager.generate_summary() + if self.v2x_communication: + metadata['v2x_status'] = self.v2x_communication.get_network_status() + + if self.multi_vehicle_manager: + metadata['cooperative_summary'] = self.multi_vehicle_manager.generate_summary() meta_path = os.path.join(self.output_dir, "metadata", "collection_info.json") with open(meta_path, 'w', encoding='utf-8') as f: @@ -1553,29 +1575,37 @@ def _print_summary(self): if os.path.exists(kitti_dir): print(f"KITTI格式: 已生成") - performance = self.performance_monitor.get_performance_summary() + total_frames = self.collected_frames + elapsed = time.time() - self.start_time + print(f"\n性能统计:") - print(f" 平均帧率: {performance['frames_per_second']:.2f} FPS") - print(f" 帧时统计:") - print(f" 平均: {performance['average_frame_time']:.3f}秒") - print(f" P50: {performance['frame_time_stats']['p50']:.3f}秒") - print(f" P95: {performance['frame_time_stats']['p95']:.3f}秒") - print(f" P99: {performance['frame_time_stats']['p99']:.3f}秒") - print(f" 平均内存: {performance['average_memory_mb']:.1f} MB") - print(f" 最大内存: {performance['max_memory_mb']:.1f} MB") - print(f" 平均CPU: {performance['average_cpu_percent']:.1f}%") - print(f" 总帧数: {performance['total_frames']}") + if total_frames > 0: + fps = total_frames / max(elapsed, 0.1) + print(f" 平均帧率: {fps:.2f} FPS") + + performance = self.performance_monitor.get_performance_summary() + print(f" 帧时统计:") + print(f" 平均: {performance['average_frame_time']:.3f}秒") + print(f" P50: {performance['frame_time_stats']['p50']:.3f}秒") + print(f" P95: {performance['frame_time_stats']['p95']:.3f}秒") + print(f" P99: {performance['frame_time_stats']['p99']:.3f}秒") + print(f" 平均内存: {performance['average_memory_mb']:.1f} MB") + print(f" 最大内存: {performance['max_memory_mb']:.1f} MB") + print(f" 平均CPU: {performance['average_cpu_percent']:.1f}%") + print(f" 总帧数: {total_frames}") + else: + print(" 未收集到有效帧数据") print(f"\n输出目录: {self.output_dir}") print("=" * 60) def run_validation(self): - if self.config['output'].get('validate_data', True): + if self.config['output'].get('validate_data', True) and self.collected_frames > 0: Log.info("运行数据验证...") DataValidator.validate_dataset(self.output_dir) def run_analysis(self): - if self.config['output'].get('run_analysis', False): + if self.config['output'].get('run_analysis', False) and self.collected_frames > 0: Log.info("运行数据分析...") DataAnalyzer.analyze_dataset(self.output_dir) @@ -1687,4 +1717,4 @@ def main(): if __name__ == "__main__": - main() \ No newline at end of file + main() From 84d1f41eb5bde5f8a3911bcc7c505cea5d99906b Mon Sep 17 00:00:00 2001 From: Z-w-7799 <1011069369@qq.com> Date: Sun, 21 Dec 2025 21:47:38 +0800 Subject: [PATCH 18/20] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/enhance_pedestrian_safety/package.xml | 71 +++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 src/enhance_pedestrian_safety/package.xml diff --git a/src/enhance_pedestrian_safety/package.xml b/src/enhance_pedestrian_safety/package.xml new file mode 100644 index 0000000000..d6380991bb --- /dev/null +++ b/src/enhance_pedestrian_safety/package.xml @@ -0,0 +1,71 @@ + + + carla_sensor + 0.0.0 + The carla_sensor package + + + + + ros-industrial + + + + + + TODO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + catkin + cv_bridge + rospy + sensor_msgs + std_msgs + cv_bridge + rospy + sensor_msgs + std_msgs + cv_bridge + rospy + sensor_msgs + std_msgs + + + + + + + + From 9552e812d70e6ac562781916ba9b18464d8f5559 Mon Sep 17 00:00:00 2001 From: Z-w-7799 <1011069369@qq.com> Date: Thu, 25 Dec 2025 00:37:39 +0800 Subject: [PATCH 19/20] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=A1=8C=E4=BA=BA?= =?UTF-8?q?=E5=AE=89=E5=85=A8=E7=9B=91=E6=8E=A7=E5=99=A8,=E5=A2=9E?= =?UTF-8?q?=E5=BC=BA=E5=9C=BA=E6=99=AF=E7=AE=A1=E7=90=86=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config_manager.py | 105 ++++- .../data_analyzer.py | 160 +++++++- .../data_validator.py | 82 +++- src/enhance_pedestrian_safety/main.py | 50 ++- .../multi_vehicle_manager.py | 28 ++ .../pedestrian_safety_monitor.py | 368 ++++++++++++++++++ .../scene_manager.py | 85 ++++ .../sensor_enhancer.py | 54 +++ 8 files changed, 905 insertions(+), 27 deletions(-) create mode 100644 src/enhance_pedestrian_safety/pedestrian_safety_monitor.py diff --git a/src/enhance_pedestrian_safety/config_manager.py b/src/enhance_pedestrian_safety/config_manager.py index c608de6674..aa07724783 100644 --- a/src/enhance_pedestrian_safety/config_manager.py +++ b/src/enhance_pedestrian_safety/config_manager.py @@ -133,7 +133,13 @@ def optimize_for_quality(config: Dict[str, Any]) -> Dict[str, Any]: 'enabled': True, 'enable_random': True, 'quality_check': True, - 'methods': ['normalize', 'contrast', 'sharpness', 'noise'] + 'save_original': True, + 'save_enhanced': True, + 'calibration_generation': True, + 'enhanced_dir_name': 'enhanced', + 'methods': ['normalize', 'contrast', 'brightness'], + 'weather_effects': True, + 'augmentation_level': 'medium' }) return optimized @@ -173,6 +179,94 @@ def optimize_for_speed(config: Dict[str, Any]) -> Dict[str, Any]: return optimized + @staticmethod + def optimize_for_safety(config: Dict[str, Any]) -> Dict[str, Any]: + """优化配置以增强行人安全""" + optimized = copy.deepcopy(config) + + # 增加行人密度 + traffic = optimized['traffic'] + traffic.update({ + 'pedestrians': 12, # 增加行人数量 + 'pedestrian_types': [ + 'walker.pedestrian.0001', + 'walker.pedestrian.0002', + 'walker.pedestrian.0003', + 'walker.pedestrian.0004' + ] + }) + + # 优化传感器配置以更好地检测行人 + sensors = optimized['sensors'] + sensors.update({ + 'image_size': [1280, 720], + 'capture_interval': 1.5, # 更频繁地捕获 + 'vehicle_cameras': 4, + 'camera_config': { + 'fov': 100.0, # 更宽的视野 + 'post_processing': 'default', + 'exposure_mode': 'auto', + 'motion_blur': 0.0 + } + }) + + # 启用LiDAR以检测行人 + sensors['lidar_sensors'] = 1 + sensors['lidar_config'].update({ + 'channels': 64, # 更多通道以检测行人 + 'range': 120.0, + 'points_per_second': 100000, + 'max_points_per_frame': 80000, + 'downsample_ratio': 0.2 + }) + + # 启用V2X和协同感知 + v2x = optimized.setdefault('v2x', {}) + v2x.update({ + 'enabled': True, + 'communication_range': 300.0, + 'update_interval': 1.0 # 更频繁地更新 + }) + + coop = optimized.setdefault('cooperative', {}) + coop.update({ + 'num_coop_vehicles': 2, + 'enable_shared_perception': True, + 'enable_traffic_warnings': True, + 'enable_maneuver_coordination': False, + 'data_fusion_interval': 0.5, # 更频繁地融合 + 'max_shared_objects': 100, + 'object_matching_threshold': 3.0 # 更严格的对象匹配 + }) + + # 性能优化 + perf = optimized.setdefault('performance', {}) + perf.update({ + 'batch_size': 5, + 'enable_compression': True, + 'compression_level': 3, + 'enable_memory_cache': True, + 'max_cache_size': 40, + 'frame_rate_limit': 8.0 + }) + + # 输出配置 + output = optimized['output'] + output.update({ + 'save_raw': True, + 'save_stitched': True, + 'save_annotations': True, + 'save_lidar': True, + 'save_fusion': True, + 'save_cooperative': True, + 'save_enhanced': True, + 'validate_data': True, + 'run_analysis': True, + 'run_quality_check': True + }) + + return optimized + class ConfigManager: @@ -189,6 +283,10 @@ class ConfigManager: 'description': '快速采集配置 - 优先处理速度', 'optimization': 'speed' }, + 'pedestrian_safety': { + 'description': '行人安全配置 - 优化行人检测和安全评估', + 'optimization': 'safety' + }, 'v2x_focused': { 'description': 'V2X重点配置 - 优化协同数据采集', 'optimization': 'custom', @@ -438,6 +536,8 @@ def _apply_preset(config: Dict[str, Any], preset_name: str) -> Dict[str, Any]: config = ConfigOptimizer.optimize_for_quality(config) elif optimization == 'speed': config = ConfigOptimizer.optimize_for_speed(config) + elif optimization == 'safety': + config = ConfigOptimizer.optimize_for_safety(config) elif optimization == 'custom' and 'settings' in preset: config = ConfigManager._deep_update(config, preset['settings']) @@ -536,6 +636,9 @@ def merge_args(config: Dict[str, Any], args: argparse.Namespace) -> Dict[str, An if hasattr(args, 'output_format') and args.output_format: config['output']['output_format'] = args.output_format + if hasattr(args, 'enable_safety_monitor'): + config['monitoring']['enable_safety_monitor'] = args.enable_safety_monitor + return config @staticmethod diff --git a/src/enhance_pedestrian_safety/data_analyzer.py b/src/enhance_pedestrian_safety/data_analyzer.py index 14690041fb..38ee4dd6f4 100644 --- a/src/enhance_pedestrian_safety/data_analyzer.py +++ b/src/enhance_pedestrian_safety/data_analyzer.py @@ -85,7 +85,8 @@ def analyze_dataset(data_dir, force_refresh=False): 'object_statistics': DataAnalyzer._analyze_objects(data_dir), 'temporal_analysis': DataAnalyzer._analyze_temporal(data_dir), 'cooperative_data': DataAnalyzer._analyze_cooperative_data(data_dir), - 'quality_metrics': DataAnalyzer._calculate_quality_metrics(data_dir) + 'quality_metrics': DataAnalyzer._calculate_quality_metrics(data_dir), + 'safety_analysis': DataAnalyzer._analyze_safety_data(data_dir) } # 生成评分 @@ -231,6 +232,10 @@ def _analyze_file_distribution(data_dir): fusion_dir = os.path.join(data_dir, "fusion") if os.path.exists(fusion_dir): distribution['fusion'] = DataAnalyzer._analyze_fusion_data(fusion_dir) + elif dir_name == "safety_reports": + safety_dir = os.path.join(data_dir, "safety_reports") + if os.path.exists(safety_dir): + distribution['safety_reports'] = DataAnalyzer._analyze_safety_reports(safety_dir) return distribution @@ -335,6 +340,41 @@ def _analyze_fusion_data(fusion_dir): fusion_stats['total_size_mb'] = round(total_size / (1024 * 1024), 2) return fusion_stats + @staticmethod + def _analyze_safety_reports(safety_dir): + """分析安全报告数据""" + safety_stats = { + 'reports': 0, + 'high_risk': 0, + 'medium_risk': 0, + 'low_risk': 0, + 'total_interactions': 0 + } + + json_files = [f for f in os.listdir(safety_dir) if f.lower().endswith('.json')] + safety_stats['reports'] = len(json_files) + + if json_files: + # 采样分析几个文件 + sample_files = json_files[:min(5, len(json_files))] + for json_file in sample_files: + try: + with open(os.path.join(safety_dir, json_file), 'r', encoding='utf-8') as f: + data = json.load(f) + + if 'high_risk_cases' in data: + safety_stats['high_risk'] += data['high_risk_cases'] + if 'medium_risk_cases' in data: + safety_stats['medium_risk'] += data['medium_risk_cases'] + if 'low_risk_cases' in data: + safety_stats['low_risk_cases'] += data['low_risk_cases'] + if 'total_interactions' in data: + safety_stats['total_interactions'] += data['total_interactions'] + except: + pass + + return safety_stats + @staticmethod def _analyze_objects(data_dir): """分析物体统计(优化版)""" @@ -647,6 +687,70 @@ def _analyze_cooperative_data(data_dir): return analysis + @staticmethod + def _analyze_safety_data(data_dir): + """分析安全数据""" + safety_dir = os.path.join(data_dir, "safety_reports") + + if not os.path.exists(safety_dir): + return { + 'total_reports': 0, + 'risk_levels': {'high': 0, 'medium': 0, 'low': 0}, + 'safety_score': 0, + 'pedestrian_interactions': 0, + 'average_distance': 0 + } + + json_files = [f for f in os.listdir(safety_dir) if f.lower().endswith('.json')] + + safety_data = { + 'total_reports': len(json_files), + 'risk_levels': {'high': 0, 'medium': 0, 'low': 0}, + 'safety_score': 0, + 'pedestrian_interactions': 0, + 'average_distance': 0, + 'near_misses': 0, + 'safety_warnings': 0 + } + + if json_files: + distances = [] + for json_file in json_files[:min(10, len(json_files))]: + try: + with open(os.path.join(safety_dir, json_file), 'r', encoding='utf-8') as f: + data = json.load(f) + + if 'high_risk_cases' in data: + safety_data['risk_levels']['high'] += data['high_risk_cases'] + if 'medium_risk_cases' in data: + safety_data['risk_levels']['medium'] += data['medium_risk_cases'] + if 'low_risk_cases' in data: + safety_data['risk_levels']['low'] += data['low_risk_cases'] + if 'total_interactions' in data: + safety_data['pedestrian_interactions'] += data['total_interactions'] + if 'average_distance' in data: + distances.append(data['average_distance']) + if 'near_misses' in data: + safety_data['near_misses'] += data['near_misses'] + if 'safety_warnings' in data: + safety_data['safety_warnings'] += data['safety_warnings'] + + except Exception as e: + print(f"分析安全报告 {json_file} 失败: {e}") + + if distances: + safety_data['average_distance'] = round(np.mean(distances), 2) + + # 计算安全评分 + total_risks = sum(safety_data['risk_levels'].values()) + if total_risks > 0: + high_risk_ratio = safety_data['risk_levels']['high'] / total_risks + safety_data['safety_score'] = max(0, 100 - high_risk_ratio * 100) + else: + safety_data['safety_score'] = 100 + + return safety_data + @staticmethod def _calculate_quality_metrics(data_dir): """计算质量指标(增强版)""" @@ -657,6 +761,7 @@ def _calculate_quality_metrics(data_dir): 'cooperative_score': 0, 'temporal_score': 0, 'structural_score': 0, + 'safety_score': 0, 'issues_found': [], 'recommendations': [] } @@ -674,7 +779,8 @@ def _calculate_quality_metrics(data_dir): "lidar", "fusion", "annotations", - "calibration" + "calibration", + "safety_reports" ] missing_required = [] @@ -781,9 +887,17 @@ def _calculate_quality_metrics(data_dir): quality_metrics['temporal_score'] *= 0.8 # 时长不足,降低分数 quality_metrics['recommendations'].append("建议增加数据收集时长以获得更完整的时间序列") + # 7. 安全评分 + safety_data = DataAnalyzer._analyze_safety_data(data_dir) + quality_metrics['safety_score'] = safety_data.get('safety_score', 0) + + if quality_metrics['safety_score'] < 80: + quality_metrics['issues_found'].append(f"安全评分较低: {quality_metrics['safety_score']}") + quality_metrics['recommendations'].append("建议增加行人安全相关的场景和数据收集") + # 限制分数在0-100之间 for key in ['completeness_score', 'consistency_score', 'diversity_score', - 'cooperative_score', 'temporal_score', 'structural_score']: + 'cooperative_score', 'temporal_score', 'structural_score', 'safety_score']: quality_metrics[key] = max(0, min(100, quality_metrics[key])) return quality_metrics @@ -792,12 +906,13 @@ def _calculate_quality_metrics(data_dir): def _calculate_overall_score(analysis): """计算总体评分(增强版)""" weights = { - 'completeness': 0.20, # 完整性 - 'consistency': 0.15, # 一致性 - 'demporal': 0.15, # 时间性 - 'structural': 0.10, # 结构性 - 'diversity': 0.15, # 多样性 - 'cooperative': 0.15, # 协同性 + 'completeness': 0.15, # 完整性 + 'consistency': 0.12, # 一致性 + 'temporal': 0.12, # 时间性 + 'structural': 0.08, # 结构性 + 'diversity': 0.12, # 多样性 + 'cooperative': 0.12, # 协同性 + 'safety': 0.19, # 安全性 'quality_bonus': 0.10 # 质量加成 } @@ -810,7 +925,8 @@ def _calculate_overall_score(analysis): quality['temporal_score'] * weights['temporal'] + quality['structural_score'] * weights['structural'] + quality['diversity_score'] * weights['diversity'] + - quality['cooperative_score'] * weights['cooperative'] + quality['cooperative_score'] * weights['cooperative'] + + quality['safety_score'] * weights['safety'] ) # 质量加成(基于问题数量) @@ -822,7 +938,8 @@ def _calculate_overall_score(analysis): # 额外加成(如果数据集特别优秀) if (quality['completeness_score'] >= 95 and quality['consistency_score'] >= 90 and - quality['diversity_score'] >= 85): + quality['diversity_score'] >= 85 and + quality['safety_score'] >= 90): total_score += 5 return round(min(total_score, 100), 1) @@ -852,6 +969,7 @@ def _save_analysis_report(data_dir, analysis): 'total_objects': analysis['object_statistics']['total_objects'], 'num_classes': len(analysis['object_statistics']['by_class']) }, + 'safety_data': analysis.get('safety_analysis', {}), 'analysis_metadata': analysis.get('metadata', {}) } @@ -919,6 +1037,20 @@ def _print_analysis_summary(analysis): print(f" 每帧物体数统计:") print(f" 最小: {stats['min']}, 最大: {stats['max']}, 平均: {stats['mean']}, 中位数: {stats['median']}") + # 安全数据分析 + if 'safety_analysis' in analysis: + safety = analysis['safety_analysis'] + print(f"\n🚸 安全数据分析:") + print(f" 安全评分: {safety.get('safety_score', 0)}/100") + print(f" 风险等级分布:") + print(f" 高风险: {safety.get('risk_levels', {}).get('high', 0)}") + print(f" 中风险: {safety.get('risk_levels', {}).get('medium', 0)}") + print(f" 低风险: {safety.get('risk_levels', {}).get('low', 0)}") + print(f" 行人交互次数: {safety.get('pedestrian_interactions', 0)}") + print(f" 平均距离: {safety.get('average_distance', 0):.2f}米") + print(f" 近距离事件: {safety.get('near_misses', 0)}") + print(f" 安全警告: {safety.get('safety_warnings', 0)}") + # 协同数据分析 cooperative = analysis['cooperative_data'] print(f"\n🤝 协同数据分析:") @@ -958,7 +1090,8 @@ def _print_analysis_summary(analysis): ('结构性', quality['structural_score']), ('时间性', quality['temporal_score']), ('多样性', quality['diversity_score']), - ('协同性', quality['cooperative_score']) + ('协同性', quality['cooperative_score']), + ('安全性', quality['safety_score']) ] for name, score in metrics: @@ -1015,7 +1148,8 @@ def generate_comparison_report(data_dirs, output_file=None): 'object_statistics': { 'total_objects': analysis['object_statistics']['total_objects'], 'num_classes': len(analysis['object_statistics']['by_class']) - } + }, + 'safety_analysis': analysis.get('safety_analysis', {}) } if output_file: diff --git a/src/enhance_pedestrian_safety/data_validator.py b/src/enhance_pedestrian_safety/data_validator.py index fa67f6425b..b472b40eb8 100644 --- a/src/enhance_pedestrian_safety/data_validator.py +++ b/src/enhance_pedestrian_safety/data_validator.py @@ -1,5 +1,5 @@ -import os import json +import os class DataValidator: @@ -16,7 +16,8 @@ def validate_dataset(data_dir): 'metadata': DataValidator._validate_metadata(data_dir), 'lidar_data': DataValidator._validate_lidar_data(data_dir), 'cooperative_data': DataValidator._validate_cooperative_data(data_dir), - 'fusion_data': DataValidator._validate_fusion_data(data_dir) + 'fusion_data': DataValidator._validate_fusion_data(data_dir), + 'safety_data': DataValidator._validate_safety_data(data_dir) } validation_results['overall_score'] = DataValidator._calculate_score(validation_results) @@ -39,18 +40,33 @@ def _check_directory_structure(data_dir): "fusion" ] + optional_dirs = [ + "lidar", + "calibration", + "annotations", + "safety_reports" + ] + missing_dirs = [] for dir_path in required_dirs: full_path = os.path.join(data_dir, dir_path) if not os.path.exists(full_path): missing_dirs.append(dir_path) + missing_optional = [] + for dir_path in optional_dirs: + full_path = os.path.join(data_dir, dir_path) + if not os.path.exists(full_path): + missing_optional.append(dir_path) + status = 'PASS' if len(missing_dirs) == 0 else 'FAIL' result = { 'status': status, 'missing_directories': missing_dirs, - 'required_directories': required_dirs + 'missing_optional_directories': missing_optional, + 'required_directories': required_dirs, + 'optional_directories': optional_dirs } return result @@ -376,17 +392,59 @@ def _validate_fusion_data(data_dir): 'errors': errors } + @staticmethod + def _validate_safety_data(data_dir): + """验证安全数据""" + safety_dir = os.path.join(data_dir, "safety_reports") + + if not os.path.exists(safety_dir): + return {'status': 'MISSING', 'count': 0, 'errors': []} + + json_files = [f for f in os.listdir(safety_dir) if f.endswith('.json')] + errors = [] + valid_files = 0 + + for json_file in json_files[:min(5, len(json_files))]: + json_path = os.path.join(safety_dir, json_file) + try: + with open(json_path, 'r') as f: + data = json.load(f) + + # 检查必要字段 + required_keys = ['timestamp', 'total_interactions'] + for key in required_keys: + if key not in data: + errors.append(f"安全报告缺失字段 {key}: {json_file}") + + valid_files += 1 + except Exception as e: + errors.append(f"安全报告无效: {json_file} - {str(e)}") + + if len(errors) == 0 and valid_files > 0: + status = 'PASS' + elif len(errors) < 3 and valid_files > 0: + status = 'WARNING' + else: + status = 'FAIL' + + return { + 'status': status, + 'count': len(json_files), + 'errors': errors + } + @staticmethod def _calculate_score(results): weights = { 'directory_structure': 0.10, - 'raw_images': 0.20, - 'stitched_images': 0.10, - 'annotations': 0.10, - 'metadata': 0.10, - 'lidar_data': 0.15, - 'cooperative_data': 0.15, - 'fusion_data': 0.10 + 'raw_images': 0.15, + 'stitched_images': 0.05, + 'annotations': 0.08, + 'metadata': 0.08, + 'lidar_data': 0.12, + 'cooperative_data': 0.12, + 'fusion_data': 0.10, + 'safety_data': 0.20 } score = 0 @@ -459,6 +517,10 @@ def _print_validation_report(results): if 'json_files' in result: print(f" JSON文件: {result['json_files']}") + if key == 'safety_data' and isinstance(result, dict): + if 'count' in result: + print(f" 安全报告: {result['count']} 个") + if 'errors' in result and result['errors']: print(f" 错误 ({len(result['errors'])}):") for error in result['errors'][:3]: diff --git a/src/enhance_pedestrian_safety/main.py b/src/enhance_pedestrian_safety/main.py index 6861758c1d..2626fe0a82 100644 --- a/src/enhance_pedestrian_safety/main.py +++ b/src/enhance_pedestrian_safety/main.py @@ -20,6 +20,7 @@ from data_analyzer import DataAnalyzer from lidar_processor import LidarProcessor, MultiSensorFusion from multi_vehicle_manager import MultiVehicleManager +from pedestrian_safety_monitor import PedestrianSafetyMonitor carla_egg_path, remaining_argv = setup_carla_path() carla = import_carla_module() @@ -143,6 +144,11 @@ def performance(msg): timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") print(f"[PERF][{timestamp}] {msg}") + @staticmethod + def safety(msg): + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + print(f"[SAFETY][{timestamp}] {msg}") + class WeatherSystem: WEATHER_PRESETS = { @@ -964,6 +970,7 @@ def __init__(self, config): self.sensor_managers = {} self.multi_vehicle_manager = None self.v2x_communication = None + self.safety_monitor = None self.start_time = None self.is_running = False @@ -994,7 +1001,8 @@ def setup_directories(self): "v2x_messages", "v2xformer_format", "kitti_format", - "metadata" + "metadata", + "safety_reports" ] for subdir in directories: @@ -1082,6 +1090,9 @@ def setup_scene(self): {'type': 'vehicle', 'capabilities': ['bsm', 'rsm']} ) + # 初始化行人安全监控器 + self.safety_monitor = PedestrianSafetyMonitor(self.world, self.output_dir) + time.sleep(3.0) return True @@ -1114,6 +1125,7 @@ def collect_data(self): last_performance_sample = time.time() last_detailed_log = time.time() last_memory_check = time.time() + last_safety_check = time.time() memory_warning_issued = False early_stop_triggered = False @@ -1167,6 +1179,13 @@ def collect_data(self): self._share_perception_data() last_perception_share = current_time + # 行人安全检查 + if current_time - last_safety_check >= 1.0 and self.safety_monitor: + safety_report = self.safety_monitor.check_pedestrian_safety() + if safety_report['risk_distribution']['high'] > 0: + Log.safety(f"行人安全警告: {safety_report['high_risk_cases']}个高风险情况") + last_safety_check = current_time + if current_time - last_performance_sample >= 10.0: memory_info = self.performance_monitor.sample_memory() cpu_info = self.performance_monitor.sample_cpu() @@ -1228,6 +1247,12 @@ def collect_data(self): Log.info(f"平均帧率: {fps:.2f} FPS") Log.info(f"最大内存使用: {performance_summary['max_memory_mb']:.1f} MB") Log.info(f"平均CPU使用: {performance_summary['average_cpu_percent']:.1f}%") + + # 生成行人安全报告 + if self.safety_monitor: + final_report = self.safety_monitor.generate_final_report() + Log.safety( + f"行人安全报告: {final_report['risk_distribution']['high']}高风险, {final_report['risk_distribution']['medium']}中风险") else: Log.warning("未收集到任何数据帧") @@ -1279,6 +1304,13 @@ def cleanup(self): except: pass + if self.safety_monitor: + try: + Log.info("保存行人安全数据...") + self.safety_monitor.save_data() + except: + pass + Log.info(f"清理 {len(self.sensor_managers)} 个传感器管理器...") for vehicle_id, sensor_manager in self.sensor_managers.items(): try: @@ -1328,6 +1360,7 @@ def cleanup(self): self.traffic_manager = None self.multi_vehicle_manager = None self.v2x_communication = None + self.safety_monitor = None self.scene_center = None gc.collect() @@ -1342,6 +1375,7 @@ def cleanup(self): self.traffic_manager = None self.multi_vehicle_manager = None self.v2x_communication = None + self.safety_monitor = None gc.collect() except: pass @@ -1527,6 +1561,9 @@ def _save_metadata(self): if self.multi_vehicle_manager: metadata['cooperative_summary'] = self.multi_vehicle_manager.generate_summary() + if self.safety_monitor: + metadata['safety_report'] = self.safety_monitor.generate_final_report() + meta_path = os.path.join(self.output_dir, "metadata", "collection_info.json") with open(meta_path, 'w', encoding='utf-8') as f: json.dump(metadata, f, indent=2, ensure_ascii=False) @@ -1565,6 +1602,11 @@ def _print_summary(self): [f for f in os.listdir(os.path.join(coop_dir, "shared_perception")) if f.endswith('.json')]) print(f"协同数据: {v2x_files} V2X消息, {perception_files} 共享感知文件") + safety_dir = os.path.join(self.output_dir, "safety_reports") + if os.path.exists(safety_dir): + safety_files = len([f for f in os.listdir(safety_dir) if f.endswith('.json')]) + print(f"安全报告: {safety_files} 个") + if self.output_format == 'v2xformer': v2x_dir = os.path.join(self.output_dir, "v2xformer_format") if os.path.exists(v2x_dir): @@ -1643,6 +1685,7 @@ def main(): parser.add_argument('--enable-cooperative', action='store_true', help='启用协同感知') parser.add_argument('--enable-enhancement', action='store_true', help='启用数据增强') parser.add_argument('--enable-annotations', action='store_true', help='启用自动标注') + parser.add_argument('--enable-safety-monitor', action='store_true', default=True, help='启用行人安全监控') parser.add_argument('--run-analysis', action='store_true', help='运行数据集分析') parser.add_argument('--skip-validation', action='store_true', help='跳过数据验证') @@ -1659,7 +1702,7 @@ def main(): config['output']['output_format'] = args.output_format print("\n" + "=" * 60) - print("CVIPS 性能优化数据收集系统") + print("CVIPS 性能优化数据收集系统 - 行人安全增强版") print("=" * 60) print(f"场景: {config['scenario']['name']}") @@ -1678,6 +1721,7 @@ def main(): print(f" V2X: {'启用' if config['v2x']['enabled'] else '禁用'}") print(f" 协同: {'启用' if config['output']['save_cooperative'] else '禁用'}") print(f" 增强: {'启用' if config['enhancement']['enabled'] else '禁用'}") + print(f" 安全监控: {'启用' if args.enable_safety_monitor else '禁用'}") print(f"性能:") print(f" 批处理大小: {config['performance']['batch_size']}") @@ -1717,4 +1761,4 @@ def main(): if __name__ == "__main__": - main() + main() \ No newline at end of file diff --git a/src/enhance_pedestrian_safety/multi_vehicle_manager.py b/src/enhance_pedestrian_safety/multi_vehicle_manager.py index 4ed8890d91..62fcd37cf8 100644 --- a/src/enhance_pedestrian_safety/multi_vehicle_manager.py +++ b/src/enhance_pedestrian_safety/multi_vehicle_manager.py @@ -297,6 +297,34 @@ def share_traffic_warning(self, vehicle_id: int, warning_type: str, print(f"共享交通警告失败: {e}") return None + def share_pedestrian_warning(self, vehicle_id: int, pedestrian_location: Tuple[float, float, float], + distance: float, speed: float): + """共享行人警告""" + try: + warning_data = { + 'warning_type': 'pedestrian', + 'pedestrian_location': pedestrian_location, + 'distance': distance, + 'vehicle_speed': speed, + 'timestamp': time.time(), + 'source_vehicle': vehicle_id, + 'severity': 'high' if distance < 10.0 else 'medium' if distance < 20.0 else 'low' + } + + message = self.create_v2x_message( + vehicle_id, + 'warning', + warning_data, + priority=4 # 行人警告优先级最高 + ) + + recipients = self.broadcast_message(message) + + return message, recipients + except Exception as e: + print(f"共享行人警告失败: {e}") + return None, [] + def _fuse_shared_perception(self, source_id: int, objects: List[Dict], recipients: List[int]): """融合共享的感知数据""" fused_objects = [] diff --git a/src/enhance_pedestrian_safety/pedestrian_safety_monitor.py b/src/enhance_pedestrian_safety/pedestrian_safety_monitor.py new file mode 100644 index 0000000000..5ec545f100 --- /dev/null +++ b/src/enhance_pedestrian_safety/pedestrian_safety_monitor.py @@ -0,0 +1,368 @@ +import json +import os +import time +import math +import numpy as np +from datetime import datetime +from typing import List, Dict, Tuple, Optional +import carla + + +class PedestrianSafetyMonitor: + """行人安全监控器""" + + def __init__(self, world, output_dir): + self.world = world + self.output_dir = output_dir + self.safety_dir = os.path.join(output_dir, "safety_reports") + os.makedirs(self.safety_dir, exist_ok=True) + + # 安全参数 + self.safety_thresholds = { + 'high_risk_distance': 5.0, # 高风险距离 (米) + 'medium_risk_distance': 10.0, # 中风险距离 (米) + 'low_risk_distance': 20.0, # 低风险距离 (米) + 'safe_speed_limit': 30.0, # 安全速度限制 (km/h) + 'reaction_time': 1.5, # 反应时间 (秒) + 'braking_deceleration': 6.0 # 制动减速度 (m/s²) + } + + # 统计数据 + self.stats = { + 'total_interactions': 0, + 'high_risk_cases': 0, + 'medium_risk_cases': 0, + 'low_risk_cases': 0, + 'safe_cases': 0, + 'near_misses': 0, + 'safety_warnings': 0, + 'average_distance': 0, + 'min_distance': float('inf'), + 'max_distance': 0, + 'interaction_times': [] + } + + # 详细记录 + self.interaction_records = [] + self.warning_logs = [] + + def check_pedestrian_safety(self) -> Dict: + """检查行人安全""" + vehicles = self._get_vehicles() + pedestrians = self._get_pedestrians() + + current_interactions = [] + + for vehicle in vehicles: + vehicle_location = vehicle.get_location() + vehicle_velocity = vehicle.get_velocity() + vehicle_speed = math.sqrt(vehicle_velocity.x ** 2 + vehicle_velocity.y ** 2 + vehicle_velocity.z ** 2) + + for pedestrian in pedestrians: + pedestrian_location = pedestrian.get_location() + + # 计算距离 + distance = vehicle_location.distance(pedestrian_location) + + # 计算相对速度 + pedestrian_velocity = pedestrian.get_velocity() + relative_speed = self._calculate_relative_speed(vehicle_velocity, pedestrian_velocity) + + # 计算碰撞时间 + time_to_collision = self._calculate_ttc(distance, relative_speed) + + # 评估风险 + risk_level = self._assess_risk(distance, vehicle_speed, time_to_collision) + + interaction = { + 'timestamp': time.time(), + 'vehicle_id': vehicle.id, + 'pedestrian_id': pedestrian.id, + 'distance': distance, + 'vehicle_speed': vehicle_speed * 3.6, # 转换为km/h + 'relative_speed': relative_speed * 3.6, + 'time_to_collision': time_to_collision if time_to_collision < 100 else None, + 'risk_level': risk_level, + 'vehicle_location': { + 'x': vehicle_location.x, + 'y': vehicle_location.y, + 'z': vehicle_location.z + }, + 'pedestrian_location': { + 'x': pedestrian_location.x, + 'y': pedestrian_location.y, + 'z': pedestrian_location.z + } + } + + current_interactions.append(interaction) + + # 更新统计 + self._update_stats(interaction) + + # 记录高风险情况 + if risk_level == 'high': + self._log_high_risk(interaction) + + # 保存当前检查结果 + if current_interactions: + self._save_interaction_report(current_interactions) + + return self._generate_safety_report() + + def _get_vehicles(self) -> List[carla.Actor]: + """获取所有车辆""" + return [actor for actor in self.world.get_actors() if 'vehicle' in actor.type_id] + + def _get_pedestrians(self) -> List[carla.Actor]: + """获取所有行人""" + return [actor for actor in self.world.get_actors() if 'walker' in actor.type_id] + + def _calculate_relative_speed(self, v1: carla.Vector3D, v2: carla.Vector3D) -> float: + """计算相对速度""" + return math.sqrt((v1.x - v2.x) ** 2 + (v1.y - v2.y) ** 2 + (v1.z - v2.z) ** 2) + + def _calculate_ttc(self, distance: float, relative_speed: float) -> float: + """计算碰撞时间 (Time to Collision)""" + if relative_speed > 0.1: # 避免除以零 + return distance / relative_speed + return float('inf') + + def _assess_risk(self, distance: float, speed: float, ttc: Optional[float]) -> str: + """评估风险等级""" + speed_kmh = speed * 3.6 + + # 基于距离的风险评估 + if distance < self.safety_thresholds['high_risk_distance']: + if ttc is not None and ttc < 2.0: + return 'high' + else: + return 'medium' + elif distance < self.safety_thresholds['medium_risk_distance']: + if speed_kmh > self.safety_thresholds['safe_speed_limit']: + return 'medium' + else: + return 'low' + elif distance < self.safety_thresholds['low_risk_distance']: + return 'low' + else: + return 'safe' + + def _update_stats(self, interaction: Dict): + """更新统计数据""" + self.stats['total_interactions'] += 1 + distance = interaction['distance'] + + # 更新距离统计 + self.stats['average_distance'] = ( + (self.stats['average_distance'] * (self.stats['total_interactions'] - 1) + distance) / + self.stats['total_interactions'] + ) + self.stats['min_distance'] = min(self.stats['min_distance'], distance) + self.stats['max_distance'] = max(self.stats['max_distance'], distance) + + # 更新风险统计 + risk_level = interaction['risk_level'] + if risk_level == 'high': + self.stats['high_risk_cases'] += 1 + self.stats['near_misses'] += 1 + self.stats['safety_warnings'] += 1 + elif risk_level == 'medium': + self.stats['medium_risk_cases'] += 1 + self.stats['safety_warnings'] += 1 + elif risk_level == 'low': + self.stats['low_risk_cases'] += 1 + else: + self.stats['safe_cases'] += 1 + + # 记录交互时间 + self.stats['interaction_times'].append(interaction['timestamp']) + + # 添加到详细记录 + self.interaction_records.append(interaction) + + # 限制记录数量 + if len(self.interaction_records) > 1000: + self.interaction_records = self.interaction_records[-1000:] + + def _log_high_risk(self, interaction: Dict): + """记录高风险情况""" + warning = { + 'timestamp': datetime.now().isoformat(), + 'interaction': interaction, + 'safety_measures': self._suggest_safety_measures(interaction) + } + self.warning_logs.append(warning) + + # 保存高风险警告 + if len(self.warning_logs) % 10 == 0: + self._save_warning_logs() + + def _suggest_safety_measures(self, interaction: Dict) -> List[str]: + """建议安全措施""" + measures = [] + + if interaction['risk_level'] == 'high': + measures.extend([ + "立即制动", + "鸣喇叭警告", + "准备紧急避让", + "向其他车辆发送警告" + ]) + elif interaction['risk_level'] == 'medium': + measures.extend([ + "减速行驶", + "保持警惕", + "准备制动", + "观察行人动向" + ]) + elif interaction['risk_level'] == 'low': + measures.extend([ + "保持安全距离", + "观察周围环境", + "准备应对突发情况" + ]) + + return measures + + def _save_interaction_report(self, interactions: List[Dict]): + """保存交互报告""" + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + report_file = os.path.join(self.safety_dir, f"interactions_{timestamp}.json") + + report = { + 'timestamp': datetime.now().isoformat(), + 'total_interactions': len(interactions), + 'interactions': interactions, + 'summary': { + 'high_risk': len([i for i in interactions if i['risk_level'] == 'high']), + 'medium_risk': len([i for i in interactions if i['risk_level'] == 'medium']), + 'low_risk': len([i for i in interactions if i['risk_level'] == 'low']), + 'safe': len([i for i in interactions if i['risk_level'] == 'safe']) + } + } + + with open(report_file, 'w', encoding='utf-8') as f: + json.dump(report, f, indent=2, ensure_ascii=False) + + def _save_warning_logs(self): + """保存警告日志""" + if not self.warning_logs: + return + + warning_file = os.path.join(self.safety_dir, "warning_logs.json") + with open(warning_file, 'w', encoding='utf-8') as f: + json.dump(self.warning_logs, f, indent=2, ensure_ascii=False) + + def _generate_safety_report(self) -> Dict: + """生成安全报告""" + report = { + 'timestamp': datetime.now().isoformat(), + 'statistics': self.stats.copy(), + 'safety_thresholds': self.safety_thresholds, + 'risk_distribution': { + 'high': self.stats['high_risk_cases'], + 'medium': self.stats['medium_risk_cases'], + 'low': self.stats['low_risk_cases'], + 'safe': self.stats['safe_cases'] + }, + 'safety_score': self._calculate_safety_score(), + 'recommendations': self._generate_recommendations() + } + + return report + + def _calculate_safety_score(self) -> float: + """计算安全评分""" + if self.stats['total_interactions'] == 0: + return 100.0 + + high_risk_ratio = self.stats['high_risk_cases'] / self.stats['total_interactions'] + medium_risk_ratio = self.stats['medium_risk_cases'] / self.stats['total_interactions'] + + # 评分公式:基础分减去风险比例 + score = 100 - (high_risk_ratio * 60 + medium_risk_ratio * 30) * 100 + + # 考虑平均距离 + if self.stats['average_distance'] > 15.0: + score += 10 + elif self.stats['average_distance'] < 5.0: + score -= 20 + + return max(0, min(100, score)) + + def _generate_recommendations(self) -> List[str]: + """生成改进建议""" + recommendations = [] + + if self.stats['high_risk_cases'] > 0: + recommendations.extend([ + "增加行人安全距离阈值", + "加强车辆行人检测系统", + "实施更严格的限速措施", + "增加行人警告系统" + ]) + + if self.stats['average_distance'] < 10.0: + recommendations.append("增加车辆与行人的平均距离") + + if self.stats['near_misses'] > 5: + recommendations.append("实施紧急制动系统") + + return recommendations + + def generate_final_report(self) -> Dict: + """生成最终报告""" + final_report = self._generate_safety_report() + + # 添加历史数据 + final_report['historical_data'] = { + 'total_interaction_records': len(self.interaction_records), + 'total_warning_logs': len(self.warning_logs), + 'analysis_period': self._get_analysis_period() + } + + # 保存最终报告 + final_file = os.path.join(self.safety_dir, "final_safety_report.json") + with open(final_file, 'w', encoding='utf-8') as f: + json.dump(final_report, f, indent=2, ensure_ascii=False) + + return final_report + + def _get_analysis_period(self) -> Dict: + """获取分析时间段""" + if not self.stats['interaction_times']: + return {'start': None, 'end': None, 'duration': 0} + + start_time = min(self.stats['interaction_times']) + end_time = max(self.stats['interaction_times']) + duration = end_time - start_time + + return { + 'start': datetime.fromtimestamp(start_time).isoformat(), + 'end': datetime.fromtimestamp(end_time).isoformat(), + 'duration_seconds': duration, + 'duration_minutes': duration / 60, + 'duration_hours': duration / 3600 + } + + def save_data(self): + """保存所有数据""" + # 保存统计数据 + stats_file = os.path.join(self.safety_dir, "safety_statistics.json") + with open(stats_file, 'w', encoding='utf-8') as f: + json.dump(self.stats, f, indent=2, ensure_ascii=False) + + # 保存详细记录 + if self.interaction_records: + records_file = os.path.join(self.safety_dir, "interaction_records.json") + with open(records_file, 'w', encoding='utf-8') as f: + json.dump(self.interaction_records, f, indent=2, ensure_ascii=False) + + # 保存警告日志 + self._save_warning_logs() + + # 生成并保存最终报告 + self.generate_final_report() + + print(f"行人安全数据已保存到: {self.safety_dir}") \ No newline at end of file diff --git a/src/enhance_pedestrian_safety/scene_manager.py b/src/enhance_pedestrian_safety/scene_manager.py index dbe424d7d4..f7e081f9ed 100644 --- a/src/enhance_pedestrian_safety/scene_manager.py +++ b/src/enhance_pedestrian_safety/scene_manager.py @@ -45,6 +45,23 @@ class SceneManager: 'vehicle_density': 0.6, 'pedestrian_density': 0.5, 'weather_variations': ['rainy'] + }, + 'school_zone': { + 'description': '学校区域场景 - 行人安全重点', + 'pedestrian_behavior': ['crossing', 'walking', 'playing', 'running'], + 'vehicle_density': 0.4, + 'pedestrian_density': 0.9, + 'weather_variations': ['clear', 'cloudy'], + 'speed_limit': 20.0, + 'safety_zone_radius': 30.0 + }, + 'pedestrian_crossing': { + 'description': '人行横道场景', + 'pedestrian_behavior': ['crossing', 'waiting', 'walking'], + 'vehicle_density': 0.5, + 'pedestrian_density': 0.8, + 'weather_variations': ['clear', 'rainy'], + 'crossing_intensity': 'high' } } @@ -74,6 +91,10 @@ def setup_scene(world, config, scene_type='intersection_4way'): # 设置行人行为 config['traffic']['pedestrian_behaviors'] = scene_config['pedestrian_behavior'] + # 如果是学校区域,设置车速限制 + if scene_type == 'school_zone' and 'speed_limit' in scene_config: + config['traffic']['speed_limit'] = scene_config['speed_limit'] + # 应用场景特定设置 SceneManager._apply_scene_specifics(world, scene_type) @@ -102,9 +123,73 @@ def _apply_scene_specifics(world, scene_type): except: pass + elif scene_type == 'school_zone': + # 学校区域:设置车辆限速和行人保护区域 + for actor in world.get_actors(): + if 'vehicle' in actor.type_id: + try: + # 限制车速 + actor.enable_constant_velocity(carla.Vector3D(15, 0, 0)) + # 开启行人检测警告 + actor.set_light_state(carla.VehicleLightState.LowBeam) + except: + pass + + elif scene_type == 'pedestrian_crossing': + # 人行横道:增加可见性 + for actor in world.get_actors(): + if 'vehicle' in actor.type_id: + try: + actor.set_light_state(carla.VehicleLightState.LowBeam) + except: + pass + except Exception as e: print(f"场景特定设置失败: {e}") + @staticmethod + def spawn_pedestrian_safety_features(world, location, feature_type='crosswalk'): + """生成行人安全设施""" + blueprint_lib = world.get_blueprint_library() + features = [] + + try: + if feature_type == 'crosswalk': + # 生成人行横道标记 + for i in range(-4, 5): + line_location = carla.Location( + x=location.x + i * 0.8, + y=location.y, + z=location.z + 0.02 + ) + rotation = carla.Rotation(0, 0, 0) + + line_bp = blueprint_lib.find('static.prop.linepainting') + if line_bp: + line = world.spawn_actor(line_bp, carla.Transform(line_location, rotation)) + features.append(line) + + elif feature_type == 'speed_bump': + # 生成减速带 + bump_location = carla.Location(location.x, location.y, location.z + 0.1) + bump_bp = blueprint_lib.find('static.prop.speedbump') + if bump_bp: + bump = world.spawn_actor(bump_bp, carla.Transform(bump_location, carla.Rotation(0, 0, 0))) + features.append(bump) + + elif feature_type == 'warning_sign': + # 生成警告标志 + sign_location = carla.Location(location.x, location.y, location.z + 2.0) + sign_bp = blueprint_lib.find('static.prop.trafficsign') + if sign_bp: + sign = world.spawn_actor(sign_bp, carla.Transform(sign_location, carla.Rotation(0, 90, 0))) + features.append(sign) + + except Exception as e: + print(f"生成行人安全设施失败: {e}") + + return features + @staticmethod def spawn_traffic_cones(world, center_location, num_cones=10): """生成交通锥桶""" diff --git a/src/enhance_pedestrian_safety/sensor_enhancer.py b/src/enhance_pedestrian_safety/sensor_enhancer.py index b446c71bbe..3dba1d767b 100644 --- a/src/enhance_pedestrian_safety/sensor_enhancer.py +++ b/src/enhance_pedestrian_safety/sensor_enhancer.py @@ -778,6 +778,60 @@ def generate_enhancement_report(self, output_dir: str) -> Dict: return report + def _add_pedestrian_detection_markers(self, image: np.ndarray, pedestrians: List[Dict]) -> np.ndarray: + """添加行人检测标记""" + if not pedestrians: + return image + + # 创建副本 + marked_image = image.copy() + + for pedestrian in pedestrians: + # 模拟行人检测框 + x = random.randint(100, image.shape[1] - 100) + y = random.randint(100, image.shape[0] - 100) + width = random.randint(30, 80) + height = random.randint(80, 180) + + # 绘制检测框 + color = (0, 255, 0) # 绿色表示安全 + thickness = 2 + + cv2.rectangle(marked_image, (x, y), (x + width, y + height), color, thickness) + + # 添加标签 + label = f"Pedestrian {pedestrian.get('distance', 0):.1f}m" + cv2.putText(marked_image, label, (x, y - 10), + cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, thickness) + + return marked_image + + def _simulate_safety_warnings(self, image: np.ndarray, warnings: List[Dict]) -> np.ndarray: + """模拟安全警告""" + if not warnings: + return image + + warning_image = image.copy() + + for warning in warnings[:2]: # 最多显示2个警告 + # 添加警告文本 + text = f"WARNING: {warning.get('type', 'Pedestrian')} {warning.get('distance', 0):.1f}m" + color = (0, 0, 255) # 红色表示警告 + thickness = 2 + + position = (50, 50 + warnings.index(warning) * 40) + cv2.putText(warning_image, text, position, + cv2.FONT_HERSHEY_SIMPLEX, 0.7, color, thickness) + + # 添加警告图标 + icon_size = 30 + icon_position = (position[0] - icon_size - 10, position[1] - icon_size // 2) + cv2.rectangle(warning_image, icon_position, + (icon_position[0] + icon_size, icon_position[1] + icon_size), + color, -1) # 填充红色矩形 + + return warning_image + # 保持向后兼容的类 class SensorCalibrator: From aa12359073e2a6e66aebf0f3e887c419cb096c96 Mon Sep 17 00:00:00 2001 From: Z-w-7799 <1011069369@qq.com> Date: Thu, 25 Dec 2025 20:37:07 +0800 Subject: [PATCH 20/20] =?UTF-8?q?=E5=9C=BA=E6=99=AF=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=A2=9E=E5=BC=BA,=E4=BC=A0=E6=84=9F=E5=99=A8=E5=A2=9E?= =?UTF-8?q?=E5=BC=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../annotation_generator.py | 59 +++- src/enhance_pedestrian_safety/carla_utils.py | 1 - .../config_manager.py | 87 +++-- src/enhance_pedestrian_safety/main.py | 72 +++- .../multi_vehicle_manager.py | 92 ++--- .../pedestrian_safety_monitor.py | 287 +++++++++++++--- .../scene_manager.py | 80 ++++- .../sensor_enhancer.py | 323 +++++++----------- .../v2x_communication.py | 69 ++-- 9 files changed, 682 insertions(+), 388 deletions(-) diff --git a/src/enhance_pedestrian_safety/annotation_generator.py b/src/enhance_pedestrian_safety/annotation_generator.py index d443854d96..734fa91616 100644 --- a/src/enhance_pedestrian_safety/annotation_generator.py +++ b/src/enhance_pedestrian_safety/annotation_generator.py @@ -21,7 +21,12 @@ def detect_objects(self, world, frame_num, timestamp): 'frame_id': frame_num, 'timestamp': timestamp, 'objects': [], - 'camera_info': {} + 'camera_info': {}, + 'safety_info': { + 'pedestrian_count': 0, + 'vehicle_count': 0, + 'high_risk_interactions': 0 + } } try: @@ -36,6 +41,16 @@ def detect_objects(self, world, frame_num, timestamp): annotations['objects'].append(obj_info) self.object_counter += 1 + # 更新安全统计 + if 'walker' in obj_type: + annotations['safety_info']['pedestrian_count'] += 1 + elif 'vehicle' in obj_type: + annotations['safety_info']['vehicle_count'] += 1 + + # 检测高风险交互 + annotations['safety_info']['high_risk_interactions'] = self._detect_high_risk_interactions( + annotations['objects']) + self._save_annotations(frame_num, annotations) return annotations @@ -110,6 +125,30 @@ def _get_object_class(self, type_id): else: return 'unknown' + def _detect_high_risk_interactions(self, objects): + """检测高风险交互""" + high_risk_count = 0 + pedestrians = [obj for obj in objects if obj['class'] == 'pedestrian'] + vehicles = [obj for obj in objects if obj['class'] in ['car', 'vehicle']] + + for pedestrian in pedestrians: + for vehicle in vehicles: + # 计算距离 + p_loc = pedestrian['location'] + v_loc = vehicle['location'] + + distance = np.sqrt( + (p_loc['x'] - v_loc['x']) ** 2 + + (p_loc['y'] - v_loc['y']) ** 2 + + (p_loc['z'] - v_loc['z']) ** 2 + ) + + # 如果距离小于5米,认为是高风险交互 + if distance < 5.0: + high_risk_count += 1 + + return high_risk_count + def _save_annotations(self, frame_num, annotations): """保存标注到文件""" filename = f"frame_{frame_num:06d}.json" @@ -130,7 +169,13 @@ def _update_master_annotation(self): 'total_frames': len(self.frame_annotations), 'total_objects': self.object_counter, 'frames': list(self.frame_annotations.keys()), - 'created': datetime.now().isoformat() + 'created': datetime.now().isoformat(), + 'safety_summary': { + 'total_pedestrians': sum(f['safety_info']['pedestrian_count'] for f in self.frame_annotations.values()), + 'total_vehicles': sum(f['safety_info']['vehicle_count'] for f in self.frame_annotations.values()), + 'total_high_risk': sum( + f['safety_info']['high_risk_interactions'] for f in self.frame_annotations.values()) + } } with open(master_file, 'w', encoding='utf-8') as f: @@ -141,6 +186,7 @@ def generate_summary(self): vehicle_count = 0 pedestrian_count = 0 other_count = 0 + high_risk_count = 0 for frame_data in self.frame_annotations.values(): for obj in frame_data.get('objects', []): @@ -151,14 +197,21 @@ def generate_summary(self): else: other_count += 1 + high_risk_count += frame_data['safety_info']['high_risk_interactions'] + summary = { 'total_frames': len(self.frame_annotations), 'total_objects': self.object_counter, 'vehicles': vehicle_count, 'pedestrians': pedestrian_count, 'other_objects': other_count, + 'high_risk_interactions': high_risk_count, 'average_objects_per_frame': self.object_counter / len( - self.frame_annotations) if self.frame_annotations else 0 + self.frame_annotations) if self.frame_annotations else 0, + 'safety_metrics': { + 'pedestrian_to_vehicle_ratio': pedestrian_count / max(1, vehicle_count), + 'high_risk_percentage': high_risk_count / max(1, self.object_counter) * 100 + } } summary_file = os.path.join(self.annotations_dir, "summary.json") diff --git a/src/enhance_pedestrian_safety/carla_utils.py b/src/enhance_pedestrian_safety/carla_utils.py index 0fd30ef025..349f4e7c78 100644 --- a/src/enhance_pedestrian_safety/carla_utils.py +++ b/src/enhance_pedestrian_safety/carla_utils.py @@ -1,7 +1,6 @@ """ CARLA 工具模块 - 用于自动查找和配置 CARLA 路径 """ -# carla_utils.py import sys import os import glob diff --git a/src/enhance_pedestrian_safety/config_manager.py b/src/enhance_pedestrian_safety/config_manager.py index aa07724783..7276b09a15 100644 --- a/src/enhance_pedestrian_safety/config_manager.py +++ b/src/enhance_pedestrian_safety/config_manager.py @@ -6,6 +6,7 @@ try: import yaml + YAML_AVAILABLE = True except ImportError: YAML_AVAILABLE = False @@ -65,6 +66,13 @@ def suggest_optimizations(config: Dict[str, Any]) -> List[str]: if len(enabled_outputs) > 5: suggestions.append(f"启用的输出类型过多({len(enabled_outputs)}),可能影响性能,建议只启用必要的输出") + # 行人安全相关建议 + if config.get('traffic', {}).get('pedestrians', 0) < 5: + suggestions.append("行人数量较少,建议增加行人数量以更好地测试行人安全") + + if not config.get('v2x', {}).get('enabled', False): + suggestions.append("V2X通信未启用,建议启用以支持行人安全预警") + return suggestions @@ -193,7 +201,8 @@ def optimize_for_safety(config: Dict[str, Any]) -> Dict[str, Any]: 'walker.pedestrian.0002', 'walker.pedestrian.0003', 'walker.pedestrian.0004' - ] + ], + 'speed_limit': 30.0 # 添加车速限制 }) # 优化传感器配置以更好地检测行人 @@ -225,7 +234,9 @@ def optimize_for_safety(config: Dict[str, Any]) -> Dict[str, Any]: v2x.update({ 'enabled': True, 'communication_range': 300.0, - 'update_interval': 1.0 # 更频繁地更新 + 'update_interval': 1.0, # 更频繁地更新 + 'enable_safety_warnings': True, + 'pedestrian_warning_threshold': 10.0 # 行人警告距离阈值 }) coop = optimized.setdefault('cooperative', {}) @@ -233,6 +244,7 @@ def optimize_for_safety(config: Dict[str, Any]) -> Dict[str, Any]: 'num_coop_vehicles': 2, 'enable_shared_perception': True, 'enable_traffic_warnings': True, + 'enable_pedestrian_warnings': True, # 启用行人警告 'enable_maneuver_coordination': False, 'data_fusion_interval': 0.5, # 更频繁地融合 'max_shared_objects': 100, @@ -247,7 +259,8 @@ def optimize_for_safety(config: Dict[str, Any]) -> Dict[str, Any]: 'compression_level': 3, 'enable_memory_cache': True, 'max_cache_size': 40, - 'frame_rate_limit': 8.0 + 'frame_rate_limit': 8.0, + 'safety_monitoring_interval': 1.0 # 安全监控间隔 }) # 输出配置 @@ -260,16 +273,33 @@ def optimize_for_safety(config: Dict[str, Any]) -> Dict[str, Any]: 'save_fusion': True, 'save_cooperative': True, 'save_enhanced': True, + 'save_safety_reports': True, # 保存安全报告 'validate_data': True, 'run_analysis': True, - 'run_quality_check': True + 'run_quality_check': True, + 'generate_safety_summary': True # 生成安全摘要 + }) + + # 增强配置 + enhanced = optimized.setdefault('enhancement', {}) + enhanced.update({ + 'enabled': True, + 'enable_random': True, + 'quality_check': True, + 'save_original': True, + 'save_enhanced': True, + 'calibration_generation': True, + 'enhanced_dir_name': 'enhanced', + 'methods': ['normalize', 'contrast', 'brightness', 'pedestrian_highlight', 'safety_warning'], + 'weather_effects': True, + 'augmentation_level': 'medium', + 'pedestrian_safety_mode': True # 启用行人安全模式 }) return optimized class ConfigManager: - PRESET_CONFIGS = { 'balanced': { 'description': '平衡配置 - 兼顾性能和质量', @@ -338,8 +368,8 @@ def load_config(config_file: Optional[str] = None, preset: Optional[str] = None) def _get_default_config() -> Dict[str, Any]: return { 'scenario': { - 'name': 'multi_sensor_scene', - 'description': '多传感器协同数据采集场景', + 'name': 'pedestrian_safety', + 'description': '行人安全增强数据采集场景', 'town': 'Town10HD', 'weather': 'clear', 'time_of_day': 'noon', @@ -351,7 +381,7 @@ def _get_default_config() -> Dict[str, Any]: 'traffic': { 'ego_vehicles': 1, 'background_vehicles': 8, - 'pedestrians': 6, + 'pedestrians': 12, # 增加默认行人数量 'traffic_lights': True, 'batch_spawn': True, 'max_spawn_attempts': 5, @@ -363,8 +393,11 @@ def _get_default_config() -> Dict[str, Any]: ], 'pedestrian_types': [ 'walker.pedestrian.0001', - 'walker.pedestrian.0002' - ] + 'walker.pedestrian.0002', + 'walker.pedestrian.0003', + 'walker.pedestrian.0004' + ], + 'speed_limit': 30.0 }, 'sensors': { 'vehicle_cameras': 4, @@ -406,16 +439,19 @@ def _get_default_config() -> Dict[str, Any]: 'latency_mean': 0.05, 'latency_std': 0.01, 'packet_loss_rate': 0.01, - 'message_types': ['bsm', 'spat', 'map', 'rsm', 'perception', 'warning'], + 'message_types': ['bsm', 'spat', 'map', 'rsm', 'perception', 'warning', 'pedestrian_warning'], 'update_interval': 2.0, 'security_enabled': False, 'encryption_level': 'none', - 'qos_policy': 'best_effort' + 'qos_policy': 'best_effort', + 'enable_safety_warnings': True, + 'pedestrian_warning_threshold': 10.0 }, 'cooperative': { 'num_coop_vehicles': 2, 'enable_shared_perception': True, 'enable_traffic_warnings': True, + 'enable_pedestrian_warnings': True, 'enable_maneuver_coordination': False, 'data_fusion_interval': 1.0, 'max_shared_objects': 50, @@ -431,9 +467,10 @@ def _get_default_config() -> Dict[str, Any]: 'save_enhanced': True, 'calibration_generation': True, 'enhanced_dir_name': 'enhanced', - 'methods': ['normalize', 'contrast', 'brightness'], + 'methods': ['normalize', 'contrast', 'brightness', 'pedestrian_highlight', 'safety_warning'], 'weather_effects': True, - 'augmentation_level': 'medium' + 'augmentation_level': 'medium', + 'pedestrian_safety_mode': True }, 'performance': { 'batch_size': 5, @@ -468,6 +505,7 @@ def _get_default_config() -> Dict[str, Any]: }, 'sensor_cleanup_timeout': 0.5, 'frame_rate_limit': 5.0, + 'safety_monitoring_interval': 1.0, 'memory_management': { 'gc_interval': 50, 'max_memory_mb': 500, @@ -479,16 +517,18 @@ def _get_default_config() -> Dict[str, Any]: 'output_format': 'standard', 'save_raw': True, 'save_stitched': True, - 'save_annotations': False, + 'save_annotations': True, 'save_lidar': True, 'save_fusion': True, 'save_cooperative': True, 'save_v2x_messages': True, 'save_enhanced': True, + 'save_safety_reports': True, 'validate_data': True, - 'run_analysis': False, + 'run_analysis': True, 'run_quality_check': True, 'generate_summary': True, + 'generate_safety_summary': True, 'compression_enabled': True, 'file_naming': 'sequential', 'backup_original': False @@ -501,7 +541,9 @@ def _get_default_config() -> Dict[str, Any]: 'performance_log_interval': 10.0, 'enable_progress_bar': True, 'enable_real_time_stats': True, - 'stats_update_interval': 5.0 + 'stats_update_interval': 5.0, + 'enable_safety_monitor': True, + 'safety_log_interval': 2.0 }, 'debug': { 'enable_debug_mode': False, @@ -514,7 +556,7 @@ def _get_default_config() -> Dict[str, Any]: 'metadata': { 'version': '1.0.0', 'author': 'CVIPS System', - 'description': '多传感器协同数据采集配置', + 'description': '行人安全增强数据采集配置', 'created': '', 'modified': '' } @@ -686,6 +728,7 @@ def print_config_summary(config: Dict[str, Any]): print(f" 主车: {traffic['ego_vehicles']}") print(f" 背景车辆: {traffic['background_vehicles']}") print(f" 行人: {traffic['pedestrians']}") + print(f" 车速限制: {traffic.get('speed_limit', '无')} km/h") print(f" 交通灯: {'启用' if traffic['traffic_lights'] else '禁用'}") sensors = config['sensors'] @@ -702,12 +745,13 @@ def print_config_summary(config: Dict[str, Any]): if v2x['enabled']: print(f" 通信范围: {v2x['communication_range']}米") print(f" 更新间隔: {v2x['update_interval']}秒") + print(f" 安全警告: {'启用' if v2x.get('enable_safety_warnings', False) else '禁用'}") coop = config['cooperative'] print(f"\n🤝 协同感知:") print(f" 协同车辆: {coop['num_coop_vehicles']}") print(f" 共享感知: {'启用' if coop['enable_shared_perception'] else '禁用'}") - print(f" 交通警告: {'启用' if coop['enable_traffic_warnings'] else '禁用'}") + print(f" 行人警告: {'启用' if coop.get('enable_pedestrian_warnings', False) else '禁用'}") perf = config['performance'] print(f"\n⚡ 性能:") @@ -715,6 +759,7 @@ def print_config_summary(config: Dict[str, Any]): print(f" 压缩: {'启用' if perf['enable_compression'] else '禁用'}") print(f" 下采样: {'启用' if perf['enable_downsampling'] else '禁用'}") print(f" 帧率限制: {perf['frame_rate_limit']} FPS") + print(f" 安全监控间隔: {perf.get('safety_monitoring_interval', 1.0)}秒") output = config['output'] print(f"\n💾 输出:") @@ -724,6 +769,10 @@ def print_config_summary(config: Dict[str, Any]): if isinstance(v, bool) and v and k.startswith('save_')] print(f" 启用输出: {', '.join(enabled_outputs)}") + print(f"\n🛡️ 行人安全:") + print(f" 安全监控: {'启用' if config['monitoring'].get('enable_safety_monitor', False) else '禁用'}") + print(f" 增强安全模式: {'启用' if config['enhancement'].get('pedestrian_safety_mode', False) else '禁用'}") + print("=" * 60) @staticmethod diff --git a/src/enhance_pedestrian_safety/main.py b/src/enhance_pedestrian_safety/main.py index 2626fe0a82..bfd920d8f4 100644 --- a/src/enhance_pedestrian_safety/main.py +++ b/src/enhance_pedestrian_safety/main.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +# !/usr/bin/env python3 import sys import os import time @@ -17,7 +17,6 @@ from config_manager import ConfigManager from annotation_generator import AnnotationGenerator from data_validator import DataValidator -from data_analyzer import DataAnalyzer from lidar_processor import LidarProcessor, MultiSensorFusion from multi_vehicle_manager import MultiVehicleManager from pedestrian_safety_monitor import PedestrianSafetyMonitor @@ -488,7 +487,7 @@ def _spawn_vehicles(self): def _spawn_pedestrians(self, center_location): blueprint_lib = self.world.get_blueprint_library() - num_peds = min(self.config['traffic']['pedestrians'], 8) + num_peds = min(self.config['traffic']['pedestrians'], 12) # 增加行人数量 spawned = 0 for _ in range(num_peds): @@ -499,8 +498,13 @@ def _spawn_pedestrians(self, center_location): ped_bp = random.choice(ped_bps) - angle = random.uniform(0, 2 * math.pi) - distance = random.uniform(5.0, 12.0) + # 在学校区域或人行横道场景中,行人更集中 + if self.config.get('scenario', {}).get('name', '').lower() in ['school_zone', 'pedestrian_crossing']: + angle = random.uniform(0, 2 * math.pi) + distance = random.uniform(3.0, 8.0) # 更靠近中心 + else: + angle = random.uniform(0, 2 * math.pi) + distance = random.uniform(5.0, 15.0) location = carla.Location( x=center_location.x + distance * math.cos(angle), @@ -1090,7 +1094,6 @@ def setup_scene(self): {'type': 'vehicle', 'capabilities': ['bsm', 'rsm']} ) - # 初始化行人安全监控器 self.safety_monitor = PedestrianSafetyMonitor(self.world, self.output_dir) time.sleep(3.0) @@ -1183,7 +1186,9 @@ def collect_data(self): if current_time - last_safety_check >= 1.0 and self.safety_monitor: safety_report = self.safety_monitor.check_pedestrian_safety() if safety_report['risk_distribution']['high'] > 0: - Log.safety(f"行人安全警告: {safety_report['high_risk_cases']}个高风险情况") + Log.safety(f"行人安全警告: {safety_report['risk_distribution']['high']}个高风险情况") + # 广播行人警告 + self._broadcast_pedestrian_warnings(safety_report) last_safety_check = current_time if current_time - last_performance_sample >= 10.0: @@ -1253,6 +1258,7 @@ def collect_data(self): final_report = self.safety_monitor.generate_final_report() Log.safety( f"行人安全报告: {final_report['risk_distribution']['high']}高风险, {final_report['risk_distribution']['medium']}中风险") + Log.safety(f"行人安全评分: {final_report['safety_score']:.1f}/100") else: Log.warning("未收集到任何数据帧") @@ -1262,6 +1268,44 @@ def collect_data(self): if self.output_format != 'standard': self._convert_to_target_format() + def _broadcast_pedestrian_warnings(self, safety_report): + """广播行人警告""" + if not self.multi_vehicle_manager: + return + + # 检查高风险交互 + if safety_report.get('risk_distribution', {}).get('high', 0) > 0: + for vehicle in self.ego_vehicles + self.multi_vehicle_manager.cooperative_vehicles: + if not hasattr(vehicle, 'is_alive') or not vehicle.is_alive: + continue + + try: + location = vehicle.get_location() + velocity = vehicle.get_velocity() + speed = math.sqrt(velocity.x ** 2 + velocity.y ** 2 + velocity.z ** 2) + + # 模拟行人位置(实际应用中应从感知系统获取) + pedestrian_location = ( + location.x + random.uniform(-5, 5), + location.y + random.uniform(-5, 5), + location.z + ) + + distance = math.sqrt( + (location.x - pedestrian_location[0]) ** 2 + + (location.y - pedestrian_location[1]) ** 2 + ) + + if distance < 20.0: # 只广播近距离行人 + self.multi_vehicle_manager.share_pedestrian_warning( + vehicle.id, + pedestrian_location, + distance, + speed + ) + except: + pass + def _force_memory_cleanup(self): Log.info("执行强制内存清理...") @@ -1646,17 +1690,12 @@ def run_validation(self): Log.info("运行数据验证...") DataValidator.validate_dataset(self.output_dir) - def run_analysis(self): - if self.config['output'].get('run_analysis', False) and self.collected_frames > 0: - Log.info("运行数据分析...") - DataAnalyzer.analyze_dataset(self.output_dir) - def main(): - parser = argparse.ArgumentParser(description='CVIPS 性能优化数据收集系统') + parser = argparse.ArgumentParser(description='CVIPS 行人安全增强数据收集系统') parser.add_argument('--config', type=str, help='配置文件路径') - parser.add_argument('--scenario', type=str, default='performance_optimized', help='场景名称') + parser.add_argument('--scenario', type=str, default='pedestrian_safety', help='场景名称') parser.add_argument('--town', type=str, default='Town10HD', choices=['Town03', 'Town04', 'Town05', 'Town10HD'], help='地图') parser.add_argument('--weather', type=str, default='clear', @@ -1665,7 +1704,7 @@ def main(): choices=['noon', 'sunset', 'night'], help='时间') parser.add_argument('--num-vehicles', type=int, default=8, help='背景车辆数') - parser.add_argument('--num-pedestrians', type=int, default=6, help='行人数') + parser.add_argument('--num-pedestrians', type=int, default=12, help='行人数') parser.add_argument('--num-coop-vehicles', type=int, default=2, help='协同车辆数') parser.add_argument('--duration', type=int, default=60, help='收集时长(秒)') @@ -1702,7 +1741,7 @@ def main(): config['output']['output_format'] = args.output_format print("\n" + "=" * 60) - print("CVIPS 性能优化数据收集系统 - 行人安全增强版") + print("CVIPS 行人安全增强数据收集系统") print("=" * 60) print(f"场景: {config['scenario']['name']}") @@ -1747,7 +1786,6 @@ def main(): collector.collect_data() - collector.run_analysis() collector.run_validation() except KeyboardInterrupt: diff --git a/src/enhance_pedestrian_safety/multi_vehicle_manager.py b/src/enhance_pedestrian_safety/multi_vehicle_manager.py index 62fcd37cf8..bb9edaafb5 100644 --- a/src/enhance_pedestrian_safety/multi_vehicle_manager.py +++ b/src/enhance_pedestrian_safety/multi_vehicle_manager.py @@ -40,7 +40,7 @@ def __init__(self, world, config, output_dir): self.config = config self.output_dir = output_dir - # 创建协同数据目录 - 确保所有子目录都创建 + # 创建协同数据目录 self.coop_dir = os.path.join(output_dir, "cooperative") os.makedirs(self.coop_dir, exist_ok=True) @@ -59,8 +59,8 @@ def __init__(self, world, config, output_dir): # V2X通信 self.v2x_messages = [] - self.communication_range = config.get('v2x', {}).get('communication_range', 300.0) # 通信范围 - self.message_buffer = defaultdict(list) # 车辆ID -> 接收的消息列表 + self.communication_range = config.get('v2x', {}).get('communication_range', 300.0) + self.message_buffer = defaultdict(list) # 协同感知 self.shared_objects = [] # 共享的感知对象 @@ -71,7 +71,9 @@ def __init__(self, world, config, output_dir): 'total_messages': 0, 'successful_transmissions': 0, 'collaborative_detections': 0, - 'data_exchange_mb': 0.0 + 'data_exchange_mb': 0.0, + 'pedestrian_warnings_sent': 0, + 'safety_alerts': 0 } def spawn_cooperative_vehicles(self, num_vehicles: int = 3) -> List[carla.Actor]: @@ -83,7 +85,6 @@ def spawn_cooperative_vehicles(self, num_vehicles: int = 3) -> List[carla.Actor] print("警告:无生成点") return [] - # 车辆类型 vehicle_types = [ 'vehicle.tesla.model3', 'vehicle.audi.tt', @@ -96,17 +97,13 @@ def spawn_cooperative_vehicles(self, num_vehicles: int = 3) -> List[carla.Actor] for i in range(min(num_vehicles, len(spawn_points))): try: - # 选择车辆类型 vtype = random.choice(vehicle_types) vehicle_bp = random.choice(blueprint_lib.filter(vtype)) - # 设置车辆属性 vehicle_bp.set_attribute('role_name', f'coop_vehicle_{i}') - # 选择生成点 spawn_point = spawn_points[i % len(spawn_points)] - # 调整位置,使车辆不在同一位置 offset_x = random.uniform(-5.0, 5.0) offset_y = random.uniform(-5.0, 5.0) location = carla.Location( @@ -115,7 +112,6 @@ def spawn_cooperative_vehicles(self, num_vehicles: int = 3) -> List[carla.Actor] z=spawn_point.location.z ) - # 轻微调整朝向 rotation = carla.Rotation( pitch=spawn_point.rotation.pitch, yaw=spawn_point.rotation.yaw + random.uniform(-15, 15), @@ -124,17 +120,12 @@ def spawn_cooperative_vehicles(self, num_vehicles: int = 3) -> List[carla.Actor] transform = carla.Transform(location, rotation) - # 生成车辆 vehicle = self.world.spawn_actor(vehicle_bp, transform) - - # 设置自动驾驶 vehicle.set_autopilot(True) - # 添加到列表 self.cooperative_vehicles.append(vehicle) spawned_vehicles.append(vehicle) - # 初始化车辆状态 self.vehicle_states[vehicle.id] = VehicleState( vehicle_id=vehicle.id, type_id=vehicle.type_id, @@ -183,7 +174,7 @@ def create_v2x_message(self, sender_id: int, message_type: str, data: Dict, message_type=message_type, data=data, timestamp=time.time(), - ttl=5.0, # 5秒生存时间 + ttl=5.0, priority=priority ) @@ -202,13 +193,11 @@ def broadcast_message(self, message: V2XMessage): recipients = [] - # 检查所有车辆是否在通信范围内 for vehicle in self.ego_vehicles + self.cooperative_vehicles: if vehicle.id != message.sender_id and vehicle.id in self.vehicle_states: receiver_state = self.vehicle_states[vehicle.id] receiver_pos = receiver_state.position - # 计算距离 distance = math.sqrt( (sender_pos[0] - receiver_pos[0]) ** 2 + (sender_pos[1] - receiver_pos[1]) ** 2 + @@ -218,7 +207,6 @@ def broadcast_message(self, message: V2XMessage): if distance <= self.communication_range: recipients.append(vehicle.id) - # 添加到接收者消息缓冲区 self.message_buffer[vehicle.id].append({ 'message': message, 'receive_time': time.time(), @@ -228,7 +216,6 @@ def broadcast_message(self, message: V2XMessage): if recipients: self.stats['successful_transmissions'] += len(recipients) - # 保存消息 try: self._save_v2x_message(message, recipients) except Exception as e: @@ -242,7 +229,6 @@ def share_perception_data(self, vehicle_id: int, detected_objects: List[Dict]): return None try: - # 创建感知消息 perception_data = { 'vehicle_id': vehicle_id, 'timestamp': time.time(), @@ -255,13 +241,11 @@ def share_perception_data(self, vehicle_id: int, detected_objects: List[Dict]): vehicle_id, 'perception', perception_data, - priority=2 # 感知数据优先级较高 + priority=2 ) - # 广播消息 recipients = self.broadcast_message(message) - # 融合共享的感知数据 if recipients: self._fuse_shared_perception(vehicle_id, detected_objects, recipients) @@ -276,7 +260,7 @@ def share_traffic_warning(self, vehicle_id: int, warning_type: str, """共享交通警告""" try: warning_data = { - 'warning_type': warning_type, # 'accident', 'congestion', 'hazard', 'construction' + 'warning_type': warning_type, 'location': location, 'severity': severity, 'timestamp': time.time(), @@ -287,7 +271,7 @@ def share_traffic_warning(self, vehicle_id: int, warning_type: str, vehicle_id, 'warning', warning_data, - priority=3 # 警告消息优先级最高 + priority=3 ) self.broadcast_message(message) @@ -298,9 +282,19 @@ def share_traffic_warning(self, vehicle_id: int, warning_type: str, return None def share_pedestrian_warning(self, vehicle_id: int, pedestrian_location: Tuple[float, float, float], - distance: float, speed: float): + distance: float, speed: float, pedestrian_id: Optional[int] = None): """共享行人警告""" try: + # 风险评估 + if distance < 5.0: + severity = 'critical' + elif distance < 10.0: + severity = 'high' + elif distance < 20.0: + severity = 'medium' + else: + severity = 'low' + warning_data = { 'warning_type': 'pedestrian', 'pedestrian_location': pedestrian_location, @@ -308,7 +302,9 @@ def share_pedestrian_warning(self, vehicle_id: int, pedestrian_location: Tuple[f 'vehicle_speed': speed, 'timestamp': time.time(), 'source_vehicle': vehicle_id, - 'severity': 'high' if distance < 10.0 else 'medium' if distance < 20.0 else 'low' + 'pedestrian_id': pedestrian_id, + 'severity': severity, + 'recommended_action': self._get_recommended_action(distance, speed, severity) } message = self.create_v2x_message( @@ -320,27 +316,39 @@ def share_pedestrian_warning(self, vehicle_id: int, pedestrian_location: Tuple[f recipients = self.broadcast_message(message) + # 更新统计 + self.stats['pedestrian_warnings_sent'] += 1 + if severity in ['critical', 'high']: + self.stats['safety_alerts'] += 1 + return message, recipients except Exception as e: print(f"共享行人警告失败: {e}") return None, [] + def _get_recommended_action(self, distance: float, speed: float, severity: str) -> str: + """获取推荐的安全措施""" + if severity == 'critical': + return "立即紧急制动,准备避让" + elif severity == 'high': + return "减速至20km/h以下,准备制动" + elif severity == 'medium': + return "减速至30km/h,保持警惕" + else: + return "保持当前速度,注意观察" + def _fuse_shared_perception(self, source_id: int, objects: List[Dict], recipients: List[int]): """融合共享的感知数据""" fused_objects = [] for obj in objects: - # 转换为全局坐标系(简化处理,实际需要坐标变换) global_obj = obj.copy() global_obj['source_vehicles'] = [source_id] - global_obj['confidence'] = obj.get('confidence', 0.8) # 降低置信度 + global_obj['confidence'] = obj.get('confidence', 0.8) - # 检查是否已有类似对象 matched = False for existing_obj in self.shared_objects: - # 简单的对象匹配(基于位置) if self._objects_match(global_obj, existing_obj): - # 更新现有对象 existing_obj['source_vehicles'].append(source_id) existing_obj['confidence'] = min(1.0, existing_obj.get('confidence', 0) + 0.1) existing_obj['update_time'] = time.time() @@ -353,11 +361,10 @@ def _fuse_shared_perception(self, source_id: int, objects: List[Dict], recipient self.shared_objects.extend(fused_objects) - # 清理旧对象 current_time = time.time() self.shared_objects = [ obj for obj in self.shared_objects - if current_time - obj.get('detection_time', 0) < 10.0 # 保留10秒内的对象 + if current_time - obj.get('detection_time', 0) < 10.0 ] if fused_objects: @@ -368,7 +375,6 @@ def _objects_match(self, obj1: Dict, obj2: Dict, distance_threshold: float = 5.0 if obj1.get('class') != obj2.get('class'): return False - # 计算位置距离 pos1 = obj1.get('position', {'x': 0, 'y': 0, 'z': 0}) pos2 = obj2.get('position', {'x': 0, 'y': 0, 'z': 0}) @@ -385,23 +391,20 @@ def get_shared_perception_for_vehicle(self, vehicle_id: int) -> List[Dict]: shared_data = [] for obj in self.shared_objects: - # 检查对象是否在车辆视野内(简化) shared_data.append(obj) return shared_data def coordinate_maneuvers(self, maneuvers: List[Dict]): """协调多车辆机动""" - # 分配优先级和时序 coordinated = [] for i, maneuver in enumerate(maneuvers): coordinated_maneuver = maneuver.copy() coordinated_maneuver['sequence'] = i - coordinated_maneuver['start_time'] = time.time() + i * 2.0 # 间隔2秒 + coordinated_maneuver['start_time'] = time.time() + i * 2.0 coordinated.append(coordinated_maneuver) - # 发送协调消息 message = self.create_v2x_message( maneuver.get('vehicle_id', 0), 'coordination', @@ -421,7 +424,6 @@ def _save_v2x_message(self, message: V2XMessage, recipients: List[int]): 'transmission_time': time.time() } - # 确保目录存在 v2x_messages_dir = os.path.join(self.coop_dir, "v2x_messages") os.makedirs(v2x_messages_dir, exist_ok=True) @@ -446,7 +448,6 @@ def save_shared_perception(self, frame_num: int): 'stats': self.stats } - # 确保目录存在 shared_perception_dir = os.path.join(self.coop_dir, "shared_perception") os.makedirs(shared_perception_dir, exist_ok=True) @@ -467,7 +468,12 @@ def generate_summary(self): 'shared_objects_count': len(self.shared_objects), 'communication_range': self.communication_range, 'average_messages_per_vehicle': self.stats['total_messages'] / max(1, len(self.ego_vehicles) + len( - self.cooperative_vehicles)) + self.cooperative_vehicles)), + 'safety_metrics': { + 'pedestrian_warnings': self.stats['pedestrian_warnings_sent'], + 'safety_alerts': self.stats['safety_alerts'], + 'collaborative_detections': self.stats['collaborative_detections'] + } } filepath = os.path.join(self.coop_dir, "cooperative_summary.json") diff --git a/src/enhance_pedestrian_safety/pedestrian_safety_monitor.py b/src/enhance_pedestrian_safety/pedestrian_safety_monitor.py index 5ec545f100..c5513fa9c4 100644 --- a/src/enhance_pedestrian_safety/pedestrian_safety_monitor.py +++ b/src/enhance_pedestrian_safety/pedestrian_safety_monitor.py @@ -19,17 +19,22 @@ def __init__(self, world, output_dir): # 安全参数 self.safety_thresholds = { + 'critical_distance': 2.0, # 临界距离 (米) 'high_risk_distance': 5.0, # 高风险距离 (米) 'medium_risk_distance': 10.0, # 中风险距离 (米) 'low_risk_distance': 20.0, # 低风险距离 (米) 'safe_speed_limit': 30.0, # 安全速度限制 (km/h) 'reaction_time': 1.5, # 反应时间 (秒) - 'braking_deceleration': 6.0 # 制动减速度 (m/s²) + 'braking_deceleration': 6.0, # 制动减速度 (m/s²) + 'ttc_critical': 1.0, # 临界碰撞时间 (秒) + 'ttc_high': 2.0, # 高风险碰撞时间 (秒) + 'ttc_medium': 3.0 # 中风险碰撞时间 (秒) } # 统计数据 self.stats = { 'total_interactions': 0, + 'critical_cases': 0, 'high_risk_cases': 0, 'medium_risk_cases': 0, 'low_risk_cases': 0, @@ -39,12 +44,17 @@ def __init__(self, world, output_dir): 'average_distance': 0, 'min_distance': float('inf'), 'max_distance': 0, - 'interaction_times': [] + 'average_ttc': 0, + 'min_ttc': float('inf'), + 'interaction_times': [], + 'vehicle_speeds': [], + 'pedestrian_speeds': [] } # 详细记录 self.interaction_records = [] self.warning_logs = [] + self.critical_events = [] def check_pedestrian_safety(self) -> Dict: """检查行人安全""" @@ -60,19 +70,19 @@ def check_pedestrian_safety(self) -> Dict: for pedestrian in pedestrians: pedestrian_location = pedestrian.get_location() + pedestrian_velocity = pedestrian.get_velocity() # 计算距离 distance = vehicle_location.distance(pedestrian_location) # 计算相对速度 - pedestrian_velocity = pedestrian.get_velocity() relative_speed = self._calculate_relative_speed(vehicle_velocity, pedestrian_velocity) # 计算碰撞时间 time_to_collision = self._calculate_ttc(distance, relative_speed) # 评估风险 - risk_level = self._assess_risk(distance, vehicle_speed, time_to_collision) + risk_level = self._assess_risk(distance, vehicle_speed, time_to_collision, relative_speed) interaction = { 'timestamp': time.time(), @@ -80,6 +90,7 @@ def check_pedestrian_safety(self) -> Dict: 'pedestrian_id': pedestrian.id, 'distance': distance, 'vehicle_speed': vehicle_speed * 3.6, # 转换为km/h + 'pedestrian_speed': math.sqrt(pedestrian_velocity.x ** 2 + pedestrian_velocity.y ** 2) * 3.6, 'relative_speed': relative_speed * 3.6, 'time_to_collision': time_to_collision if time_to_collision < 100 else None, 'risk_level': risk_level, @@ -92,7 +103,9 @@ def check_pedestrian_safety(self) -> Dict: 'x': pedestrian_location.x, 'y': pedestrian_location.y, 'z': pedestrian_location.z - } + }, + 'safety_measures': self._suggest_safety_measures(distance, vehicle_speed, time_to_collision, + risk_level) } current_interactions.append(interaction) @@ -100,9 +113,9 @@ def check_pedestrian_safety(self) -> Dict: # 更新统计 self._update_stats(interaction) - # 记录高风险情况 - if risk_level == 'high': - self._log_high_risk(interaction) + # 记录高风险和临界情况 + if risk_level in ['critical', 'high']: + self._log_risk_event(interaction) # 保存当前检查结果 if current_interactions: @@ -124,22 +137,33 @@ def _calculate_relative_speed(self, v1: carla.Vector3D, v2: carla.Vector3D) -> f def _calculate_ttc(self, distance: float, relative_speed: float) -> float: """计算碰撞时间 (Time to Collision)""" - if relative_speed > 0.1: # 避免除以零 + if relative_speed > 0.1: return distance / relative_speed return float('inf') - def _assess_risk(self, distance: float, speed: float, ttc: Optional[float]) -> str: + def _assess_risk(self, distance: float, speed: float, ttc: Optional[float], relative_speed: float) -> str: """评估风险等级""" speed_kmh = speed * 3.6 + # 基于碰撞时间的风险评估 + if ttc is not None: + if ttc < self.safety_thresholds['ttc_critical']: + return 'critical' + elif ttc < self.safety_thresholds['ttc_high']: + return 'high' + elif ttc < self.safety_thresholds['ttc_medium']: + return 'medium' + # 基于距离的风险评估 - if distance < self.safety_thresholds['high_risk_distance']: - if ttc is not None and ttc < 2.0: + if distance < self.safety_thresholds['critical_distance']: + return 'critical' + elif distance < self.safety_thresholds['high_risk_distance']: + if speed_kmh > self.safety_thresholds['safe_speed_limit']: return 'high' else: return 'medium' elif distance < self.safety_thresholds['medium_risk_distance']: - if speed_kmh > self.safety_thresholds['safe_speed_limit']: + if speed_kmh > self.safety_thresholds['safe_speed_limit'] * 1.5: return 'medium' else: return 'low' @@ -148,10 +172,65 @@ def _assess_risk(self, distance: float, speed: float, ttc: Optional[float]) -> s else: return 'safe' + def _suggest_safety_measures(self, distance: float, speed: float, ttc: Optional[float], risk_level: str) -> List[ + str]: + """建议安全措施""" + measures = [] + speed_kmh = speed * 3.6 + + if risk_level == 'critical': + measures.extend([ + "立即紧急制动", + "鸣喇叭警告行人", + "准备紧急避让", + "向其他车辆发送紧急警告", + "记录事故数据" + ]) + elif risk_level == 'high': + measures.extend([ + "立即减速至20km/h以下", + "保持警惕,准备制动", + "观察行人动向", + "准备避让", + "向附近车辆发送警告" + ]) + elif risk_level == 'medium': + measures.extend([ + "减速至安全速度", + "保持安全距离", + "观察周围环境", + "准备应对突发情况", + "评估避让路径" + ]) + elif risk_level == 'low': + measures.extend([ + "保持当前速度", + "注意观察", + "准备减速", + "保持安全车距" + ]) + else: + measures.append("正常行驶,保持警惕") + + # 添加基于距离的额外建议 + if distance < 3.0: + measures.append("保持极端警惕,准备紧急操作") + elif distance < 5.0: + measures.append("准备随时制动") + + # 添加基于车速的额外建议 + if speed_kmh > 50.0: + measures.append("车速过快,建议减速") + elif speed_kmh > 30.0: + measures.append("注意控制车速") + + return measures + def _update_stats(self, interaction: Dict): """更新统计数据""" self.stats['total_interactions'] += 1 distance = interaction['distance'] + ttc = interaction.get('time_to_collision') # 更新距离统计 self.stats['average_distance'] = ( @@ -161,9 +240,25 @@ def _update_stats(self, interaction: Dict): self.stats['min_distance'] = min(self.stats['min_distance'], distance) self.stats['max_distance'] = max(self.stats['max_distance'], distance) + # 更新碰撞时间统计 + if ttc is not None and ttc < 100: + self.stats['average_ttc'] = ( + (self.stats['average_ttc'] * (self.stats['total_interactions'] - 1) + ttc) / + self.stats['total_interactions'] + ) + self.stats['min_ttc'] = min(self.stats['min_ttc'], ttc) + + # 更新速度统计 + self.stats['vehicle_speeds'].append(interaction['vehicle_speed']) + self.stats['pedestrian_speeds'].append(interaction['pedestrian_speed']) + # 更新风险统计 risk_level = interaction['risk_level'] - if risk_level == 'high': + if risk_level == 'critical': + self.stats['critical_cases'] += 1 + self.stats['near_misses'] += 1 + self.stats['safety_warnings'] += 1 + elif risk_level == 'high': self.stats['high_risk_cases'] += 1 self.stats['near_misses'] += 1 self.stats['safety_warnings'] += 1 @@ -185,45 +280,44 @@ def _update_stats(self, interaction: Dict): if len(self.interaction_records) > 1000: self.interaction_records = self.interaction_records[-1000:] - def _log_high_risk(self, interaction: Dict): - """记录高风险情况""" - warning = { + def _log_risk_event(self, interaction: Dict): + """记录风险事件""" + event_type = 'critical' if interaction['risk_level'] == 'critical' else 'high_risk' + + event = { 'timestamp': datetime.now().isoformat(), + 'event_type': event_type, 'interaction': interaction, - 'safety_measures': self._suggest_safety_measures(interaction) + 'safety_measures': interaction.get('safety_measures', []), + 'environment': { + 'weather': str(self.world.get_weather()), + 'time_of_day': self._get_time_of_day() + } } - self.warning_logs.append(warning) - # 保存高风险警告 + if event_type == 'critical': + self.critical_events.append(event) + # 保存临界事件 + if len(self.critical_events) % 5 == 0: + self._save_critical_events() + else: + self.warning_logs.append(event) + + # 保存警告日志 if len(self.warning_logs) % 10 == 0: self._save_warning_logs() - def _suggest_safety_measures(self, interaction: Dict) -> List[str]: - """建议安全措施""" - measures = [] - - if interaction['risk_level'] == 'high': - measures.extend([ - "立即制动", - "鸣喇叭警告", - "准备紧急避让", - "向其他车辆发送警告" - ]) - elif interaction['risk_level'] == 'medium': - measures.extend([ - "减速行驶", - "保持警惕", - "准备制动", - "观察行人动向" - ]) - elif interaction['risk_level'] == 'low': - measures.extend([ - "保持安全距离", - "观察周围环境", - "准备应对突发情况" - ]) + def _get_time_of_day(self) -> str: + """获取当前时间""" + weather = self.world.get_weather() + sun_altitude = weather.sun_altitude_angle - return measures + if sun_altitude > 45: + return 'day' + elif sun_altitude > 0: + return 'sunset' + else: + return 'night' def _save_interaction_report(self, interactions: List[Dict]): """保存交互报告""" @@ -235,10 +329,15 @@ def _save_interaction_report(self, interactions: List[Dict]): 'total_interactions': len(interactions), 'interactions': interactions, 'summary': { + 'critical': len([i for i in interactions if i['risk_level'] == 'critical']), 'high_risk': len([i for i in interactions if i['risk_level'] == 'high']), 'medium_risk': len([i for i in interactions if i['risk_level'] == 'medium']), 'low_risk': len([i for i in interactions if i['risk_level'] == 'low']), 'safe': len([i for i in interactions if i['risk_level'] == 'safe']) + }, + 'environment': { + 'weather': str(self.world.get_weather()), + 'time_of_day': self._get_time_of_day() } } @@ -254,6 +353,15 @@ def _save_warning_logs(self): with open(warning_file, 'w', encoding='utf-8') as f: json.dump(self.warning_logs, f, indent=2, ensure_ascii=False) + def _save_critical_events(self): + """保存临界事件""" + if not self.critical_events: + return + + critical_file = os.path.join(self.safety_dir, "critical_events.json") + with open(critical_file, 'w', encoding='utf-8') as f: + json.dump(self.critical_events, f, indent=2, ensure_ascii=False) + def _generate_safety_report(self) -> Dict: """生成安全报告""" report = { @@ -261,13 +369,22 @@ def _generate_safety_report(self) -> Dict: 'statistics': self.stats.copy(), 'safety_thresholds': self.safety_thresholds, 'risk_distribution': { + 'critical': self.stats['critical_cases'], 'high': self.stats['high_risk_cases'], 'medium': self.stats['medium_risk_cases'], 'low': self.stats['low_risk_cases'], 'safe': self.stats['safe_cases'] }, 'safety_score': self._calculate_safety_score(), - 'recommendations': self._generate_recommendations() + 'recommendations': self._generate_recommendations(), + 'performance_metrics': { + 'average_vehicle_speed': np.mean(self.stats['vehicle_speeds']) if self.stats['vehicle_speeds'] else 0, + 'average_pedestrian_speed': np.mean(self.stats['pedestrian_speeds']) if self.stats[ + 'pedestrian_speeds'] else 0, + 'max_vehicle_speed': max(self.stats['vehicle_speeds']) if self.stats['vehicle_speeds'] else 0, + 'interaction_frequency': len(self.stats['interaction_times']) / max(1, ( + time.time() - min(self.stats['interaction_times']) if self.stats['interaction_times'] else 1)) + } } return report @@ -277,17 +394,30 @@ def _calculate_safety_score(self) -> float: if self.stats['total_interactions'] == 0: return 100.0 + critical_ratio = self.stats['critical_cases'] / self.stats['total_interactions'] high_risk_ratio = self.stats['high_risk_cases'] / self.stats['total_interactions'] medium_risk_ratio = self.stats['medium_risk_cases'] / self.stats['total_interactions'] - # 评分公式:基础分减去风险比例 - score = 100 - (high_risk_ratio * 60 + medium_risk_ratio * 30) * 100 + # 评分公式:基础分减去风险比例加权 + score = 100 - (critical_ratio * 80 + high_risk_ratio * 50 + medium_risk_ratio * 20) * 100 # 考虑平均距离 if self.stats['average_distance'] > 15.0: score += 10 elif self.stats['average_distance'] < 5.0: score -= 20 + elif self.stats['average_distance'] < 2.0: + score -= 40 + + # 考虑平均车速 + if self.stats['vehicle_speeds']: + avg_speed = np.mean(self.stats['vehicle_speeds']) + if avg_speed > 50.0: + score -= 15 + elif avg_speed > 30.0: + score -= 5 + elif avg_speed < 20.0: + score += 10 return max(0, min(100, score)) @@ -295,19 +425,37 @@ def _generate_recommendations(self) -> List[str]: """生成改进建议""" recommendations = [] - if self.stats['high_risk_cases'] > 0: + if self.stats['critical_cases'] > 0: + recommendations.extend([ + "立即改进行人检测系统", + "增加紧急制动系统", + "实施更严格的限速措施", + "增加行人预警系统", + "记录和分析所有临界事件" + ]) + + if self.stats['high_risk_cases'] > 5: recommendations.extend([ "增加行人安全距离阈值", "加强车辆行人检测系统", - "实施更严格的限速措施", - "增加行人警告系统" + "实施自动减速系统", + "增加行人警告频率" ]) if self.stats['average_distance'] < 10.0: recommendations.append("增加车辆与行人的平均距离") - if self.stats['near_misses'] > 5: - recommendations.append("实施紧急制动系统") + if self.stats['near_misses'] > 3: + recommendations.append("实施紧急避让系统") + + if self.stats['vehicle_speeds']: + avg_speed = np.mean(self.stats['vehicle_speeds']) + if avg_speed > 40.0: + recommendations.append("降低平均车速,特别是在行人密集区域") + + # 基于碰撞时间的建议 + if self.stats['min_ttc'] < 2.0: + recommendations.append("改进碰撞时间预测算法") return recommendations @@ -319,9 +467,32 @@ def generate_final_report(self) -> Dict: final_report['historical_data'] = { 'total_interaction_records': len(self.interaction_records), 'total_warning_logs': len(self.warning_logs), + 'total_critical_events': len(self.critical_events), 'analysis_period': self._get_analysis_period() } + # 添加详细分析 + final_report['detailed_analysis'] = { + 'distance_analysis': { + 'average': self.stats['average_distance'], + 'min': self.stats['min_distance'], + 'max': self.stats['max_distance'], + 'std': np.std([r['distance'] for r in self.interaction_records]) if self.interaction_records else 0 + }, + 'speed_analysis': { + 'vehicle_avg': np.mean(self.stats['vehicle_speeds']) if self.stats['vehicle_speeds'] else 0, + 'pedestrian_avg': np.mean(self.stats['pedestrian_speeds']) if self.stats['pedestrian_speeds'] else 0, + 'vehicle_std': np.std(self.stats['vehicle_speeds']) if self.stats['vehicle_speeds'] else 0 + }, + 'ttc_analysis': { + 'average': self.stats['average_ttc'], + 'min': self.stats['min_ttc'], + 'percentage_below_2s': len( + [r for r in self.interaction_records if r.get('time_to_collision', 100) < 2.0]) / max(1, + len(self.interaction_records)) * 100 + } + } + # 保存最终报告 final_file = os.path.join(self.safety_dir, "final_safety_report.json") with open(final_file, 'w', encoding='utf-8') as f: @@ -359,10 +530,14 @@ def save_data(self): with open(records_file, 'w', encoding='utf-8') as f: json.dump(self.interaction_records, f, indent=2, ensure_ascii=False) - # 保存警告日志 + # 保存警告日志和临界事件 self._save_warning_logs() + self._save_critical_events() # 生成并保存最终报告 - self.generate_final_report() + final_report = self.generate_final_report() - print(f"行人安全数据已保存到: {self.safety_dir}") \ No newline at end of file + print(f"行人安全数据已保存到: {self.safety_dir}") + print(f"安全评分: {final_report['safety_score']:.1f}/100") + print(f"高风险事件: {final_report['risk_distribution']['high']}次") + print(f"临界事件: {final_report['risk_distribution']['critical']}次") \ No newline at end of file diff --git a/src/enhance_pedestrian_safety/scene_manager.py b/src/enhance_pedestrian_safety/scene_manager.py index f7e081f9ed..f43601c4fd 100644 --- a/src/enhance_pedestrian_safety/scene_manager.py +++ b/src/enhance_pedestrian_safety/scene_manager.py @@ -52,8 +52,8 @@ class SceneManager: 'vehicle_density': 0.4, 'pedestrian_density': 0.9, 'weather_variations': ['clear', 'cloudy'], - 'speed_limit': 20.0, - 'safety_zone_radius': 30.0 + 'speed_limit': 15.0, # 降低限速 + 'safety_zone_radius': 50.0 # 扩大安全区域 }, 'pedestrian_crossing': { 'description': '人行横道场景', @@ -94,14 +94,16 @@ def setup_scene(world, config, scene_type='intersection_4way'): # 如果是学校区域,设置车速限制 if scene_type == 'school_zone' and 'speed_limit' in scene_config: config['traffic']['speed_limit'] = scene_config['speed_limit'] + # 添加行人安全区域标记 + SceneManager._add_safety_zone_markers(world, config) # 应用场景特定设置 - SceneManager._apply_scene_specifics(world, scene_type) + SceneManager._apply_scene_specifics(world, scene_type, config) return config @staticmethod - def _apply_scene_specifics(world, scene_type): + def _apply_scene_specifics(world, scene_type, config): """应用场景特定的设置""" try: if scene_type == 'highway': @@ -134,6 +136,8 @@ def _apply_scene_specifics(world, scene_type): actor.set_light_state(carla.VehicleLightState.LowBeam) except: pass + # 添加学校区域警告标志 + SceneManager._add_school_zone_signs(world) elif scene_type == 'pedestrian_crossing': # 人行横道:增加可见性 @@ -143,10 +147,71 @@ def _apply_scene_specifics(world, scene_type): actor.set_light_state(carla.VehicleLightState.LowBeam) except: pass + # 添加人行横道标记 + SceneManager._add_crosswalk_markings(world) except Exception as e: print(f"场景特定设置失败: {e}") + @staticmethod + def _add_safety_zone_markers(world, config): + """添加安全区域标记""" + try: + blueprint_lib = world.get_blueprint_library() + + # 在学校区域周围添加安全锥 + spawn_points = world.get_map().get_spawn_points() + if spawn_points: + center_point = spawn_points[len(spawn_points) // 2].location + SceneManager.spawn_traffic_cones(world, center_point, num_cones=15) + + # 添加警告标志 + warning_sign_bp = blueprint_lib.find('static.prop.trafficsign') + if warning_sign_bp: + sign_location = carla.Location(center_point.x, center_point.y, center_point.z + 2.0) + world.spawn_actor(warning_sign_bp, carla.Transform(sign_location, carla.Rotation(0, 90, 0))) + + except Exception as e: + print(f"添加安全区域标记失败: {e}") + + @staticmethod + def _add_school_zone_signs(world): + """添加学校区域标志""" + try: + blueprint_lib = world.get_blueprint_library() + + # 查找学校区域标志蓝图 + school_sign_bp = None + for bp in blueprint_lib.filter('static.prop.*'): + if 'school' in bp.id.lower() or 'warning' in bp.id.lower(): + school_sign_bp = bp + break + + if school_sign_bp: + # 在学校区域周围放置标志 + spawn_points = world.get_map().get_spawn_points() + if spawn_points: + for i in range(min(4, len(spawn_points))): + location = spawn_points[i].location + sign_location = carla.Location(location.x, location.y, location.z + 2.0) + world.spawn_actor(school_sign_bp, + carla.Transform(sign_location, carla.Rotation(0, i * 90, 0))) + + except Exception as e: + print(f"添加学校区域标志失败: {e}") + + @staticmethod + def _add_crosswalk_markings(world): + """添加人行横道标记""" + try: + spawn_points = world.get_map().get_spawn_points() + if spawn_points: + center_point = spawn_points[len(spawn_points) // 2].location + SceneManager.spawn_pedestrian_safety_features(world, center_point, 'crosswalk') + + except Exception as e: + print(f"添加人行横道标记失败: {e}") + @staticmethod def spawn_pedestrian_safety_features(world, location, feature_type='crosswalk'): """生成行人安全设施""" @@ -315,7 +380,12 @@ def save_scene_description(output_dir, scene_type, config, extra_info=None): 'description': SceneManager.SCENES.get(scene_type, {}).get('description', '未知场景'), 'config': config, 'created': SceneManager._get_timestamp(), - 'extra_info': extra_info or {} + 'extra_info': extra_info or {}, + 'safety_features': { + 'has_crosswalk': scene_type in ['school_zone', 'pedestrian_crossing', 'intersection_4way'], + 'has_traffic_cones': scene_type in ['school_zone', 'construction_zone'], + 'has_warning_signs': scene_type in ['school_zone', 'pedestrian_crossing'] + } } scene_file = os.path.join(output_dir, "metadata", "scene_description.json") diff --git a/src/enhance_pedestrian_safety/sensor_enhancer.py b/src/enhance_pedestrian_safety/sensor_enhancer.py index 3dba1d767b..4f2d2b8f27 100644 --- a/src/enhance_pedestrian_safety/sensor_enhancer.py +++ b/src/enhance_pedestrian_safety/sensor_enhancer.py @@ -1,5 +1,5 @@ """ -传感器数据增强模块 - 提高数据质量和多样性(优化版) +传感器数据增强模块 - 提高数据质量和多样性(行人安全优化版) """ import numpy as np @@ -7,7 +7,6 @@ import random import os import json -from PIL import Image, ImageEnhance, ImageFilter from datetime import datetime from typing import Dict, List, Tuple, Optional, Union, Callable import concurrent.futures @@ -46,6 +45,8 @@ class EnhancementMethod(Enum): COLOR_TEMP = "color_temperature" JPEG_COMPRESSION = "jpeg_compression" COLOR_JITTER = "color_jitter" + PEDESTRIAN_HIGHLIGHT = "pedestrian_highlight" # 新增:行人高亮 + SAFETY_WARNING = "safety_warning" # 新增:安全警告 @dataclass @@ -61,6 +62,7 @@ class EnhancementConfig: save_enhanced: bool = True output_format: str = "jpg" compression_quality: int = 90 + pedestrian_safety_mode: bool = True # 新增:行人安全模式 def __post_init__(self): if self.enabled_methods is None: @@ -69,6 +71,12 @@ def __post_init__(self): EnhancementMethod.CONTRAST, EnhancementMethod.BRIGHTNESS ] + # 如果启用行人安全模式,添加相关方法 + if self.pedestrian_safety_mode: + if EnhancementMethod.PEDESTRIAN_HIGHLIGHT not in self.enabled_methods: + self.enabled_methods.append(EnhancementMethod.PEDESTRIAN_HIGHLIGHT) + if EnhancementMethod.SAFETY_WARNING not in self.enabled_methods: + self.enabled_methods.append(EnhancementMethod.SAFETY_WARNING) class BatchEnhancer: @@ -193,7 +201,7 @@ def get_stats(self) -> Dict: class SensorDataEnhancer: - """传感器数据增强器(优化版)""" + """传感器数据增强器(行人安全优化版)""" def __init__(self, config: Union[EnhancementConfig, Dict]): if isinstance(config, dict): @@ -210,9 +218,13 @@ def __init__(self, config: Union[EnhancementConfig, Dict]): 'method_times': {} } + # 行人检测模拟数据 + self.pedestrian_detections = [] + self.safety_warnings = [] + def _setup_method_registry(self) -> Dict[EnhancementMethod, Callable]: """设置方法注册表""" - return { + registry = { EnhancementMethod.NORMALIZE: self._normalize_image, EnhancementMethod.BRIGHTNESS: self._adjust_brightness, EnhancementMethod.CONTRAST: self._adjust_contrast, @@ -228,8 +240,11 @@ def _setup_method_registry(self) -> Dict[EnhancementMethod, Callable]: EnhancementMethod.VIGNETTE: self._add_vignette, EnhancementMethod.COLOR_TEMP: self._adjust_color_temperature, EnhancementMethod.JPEG_COMPRESSION: self._simulate_jpeg_compression, - EnhancementMethod.COLOR_JITTER: self._color_jitter + EnhancementMethod.COLOR_JITTER: self._color_jitter, + EnhancementMethod.PEDESTRIAN_HIGHLIGHT: self._highlight_pedestrians, # 新增 + EnhancementMethod.SAFETY_WARNING: self._add_safety_warnings # 新增 } + return registry def _setup_weather_methods(self) -> Dict[WeatherType, List[EnhancementMethod]]: """设置天气相关方法""" @@ -262,7 +277,8 @@ def _setup_weather_methods(self) -> Dict[WeatherType, List[EnhancementMethod]]: EnhancementMethod.BRIGHTNESS, EnhancementMethod.CONTRAST, EnhancementMethod.NOISE, - EnhancementMethod.VIGNETTE + EnhancementMethod.VIGNETTE, + EnhancementMethod.PEDESTRIAN_HIGHLIGHT # 夜间特别关注行人 ], WeatherType.SUNSET: [ EnhancementMethod.NORMALIZE, @@ -276,7 +292,7 @@ def enhance_image(self, image_data: np.ndarray, sensor_type: str = 'camera', return_methods: bool = False) -> Union[np.ndarray, Tuple]: """ - 增强图像数据(优化版) + 增强图像数据(行人安全优化版) Args: image_data: 原始图像数据 (H, W, C) @@ -404,11 +420,10 @@ def get_performance_stats(self) -> Dict: stats['avg_time_per_call'] = stats['total_time'] / stats['calls'] return stats - # ========== 增强方法实现(优化版)========== + # ========== 增强方法实现(行人安全优化版)========== def _normalize_image(self, image: np.ndarray) -> np.ndarray: """图像归一化(优化版)""" - # 使用OpenCV加速 if image.dtype != np.uint8: normalized = cv2.normalize(image, None, 0, 255, cv2.NORM_MINMAX) return normalized.astype(np.uint8) @@ -418,9 +433,7 @@ def _adjust_brightness(self, image: np.ndarray) -> np.ndarray: """调整亮度(优化版)""" factor = random.uniform(*self.config.intensity_range) - # 使用NumPy向量化操作 if factor != 1.0: - # 转换为浮点数进行计算 img_float = image.astype(np.float32) * factor return np.clip(img_float, 0, 255).astype(np.uint8) return image @@ -430,9 +443,7 @@ def _adjust_contrast(self, image: np.ndarray) -> np.ndarray: factor = random.uniform(*self.config.intensity_range) if factor != 1.0: - # 计算平均值 mean = np.mean(image, axis=(0, 1), keepdims=True) - # 应用对比度调整 contrasted = mean + factor * (image.astype(np.float32) - mean) return np.clip(contrasted, 0, 255).astype(np.uint8) return image @@ -442,18 +453,14 @@ def _adjust_saturation(self, image: np.ndarray) -> np.ndarray: factor = random.uniform(*self.config.intensity_range) if factor != 1.0: - # 转换为HSV空间 hsv = cv2.cvtColor(image, cv2.COLOR_RGB2HSV).astype(np.float32) - # 调整饱和度通道 hsv[:, :, 1] = np.clip(hsv[:, :, 1] * factor, 0, 255) - # 转换回RGB saturated = cv2.cvtColor(hsv.astype(np.uint8), cv2.COLOR_HSV2RGB) return saturated return image def _enhance_sharpness(self, image: np.ndarray) -> np.ndarray: """增强锐度(优化版)""" - # 使用拉普拉斯算子增强边缘 kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]]) @@ -464,7 +471,6 @@ def _gamma_correction(self, image: np.ndarray) -> np.ndarray: """伽马校正(优化版)""" gamma = random.uniform(0.5, 2.0) - # 使用LUT加速 table = np.array([((i / 255.0) ** gamma) * 255 for i in range(256)]).astype(np.uint8) corrected = cv2.LUT(image, table) return corrected @@ -474,7 +480,6 @@ def _add_noise(self, image: np.ndarray) -> np.ndarray: noise_type = random.choice(['gaussian', 'salt_pepper']) if noise_type == 'gaussian': - # 高斯噪声 mean = 0 var = random.uniform(0.001, 0.005) sigma = var ** 0.5 @@ -483,18 +488,15 @@ def _add_noise(self, image: np.ndarray) -> np.ndarray: return np.clip(noisy, 0, 255).astype(np.uint8) else: # salt_pepper - # 椒盐噪声 amount = random.uniform(0.001, 0.005) s_vs_p = random.uniform(0.3, 0.7) noisy = image.copy() - # 盐噪声 num_salt = np.ceil(amount * image.size * s_vs_p / 3) coords = [np.random.randint(0, i, int(num_salt)) for i in image.shape] noisy[coords[0], coords[1], :] = 255 - # 椒噪声 num_pepper = np.ceil(amount * image.size * (1.0 - s_vs_p) / 3) coords = [np.random.randint(0, i, int(num_pepper)) for i in image.shape] noisy[coords[0], coords[1], :] = 0 @@ -513,7 +515,6 @@ def _apply_motion_blur(self, image: np.ndarray) -> np.ndarray: kernel_size = random.choice([7, 9, 11]) direction = random.choice(['horizontal', 'vertical', 'diagonal']) - # 创建运动模糊核 kernel = np.zeros((kernel_size, kernel_size)) if direction == 'horizontal': @@ -526,7 +527,6 @@ def _apply_motion_blur(self, image: np.ndarray) -> np.ndarray: blurred = cv2.filter2D(image, -1, kernel) - # 混合原图和模糊图 alpha = random.uniform(0.3, 0.7) result = cv2.addWeighted(image, 1 - alpha, blurred, alpha, 0) return result.astype(np.uint8) @@ -535,10 +535,8 @@ def _add_rain_effect(self, image: np.ndarray) -> np.ndarray: """添加雨滴效果(优化版)""" h, w = image.shape[:2] - # 创建雨滴层 rain_layer = np.zeros((h, w), dtype=np.float32) - # 生成随机雨滴 num_drops = random.randint(200, 800) drop_length = random.randint(8, 15) @@ -547,15 +545,12 @@ def _add_rain_effect(self, image: np.ndarray) -> np.ndarray: y = random.randint(0, h - 1) brightness = random.uniform(0.3, 0.6) - # 绘制雨滴线 for i in range(drop_length): if y + i < h and x + i < w: rain_layer[y + i, x + i] += brightness - # 模糊雨滴层 rain_layer = cv2.GaussianBlur(rain_layer, (3, 3), 0) - # 叠加到原图 rain_layer_3d = np.stack([rain_layer] * 3, axis=2) enhanced = image.astype(np.float32) * 0.9 + rain_layer_3d * 0.1 * 255 return np.clip(enhanced, 0, 255).astype(np.uint8) @@ -564,17 +559,14 @@ def _add_fog_effect(self, image: np.ndarray) -> np.ndarray: """添加雾效(优化版)""" h, w = image.shape[:2] - # 创建深度图(简化版,假设中心最近) center_y, center_x = h // 2, w // 2 y_coords, x_coords = np.ogrid[:h, :w] distance = np.sqrt((x_coords - center_x) ** 2 + (y_coords - center_y) ** 2) distance = distance / np.max(distance) - # 雾效强度 fog_intensity = random.uniform(0.2, 0.5) fog_color = random.choice([200, 210, 220]) - # 应用雾效 fog_strength = distance * fog_intensity fog_strength_3d = np.stack([fog_strength] * 3, axis=2) fog_layer = np.ones_like(image) * fog_color @@ -586,19 +578,15 @@ def _add_fog_effect(self, image: np.ndarray) -> np.ndarray: def _add_cloud_effect(self, image: np.ndarray) -> np.ndarray: """添加云层效果(优化版)""" - # 降低饱和度和对比度,模拟多云天气 hsv = cv2.cvtColor(image, cv2.COLOR_RGB2HSV).astype(np.float32) - # 调整饱和度 hsv[:, :, 1] *= random.uniform(0.7, 0.9) - # 调整亮度和对比度 brightness_factor = random.uniform(0.9, 1.1) contrast_factor = random.uniform(0.8, 0.95) hsv[:, :, 2] = np.clip(hsv[:, :, 2] * brightness_factor, 0, 255) - # 应用对比度调整 mean = np.mean(hsv[:, :, 2]) hsv[:, :, 2] = mean + contrast_factor * (hsv[:, :, 2] - mean) hsv[:, :, 2] = np.clip(hsv[:, :, 2], 0, 255) @@ -610,21 +598,17 @@ def _add_vignette(self, image: np.ndarray) -> np.ndarray: """添加暗角效果(优化版)""" h, w = image.shape[:2] - # 创建暗角蒙版 center_y, center_x = h // 2, w // 2 y_coords, x_coords = np.ogrid[:h, :w] - # 计算距离(使用椭圆形状) y_dist = (y_coords - center_y) / (h / 2) x_dist = (x_coords - center_x) / (w / 2) distance = np.sqrt(x_dist ** 2 + y_dist ** 2) - # 创建暗角 vignette_intensity = random.uniform(0.2, 0.4) vignette = 1 - distance * vignette_intensity vignette = np.clip(vignette, 0.6, 1.0) - # 应用暗角 vignette_3d = np.stack([vignette] * 3, axis=2) enhanced = image.astype(np.float32) * vignette_3d @@ -634,16 +618,13 @@ def _adjust_color_temperature(self, image: np.ndarray) -> np.ndarray: """调整色温(优化版)""" temp_type = random.choice(['warm', 'cool']) - # 转换为浮点数 img_float = image.astype(np.float32) if temp_type == 'warm': - # 暖色调:增加红色和黄色 img_float[:, :, 0] *= random.uniform(1.0, 1.15) # 红色 img_float[:, :, 1] *= random.uniform(1.0, 1.1) # 绿色 img_float[:, :, 2] *= random.uniform(0.9, 1.0) # 蓝色 else: - # 冷色调:增加蓝色 img_float[:, :, 0] *= random.uniform(0.9, 1.0) # 红色 img_float[:, :, 1] *= random.uniform(0.95, 1.0) # 绿色 img_float[:, :, 2] *= random.uniform(1.0, 1.15) # 蓝色 @@ -653,15 +634,12 @@ def _adjust_color_temperature(self, image: np.ndarray) -> np.ndarray: def _simulate_jpeg_compression(self, image: np.ndarray) -> np.ndarray: """模拟JPEG压缩(优化版)""" - # 使用OpenCV的JPEG编码/解码模拟压缩 quality = random.randint(70, 95) - # 编码为JPEG encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), quality] result, encoded = cv2.imencode('.jpg', cv2.cvtColor(image, cv2.COLOR_RGB2BGR), encode_param) if result: - # 解码 decoded = cv2.imdecode(encoded, cv2.IMREAD_COLOR) return cv2.cvtColor(decoded, cv2.COLOR_BGR2RGB) @@ -669,57 +647,124 @@ def _simulate_jpeg_compression(self, image: np.ndarray) -> np.ndarray: def _color_jitter(self, image: np.ndarray) -> np.ndarray: """颜色抖动(优化版)""" - # 随机应用多种颜色变换 transforms = [] - # 亮度调整 if random.random() > 0.5: brightness = random.uniform(0.8, 1.2) transforms.append(lambda img: self._adjust_brightness(img, brightness)) - # 对比度调整 if random.random() > 0.5: contrast = random.uniform(0.8, 1.2) transforms.append(lambda img: self._adjust_contrast(img, contrast)) - # 饱和度调整 if random.random() > 0.5: saturation = random.uniform(0.8, 1.2) transforms.append(lambda img: self._adjust_saturation(img, saturation)) - # 随机应用变换 if transforms: random.shuffle(transforms) - for transform in transforms[:2]: # 最多应用2个变换 + for transform in transforms[:2]: image = transform(image) return image + def _highlight_pedestrians(self, image: np.ndarray) -> np.ndarray: + """高亮行人(新增方法)""" + if not self.config.pedestrian_safety_mode: + return image + + h, w = image.shape[:2] + highlighted = image.copy() + + # 模拟行人检测(随机位置) + num_pedestrians = random.randint(1, 3) + + for i in range(num_pedestrians): + # 随机位置 + x = random.randint(50, w - 150) + y = random.randint(50, h - 200) + + # 随机大小 + width = random.randint(30, 70) + height = random.randint(80, 180) + + # 根据距离设置颜色(模拟风险评估) + distance = random.uniform(5.0, 50.0) + if distance < 10.0: + color = (0, 0, 255) # 红色:高风险 + thickness = 3 + elif distance < 20.0: + color = (0, 165, 255) # 橙色:中风险 + thickness = 2 + else: + color = (0, 255, 0) # 绿色:低风险 + thickness = 1 + + # 绘制边界框 + cv2.rectangle(highlighted, (x, y), (x + width, y + height), color, thickness) + + # 添加标签 + label = f"Pedestrian {distance:.1f}m" + cv2.putText(highlighted, label, (x, y - 10), + cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, thickness) + + # 记录检测 + self.pedestrian_detections.append({ + 'position': (x, y, width, height), + 'distance': distance, + 'risk_level': 'high' if distance < 10.0 else 'medium' if distance < 20.0 else 'low' + }) + + return highlighted + + def _add_safety_warnings(self, image: np.ndarray) -> np.ndarray: + """添加安全警告(新增方法)""" + if not self.config.pedestrian_safety_mode: + return image + + warned = image.copy() + + # 检查是否有高风险行人 + high_risk = [d for d in self.pedestrian_detections if d['risk_level'] == 'high'] + + if high_risk: + # 在图像顶部添加警告条 + warning_height = 40 + warning_bar = np.zeros((warning_height, image.shape[1], 3), dtype=np.uint8) + warning_bar[:] = (0, 0, 255) # 红色背景 + + # 添加警告文本 + warning_text = f"WARNING: {len(high_risk)} high-risk pedestrians detected!" + cv2.putText(warning_bar, warning_text, (10, 25), + cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2) + + # 将警告条添加到图像顶部 + warned = np.vstack([warning_bar, warned]) + + # 记录警告 + self.safety_warnings.append({ + 'timestamp': time.time(), + 'high_risk_count': len(high_risk), + 'message': warning_text + }) + + return warned + def _enhance_non_rgb_image(self, image_data: np.ndarray, sensor_type: str) -> np.ndarray: """增强非RGB图像(深度、语义分割)""" if sensor_type == 'depth': - # 深度图增强:归一化和去噪 if image_data.dtype != np.uint8: - # 归一化到0-255 normalized = cv2.normalize(image_data, None, 0, 255, cv2.NORM_MINMAX) else: normalized = image_data - # 应用中值滤波去除噪声 enhanced = cv2.medianBlur(normalized, 3) return enhanced.astype(np.uint8) elif sensor_type == 'semantic': - # 语义分割图增强:保持类别不变,只做边界平滑 kernel = np.ones((3, 3), np.uint8) - - # 形态学操作:先腐蚀再膨胀(闭运算)填充小孔 enhanced = cv2.morphologyEx(image_data, cv2.MORPH_CLOSE, kernel) - - # 高斯模糊平滑边界 enhanced = cv2.GaussianBlur(enhanced, (3, 3), 0.5) - - # 恢复类别值 enhanced = np.round(enhanced).astype(image_data.dtype) return enhanced @@ -727,19 +772,15 @@ def _enhance_non_rgb_image(self, image_data: np.ndarray, sensor_type: str) -> np def save_enhanced_image(self, image_data: np.ndarray, output_path: str, metadata: Optional[Dict] = None): - """保存增强后的图像和元数据(优化版)""" + """保存增强后的图像和元数据""" try: - # 确保目录存在 os.makedirs(os.path.dirname(output_path), exist_ok=True) - # 保存图像 if output_path.lower().endswith(('.png', '.jpg', '.jpeg')): cv2.imwrite(output_path, cv2.cvtColor(image_data, cv2.COLOR_RGB2BGR)) else: - # 默认保存为PNG cv2.imwrite(output_path + '.png', cv2.cvtColor(image_data, cv2.COLOR_RGB2BGR)) - # 保存元数据 if metadata: meta_path = output_path.rsplit('.', 1)[0] + '_meta.json' with open(meta_path, 'w', encoding='utf-8') as f: @@ -750,7 +791,7 @@ def save_enhanced_image(self, image_data: np.ndarray, output_path: str, raise def generate_enhancement_report(self, output_dir: str) -> Dict: - """生成增强报告(优化版)""" + """生成增强报告""" report = { 'timestamp': datetime.now().isoformat(), 'config': { @@ -758,7 +799,8 @@ def generate_enhancement_report(self, output_dir: str) -> Dict: 'time_of_day': self.config.time_of_day, 'enabled_methods': [m.value for m in self.config.enabled_methods], 'intensity_range': self.config.intensity_range, - 'probability': self.config.probability + 'probability': self.config.probability, + 'pedestrian_safety_mode': self.config.pedestrian_safety_mode }, 'performance_stats': self.get_performance_stats(), 'method_usage': { @@ -768,153 +810,20 @@ def generate_enhancement_report(self, output_dir: str) -> Dict: 'cache_info': { 'cache_size': len(self.method_cache), 'cache_hits': self.perf_stats.get('cache_hit_calls', 0) + }, + 'safety_statistics': { + 'pedestrian_detections': len(self.pedestrian_detections), + 'safety_warnings': len(self.safety_warnings), + 'high_risk_cases': len([d for d in self.pedestrian_detections if d['risk_level'] == 'high']) } } - # 保存报告 report_path = os.path.join(output_dir, 'enhancement_report.json') with open(report_path, 'w', encoding='utf-8') as f: json.dump(report, f, indent=2, ensure_ascii=False) return report - def _add_pedestrian_detection_markers(self, image: np.ndarray, pedestrians: List[Dict]) -> np.ndarray: - """添加行人检测标记""" - if not pedestrians: - return image - - # 创建副本 - marked_image = image.copy() - - for pedestrian in pedestrians: - # 模拟行人检测框 - x = random.randint(100, image.shape[1] - 100) - y = random.randint(100, image.shape[0] - 100) - width = random.randint(30, 80) - height = random.randint(80, 180) - - # 绘制检测框 - color = (0, 255, 0) # 绿色表示安全 - thickness = 2 - - cv2.rectangle(marked_image, (x, y), (x + width, y + height), color, thickness) - - # 添加标签 - label = f"Pedestrian {pedestrian.get('distance', 0):.1f}m" - cv2.putText(marked_image, label, (x, y - 10), - cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, thickness) - - return marked_image - - def _simulate_safety_warnings(self, image: np.ndarray, warnings: List[Dict]) -> np.ndarray: - """模拟安全警告""" - if not warnings: - return image - - warning_image = image.copy() - - for warning in warnings[:2]: # 最多显示2个警告 - # 添加警告文本 - text = f"WARNING: {warning.get('type', 'Pedestrian')} {warning.get('distance', 0):.1f}m" - color = (0, 0, 255) # 红色表示警告 - thickness = 2 - - position = (50, 50 + warnings.index(warning) * 40) - cv2.putText(warning_image, text, position, - cv2.FONT_HERSHEY_SIMPLEX, 0.7, color, thickness) - - # 添加警告图标 - icon_size = 30 - icon_position = (position[0] - icon_size - 10, position[1] - icon_size // 2) - cv2.rectangle(warning_image, icon_position, - (icon_position[0] + icon_size, icon_position[1] + icon_size), - color, -1) # 填充红色矩形 - - return warning_image - - -# 保持向后兼容的类 -class SensorCalibrator: - """传感器校准模块(优化版)""" - - def __init__(self, config: Dict): - self.config = config - self.calibration_data = {} - - def generate_calibration_files(self, output_dir: str, - vehicle_locations: List[Dict], - camera_positions: List[Dict]): - """生成传感器校准文件(优化版)""" - calib_dir = os.path.join(output_dir, "calibration") - os.makedirs(calib_dir, exist_ok=True) - - # 1. 生成相机内参 - self._generate_camera_intrinsics(calib_dir) - - # 2. 生成外参(相机到车辆) - self._generate_extrinsics(calib_dir, vehicle_locations, camera_positions) - - # 3. 生成传感器间标定 - self._generate_sensor_calibration(calib_dir) - - # 4. 生成时间同步校准 - self._generate_temporal_calibration(calib_dir) - - # 5. 生成验证数据 - self._generate_validation_data(calib_dir) - - print(f"校准文件已生成到: {calib_dir}") - return calib_dir - - def _generate_camera_intrinsics(self, calib_dir: str): - """生成相机内参(优化版)""" - image_size = self.config.get('sensors', {}).get('image_size', [1280, 720]) - width, height = image_size[0], image_size[1] - - # 为不同相机生成不同的内参 - camera_types = [ - ('front_wide', 100.0), # 前视广角 - ('front_narrow', 60.0), # 前视窄角 - ('side', 90.0), # 侧视 - ('rear', 120.0), # 后视 - ('infrastructure', 120.0) # 基础设施 - ] - - for camera_name, fov in camera_types: - # 内参矩阵 [fx, 0, cx; 0, fy, cy; 0, 0, 1] - fx = width / (2 * np.tan(np.radians(fov / 2))) - fy = fx - cx = width / 2.0 - cy = height / 2.0 - - intrinsics = { - 'camera_name': camera_name, - 'camera_matrix': [ - [float(fx), 0.0, float(cx)], - [0.0, float(fy), float(cy)], - [0.0, 0.0, 1.0] - ], - 'distortion_coefficients': [ - random.uniform(-0.1, 0.1), # k1 - random.uniform(-0.01, 0.01), # k2 - 0.0, # p1 - 0.0, # p2 - random.uniform(-0.001, 0.001) # k3 - ], - 'image_size': [int(width), int(height)], - 'fov': float(fov), - 'pixel_size': [0.003, 0.003], # 假设像素大小 - 'sensor_type': 'pinhole', - 'calibration_date': datetime.now().isoformat(), - 'accuracy': random.uniform(0.5, 1.0) # 标定精度(像素) - } - - file_path = os.path.join(calib_dir, f'{camera_name}_intrinsic.json') - with open(file_path, 'w', encoding='utf-8') as f: - json.dump(intrinsics, f, indent=2, ensure_ascii=False) - - # ... 其他方法保持不变,但可以添加更多优化 ... - # 兼容旧版本接口 def enhance_image(image_data, sensor_type='camera'): diff --git a/src/enhance_pedestrian_safety/v2x_communication.py b/src/enhance_pedestrian_safety/v2x_communication.py index 63079cc46a..937c00d15e 100644 --- a/src/enhance_pedestrian_safety/v2x_communication.py +++ b/src/enhance_pedestrian_safety/v2x_communication.py @@ -29,32 +29,29 @@ def __init__(self, config: Dict): self.config = config self.enabled = config.get('enabled', True) - # 通信参数 - self.communication_range = config.get('communication_range', 300.0) # 通信范围(米) - self.bandwidth = config.get('bandwidth', 10.0) # 带宽(Mbps) - self.latency_mean = config.get('latency_mean', 0.05) # 平均延迟(秒) - self.latency_std = config.get('latency_std', 0.01) # 延迟标准差 - self.packet_loss_rate = config.get('packet_loss_rate', 0.01) # 丢包率 - - # 消息管理 + self.communication_range = config.get('communication_range', 300.0) + self.bandwidth = config.get('bandwidth', 10.0) + self.latency_mean = config.get('latency_mean', 0.05) + self.latency_std = config.get('latency_std', 0.01) + self.packet_loss_rate = config.get('packet_loss_rate', 0.01) + self.message_queue = queue.PriorityQueue() self.received_messages = [] self.message_counter = 0 - # 网络模拟 - self.network_nodes = {} # 节点ID -> 节点信息 - self.connections = {} # 连接状态 + self.network_nodes = {} + self.connections = {} - # 统计信息 self.stats = { 'messages_sent': 0, 'messages_received': 0, 'messages_dropped': 0, 'total_latency': 0.0, - 'bandwidth_used': 0.0 + 'bandwidth_used': 0.0, + 'pedestrian_warnings': 0, + 'safety_alerts': 0 } - # 启动消息处理线程 if self.enabled: self.running = True self.processor_thread = threading.Thread(target=self._message_processor, daemon=True) @@ -79,17 +76,14 @@ def send_message(self, message: V2XMessage) -> bool: if not self.enabled: return False - # 模拟网络延迟和丢包 if np.random.random() < self.packet_loss_rate: self.stats['messages_dropped'] += 1 return False - # 添加延迟 latency = np.random.normal(self.latency_mean, self.latency_std) delivery_time = time.time() + max(0, latency) - # 添加到消息队列(按优先级和发送时间排序) - priority = -message.priority # 负号因为PriorityQueue是小顶堆 + priority = -message.priority self.message_queue.put((priority, delivery_time, message)) self.stats['messages_sent'] += 1 @@ -162,7 +156,7 @@ def broadcast_map_data(self, map_id: str, map_data: Dict) -> Optional[V2XMessage message_type='map', data=map_data, timestamp=time.time(), - ttl=30.0, # 地图数据TTL较长 + ttl=30.0, priority=1, position=map_data.get('reference_point') ) @@ -183,13 +177,18 @@ def broadcast_roadside_safety_message(self, rsu_id: str, warning_data: Dict) -> data=warning_data, timestamp=time.time(), ttl=3.0, - priority=3, # 安全消息优先级高 + priority=3, position=warning_data.get('event_position') ) self.message_counter += 1 if self.send_message(message): + # 更新安全统计 + if warning_data.get('type') == 'pedestrian': + self.stats['pedestrian_warnings'] += 1 + if warning_data.get('severity') in ['high', 'critical']: + self.stats['safety_alerts'] += 1 return message return None @@ -202,13 +201,11 @@ def get_reachable_nodes(self, sender_id: str, sender_position: tuple) -> List[st if node_id == sender_id: continue - # 计算距离 distance = self._calculate_distance(sender_position, node_info['position']) if distance <= self.communication_range: - # 模拟信号衰减 signal_strength = 1.0 - (distance / self.communication_range) - if signal_strength > 0.3: # 最小信号强度阈值 + if signal_strength > 0.3: reachable.append(node_id) return reachable @@ -228,19 +225,16 @@ def _message_processor(self): """消息处理线程""" while self.running: try: - # 获取下一个要传递的消息 if not self.message_queue.empty(): priority, delivery_time, message = self.message_queue.get_nowait() current_time = time.time() if current_time >= delivery_time: - # 消息已到传递时间 self._deliver_message(message) else: - # 重新放回队列 self.message_queue.put((priority, delivery_time, message)) - time.sleep(0.001) # 短暂休眠 + time.sleep(0.001) else: time.sleep(0.01) @@ -254,18 +248,15 @@ def _deliver_message(self, message: V2XMessage): if not message.position: return - # 获取可达节点 reachable_nodes = self.get_reachable_nodes(message.sender_id, message.position) - # 模拟带宽限制 - message_size = len(json.dumps(asdict(message)).encode('utf-8')) / 1024 / 1024 # MB - bandwidth_required = message_size * 8 * len(reachable_nodes) # Mbps + message_size = len(json.dumps(asdict(message)).encode('utf-8')) / 1024 / 1024 + bandwidth_required = message_size * 8 * len(reachable_nodes) - if bandwidth_required > self.bandwidth * 0.8: # 带宽超过80%时随机丢包 + if bandwidth_required > self.bandwidth * 0.8: drop_probability = min(1.0, bandwidth_required / self.bandwidth - 0.8) reachable_nodes = [n for n in reachable_nodes if np.random.random() > drop_probability] - # 添加到接收消息列表 for node_id in reachable_nodes: received_msg = { 'message': asdict(message), @@ -278,7 +269,6 @@ def _deliver_message(self, message: V2XMessage): self.received_messages.append(received_msg) self.stats['messages_received'] += 1 - # 更新带宽使用统计 self.stats['bandwidth_used'] += bandwidth_required def get_messages_for_node(self, node_id: str, message_types: List[str] = None) -> List[Dict]: @@ -291,7 +281,6 @@ def get_messages_for_node(self, node_id: str, message_types: List[str] = None) - if not message_types or message['message_type'] in message_types: messages.append(msg_record) - # 清理已处理的消息 self.received_messages = [ msg for msg in self.received_messages if msg['receiver_id'] != node_id or @@ -308,7 +297,11 @@ def get_network_status(self) -> Dict: 'bandwidth_utilization': (self.stats['bandwidth_used'] / ( self.bandwidth * time.time())) * 100 if time.time() > 0 else 0, 'message_delivery_rate': self.stats['messages_received'] / max(1, self.stats['messages_sent']), - 'average_latency': self.stats['total_latency'] / max(1, self.stats['messages_sent']) + 'average_latency': self.stats['total_latency'] / max(1, self.stats['messages_sent']), + 'safety_metrics': { + 'pedestrian_warnings': self.stats['pedestrian_warnings'], + 'safety_alerts': self.stats['safety_alerts'] + } } def reset_stats(self): @@ -318,7 +311,9 @@ def reset_stats(self): 'messages_received': 0, 'messages_dropped': 0, 'total_latency': 0.0, - 'bandwidth_used': 0.0 + 'bandwidth_used': 0.0, + 'pedestrian_warnings': 0, + 'safety_alerts': 0 } def stop(self):