Skip to content

Commit f15f7df

Browse files
authored
天气序列随机化 + 多轮测试支持 (#7575)
* readme完成选题 * 更改readme * readme更改 Removed experimental results, author, and license sections from README. * 更改readme * 更改readme * 仿真环境搭建与自车部署 * Add files via upload 操作视频 * 仿真环境搭建与自车部署 * Delete src/.vscode/extensions.json * Delete compressed.mp4 * 添加道路循迹控制器 * 添加天气渐变过渡功能 * 添加雨天视觉效果渲染 * 图像质量多维度详细分解 * 优化:修复空白图表、雨滴效果卡顿及性能问题 * 修复依赖配置并扩展天气场景至10种 * 期末文档提交 * 合并冲突:保留所有感知/规划/控制/其他条目 * 图片 * 更改 * Update index.md * Fix duplicate entry for CARLA weather robustness test * 气序列随机化 + 多轮测试支持 * Update index.md with new entries and corrections * Add new sections for autonomous vehicle features * Fix formatting in index.md for carla monitor entry
1 parent 25a7215 commit f15f7df

1 file changed

Lines changed: 234 additions & 32 deletions

File tree

  • src/carla_weather_robustness

src/carla_weather_robustness/main.py

Lines changed: 234 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import json
1515
import logging
1616
import time
17+
import random
1718
import matplotlib
1819
matplotlib.use("TkAgg")
1920
import matplotlib.pyplot as plt
@@ -349,9 +350,19 @@ def step(self, target_speed, current_speed, dt=0.05):
349350

350351

351352
class WeatherRobustnessSystem:
352-
"""主系统:自动遍历7种天气,实时显示感知数据,输出鲁棒性评分报告"""
353-
354-
def __init__(self):
353+
"""主系统:自动遍历10种天气(随机顺序),实时显示感知数据,输出鲁棒性评分报告"""
354+
355+
def __init__(self, num_rounds=1, shuffle_weather=True, random_seed=None):
356+
"""
357+
Args:
358+
num_rounds: 测试轮数(默认1轮),多轮时每轮天气顺序随机,结果取平均
359+
shuffle_weather: 是否随机打乱天气顺序
360+
random_seed: 随机种子(固定种子可复现结果)
361+
"""
362+
self.num_rounds = num_rounds
363+
self.shuffle_weather = shuffle_weather
364+
if random_seed is not None:
365+
random.seed(random_seed)
355366
self.client = carla.Client(CARLA_HOST, CARLA_PORT)
356367
self.client.set_timeout(CARLA_TIMEOUT)
357368
self.world = self.vehicle = self.camera = self.lidar = None
@@ -367,6 +378,9 @@ def __init__(self):
367378
self._is_transitioning = False # 是否正在过渡
368379
self._is_stable = True # 是否处于稳定期
369380
self._stable_steps = 0 # 稳定期步数计数
381+
# 多轮累积统计
382+
self._all_rounds_reports = [] # 存储每轮报告
383+
self._weather_order_history = [] # 记录每轮天气顺序
370384

371385
def connect(self):
372386
self.world = self.client.load_world(CARLA_MAP)
@@ -698,46 +712,157 @@ def _add_dust_effect(self, frame):
698712
frame = cv2.GaussianBlur(frame, (7, 7), 0)
699713
return frame
700714

715+
def _run_single_round(self, weather_names_ordered, round_idx):
716+
"""运行单轮测试(指定天气顺序)"""
717+
logger.info(f"\n{'='*50}\n{round_idx+1}/{self.num_rounds} 轮测试开始\n 天气顺序: {' -> '.join(WEATHER_LABELS.get(n, n) for n in weather_names_ordered)}\n{'='*50}")
718+
self._weather_order_history.append(weather_names_ordered)
719+
720+
# 重置评分器(每轮独立)
721+
self.scorer = RobustnessScorer()
722+
723+
for weather_name in weather_names_ordered:
724+
self.apply_weather(weather_name)
725+
for step in range(STEPS_PER_WEATHER):
726+
self.run_step(weather_name, step)
727+
# 按Q可提前退出
728+
key = cv2.waitKey(1) & 0xFF
729+
if key == ord('q') or key == ord('Q'):
730+
return False # 提前退出
731+
return True # 正常完成
732+
701733
def run(self):
702734
try:
703735
self.connect()
704-
self.spawn_ego_vehicle()
705-
706-
for weather_name in WEATHER_NAMES:
707-
self.apply_weather(weather_name)
708-
for step in range(STEPS_PER_WEATHER):
709-
self.run_step(weather_name, step)
710-
# 按Q可提前退出
711-
key = cv2.waitKey(1) & 0xFF
712-
if key == ord('q') or key == ord('Q'):
713-
break
714-
else:
715-
continue
716-
break
717736

718-
report = self.scorer.generate_report()
719-
print("\n" + "=" * 60)
720-
print(" 鲁棒性测试报告")
721-
print("=" * 60)
722-
for wname, score in report.items():
723-
if wname == "__overall__":
724-
print(f"\n >>> 总体鲁棒性评分: {score:.1f}/100 <<<")
737+
for round_idx in range(self.num_rounds):
738+
# 每轮重新 spawn 车辆(避免上一轮碰撞影响)
739+
if round_idx == 0:
740+
self.spawn_ego_vehicle()
741+
else:
742+
# 清理上一轮传感器和车辆
743+
for sensor in [self.camera, self.lidar]:
744+
if sensor:
745+
sensor.stop()
746+
sensor.destroy()
747+
if self.vehicle:
748+
self.vehicle.destroy()
749+
self.spawn_ego_vehicle()
750+
# 重置内部状态
751+
self._camera_image = self._lidar_data = None
752+
self._current_weather_params = None
753+
self._target_weather_params = None
754+
self._transition_step = 0
755+
self._is_transitioning = False
756+
self._is_stable = True
757+
self._stable_steps = 0
758+
759+
# 生成本轮天气顺序
760+
if self.shuffle_weather:
761+
weather_order = list(WEATHER_NAMES)
762+
random.shuffle(weather_order)
725763
else:
726-
print(f"\n [{WEATHER_LABELS.get(wname, wname)}] {wname}:")
727-
print(f" 评分={score['score']:.1f}, 碰撞={score['collisions']}, 碰撞率={score['collision_rate']:.3f}")
728-
print(f" 图像质量={score['avg_image_quality']:.2f}, LiDAR主导比例={score['lidar_dominant_ratio']:.2f}")
729-
if "detection_rate" in score:
730-
print(f" 检测率={score['detection_rate']:.3f}, 平均检测距离={score['avg_detection_dist']:.1f}m")
731-
print("\n" + "=" * 60)
764+
weather_order = list(WEATHER_NAMES)
765+
766+
completed = self._run_single_round(weather_order, round_idx)
767+
report = self.scorer.generate_report()
768+
self._all_rounds_reports.append(report)
769+
770+
# 打印单轮报告
771+
self._print_report(report, round_idx)
732772

733-
# 生成多天气对比柱状图
734-
self._plot_comparison_chart(report)
773+
if not completed:
774+
logger.info(f"第 {round_idx+1} 轮被用户中断")
775+
break
776+
777+
# 汇总多轮结果
778+
if len(self._all_rounds_reports) > 1:
779+
self._print_multi_round_summary()
780+
781+
# 生成图表
782+
if len(self._all_rounds_reports) == 1:
783+
self._plot_comparison_chart(self._all_rounds_reports[0])
784+
else:
785+
self._plot_multi_round_chart()
735786

736787
except KeyboardInterrupt:
737788
logger.info("用户中断")
738789
finally:
739790
self.cleanup()
740791

792+
def _print_report(self, report, round_idx):
793+
"""打印单轮测试报告"""
794+
round_label = f"第{round_idx+1}轮" if self.num_rounds > 1 else ""
795+
print("\n" + "=" * 60)
796+
print(f" 鲁棒性测试报告 {round_label}")
797+
print("=" * 60)
798+
for wname, score in report.items():
799+
if wname == "__overall__":
800+
print(f"\n >>> 总体鲁棒性评分: {score:.1f}/100 <<<")
801+
else:
802+
print(f"\n [{WEATHER_LABELS.get(wname, wname)}] {wname}:")
803+
print(f" 评分={score['score']:.1f}, 碰撞={score['collisions']}, 碰撞率={score['collision_rate']:.3f}")
804+
print(f" 图像质量={score['avg_image_quality']:.2f}, LiDAR主导比例={score['lidar_dominant_ratio']:.2f}")
805+
if "detection_rate" in score:
806+
print(f" 检测率={score['detection_rate']:.3f}, 平均检测距离={score['avg_detection_dist']:.1f}m")
807+
print("\n" + "=" * 60)
808+
809+
def _compute_multi_round_average(self):
810+
"""计算多轮平均值"""
811+
avg = {}
812+
all_overalls = []
813+
# 以天气名称为 key 汇总
814+
weather_accum = {wn: {"scores": [], "collisions": [], "collision_rates": [],
815+
"img_qualities": [], "lidar_ratios": [], "detection_rates": [],
816+
"detection_dists": []} for wn in WEATHER_NAMES}
817+
for report in self._all_rounds_reports:
818+
all_overalls.append(report.get("__overall__", 0))
819+
for wn in WEATHER_NAMES:
820+
if wn in report:
821+
s = report[wn]
822+
weather_accum[wn]["scores"].append(s["score"])
823+
weather_accum[wn]["collisions"].append(s["collisions"])
824+
weather_accum[wn]["collision_rates"].append(s["collision_rate"])
825+
weather_accum[wn]["img_qualities"].append(s["avg_image_quality"])
826+
weather_accum[wn]["lidar_ratios"].append(s["lidar_dominant_ratio"])
827+
weather_accum[wn]["detection_rates"].append(s.get("detection_rate", 0))
828+
weather_accum[wn]["detection_dists"].append(s.get("avg_detection_dist", 0))
829+
830+
for wn in WEATHER_NAMES:
831+
acc = weather_accum[wn]
832+
avg[wn] = {
833+
"score": np.mean(acc["scores"]) if acc["scores"] else 0,
834+
"score_std": np.std(acc["scores"]) if len(acc["scores"]) > 1 else 0,
835+
"collisions": np.mean(acc["collisions"]) if acc["collisions"] else 0,
836+
"collision_rate": np.mean(acc["collision_rates"]) if acc["collision_rates"] else 0,
837+
"avg_image_quality": np.mean(acc["img_qualities"]) if acc["img_qualities"] else 0,
838+
"lidar_dominant_ratio": np.mean(acc["lidar_ratios"]) if acc["lidar_ratios"] else 0,
839+
"detection_rate": np.mean(acc["detection_rates"]) if acc["detection_rates"] else 0,
840+
"avg_detection_dist": np.mean(acc["detection_dists"]) if acc["detection_dists"] else 0,
841+
}
842+
avg["__overall__"] = np.mean(all_overalls) if all_overalls else 0
843+
avg["__overall_std__"] = np.std(all_overalls) if len(all_overalls) > 1 else 0
844+
avg["__num_rounds__"] = len(self._all_rounds_reports)
845+
return avg
846+
847+
def _print_multi_round_summary(self):
848+
"""打印多轮汇总报告"""
849+
avg = self._compute_multi_round_average()
850+
print("\n" + "=" * 70)
851+
print(f" 多轮测试汇总报告(共 {avg['__num_rounds__']} 轮,随机天气顺序)")
852+
print("=" * 70)
853+
print(f"\n 天气顺序历史:")
854+
for i, order in enumerate(self._weather_order_history):
855+
names = [WEATHER_LABELS.get(n, n) for n in order]
856+
print(f" 第{i+1}轮: {' -> '.join(names)}")
857+
print(f"\n >>> 综合鲁棒性评分: {avg['__overall__']:.1f} ± {avg['__overall_std__']:.1f} /100 <<<")
858+
for wn in WEATHER_NAMES:
859+
s = avg[wn]
860+
print(f"\n [{WEATHER_LABELS.get(wn, wn)}] {wn}:")
861+
print(f" 评分={s['score']:.1f}±{s['score_std']:.1f}, 碰撞={s['collisions']:.1f}, 碰撞率={s['collision_rate']:.3f}")
862+
print(f" 图像质量={s['avg_image_quality']:.2f}, LiDAR主导比例={s['lidar_dominant_ratio']:.2f}")
863+
print(f" 检测率={s['detection_rate']:.3f}, 平均检测距离={s['avg_detection_dist']:.1f}m")
864+
print("\n" + "=" * 70)
865+
741866
def _plot_comparison_chart(self, report):
742867
"""生成多天气对比柱状图"""
743868
weather_names = [n for n in WEATHER_NAMES if n in report]
@@ -866,5 +991,82 @@ def cleanup(self):
866991
logger.info("资源已清理")
867992

868993

994+
def _plot_multi_round_chart(self):
995+
"""生成多轮平均对比柱状图(带误差线)"""
996+
avg = self._compute_multi_round_average()
997+
weather_names = [n for n in WEATHER_NAMES if n in avg]
998+
if not weather_names:
999+
return
1000+
1001+
labels = [WEATHER_LABELS.get(n, n) for n in weather_names]
1002+
scores = [avg[n]["score"] for n in weather_names]
1003+
score_stds = [avg[n]["score_std"] for n in weather_names]
1004+
img_qualities = [avg[n]["avg_image_quality"] for n in weather_names]
1005+
detection_rates = [avg[n]["detection_rate"] for n in weather_names]
1006+
overall = avg["__overall__"]
1007+
overall_std = avg["__overall_std__"]
1008+
num_rounds = avg["__num_rounds__"]
1009+
1010+
colors = ["#2ecc71", "#3498db", "#f39c12", "#e74c3c", "#9b59b6", "#1abc9c", "#e67e22",
1011+
"#c0392b", "#8e44ad", "#d4ac0d"]
1012+
1013+
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
1014+
fig.suptitle(f"CARLA Weather Robustness Comparison ({num_rounds} Rounds, Random Order)\n"
1015+
f"Overall: {overall:.1f} ± {overall_std:.1f} /100",
1016+
fontsize=14, fontweight="bold")
1017+
1018+
# 1. 鲁棒性评分(带误差线)
1019+
ax = axes[0, 0]
1020+
bars = ax.bar(labels, scores, yerr=score_stds, color=colors[:len(labels)], edgecolor="white",
1021+
capsize=5, error_kw={"linewidth": 1.5})
1022+
ax.set_title(f"Robustness Score (avg of {num_rounds} rounds)")
1023+
ax.set_ylabel("Score / 100")
1024+
ax.set_ylim(0, 100)
1025+
for bar, v, s in zip(bars, scores, score_stds):
1026+
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + s + 1,
1027+
f"{v:.0f}", ha="center", fontsize=9)
1028+
ax.tick_params(axis="x", rotation=30)
1029+
1030+
# 2. 图像质量
1031+
ax = axes[0, 1]
1032+
bars = ax.bar(labels, img_qualities, color=colors[:len(labels)], edgecolor="white")
1033+
ax.set_title("Avg Image Quality")
1034+
ax.set_ylabel("Score")
1035+
ax.set_ylim(0, 1)
1036+
for bar, v in zip(bars, img_qualities):
1037+
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.01, f"{v:.2f}", ha="center", fontsize=9)
1038+
ax.tick_params(axis="x", rotation=30)
1039+
1040+
# 3. 障碍物检测率
1041+
ax = axes[1, 0]
1042+
bars = ax.bar(labels, detection_rates, color=colors[:len(labels)], edgecolor="white")
1043+
ax.set_title("Obstacle Detection Rate")
1044+
ax.set_ylabel("Rate")
1045+
ax.set_ylim(0, 1)
1046+
for bar, v in zip(bars, detection_rates):
1047+
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.01,
1048+
f"{v*100:.0f}%", ha="center", fontsize=9)
1049+
ax.tick_params(axis="x", rotation=30)
1050+
1051+
# 4. 天气顺序历史
1052+
ax = axes[1, 1]
1053+
ax.axis("off")
1054+
ax.set_title("Weather Order History", fontweight="bold")
1055+
history_text = ""
1056+
for i, order in enumerate(self._weather_order_history):
1057+
names = [WEATHER_LABELS.get(n, n) for n in order]
1058+
history_text += f"Round {i+1}:\n"
1059+
for j, n in enumerate(names):
1060+
history_text += f" {j+1}. {n}\n"
1061+
history_text += "\n"
1062+
ax.text(0, 1, history_text.strip(), transform=ax.transAxes, fontsize=9,
1063+
verticalalignment="top", fontfamily="monospace",
1064+
bbox=dict(boxstyle="round,pad=0.5", facecolor="#f5f5f5", edgecolor="#ccc"))
1065+
1066+
plt.tight_layout()
1067+
plt.subplots_adjust(top=0.9)
1068+
plt.show()
1069+
1070+
8691071
if __name__ == "__main__":
870-
WeatherRobustnessSystem().run()
1072+
WeatherRobustnessSystem(num_rounds=2, shuffle_weather=True).run()

0 commit comments

Comments
 (0)