From 5cf452eefbe5238d115b2d0b39342d5c7df12d8a Mon Sep 17 00:00:00 2001 From: rhcgg <3136924319@qq.com> Date: Tue, 23 Sep 2025 08:23:59 +0800 Subject: [PATCH 01/14] initial submission initial submission --- src/Autonomous_vehicle/.idea/.gitignore | 8 ++++++++ src/Autonomous_vehicle/.idea/Autonomous_vehicle.iml | 8 ++++++++ .../.idea/inspectionProfiles/profiles_settings.xml | 6 ++++++ src/Autonomous_vehicle/.idea/misc.xml | 4 ++++ src/Autonomous_vehicle/.idea/modules.xml | 8 ++++++++ src/Autonomous_vehicle/.idea/vcs.xml | 6 ++++++ 6 files changed, 40 insertions(+) create mode 100644 src/Autonomous_vehicle/.idea/.gitignore create mode 100644 src/Autonomous_vehicle/.idea/Autonomous_vehicle.iml create mode 100644 src/Autonomous_vehicle/.idea/inspectionProfiles/profiles_settings.xml create mode 100644 src/Autonomous_vehicle/.idea/misc.xml create mode 100644 src/Autonomous_vehicle/.idea/modules.xml create mode 100644 src/Autonomous_vehicle/.idea/vcs.xml diff --git a/src/Autonomous_vehicle/.idea/.gitignore b/src/Autonomous_vehicle/.idea/.gitignore new file mode 100644 index 0000000000..35410cacdc --- /dev/null +++ b/src/Autonomous_vehicle/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/src/Autonomous_vehicle/.idea/Autonomous_vehicle.iml b/src/Autonomous_vehicle/.idea/Autonomous_vehicle.iml new file mode 100644 index 0000000000..d8b3f6cbf0 --- /dev/null +++ b/src/Autonomous_vehicle/.idea/Autonomous_vehicle.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/Autonomous_vehicle/.idea/inspectionProfiles/profiles_settings.xml b/src/Autonomous_vehicle/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000000..105ce2da2d --- /dev/null +++ b/src/Autonomous_vehicle/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/src/Autonomous_vehicle/.idea/misc.xml b/src/Autonomous_vehicle/.idea/misc.xml new file mode 100644 index 0000000000..23231ce59d --- /dev/null +++ b/src/Autonomous_vehicle/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/Autonomous_vehicle/.idea/modules.xml b/src/Autonomous_vehicle/.idea/modules.xml new file mode 100644 index 0000000000..7d74aa86e9 --- /dev/null +++ b/src/Autonomous_vehicle/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/Autonomous_vehicle/.idea/vcs.xml b/src/Autonomous_vehicle/.idea/vcs.xml new file mode 100644 index 0000000000..b2bdec2d71 --- /dev/null +++ b/src/Autonomous_vehicle/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file From 25e3b2850be603faf6261a9d7a580c5a316d3cce Mon Sep 17 00:00:00 2001 From: rhcgg <3136924319@qq.com> Date: Mon, 29 Sep 2025 10:02:18 +0800 Subject: [PATCH 02/14] =?UTF-8?q?=E9=A6=96=E6=AC=A1=E6=8F=90=E4=BA=A4?= =?UTF-8?q?=E7=A1=AE=E8=AE=A4=E9=80=89=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 选择的课程是无人机 --- src/drone/README.md.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/drone/README.md.txt diff --git a/src/drone/README.md.txt b/src/drone/README.md.txt new file mode 100644 index 0000000000..e69de29bb2 From 60cb6541a2c7231ae377d01ea3d5d73476c0f294 Mon Sep 17 00:00:00 2001 From: rhcgg <3136924319@qq.com> Date: Mon, 29 Sep 2025 16:29:57 +0800 Subject: [PATCH 03/14] =?UTF-8?q?=E9=A6=96=E6=AC=A1=E6=8F=90=E4=BA=A4?= =?UTF-8?q?=E7=A1=AE=E8=AE=A4=E9=80=89=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 选择的课程是无人机 --- src/drone/README.mb.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/drone/README.mb.txt diff --git a/src/drone/README.mb.txt b/src/drone/README.mb.txt new file mode 100644 index 0000000000..e69de29bb2 From 5b88833ee9452943ad9c15a9327355e2304d57fa Mon Sep 17 00:00:00 2001 From: rhcgg <3136924319@qq.com> Date: Mon, 29 Sep 2025 16:35:12 +0800 Subject: [PATCH 04/14] Delete README.md.txt --- src/drone/README.md.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/drone/README.md.txt diff --git a/src/drone/README.md.txt b/src/drone/README.md.txt deleted file mode 100644 index e69de29bb2..0000000000 From e09ad1cdb07dc79c918e25fab23e3b2d612b965d Mon Sep 17 00:00:00 2001 From: rhcgg <3136924319@qq.com> Date: Mon, 27 Oct 2025 09:25:49 +0800 Subject: [PATCH 05/14] drone_perception MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加一个了错误分析 --- src/drone_perception/main.py | 228 +++++++++++++++++++++++++++++------ 1 file changed, 193 insertions(+), 35 deletions(-) diff --git a/src/drone_perception/main.py b/src/drone_perception/main.py index 8b7c9164b0..2066600f9b 100644 --- a/src/drone_perception/main.py +++ b/src/drone_perception/main.py @@ -5,44 +5,47 @@ from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint +import matplotlib.pyplot as plt +from sklearn.metrics import classification_report, confusion_matrix +import seaborn as sns # 路径设置 base_dir = os.path.abspath("../data") # 数据根目录,包含'train'和'test'文件夹 train_dir = os.path.join(base_dir, "train") # 训练集目录路径 -test_dir = os.path.join(base_dir, "test") # 测试集目录路径 +test_dir = os.path.join(base_dir, "test") # 测试集目录路径 # 模型参数设置 img_size = (128, 128) # 图像调整尺寸为128x128像素 -batch_size = 32 # 每个训练批次的样本数量 -epochs = 70 # 训练总轮数 +batch_size = 32 # 每个训练批次的样本数量 +epochs = 70 # 训练总轮数 # 图像数据预处理与增强(用于训练集) train_datagen = ImageDataGenerator( - rescale=1./255, # 像素值归一化到0-1范围 - rotation_range=30, # 随机旋转角度范围±30度 - width_shift_range=0.1, # 水平随机平移范围10% - height_shift_range=0.1, # 垂直随机平移范围10% - shear_range=0.2, # 剪切变换强度 - zoom_range=0.2, # 随机缩放范围 - horizontal_flip=True # 启用水平翻转 + rescale=1. / 255, # 像素值归一化到0-1范围 + rotation_range=30, # 随机旋转角度范围±30度 + width_shift_range=0.1, # 水平随机平移范围10% + height_shift_range=0.1, # 垂直随机平移范围10% + shear_range=0.2, # 剪切变换强度 + zoom_range=0.2, # 随机缩放范围 + horizontal_flip=True # 启用水平翻转 ) # 测试集数据预处理(只做归一化,不进行增强) -test_datagen = ImageDataGenerator(rescale=1./255) +test_datagen = ImageDataGenerator(rescale=1. / 255) # 创建训练数据生成器 train_gen = train_datagen.flow_from_directory( - train_dir, # 训练集目录 - target_size=img_size, # 调整图像大小 - batch_size=batch_size, # 批次大小 + train_dir, # 训练集目录 + target_size=img_size, # 调整图像大小 + batch_size=batch_size, # 批次大小 class_mode="categorical" # 多分类模式 ) # 创建测试数据生成器 test_gen = test_datagen.flow_from_directory( - test_dir, # 测试集目录 - target_size=img_size, # 调整图像大小 - batch_size=batch_size, # 批次大小 + test_dir, # 测试集目录 + target_size=img_size, # 调整图像大小 + batch_size=batch_size, # 批次大小 class_mode="categorical" # 多分类模式 ) @@ -53,25 +56,25 @@ # 加载预训练的MobileNetV2基础模型 base_model = MobileNetV2( input_shape=(128, 128, 3), # 输入图像尺寸 - include_top=False, # 不包含原始顶层分类器 - weights='imagenet' # 使用在ImageNet上预训练的权重 + include_top=False, # 不包含原始顶层分类器 + weights='imagenet' # 使用在ImageNet上预训练的权重 ) -base_model.trainable = False # 冻结基础模型权重,不参与训练 +base_model.trainable = False # 冻结基础模型权重,不参与训练 # 构建迁移学习模型 model = tf.keras.Sequential([ - base_model, # 预训练的特征提取器 - GlobalAveragePooling2D(), # 全局平均池化层,减少参数数量 - Dense(128, activation="relu"), # 全连接层,128个神经元,ReLU激活 - Dropout(0.5), # 丢弃层,丢弃率50%,防止过拟合 + base_model, # 预训练的特征提取器 + GlobalAveragePooling2D(), # 全局平均池化层,减少参数数量 + Dense(128, activation="relu"), # 全连接层,128个神经元,ReLU激活 + Dropout(0.5), # 丢弃层,丢弃率50%,防止过拟合 Dense(train_gen.num_classes, activation="softmax") # 输出层,使用softmax激活 ]) # 编译模型 model.compile( - optimizer="adam", # 使用Adam优化器 - loss="categorical_crossentropy", # 分类交叉熵损失函数 - metrics=["accuracy"] # 评估指标为准确率 + optimizer="adam", # 使用Adam优化器 + loss="categorical_crossentropy", # 分类交叉熵损失函数 + metrics=["accuracy"] # 评估指标为准确率 ) # 打印模型结构摘要 @@ -80,24 +83,24 @@ # 设置训练回调函数 # 早停回调:监控验证集损失,连续5轮无改善则停止训练 early_stop = EarlyStopping( - monitor='val_loss', # 监控验证集损失 - patience=5, # 容忍轮数 + monitor='val_loss', # 监控验证集损失 + patience=5, # 容忍轮数 restore_best_weights=True # 恢复最佳权重 ) # 模型检查点回调:保存最佳模型 checkpoint = ModelCheckpoint( filepath=os.path.join(base_dir, "best_model.h5"), # 模型保存路径 - monitor='val_accuracy', # 监控验证集准确率 - save_best_only=True, # 只保存最佳模型 - verbose=1 # 显示保存信息 + monitor='val_accuracy', # 监控验证集准确率 + save_best_only=True, # 只保存最佳模型 + verbose=1 # 显示保存信息 ) # 开始训练模型 history = model.fit( - train_gen, # 训练数据生成器 - epochs=epochs, # 训练轮数 - validation_data=test_gen, # 验证数据 + train_gen, # 训练数据生成器 + epochs=epochs, # 训练轮数 + validation_data=test_gen, # 验证数据 callbacks=[early_stop, checkpoint] # 使用回调函数 ) @@ -106,3 +109,158 @@ # 训练完成提示 print("模型训练完成并已保存。") + + +# 错误分析函数 +def analyze_errors(model, test_gen, class_labels, num_samples=16): + """ + 分析模型在测试集上的错误分类情况 + + 参数: + - model: 训练好的模型 + - test_gen: 测试数据生成器 + - class_labels: 类别标签列表 + - num_samples: 要显示的错误样本数量 + """ + + # 重置测试生成器 + test_gen.reset() + + # 获取所有预测和真实标签 + predictions = model.predict(test_gen, verbose=1) + predicted_classes = np.argmax(predictions, axis=1) + true_classes = test_gen.classes + + # 计算准确率 + accuracy = np.mean(predicted_classes == true_classes) + print(f"测试集准确率: {accuracy:.4f}") + + # 分类报告 + print("\n分类报告:") + print(classification_report(true_classes, predicted_classes, target_names=class_labels)) + + # 混淆矩阵 + cm = confusion_matrix(true_classes, predicted_classes) + plt.figure(figsize=(10, 8)) + sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', + xticklabels=class_labels, yticklabels=class_labels) + plt.title('混淆矩阵') + plt.xlabel('预测标签') + plt.ylabel('真实标签') + plt.xticks(rotation=45) + plt.yticks(rotation=0) + plt.tight_layout() + plt.savefig(os.path.join(base_dir, 'confusion_matrix.png')) + plt.show() + + # 找出错误分类的样本 + misclassified_indices = np.where(predicted_classes != true_classes)[0] + + print(f"\n总错误分类样本数: {len(misclassified_indices)}") + print(f"总样本数: {len(true_classes)}") + print(f"错误率: {len(misclassified_indices) / len(true_classes):.4f}") + + # 显示一些错误分类的样本 + if len(misclassified_indices) > 0: + # 随机选择一些错误样本进行可视化 + if len(misclassified_indices) > num_samples: + selected_indices = np.random.choice(misclassified_indices, num_samples, replace=False) + else: + selected_indices = misclassified_indices + + # 获取文件名 + filenames = test_gen.filenames + + # 创建错误分类可视化 + plot_misclassified_samples(selected_indices, filenames, true_classes, + predicted_classes, predictions, class_labels, test_gen) + + return misclassified_indices + + +def plot_misclassified_samples(indices, filenames, true_classes, predicted_classes, + predictions, class_labels, test_gen): + """ + 绘制错误分类的样本图像 + """ + # 计算网格大小 + n_cols = 4 + n_rows = (len(indices) + n_cols - 1) // n_cols + + fig, axes = plt.subplots(n_rows, n_cols, figsize=(15, n_rows * 3)) + if n_rows == 1: + axes = [axes] if n_cols == 1 else axes + else: + axes = axes.flatten() + + # 重置生成器以获取原始图像 + test_gen.reset() + all_images = [] + all_batches = len(test_gen) + + # 收集所有图像 + for i in range(all_batches): + images, _ = test_gen[i] + all_images.extend(images) + + for i, idx in enumerate(indices): + if i < len(axes): + ax = axes[i] + + # 显示图像 + ax.imshow(all_images[idx]) + + # 设置标题 + true_label = class_labels[true_classes[idx]] + pred_label = class_labels[predicted_classes[idx]] + confidence = np.max(predictions[idx]) + + title = f"True: {true_label}\nPred: {pred_label}\nConf: {confidence:.3f}" + ax.set_title(title, fontsize=10, color='red') + + # 获取文件名(不包含路径) + filename = os.path.basename(filenames[idx]) + ax.set_xlabel(f"File: {filename}", fontsize=8) + + ax.axis('off') + + # 隐藏多余的子图 + for j in range(len(indices), len(axes)): + axes[j].axis('off') + + plt.suptitle('错误分类样本示例', fontsize=16, y=1.02) + plt.tight_layout() + plt.savefig(os.path.join(base_dir, 'misclassified_samples.png'), + bbox_inches='tight', dpi=300) + plt.show() + + # 打印错误样本的详细信息 + print("\n错误分类样本详情:") + print("-" * 80) + for i, idx in enumerate(indices[:10]): # 只显示前10个的详细信息 + true_label = class_labels[true_classes[idx]] + pred_label = class_labels[predicted_classes[idx]] + confidence = np.max(predictions[idx]) + filename = os.path.basename(filenames[idx]) + + print(f"{i + 1:2d}. 文件: {filename:20s} | 真实: {true_label:15s} | " + f"预测: {pred_label:15s} | 置信度: {confidence:.4f}") + + +# 在训练完成后调用错误分析 +print("开始错误分析...") + +# 获取类别标签 +class_labels = list(train_gen.class_indices.keys()) + +# 加载最佳模型进行错误分析(如果有保存的最佳模型) +best_model_path = os.path.join(base_dir, "best_model.h5") +if os.path.exists(best_model_path): + print("加载最佳模型进行错误分析...") + best_model = tf.keras.models.load_model(best_model_path) + misclassified_indices = analyze_errors(best_model, test_gen, class_labels) +else: + print("使用最终训练模型进行错误分析...") + misclassified_indices = analyze_errors(model, test_gen, class_labels) + +print("\n错误分析完成!") \ No newline at end of file From a7b0d05efcaca89463f102510f21a1771f3921a1 Mon Sep 17 00:00:00 2001 From: rhcgg <3136924319@qq.com> Date: Mon, 27 Oct 2025 09:30:24 +0800 Subject: [PATCH 06/14] drone_perception MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加了一个错误分析 --- src/.idea/.gitignore | 5 +++++ src/.idea/.name | 1 + src/.idea/inspectionProfiles/profiles_settings.xml | 6 ++++++ src/.idea/misc.xml | 7 +++++++ src/.idea/modules.xml | 8 ++++++++ src/.idea/src.iml | 12 ++++++++++++ src/.idea/vcs.xml | 6 ++++++ 7 files changed, 45 insertions(+) create mode 100644 src/.idea/.gitignore create mode 100644 src/.idea/.name create mode 100644 src/.idea/inspectionProfiles/profiles_settings.xml create mode 100644 src/.idea/misc.xml create mode 100644 src/.idea/modules.xml create mode 100644 src/.idea/src.iml create mode 100644 src/.idea/vcs.xml diff --git a/src/.idea/.gitignore b/src/.idea/.gitignore new file mode 100644 index 0000000000..10b731c518 --- /dev/null +++ b/src/.idea/.gitignore @@ -0,0 +1,5 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ diff --git a/src/.idea/.name b/src/.idea/.name new file mode 100644 index 0000000000..11a5d8e18a --- /dev/null +++ b/src/.idea/.name @@ -0,0 +1 @@ +main.py \ No newline at end of file diff --git a/src/.idea/inspectionProfiles/profiles_settings.xml b/src/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000000..105ce2da2d --- /dev/null +++ b/src/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/src/.idea/misc.xml b/src/.idea/misc.xml new file mode 100644 index 0000000000..1d3ce46ba0 --- /dev/null +++ b/src/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/src/.idea/modules.xml b/src/.idea/modules.xml new file mode 100644 index 0000000000..f669a0e594 --- /dev/null +++ b/src/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/.idea/src.iml b/src/.idea/src.iml new file mode 100644 index 0000000000..460d4026f7 --- /dev/null +++ b/src/.idea/src.iml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/.idea/vcs.xml b/src/.idea/vcs.xml new file mode 100644 index 0000000000..6c0b863585 --- /dev/null +++ b/src/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file From e0b6205e2ccbe0c6401b65efc99129b2c24a0a4d Mon Sep 17 00:00:00 2001 From: rhcgg <3136924319@qq.com> Date: Mon, 27 Oct 2025 10:30:23 +0800 Subject: [PATCH 07/14] =?UTF-8?q?=E6=8C=89=E8=A6=81=E6=B1=82=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 按要求修改 --- src/drone_perception/main.py | 38 ++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/drone_perception/main.py b/src/drone_perception/main.py index 2066600f9b..e3a33636a7 100644 --- a/src/drone_perception/main.py +++ b/src/drone_perception/main.py @@ -17,17 +17,17 @@ # 模型参数设置 img_size = (128, 128) # 图像调整尺寸为128x128像素 batch_size = 32 # 每个训练批次的样本数量 -epochs = 70 # 训练总轮数 +epochs = 70 # 训练总轮数 # 图像数据预处理与增强(用于训练集) train_datagen = ImageDataGenerator( - rescale=1. / 255, # 像素值归一化到0-1范围 - rotation_range=30, # 随机旋转角度范围±30度 - width_shift_range=0.1, # 水平随机平移范围10% - height_shift_range=0.1, # 垂直随机平移范围10% - shear_range=0.2, # 剪切变换强度 - zoom_range=0.2, # 随机缩放范围 - horizontal_flip=True # 启用水平翻转 + rescale=1. / 255, # 像素值归一化到0-1范围 + rotation_range=30, # 随机旋转角度范围±30度 + width_shift_range=0.1, # 水平随机平移范围10% + height_shift_range=0.1, # 垂直随机平移范围10% + shear_range=0.2, # 剪切变换强度 + zoom_range=0.2,# 随机缩放范围 + horizontal_flip=True # 启用水平翻转 ) # 测试集数据预处理(只做归一化,不进行增强) @@ -43,7 +43,7 @@ # 创建测试数据生成器 test_gen = test_datagen.flow_from_directory( - test_dir, # 测试集目录 + test_dir, # 测试集目录 target_size=img_size, # 调整图像大小 batch_size=batch_size, # 批次大小 class_mode="categorical" # 多分类模式 @@ -56,7 +56,7 @@ # 加载预训练的MobileNetV2基础模型 base_model = MobileNetV2( input_shape=(128, 128, 3), # 输入图像尺寸 - include_top=False, # 不包含原始顶层分类器 + include_top=False, # 不包含原始顶层分类器 weights='imagenet' # 使用在ImageNet上预训练的权重 ) base_model.trainable = False # 冻结基础模型权重,不参与训练 @@ -64,9 +64,9 @@ # 构建迁移学习模型 model = tf.keras.Sequential([ base_model, # 预训练的特征提取器 - GlobalAveragePooling2D(), # 全局平均池化层,减少参数数量 - Dense(128, activation="relu"), # 全连接层,128个神经元,ReLU激活 - Dropout(0.5), # 丢弃层,丢弃率50%,防止过拟合 + GlobalAveragePooling2D(), # 全局平均池化层,减少参数数量 + Dense(128, activation="relu"), # 全连接层,128个神经元,ReLU激活 + Dropout(0.5), # 丢弃层,丢弃率50%,防止过拟合 Dense(train_gen.num_classes, activation="softmax") # 输出层,使用softmax激活 ]) @@ -84,21 +84,21 @@ # 早停回调:监控验证集损失,连续5轮无改善则停止训练 early_stop = EarlyStopping( monitor='val_loss', # 监控验证集损失 - patience=5, # 容忍轮数 + patience=5, # 容忍轮数 restore_best_weights=True # 恢复最佳权重 ) # 模型检查点回调:保存最佳模型 checkpoint = ModelCheckpoint( filepath=os.path.join(base_dir, "best_model.h5"), # 模型保存路径 - monitor='val_accuracy', # 监控验证集准确率 - save_best_only=True, # 只保存最佳模型 - verbose=1 # 显示保存信息 + monitor='val_accuracy', # 监控验证集准确率 + save_best_only=True, # 只保存最佳模型 + verbose=1 # 显示保存信息 ) # 开始训练模型 history = model.fit( - train_gen, # 训练数据生成器 + train_gen, # 训练数据生成器 epochs=epochs, # 训练轮数 validation_data=test_gen, # 验证数据 callbacks=[early_stop, checkpoint] # 使用回调函数 @@ -160,7 +160,7 @@ def analyze_errors(model, test_gen, class_labels, num_samples=16): print(f"总样本数: {len(true_classes)}") print(f"错误率: {len(misclassified_indices) / len(true_classes):.4f}") - # 显示一些错误分类的样本 + # 显示一些错误分类的样本 if len(misclassified_indices) > 0: # 随机选择一些错误样本进行可视化 if len(misclassified_indices) > num_samples: From 2fc55584bb9c06cf161b5506adfbb2ef91f9825d Mon Sep 17 00:00:00 2001 From: rhcgg <3136924319@qq.com> Date: Mon, 3 Nov 2025 09:08:29 +0800 Subject: [PATCH 08/14] =?UTF-8?q?=E6=8C=89=E8=A6=81=E6=B1=82=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 按要求修改 --- src/.idea/.gitignore | 5 ----- src/.idea/.name | 1 - src/.idea/inspectionProfiles/profiles_settings.xml | 6 ------ src/.idea/misc.xml | 7 ------- src/.idea/modules.xml | 8 -------- src/.idea/src.iml | 12 ------------ src/.idea/vcs.xml | 6 ------ 7 files changed, 45 deletions(-) delete mode 100644 src/.idea/.gitignore delete mode 100644 src/.idea/.name delete mode 100644 src/.idea/inspectionProfiles/profiles_settings.xml delete mode 100644 src/.idea/misc.xml delete mode 100644 src/.idea/modules.xml delete mode 100644 src/.idea/src.iml delete mode 100644 src/.idea/vcs.xml diff --git a/src/.idea/.gitignore b/src/.idea/.gitignore deleted file mode 100644 index 10b731c518..0000000000 --- a/src/.idea/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# 默认忽略的文件 -/shelf/ -/workspace.xml -# 基于编辑器的 HTTP 客户端请求 -/httpRequests/ diff --git a/src/.idea/.name b/src/.idea/.name deleted file mode 100644 index 11a5d8e18a..0000000000 --- a/src/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -main.py \ No newline at end of file diff --git a/src/.idea/inspectionProfiles/profiles_settings.xml b/src/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 105ce2da2d..0000000000 --- a/src/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/.idea/misc.xml b/src/.idea/misc.xml deleted file mode 100644 index 1d3ce46ba0..0000000000 --- a/src/.idea/misc.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/.idea/modules.xml b/src/.idea/modules.xml deleted file mode 100644 index f669a0e594..0000000000 --- a/src/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/src/.idea/src.iml b/src/.idea/src.iml deleted file mode 100644 index 460d4026f7..0000000000 --- a/src/.idea/src.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/src/.idea/vcs.xml b/src/.idea/vcs.xml deleted file mode 100644 index 6c0b863585..0000000000 --- a/src/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file From 38a3600da47a51f85378d1f979629fa0a28f99b3 Mon Sep 17 00:00:00 2001 From: rhcgg <3136924319@qq.com> Date: Mon, 3 Nov 2025 09:28:49 +0800 Subject: [PATCH 09/14] =?UTF-8?q?=E6=8C=89=E8=A6=81=E6=B1=82=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 按要求修改 --- src/.idea/.gitignore | 5 ++ src/.idea/.name | 1 + .../inspectionProfiles/profiles_settings.xml | 6 ++ src/.idea/misc.xml | 7 +++ src/.idea/modules.xml | 8 +++ src/.idea/src.iml | 12 ++++ src/.idea/vcs.xml | 6 ++ src/drone/README.mb.txt | 0 src/drone/README.md | 1 + src/drone_perception/main.py | 58 +++++++++---------- 10 files changed, 75 insertions(+), 29 deletions(-) create mode 100644 src/.idea/.gitignore create mode 100644 src/.idea/.name create mode 100644 src/.idea/inspectionProfiles/profiles_settings.xml create mode 100644 src/.idea/misc.xml create mode 100644 src/.idea/modules.xml create mode 100644 src/.idea/src.iml create mode 100644 src/.idea/vcs.xml delete mode 100644 src/drone/README.mb.txt diff --git a/src/.idea/.gitignore b/src/.idea/.gitignore new file mode 100644 index 0000000000..10b731c518 --- /dev/null +++ b/src/.idea/.gitignore @@ -0,0 +1,5 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ diff --git a/src/.idea/.name b/src/.idea/.name new file mode 100644 index 0000000000..42061c01a1 --- /dev/null +++ b/src/.idea/.name @@ -0,0 +1 @@ +README.md \ No newline at end of file diff --git a/src/.idea/inspectionProfiles/profiles_settings.xml b/src/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000000..105ce2da2d --- /dev/null +++ b/src/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/src/.idea/misc.xml b/src/.idea/misc.xml new file mode 100644 index 0000000000..a708d0f82c --- /dev/null +++ b/src/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/src/.idea/modules.xml b/src/.idea/modules.xml new file mode 100644 index 0000000000..f669a0e594 --- /dev/null +++ b/src/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/.idea/src.iml b/src/.idea/src.iml new file mode 100644 index 0000000000..5afcb46ef9 --- /dev/null +++ b/src/.idea/src.iml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/.idea/vcs.xml b/src/.idea/vcs.xml new file mode 100644 index 0000000000..6c0b863585 --- /dev/null +++ b/src/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/drone/README.mb.txt b/src/drone/README.mb.txt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/drone/README.md b/src/drone/README.md index a977c11ab4..ce4d681d53 100644 --- a/src/drone/README.md +++ b/src/drone/README.md @@ -1,2 +1,3 @@ 无人机 飞行器感知 + diff --git a/src/drone_perception/main.py b/src/drone_perception/main.py index e3a33636a7..5ba6d38b7a 100644 --- a/src/drone_perception/main.py +++ b/src/drone_perception/main.py @@ -11,23 +11,23 @@ # 路径设置 base_dir = os.path.abspath("../data") # 数据根目录,包含'train'和'test'文件夹 -train_dir = os.path.join(base_dir, "train") # 训练集目录路径 +train_dir = os.path.join(base_dir, "train") # 训练集目录路径 test_dir = os.path.join(base_dir, "test") # 测试集目录路径 # 模型参数设置 img_size = (128, 128) # 图像调整尺寸为128x128像素 -batch_size = 32 # 每个训练批次的样本数量 -epochs = 70 # 训练总轮数 +batch_size = 32 # 每个训练批次的样本数量 +epochs = 70# 训练总轮数 # 图像数据预处理与增强(用于训练集) train_datagen = ImageDataGenerator( - rescale=1. / 255, # 像素值归一化到0-1范围 - rotation_range=30, # 随机旋转角度范围±30度 - width_shift_range=0.1, # 水平随机平移范围10% - height_shift_range=0.1, # 垂直随机平移范围10% - shear_range=0.2, # 剪切变换强度 + rescale=1. /255,# 像素值归一化到0-1范围 + rotation_range=30,# 随机旋转角度范围±30度 + width_shift_range=0.1,# 水平随机平移范围10% + height_shift_range=0.1,# 垂直随机平移范围10% + shear_range=0.2,# 剪切变换强度 zoom_range=0.2,# 随机缩放范围 - horizontal_flip=True # 启用水平翻转 + horizontal_flip=True# 启用水平翻转 ) # 测试集数据预处理(只做归一化,不进行增强) @@ -35,17 +35,17 @@ # 创建训练数据生成器 train_gen = train_datagen.flow_from_directory( - train_dir, # 训练集目录 - target_size=img_size, # 调整图像大小 - batch_size=batch_size, # 批次大小 + train_dir, # 训练集目录 + target_size=img_size, # 调整图像大小 + batch_size=batch_size, # 批次大小 class_mode="categorical" # 多分类模式 ) # 创建测试数据生成器 test_gen = test_datagen.flow_from_directory( - test_dir, # 测试集目录 - target_size=img_size, # 调整图像大小 - batch_size=batch_size, # 批次大小 + test_dir,# 测试集目录 + target_size=img_size, # 调整图像大小 + batch_size=batch_size, # 批次大小 class_mode="categorical" # 多分类模式 ) @@ -56,25 +56,25 @@ # 加载预训练的MobileNetV2基础模型 base_model = MobileNetV2( input_shape=(128, 128, 3), # 输入图像尺寸 - include_top=False, # 不包含原始顶层分类器 - weights='imagenet' # 使用在ImageNet上预训练的权重 + include_top=False,# 不包含原始顶层分类器 + weights='imagenet' # 使用在ImageNet上预训练的权重 ) -base_model.trainable = False # 冻结基础模型权重,不参与训练 +base_model.trainable = False # 冻结基础模型权重,不参与训练 # 构建迁移学习模型 model = tf.keras.Sequential([ - base_model, # 预训练的特征提取器 - GlobalAveragePooling2D(), # 全局平均池化层,减少参数数量 + base_model, # 预训练的特征提取器 + GlobalAveragePooling2D(),# 全局平均池化层,减少参数数量 Dense(128, activation="relu"), # 全连接层,128个神经元,ReLU激活 - Dropout(0.5), # 丢弃层,丢弃率50%,防止过拟合 + Dropout(0.5),# 丢弃层,丢弃率50%,防止过拟合 Dense(train_gen.num_classes, activation="softmax") # 输出层,使用softmax激活 ]) # 编译模型 model.compile( - optimizer="adam", # 使用Adam优化器 - loss="categorical_crossentropy", # 分类交叉熵损失函数 - metrics=["accuracy"] # 评估指标为准确率 + optimizer="adam", # 使用Adam优化器 + loss="categorical_crossentropy", # 分类交叉熵损失函数 + metrics=["accuracy"] # 评估指标为准确率 ) # 打印模型结构摘要 @@ -83,17 +83,17 @@ # 设置训练回调函数 # 早停回调:监控验证集损失,连续5轮无改善则停止训练 early_stop = EarlyStopping( - monitor='val_loss', # 监控验证集损失 - patience=5, # 容忍轮数 + monitor='val_loss', # 监控验证集损失 + patience=5,# 容忍轮数 restore_best_weights=True # 恢复最佳权重 ) # 模型检查点回调:保存最佳模型 checkpoint = ModelCheckpoint( filepath=os.path.join(base_dir, "best_model.h5"), # 模型保存路径 - monitor='val_accuracy', # 监控验证集准确率 - save_best_only=True, # 只保存最佳模型 - verbose=1 # 显示保存信息 + monitor='val_accuracy',# 监控验证集准确率 + save_best_only=True,# 只保存最佳模型 + verbose=1# 显示保存信息 ) # 开始训练模型 From 1f05251c31faec0e4e9ab369fecb83c93431505b Mon Sep 17 00:00:00 2001 From: rhcgg <3136924319@qq.com> Date: Mon, 3 Nov 2025 09:37:50 +0800 Subject: [PATCH 10/14] =?UTF-8?q?=E6=8C=89=E8=A6=81=E6=B1=82=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 按要求修改 --- src/.idea/.gitignore | 5 ----- src/.idea/.name | 1 - src/.idea/inspectionProfiles/profiles_settings.xml | 6 ------ src/.idea/misc.xml | 7 ------- src/.idea/modules.xml | 8 -------- src/.idea/src.iml | 12 ------------ src/.idea/vcs.xml | 6 ------ 7 files changed, 45 deletions(-) delete mode 100644 src/.idea/.gitignore delete mode 100644 src/.idea/.name delete mode 100644 src/.idea/inspectionProfiles/profiles_settings.xml delete mode 100644 src/.idea/misc.xml delete mode 100644 src/.idea/modules.xml delete mode 100644 src/.idea/src.iml delete mode 100644 src/.idea/vcs.xml diff --git a/src/.idea/.gitignore b/src/.idea/.gitignore deleted file mode 100644 index 10b731c518..0000000000 --- a/src/.idea/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# 默认忽略的文件 -/shelf/ -/workspace.xml -# 基于编辑器的 HTTP 客户端请求 -/httpRequests/ diff --git a/src/.idea/.name b/src/.idea/.name deleted file mode 100644 index 42061c01a1..0000000000 --- a/src/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -README.md \ No newline at end of file diff --git a/src/.idea/inspectionProfiles/profiles_settings.xml b/src/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 105ce2da2d..0000000000 --- a/src/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/.idea/misc.xml b/src/.idea/misc.xml deleted file mode 100644 index a708d0f82c..0000000000 --- a/src/.idea/misc.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/.idea/modules.xml b/src/.idea/modules.xml deleted file mode 100644 index f669a0e594..0000000000 --- a/src/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/src/.idea/src.iml b/src/.idea/src.iml deleted file mode 100644 index 5afcb46ef9..0000000000 --- a/src/.idea/src.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/src/.idea/vcs.xml b/src/.idea/vcs.xml deleted file mode 100644 index 6c0b863585..0000000000 --- a/src/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file From 30bde9602ef148b93a9d29bb53c17504911314cb Mon Sep 17 00:00:00 2001 From: rhcgg <3136924319@qq.com> Date: Mon, 3 Nov 2025 09:48:23 +0800 Subject: [PATCH 11/14] =?UTF-8?q?=E6=8C=89=E8=A6=81=E6=B1=82=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 按要求修改 --- src/drone_perception/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/drone_perception/main.py b/src/drone_perception/main.py index 5ba6d38b7a..99cbcf9975 100644 --- a/src/drone_perception/main.py +++ b/src/drone_perception/main.py @@ -11,7 +11,7 @@ # 路径设置 base_dir = os.path.abspath("../data") # 数据根目录,包含'train'和'test'文件夹 -train_dir = os.path.join(base_dir, "train") # 训练集目录路径 +train_dir = os.path.join(base_dir, "train")# 训练集目录路径 test_dir = os.path.join(base_dir, "test") # 测试集目录路径 # 模型参数设置 From f0ea4260d2ed8378f9459d5bda5f87c97ca60265 Mon Sep 17 00:00:00 2001 From: rhcgg <3136924319@qq.com> Date: Mon, 3 Nov 2025 10:23:58 +0800 Subject: [PATCH 12/14] =?UTF-8?q?=E6=8C=89=E8=A6=81=E6=B1=82=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 按要求修改 --- src/Autonomous_vehicle/.idea/.gitignore | 8 -------- src/Autonomous_vehicle/.idea/Autonomous_vehicle.iml | 8 -------- .../.idea/inspectionProfiles/profiles_settings.xml | 6 ------ src/Autonomous_vehicle/.idea/misc.xml | 4 ---- src/Autonomous_vehicle/.idea/modules.xml | 8 -------- src/Autonomous_vehicle/.idea/vcs.xml | 6 ------ 6 files changed, 40 deletions(-) delete mode 100644 src/Autonomous_vehicle/.idea/.gitignore delete mode 100644 src/Autonomous_vehicle/.idea/Autonomous_vehicle.iml delete mode 100644 src/Autonomous_vehicle/.idea/inspectionProfiles/profiles_settings.xml delete mode 100644 src/Autonomous_vehicle/.idea/misc.xml delete mode 100644 src/Autonomous_vehicle/.idea/modules.xml delete mode 100644 src/Autonomous_vehicle/.idea/vcs.xml diff --git a/src/Autonomous_vehicle/.idea/.gitignore b/src/Autonomous_vehicle/.idea/.gitignore deleted file mode 100644 index 35410cacdc..0000000000 --- a/src/Autonomous_vehicle/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# 默认忽略的文件 -/shelf/ -/workspace.xml -# 基于编辑器的 HTTP 客户端请求 -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/src/Autonomous_vehicle/.idea/Autonomous_vehicle.iml b/src/Autonomous_vehicle/.idea/Autonomous_vehicle.iml deleted file mode 100644 index d8b3f6cbf0..0000000000 --- a/src/Autonomous_vehicle/.idea/Autonomous_vehicle.iml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/src/Autonomous_vehicle/.idea/inspectionProfiles/profiles_settings.xml b/src/Autonomous_vehicle/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 105ce2da2d..0000000000 --- a/src/Autonomous_vehicle/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/Autonomous_vehicle/.idea/misc.xml b/src/Autonomous_vehicle/.idea/misc.xml deleted file mode 100644 index 23231ce59d..0000000000 --- a/src/Autonomous_vehicle/.idea/misc.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/Autonomous_vehicle/.idea/modules.xml b/src/Autonomous_vehicle/.idea/modules.xml deleted file mode 100644 index 7d74aa86e9..0000000000 --- a/src/Autonomous_vehicle/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/src/Autonomous_vehicle/.idea/vcs.xml b/src/Autonomous_vehicle/.idea/vcs.xml deleted file mode 100644 index b2bdec2d71..0000000000 --- a/src/Autonomous_vehicle/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file From 38a035f4b9444083e6a970de304908bbdedeb050 Mon Sep 17 00:00:00 2001 From: rhcgg <3136924319@qq.com> Date: Mon, 15 Dec 2025 10:15:47 +0800 Subject: [PATCH 13/14] Drone Perception MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Drone Perception Module ## 概述 无人机感知模块,提供目标检测、深度估计和障碍物感知功能。 ## 功能特性 - 基于颜色的目标检测 - 基于图像强度的深度估计 - 障碍物检测与安全评分 - 结果可视化 --- src/drone_perception/dron.py | 355 +++++++++++++++++++++++++++++++++++ 1 file changed, 355 insertions(+) create mode 100644 src/drone_perception/dron.py diff --git a/src/drone_perception/dron.py b/src/drone_perception/dron.py new file mode 100644 index 0000000000..9a33632dfe --- /dev/null +++ b/src/drone_perception/dron.py @@ -0,0 +1,355 @@ +""" +drone_perception.py +无人机感知模块 - 用于目标检测、深度估计和障碍物感知 +""" + +import cv2 +import numpy as np +from typing import Tuple, Optional, List +import time + + +class DronePerception: + """ + 无人机感知模块主类 + 提供目标检测、深度估计和障碍物感知功能 + """ + + def __init__(self, camera_params: Optional[dict] = None): + """ + 初始化感知模块 + + Args: + camera_params: 相机内参矩阵和畸变系数 + """ + self.camera_params = camera_params or { + 'fx': 920.0, 'fy': 920.0, # 焦距 + 'cx': 640.0, 'cy': 360.0, # 主点坐标 + 'width': 1280, 'height': 720 + } + + # 初始化目标检测器 + self.target_detector = TargetDetector() + + # 初始化深度估计器 + self.depth_estimator = DepthEstimator() + + # 状态变量 + self.obstacles = [] + self.targets = [] + self.last_update_time = time.time() + + print("DronePerception模块初始化完成") + + def process_frame(self, frame: np.ndarray) -> dict: + """ + 处理单帧图像 + + Args: + frame: 输入图像帧 (BGR格式) + + Returns: + 包含感知结果的字典 + """ + if frame is None: + return {"error": "空帧输入"} + + results = { + 'timestamp': time.time(), + 'frame_shape': frame.shape, + 'targets': [], + 'obstacles': [], + 'depth_map': None + } + + try: + # 1. 目标检测 + self.targets = self.target_detector.detect(frame) + results['targets'] = self.targets + + # 2. 深度估计 + depth_map = self.depth_estimator.estimate(frame) + results['depth_map'] = depth_map + + # 3. 障碍物检测 + self.obstacles = self.detect_obstacles(frame, depth_map) + results['obstacles'] = self.obstacles + + # 4. 安全区域分析 + results['safety_score'] = self.analyze_safety(self.obstacles) + + self.last_update_time = time.time() + + except Exception as e: + results['error'] = str(e) + print(f"处理帧时出错: {e}") + + return results + + def detect_obstacles(self, frame: np.ndarray, depth_map: Optional[np.ndarray]) -> List[dict]: + """ + 检测障碍物 + + Args: + frame: 输入图像 + depth_map: 深度图 + + Returns: + 障碍物列表 + """ + obstacles = [] + + # 简单边缘检测作为障碍物提示 + gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) + edges = cv2.Canny(gray, 50, 150) + + # 查找轮廓 + contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + + for contour in contours[:5]: # 只处理前5个最大轮廓 + if cv2.contourArea(contour) > 500: # 面积阈值 + x, y, w, h = cv2.boundingRect(contour) + + obstacle = { + 'bbox': [x, y, x + w, y + h], + 'area': cv2.contourArea(contour), + 'center': (x + w // 2, y + h // 2), + 'type': 'unknown' + } + + # 如果有深度信息,估算距离 + if depth_map is not None and y < depth_map.shape[0] and x < depth_map.shape[1]: + roi_depth = depth_map[y:y + h, x:x + w] + if roi_depth.size > 0: + obstacle['estimated_distance'] = float(np.mean(roi_depth)) + + obstacles.append(obstacle) + + return obstacles + + def analyze_safety(self, obstacles: List[dict]) -> float: + """ + 分析当前帧的安全性 + + Args: + obstacles: 障碍物列表 + + Returns: + 安全分数 (0-1) + """ + if not obstacles: + return 1.0 + + # 简单的安全评分逻辑 + danger_score = 0 + + for obs in obstacles: + # 障碍物大小越大,危险分数越高 + area_factor = min(obs['area'] / 10000, 1.0) + + # 距离越近,危险分数越高 + if 'estimated_distance' in obs: + dist_factor = max(0, 1.0 - (obs['estimated_distance'] / 20.0)) + else: + dist_factor = 0.5 + + danger_score += area_factor * dist_factor + + safety_score = max(0, 1.0 - min(danger_score, 1.0)) + return round(safety_score, 2) + + def visualize(self, frame: np.ndarray, results: dict) -> np.ndarray: + """ + 可视化感知结果 + + Args: + frame: 原始图像帧 + results: 感知结果 + + Returns: + 可视化后的图像 + """ + vis_frame = frame.copy() + + # 绘制目标边界框 + for target in results.get('targets', []): + if 'bbox' in target: + x1, y1, x2, y2 = map(int, target['bbox']) + cv2.rectangle(vis_frame, (x1, y1), (x2, y2), (0, 255, 0), 2) + if 'class_name' in target: + cv2.putText(vis_frame, target['class_name'], + (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, + 0.5, (0, 255, 0), 2) + + # 绘制障碍物边界框 + for obstacle in results.get('obstacles', []): + if 'bbox' in obstacle: + x1, y1, x2, y2 = map(int, obstacle['bbox']) + cv2.rectangle(vis_frame, (x1, y1), (x2, y2), (0, 0, 255), 2) + + # 显示估计距离 + if 'estimated_distance' in obstacle: + dist_text = f"{obstacle['estimated_distance']:.1f}m" + cv2.putText(vis_frame, dist_text, + (x1, y1 - 30), cv2.FONT_HERSHEY_SIMPLEX, + 0.5, (0, 0, 255), 2) + + # 显示安全分数 + safety_score = results.get('safety_score', 1.0) + safety_color = (0, 255, 0) if safety_score > 0.7 else (0, 255, 255) if safety_score > 0.4 else (0, 0, 255) + + cv2.putText(vis_frame, f"Safety: {safety_score}", + (10, 30), cv2.FONT_HERSHEY_SIMPLEX, + 1, safety_color, 2) + + # 显示帧信息 + cv2.putText(vis_frame, f"Targets: {len(results.get('targets', []))}", + (10, 60), cv2.FONT_HERSHEY_SIMPLEX, + 0.6, (255, 255, 255), 1) + cv2.putText(vis_frame, f"Obstacles: {len(results.get('obstacles', []))}", + (10, 80), cv2.FONT_HERSHEY_SIMPLEX, + 0.6, (255, 255, 255), 1) + + return vis_frame + + def get_status(self) -> dict: + """ + 获取模块状态 + + Returns: + 状态字典 + """ + return { + 'active': True, + 'last_update': self.last_update_time, + 'camera_params': self.camera_params, + 'target_count': len(self.targets), + 'obstacle_count': len(self.obstacles) + } + + +class TargetDetector: + """目标检测器""" + + def __init__(self): + # 这里可以加载YOLO等模型 + # 简化版本使用预定义的HSV颜色范围 + self.color_ranges = { + 'red': ([0, 100, 100], [10, 255, 255]), + 'green': ([40, 50, 50], [80, 255, 255]), + 'blue': ([100, 50, 50], [130, 255, 255]) + } + + def detect(self, frame: np.ndarray) -> List[dict]: + """ + 检测目标 + + Args: + frame: 输入图像 + + Returns: + 检测到的目标列表 + """ + targets = [] + hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) + + for color_name, (lower, upper) in self.color_ranges.items(): + lower_np = np.array(lower, dtype=np.uint8) + upper_np = np.array(upper, dtype=np.uint8) + + mask = cv2.inRange(hsv, lower_np, upper_np) + contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + + for contour in contours: + if cv2.contourArea(contour) > 300: # 面积过滤 + x, y, w, h = cv2.boundingRect(contour) + + target = { + 'bbox': [x, y, x + w, y + h], + 'class_name': color_name, + 'confidence': 0.8, + 'area': cv2.contourArea(contour) + } + targets.append(target) + + return targets + + +class DepthEstimator: + """深度估计器""" + + def __init__(self): + # 这里可以加载MiDaS等深度估计模型 + pass + + def estimate(self, frame: np.ndarray) -> Optional[np.ndarray]: + """ + 估计深度图 + + Args: + frame: 输入图像 + + Returns: + 深度图 (归一化到0-1) + """ + # 简化版本:使用图像强度作为深度代理 + gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) + depth_map = cv2.GaussianBlur(gray, (15, 15), 0) + + # 归一化 + if depth_map.max() > depth_map.min(): + depth_map = (depth_map - depth_map.min()) / (depth_map.max() - depth_map.min()) + + # 模拟距离:假设图像中心区域更近 + h, w = depth_map.shape + y_coords, x_coords = np.ogrid[:h, :w] + center_mask = 1.0 - np.sqrt(((x_coords - w // 2) / (w // 2)) ** 2 + + ((y_coords - h // 2) / (h // 2)) ** 2) + center_mask = np.clip(center_mask, 0, 1) + + # 结合强度信息和中心权重 + depth_map = 0.7 * depth_map + 0.3 * center_mask + + return depth_map + + +def test_perception(): + """测试函数""" + print("=== 测试DronePerception模块 ===") + + # 初始化感知模块 + perception = DronePerception() + + # 创建测试图像 + test_frame = np.zeros((480, 640, 3), dtype=np.uint8) + + # 添加一些彩色方块模拟目标 + cv2.rectangle(test_frame, (100, 100), (200, 200), (0, 255, 0), -1) # 绿色 + cv2.rectangle(test_frame, (400, 150), (500, 250), (0, 0, 255), -1) # 红色 + cv2.rectangle(test_frame, (300, 300), (400, 400), (255, 0, 0), -1) # 蓝色 + + # 处理帧 + results = perception.process_frame(test_frame) + + # 打印结果 + print(f"检测到目标数量: {len(results['targets'])}") + print(f"检测到障碍物数量: {len(results['obstacles'])}") + print(f"安全分数: {results['safety_score']}") + + # 可视化 + vis_frame = perception.visualize(test_frame, results) + + # 显示结果 + cv2.imshow('Perception Test', vis_frame) + cv2.waitKey(0) + cv2.destroyAllWindows() + + # 显示模块状态 + print("\n模块状态:") + for key, value in perception.get_status().items(): + print(f" {key}: {value}") + + +if __name__ == "__main__": + # 直接运行测试 + test_perception() \ No newline at end of file From 59bd4a959d514027f1c9f6a9d947212338c97ad9 Mon Sep 17 00:00:00 2001 From: rhcgg <3136924319@qq.com> Date: Mon, 15 Dec 2025 10:26:14 +0800 Subject: [PATCH 14/14] Drone Perception MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Drone Perception Module ## 概述 无人机感知模块,提供目标检测、深度估计和障碍物感知功能。 ## 功能特性 - 基于颜色的目标检测(红、绿、蓝、黄) - 基于图像强度的深度估计 - 障碍物检测与安全评分 - 结果可视化(保存为图像文件) - 无GUI依赖,适合服务器环境 --- src/drone_perception/dron.py | 338 ++++++++++++++++++++++++++++------- 1 file changed, 272 insertions(+), 66 deletions(-) diff --git a/src/drone_perception/dron.py b/src/drone_perception/dron.py index 9a33632dfe..c873064205 100644 --- a/src/drone_perception/dron.py +++ b/src/drone_perception/dron.py @@ -7,7 +7,7 @@ import numpy as np from typing import Tuple, Optional, List import time - +import os class DronePerception: """ @@ -99,29 +99,64 @@ def detect_obstacles(self, frame: np.ndarray, depth_map: Optional[np.ndarray]) - """ obstacles = [] - # 简单边缘检测作为障碍物提示 + # 只检测非目标区域的障碍物(避免重复检测目标) + mask = np.ones(frame.shape[:2], dtype=np.uint8) * 255 + + # 在目标区域创建掩码,避免将目标误检测为障碍物 + for target in self.targets: + if 'bbox' in target: + x1, y1, x2, y2 = map(int, target['bbox']) + cv2.rectangle(mask, (x1, y1), (x2, y2), 0, -1) + + # 使用掩码后的图像进行障碍物检测 gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) - edges = cv2.Canny(gray, 50, 150) + gray_masked = cv2.bitwise_and(gray, gray, mask=mask) + + # 使用自适应阈值而不是Canny,减少噪声 + blurred = cv2.GaussianBlur(gray_masked, (5, 5), 0) + edges = cv2.adaptiveThreshold(blurred, 255, + cv2.ADAPTIVE_THRESH_GAUSSIAN_C, + cv2.THRESH_BINARY, 11, 2) + + # 形态学操作去除小噪声 + kernel = np.ones((3,3), np.uint8) + edges = cv2.morphologyEx(edges, cv2.MORPH_CLOSE, kernel) # 查找轮廓 contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) - for contour in contours[:5]: # 只处理前5个最大轮廓 - if cv2.contourArea(contour) > 500: # 面积阈值 + for contour in contours[:10]: # 只处理前10个最大轮廓 + area = cv2.contourArea(contour) + if 100 < area < 50000: # 调整面积阈值范围 x, y, w, h = cv2.boundingRect(contour) + # 计算轮廓的中心 + M = cv2.moments(contour) + if M["m00"] != 0: + cX = int(M["m10"] / M["m00"]) + cY = int(M["m01"] / M["m00"]) + else: + cX, cY = x + w//2, y + h//2 + obstacle = { - 'bbox': [x, y, x + w, y + h], - 'area': cv2.contourArea(contour), - 'center': (x + w // 2, y + h // 2), - 'type': 'unknown' + 'bbox': [x, y, x+w, y+h], + 'area': area, + 'center': (cX, cY), + 'type': 'unknown', + 'contour_points': len(contour) } # 如果有深度信息,估算距离 if depth_map is not None and y < depth_map.shape[0] and x < depth_map.shape[1]: - roi_depth = depth_map[y:y + h, x:x + w] + # 获取ROI区域内的深度值 + y_end = min(y+h, depth_map.shape[0]) + x_end = min(x+w, depth_map.shape[1]) + roi_depth = depth_map[y:y_end, x:x_end] + if roi_depth.size > 0: - obstacle['estimated_distance'] = float(np.mean(roi_depth)) + # 使用中值而不是均值,减少异常值影响 + median_depth = np.median(roi_depth) + obstacle['estimated_distance'] = float(median_depth) obstacles.append(obstacle) @@ -140,22 +175,42 @@ def analyze_safety(self, obstacles: List[dict]) -> float: if not obstacles: return 1.0 - # 简单的安全评分逻辑 danger_score = 0 + max_danger_per_obstacle = 0.3 # 每个障碍物最大危险分数 for obs in obstacles: - # 障碍物大小越大,危险分数越高 - area_factor = min(obs['area'] / 10000, 1.0) + # 障碍物大小越大,危险分数越高(对数缩放) + area_factor = min(np.log10(obs['area'] / 100 + 1), 1.0) - # 距离越近,危险分数越高 + # 距离因素 if 'estimated_distance' in obs: - dist_factor = max(0, 1.0 - (obs['estimated_distance'] / 20.0)) + # 距离越近危险越大,指数衰减 + dist = obs['estimated_distance'] + if dist <= 0.1: # 非常近 + dist_factor = 1.0 + else: + dist_factor = min(2.0 / (dist + 0.1), 1.0) else: - dist_factor = 0.5 + dist_factor = 0.3 # 默认中等危险 + + # 位置因素:靠近图像中心更危险 + h, w = self.camera_params['height'], self.camera_params['width'] + center_x, center_y = w // 2, h // 2 + obs_center_x, obs_center_y = obs['center'] - danger_score += area_factor * dist_factor + # 计算到图像中心的距离(归一化) + dist_to_center = np.sqrt(((obs_center_x - center_x) / w)**2 + + ((obs_center_y - center_y) / h)**2) + center_factor = max(0, 1.0 - dist_to_center * 2) # 越靠近中心值越大 + + # 综合危险分数 + obstacle_danger = area_factor * dist_factor * center_factor + danger_score += min(obstacle_danger, max_danger_per_obstacle) + + # 确保危险分数在0-1之间 + danger_score = min(danger_score, 1.0) + safety_score = 1.0 - danger_score - safety_score = max(0, 1.0 - min(danger_score, 1.0)) return round(safety_score, 2) def visualize(self, frame: np.ndarray, results: dict) -> np.ndarray: @@ -177,38 +232,47 @@ def visualize(self, frame: np.ndarray, results: dict) -> np.ndarray: x1, y1, x2, y2 = map(int, target['bbox']) cv2.rectangle(vis_frame, (x1, y1), (x2, y2), (0, 255, 0), 2) if 'class_name' in target: - cv2.putText(vis_frame, target['class_name'], - (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, - 0.5, (0, 255, 0), 2) + cv2.putText(vis_frame, f"Target: {target['class_name']}", + (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, + 0.5, (0, 255, 0), 2) # 绘制障碍物边界框 for obstacle in results.get('obstacles', []): if 'bbox' in obstacle: x1, y1, x2, y2 = map(int, obstacle['bbox']) - cv2.rectangle(vis_frame, (x1, y1), (x2, y2), (0, 0, 255), 2) + cv2.rectangle(vis_frame, (x1, y1), (x2, y2), (0, 0, 255), 1) # 显示估计距离 if 'estimated_distance' in obstacle: - dist_text = f"{obstacle['estimated_distance']:.1f}m" + dist_text = f"{obstacle['estimated_distance']:.2f}" cv2.putText(vis_frame, dist_text, - (x1, y1 - 30), cv2.FONT_HERSHEY_SIMPLEX, - 0.5, (0, 0, 255), 2) + (x1, y2+15), cv2.FONT_HERSHEY_SIMPLEX, + 0.4, (0, 0, 255), 1) # 显示安全分数 safety_score = results.get('safety_score', 1.0) - safety_color = (0, 255, 0) if safety_score > 0.7 else (0, 255, 255) if safety_score > 0.4 else (0, 0, 255) + if safety_score > 0.7: + safety_color = (0, 255, 0) # 绿色 + elif safety_score > 0.4: + safety_color = (0, 255, 255) # 黄色 + else: + safety_color = (0, 0, 255) # 红色 + + # 绘制安全分数背景框 + cv2.rectangle(vis_frame, (5, 5), (200, 85), (40, 40, 40), -1) + cv2.rectangle(vis_frame, (5, 5), (200, 85), safety_color, 2) - cv2.putText(vis_frame, f"Safety: {safety_score}", - (10, 30), cv2.FONT_HERSHEY_SIMPLEX, - 1, safety_color, 2) + cv2.putText(vis_frame, f"Safety Score: {safety_score}", + (10, 30), cv2.FONT_HERSHEY_SIMPLEX, + 0.7, safety_color, 2) # 显示帧信息 cv2.putText(vis_frame, f"Targets: {len(results.get('targets', []))}", - (10, 60), cv2.FONT_HERSHEY_SIMPLEX, - 0.6, (255, 255, 255), 1) + (10, 55), cv2.FONT_HERSHEY_SIMPLEX, + 0.6, (255, 255, 255), 1) cv2.putText(vis_frame, f"Obstacles: {len(results.get('obstacles', []))}", - (10, 80), cv2.FONT_HERSHEY_SIMPLEX, - 0.6, (255, 255, 255), 1) + (10, 75), cv2.FONT_HERSHEY_SIMPLEX, + 0.6, (255, 255, 255), 1) return vis_frame @@ -227,6 +291,20 @@ def get_status(self) -> dict: 'obstacle_count': len(self.obstacles) } + def save_visualization(self, frame: np.ndarray, results: dict, filename: str = "perception_output.jpg"): + """ + 保存可视化结果到文件 + + Args: + frame: 原始图像帧 + results: 感知结果 + filename: 输出文件名 + """ + vis_frame = self.visualize(frame, results) + cv2.imwrite(filename, vis_frame) + print(f"可视化结果已保存到: {filename}") + return filename + class TargetDetector: """目标检测器""" @@ -235,9 +313,13 @@ def __init__(self): # 这里可以加载YOLO等模型 # 简化版本使用预定义的HSV颜色范围 self.color_ranges = { - 'red': ([0, 100, 100], [10, 255, 255]), + 'red': [ + ([0, 100, 100], [10, 255, 255]), # 红色范围1 + ([160, 100, 100], [180, 255, 255]) # 红色范围2 + ], 'green': ([40, 50, 50], [80, 255, 255]), - 'blue': ([100, 50, 50], [130, 255, 255]) + 'blue': ([100, 50, 50], [130, 255, 255]), + 'yellow': ([20, 100, 100], [30, 255, 255]) } def detect(self, frame: np.ndarray) -> List[dict]: @@ -253,22 +335,47 @@ def detect(self, frame: np.ndarray) -> List[dict]: targets = [] hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) - for color_name, (lower, upper) in self.color_ranges.items(): - lower_np = np.array(lower, dtype=np.uint8) - upper_np = np.array(upper, dtype=np.uint8) + for color_name, color_range in self.color_ranges.items(): + if color_name == 'red': + # 红色有两个范围 + lower1 = np.array(color_range[0][0], dtype=np.uint8) + upper1 = np.array(color_range[0][1], dtype=np.uint8) + lower2 = np.array(color_range[1][0], dtype=np.uint8) + upper2 = np.array(color_range[1][1], dtype=np.uint8) + + mask1 = cv2.inRange(hsv, lower1, upper1) + mask2 = cv2.inRange(hsv, lower2, upper2) + mask = cv2.bitwise_or(mask1, mask2) + else: + lower = np.array(color_range[0], dtype=np.uint8) + upper = np.array(color_range[1], dtype=np.uint8) + mask = cv2.inRange(hsv, lower, upper) + + # 形态学操作 + kernel = np.ones((5,5), np.uint8) + mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) + mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel) - mask = cv2.inRange(hsv, lower_np, upper_np) contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) for contour in contours: - if cv2.contourArea(contour) > 300: # 面积过滤 + area = cv2.contourArea(contour) + if 100 < area < 50000: # 合理的面积范围 x, y, w, h = cv2.boundingRect(contour) + # 计算轮廓的圆度 + perimeter = cv2.arcLength(contour, True) + if perimeter > 0: + circularity = 4 * np.pi * area / (perimeter * perimeter) + else: + circularity = 0 + target = { - 'bbox': [x, y, x + w, y + h], + 'bbox': [x, y, x+w, y+h], 'class_name': color_name, - 'confidence': 0.8, - 'area': cv2.contourArea(contour) + 'confidence': min(0.7 + circularity * 0.3, 0.95), # 圆度影响置信度 + 'area': area, + 'circularity': round(circularity, 2) } targets.append(target) @@ -294,21 +401,39 @@ def estimate(self, frame: np.ndarray) -> Optional[np.ndarray]: """ # 简化版本:使用图像强度作为深度代理 gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) - depth_map = cv2.GaussianBlur(gray, (15, 15), 0) - # 归一化 - if depth_map.max() > depth_map.min(): - depth_map = (depth_map - depth_map.min()) / (depth_map.max() - depth_map.min()) + # 使用双边滤波保留边缘 + blurred = cv2.bilateralFilter(gray, 9, 75, 75) + + # 计算局部对比度 + laplacian = cv2.Laplacian(blurred, cv2.CV_64F) + contrast_map = np.abs(laplacian) + + # 归一化对比度图 + if contrast_map.max() > contrast_map.min(): + contrast_map = (contrast_map - contrast_map.min()) / (contrast_map.max() - contrast_map.min()) + + # 计算强度图 + intensity_map = blurred.astype(float) / 255.0 # 模拟距离:假设图像中心区域更近 - h, w = depth_map.shape + h, w = gray.shape y_coords, x_coords = np.ogrid[:h, :w] - center_mask = 1.0 - np.sqrt(((x_coords - w // 2) / (w // 2)) ** 2 + - ((y_coords - h // 2) / (h // 2)) ** 2) - center_mask = np.clip(center_mask, 0, 1) + center_x, center_y = w // 2, h // 2 + + # 创建中心距离权重图 + dist_from_center = np.sqrt(((x_coords - center_x) / (w//2))**2 + + ((y_coords - center_y) / (h//2))**2) + center_weight = np.clip(1.0 - dist_from_center, 0, 1) + + # 创建深度图:高强度(亮)+ 高对比度(边缘)+ 靠近中心 表示更近 + depth_map = 0.4 * intensity_map + 0.3 * (1 - contrast_map) + 0.3 * center_weight - # 结合强度信息和中心权重 - depth_map = 0.7 * depth_map + 0.3 * center_mask + # 归一化到0-1 + depth_map = (depth_map - depth_map.min()) / (depth_map.max() - depth_map.min() + 1e-6) + + # 应用高斯平滑 + depth_map = cv2.GaussianBlur(depth_map, (7, 7), 0) return depth_map @@ -328,28 +453,109 @@ def test_perception(): cv2.rectangle(test_frame, (400, 150), (500, 250), (0, 0, 255), -1) # 红色 cv2.rectangle(test_frame, (300, 300), (400, 400), (255, 0, 0), -1) # 蓝色 + # 添加一些障碍物(灰色方块) + cv2.rectangle(test_frame, (50, 50), (80, 200), (100, 100, 100), -1) # 左边障碍物 + cv2.rectangle(test_frame, (550, 300), (600, 400), (150, 150, 150), -1) # 右边障碍物 + + # 添加一些随机噪声(模拟纹理) + noise = np.random.randint(0, 30, test_frame.shape[:2], dtype=np.uint8) + for i in range(3): + test_frame[:,:,i] = cv2.add(test_frame[:,:,i], noise[:,:,np.newaxis]) + # 处理帧 results = perception.process_frame(test_frame) - # 打印结果 - print(f"检测到目标数量: {len(results['targets'])}") - print(f"检测到障碍物数量: {len(results['obstacles'])}") - print(f"安全分数: {results['safety_score']}") + # 打印详细结果 + print(f"\n检测到目标数量: {len(results['targets'])}") + for i, target in enumerate(results['targets']): + print(f" 目标{i+1}: {target.get('class_name', 'unknown')}, " + f"位置: {target.get('bbox', [])}, " + f"置信度: {target.get('confidence', 0):.2f}") + + print(f"\n检测到障碍物数量: {len(results['obstacles'])}") + for i, obstacle in enumerate(results['obstacles'][:3]): # 只显示前3个 + if 'estimated_distance' in obstacle: + print(f" 障碍物{i+1}: 位置{obstacle.get('bbox', [])}, " + f"距离: {obstacle['estimated_distance']:.2f}") + else: + print(f" 障碍物{i+1}: 位置{obstacle.get('bbox', [])}") + + print(f"\n安全分数: {results['safety_score']}") - # 可视化 - vis_frame = perception.visualize(test_frame, results) + if results.get('error'): + print(f"错误信息: {results['error']}") - # 显示结果 - cv2.imshow('Perception Test', vis_frame) - cv2.waitKey(0) - cv2.destroyAllWindows() + # 保存可视化结果 + output_dir = "output" + if not os.path.exists(output_dir): + os.makedirs(output_dir) + + output_path = os.path.join(output_dir, "perception_test_output.jpg") + perception.save_visualization(test_frame, results, output_path) # 显示模块状态 print("\n模块状态:") for key, value in perception.get_status().items(): print(f" {key}: {value}") + # 保存深度图 + if results['depth_map'] is not None: + depth_map = results['depth_map'] + depth_map_display = (depth_map * 255).astype(np.uint8) + depth_map_colored = cv2.applyColorMap(depth_map_display, cv2.COLORMAP_JET) + depth_path = os.path.join(output_dir, "depth_map.jpg") + cv2.imwrite(depth_path, depth_map_colored) + print(f"深度图已保存到: {depth_path}") + + print(f"\n测试完成!结果已保存到 {output_dir} 目录") + + +def test_with_real_image(image_path: str = None): + """使用真实图像测试""" + print("=== 使用真实图像测试 ===") + + # 如果没有提供图像路径,创建一个简单的测试图像 + if image_path is None or not os.path.exists(image_path): + print("未提供有效图像路径,创建测试图像...") + test_frame = np.zeros((480, 640, 3), dtype=np.uint8) + cv2.circle(test_frame, (320, 240), 100, (0, 255, 0), -1) # 绿色圆形 + cv2.rectangle(test_frame, (100, 100), (200, 150), (255, 0, 0), -1) # 蓝色矩形 + cv2.line(test_frame, (500, 100), (600, 400), (0, 0, 255), 5) # 红色线 + else: + test_frame = cv2.imread(image_path) + if test_frame is None: + print(f"无法读取图像: {image_path}") + return + + # 初始化感知模块 + perception = DronePerception() + + # 处理帧 + results = perception.process_frame(test_frame) + + print(f"图像尺寸: {test_frame.shape}") + print(f"检测到目标: {len(results['targets'])}") + print(f"检测到障碍物: {len(results['obstacles'])}") + print(f"安全分数: {results['safety_score']}") + + # 保存结果 + output_dir = "output" + if not os.path.exists(output_dir): + os.makedirs(output_dir) + + output_path = os.path.join(output_dir, "real_image_test.jpg") + perception.save_visualization(test_frame, results, output_path) + + print(f"结果已保存到: {output_path}") + if __name__ == "__main__": - # 直接运行测试 - test_perception() \ No newline at end of file + # 创建输出目录 + if not os.path.exists("output"): + os.makedirs("output") + + # 运行测试 + test_perception() + + # 如果需要测试真实图像,取消下面一行的注释并提供图像路径 + # test_with_real_image("your_image.jpg") \ No newline at end of file