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