【Dogfight论文复现】无人机视频中检测无人机的目标检测模型
论文名称:Dogfight: Detecting Drones from Drones Videos
论文地址:https://arxiv.org/pdf/2103.17242
博客内容:复现这篇文章的Dogfight模型,也就是再自己本地,NPS数据集上跑通这个模型的代码,得到模型,成功推理,并达到论文中的精度
数据集准备→环境搭建→模型实现→训练配置→推理验证→精度调优
一、数据集准备
获取
论文指出 NPS-Drones 原始标注不精确( bounding box 过大),重新处理了一下,在Drone-Detection/annotations/NPS-Drones-Dataset
路径下。
数据划分:按论文设置,前 40 个视频为 “训练 + 验证集”(建议 9:1 划分训练 / 验证),后 10 个为测试集
二、仓库克隆与环境搭建
模型仓库
GitHub 仓库 mwaseema/Drone-Detection
论文第一作者 “Muhammad Waseem Ashraf” ,仓库用户名 “mwaseema” 对应论文作者邮箱前缀 “mohammadwaseem”
环境配置
# git clone https://github.com/mwaseema/image-segmentation-keras-implementation
# 也可下载ZIP,再unzipcd image-segmentation-keras-implementation
make
添加仓库路径到 PYTHONPATH
export PYTHONPATH="/root/autodl-tmp/Drone-Detection-main:$PYTHONPATH"
三、数据预处理
Videos to frames and annotations to masks
视频转帧与标注转掩码
- 脚本路径:
tools/video_to_frames_and_masks.py
- 作用:将原始视频拆分为帧,并根据标注文件,利用边界框信息生成每个帧对应的生成二进制掩码(前景为目标无人机,背景为 0)
- 使用:提供包含视频和标注文件的文件夹的绝对路径。还需要将
foreground_in_mask
的值设置为1(用于训练数据)或255(用于测试数据)
注意:作者改后的NPS数据集标签名称为Clip_002.txt,需要修改脚本,使能适应001,025这样的编号。
- 视频和标签文件夹路径
videos_folder = '/root/autodl-tmp/NPS-drone/Videos'
# annotations_folder = '/root/autodl-tmp/NPS-drone/Video_Annotation'# 修改后的标签
annotations_folder = '/root/autodl-tmp/Drone-Detection/annotations/NPS-Drones-Dataset'
- 帧保存间隔
根据需求设置(默认每 4 帧保存 1 帧,训练数据可保留更多帧,设为 1 则保存所有帧)
save_frame = 4 # 例如:1表示保存所有帧,4表示每4帧保存1帧
- 掩码前景值
如果处理的是训练数据,设为1;测试数据设为255:
foreground_in_mask = 1 # 训练数据用1,测试数据用255
- 输出目录
指向你创建的输出文件夹(用于存放生成的帧和掩码):
frames_output_folder = '/root/autodl-tmp/NPS-drone/frames'
masks_output_folder = '/root/autodl-tmp/NPS-drone/masks'
运行脚本
cd ~/Drone-Detection/tools
python video_to_frames_and_masks.py
四、阶段一:Spatial (2D) stage
Generate image crops 生成图像裁剪
由于无人机目标较小,,使用tools/image_crops/generate_crops_of_images_by_dividing.py
我们的图像分辨率较高,但目标(无人机)非常小,因此我们对图像进行了 9 次重叠裁剪,对高分辨率帧生成重叠裁剪块(提升小目标检测精度)
- 脚本路径:
tools/image_crops/generate_crops_of_images_by_dividing.py
。 - 该文件包含一个
GenerateCrops
类,其中有输入和输出数据的变量,为这些变量设置合适的值后运行代码即可。 - 在
“tools/image_crops”
目录下的代码中,提供了从给定图像中获取 9 个和 4 个小 patches 的实现。
- 修改上一步的两个路径和输出路径
class GenerateCrops:def __init__(self):# 输入路径:上一步生成的帧和掩码文件夹self.input_frames_folder = '/root/autodl-tmp/NPS-drone/frames'self.input_ground_truth_folder = '/root/autodl-tmp/NPS-drone/masks'# 输出路径:裁剪块保存的位置self.output_frames_folder = '/root/autodl-tmp/NPS-drone/crops/frames'self.output_ground_truth_folder = '/root/autodl-tmp/NPS-drone/crops/masks'
运行脚本:
cd Drone-Detection/tools/image_crops
python generate_crops_of_images_by_dividing.py
Training
- 第一阶段2D空间模型训练代码目录:
/root/autodl-tmp/Drone-Detection/train/spatial
- 在
train/spatial/config.py
文件中设置配置变量的值,然后执行train/spatial/train.py
文件开始模型训练。 - 使用生成的裁剪块(帧 + 掩码)训练 2D 模型,学习单帧中无人机的空间特征(如位置、形状)。
核心输入
- 裁剪后的单帧图:/root/autodl-tmp/NPS-drone/crops/frames(输入图像)
- 裁剪后的掩码:/root/autodl-tmp/NPS-drone/crops/masks(监督标签,用于计算损失)
- 修改配置文件
Drone-Detection/train/spatial/config
修改路径,使指向之前生成的裁剪块
# 1. 数据集根路径(根据你的裁剪块位置修改)
my_base_path = "/root/autodl-tmp/NPS-drone/crops" # 裁剪块的根目录
dataset_base_path = "" # 保持空,因为上面已包含完整路径# 2. 模型名称(自定义,用于保存权重文件夹)
model_used = "pspnet_drone" # 例如:pspnet_50_drone_v1# 3. 训练集路径(指向裁剪块的帧和掩码)
dataset_links = {'train': {'annotations': os.path.join(my_base_path, 'masks'), # 裁剪后的掩码文件夹'images': os.path.join(my_base_path, 'frames'), # 裁剪后的帧文件夹'temp': os.path.join(my_base_path, 'weights', model_used), # 模型权重保存路径},
}# 4. 训练超参数(根据需求调整)
n_classes = 2 # 背景 + 无人机(固定不变)
epochs = 100 # 训练轮次(可根据效果调整,如50、200)
batch_size = 3 # 批次大小(根据GPU显存调整,显存小则改小)
steps_per_epoch = 1024 # 每轮训练的步数# 5. 微调设置(若从零训练,保持默认)
is_finetune = False # 是否使用预训练权重微调
old_weight_path = '' # 若微调,填写预训练权重路径
- 直接执行
train_model.py
即可启动训练:
cd /root/autodl-tmp/Drone-Detection # 进入代码库根目录
python train/spatial/train_model.py
说明:
- 输出日志:终端会打印每轮训练的损失值(loss)
- 权重保存:每轮训练结束后,模型权重会保存到
dataset_links['train']['temp']
路径(即/root/autodl-tmp/NPS-drone/crops/weights/pspnet_drone
) - 中断与续训:若训练中断,重新运行脚本会自动从最近保存的权重继续训练(依赖 keras_segmentation 的 checkpoint 机制)。
- 训练完成后,权重文件会保存在设置的 temp 路径下,下一步可用于
test_model/spatial/test_and_score.py
进行模型评估
Testing
复制你想要在测试数据上进行评估的权重的绝对路径,将训练权重的绝对路径作为数组元素粘贴到test_model/spatial/checkpoint_paths.py
文件中。在test_model/spatial/config.py
文件中为现有变量添加配置值,然后运行test_model/spatial/test_and_score.py
文件对测试数据进行评估。
五、阶段二:Temporal (3D) stage
时间3D模型训练用数据预处理
最终需得到两个关键文件夹(对应config.py中的路径):
-
dataset_base_path/train/i3d_features
:存储视频时序片段的 I3D 特征(.npy格式)。 -
dataset_base_path/train/ground_truths
:存储与 I3D 特征对应的时序标注掩码(二值图像,无人机区域为 1,背景为 0)。 -
膨胀边界(扩大候选区域):
tools/binary_mask_dilation.py
Step 3:去除无关大区域
膨胀后可能存在过大的候选区域(非无人机),需过滤。
tools/remove_small_big_boxes.py
# 输入Step 2.2膨胀后的掩码文件夹
input_mask_folder = os.path.join(dataset_base_path, "train", "motion_boundaries_dilated")
# 输出过滤后的文件夹
output_mask_folder = os.path.join(dataset_base_path, "train", "motion_boundaries_filtered")
Step 4:CRF 优化候选框(贴合无人机边界)
用条件随机场细化候选区域,使边界更精准。
tools/crf/crf_on_labels.py
运行脚本时指定参数
python tools/crf/crf_on_labels.py \--frames_folder "path/to/nps/frames" # 2D处理时已生成的视频单帧(如drones_dataset/train/frames)\--labels_mask_folder "drones_dataset/train/motion_boundaries_filtered" # Step 3输出的文件夹\--output_folder "drones_dataset/train/crf_output" # CRF优化后的掩码输出路径\--save_boxes_as_json True # 保存候选框为JSON(用于后续生成cuboids)
Step 5:转换候选框格式(适配 cuboids 生成)
将 CRF 输出的 JSON 候选框转换为 3D 模型所需的格式。
tools/boxes_list_to_patch_information_json.py
# 输入CRF生成的JSON候选框文件夹(Step 4中--output_folder下的JSON文件)
input_json_folder = "drones_dataset/train/crf_output"
# 输出转换后的patch信息文件夹(用于生成cuboids)
output_patch_folder = "drones_dataset/train/patch_information"
Step 6:生成固定尺寸 cuboids(时序片段)
NPS 数据集使用固定尺寸 cuboids,基于候选框提取连续视频帧片段。
使用脚本:tools/cuboids/volumes_with_tracking_of_generated_boxes_with_stabilization.py
(README 明确 NPS 用此脚本)
# 原始视频帧文件夹(同Step 4的--frames_folder)
frames_folder = "path/to/nps/frames"
# Step 5输出的patch信息文件夹
patch_information_folder = "drones_dataset/train/patch_information"
# 输出cuboids的帧文件夹(连续帧片段,如5帧为一个cuboid)
output_cuboids_frames_folder = "drones_dataset/train/cuboids_frames"
# 输出cuboids对应的标注掩码文件夹(后续作为ground_truths)
output_cuboids_masks_folder = "drones_dataset/train/cuboids_masks"
# 固定尺寸设置(如224x224,根据I3D模型输入调整)
cuboid_size = (224, 224)
# 时序长度(连续帧数,如5帧)
temporal_length = 5
Step 7:提取 I3D 特征(3D 模型输入)
Motion boundaries运动边界
我们使用光流获取运动边界,以得到良好的候选区域。
- 代码文件:
- NPS数据集用:
tools/optical_flow_motion_boundaries.py
- FL-drones 数据集:
tools/optical_flow_motion_boundaries_with_stabilization.py
,这个脚本会在帧稳定化后生成运动边界。 - 因为FL数据集的背景运动占主导地位,掩盖了无人机。为解决此问题,在生成运动边界前先进行运动稳定化处理。
- NPS数据集用:
- 功能:为给定视频生成并保存运动边界,生成视频中动态区域的二值掩码(无人机为运动目标,掩码为白色)。
运行代码文件前,需提供包含视频的文件夹路径和运动边界的输出路径。
videos_folder = '/root/autodl-tmp/NPS-drone/Videos'
optical_flow_output_folder = '/root/autodl-tmp/NPS-drone/optical_flow'
Motion boundaries edges运动边界边缘
- 稳定化后生成的运动边界在边缘处有较高的值,可使用以下代码去除
- 脚本路径:
tools/remove_motion_boundary_edges.py
Motion boundaries dilation运动边界膨胀
- 对较细的运动边界进行膨胀,以得到能更好覆盖无人机的候选区域:
tools/binary_mask_dilation.py
Remove irrelevant candidate regions去除无关候选区域
- 运动边界膨胀后,一些候选区域表现为较大的运动边界,无法准确代表无人机。可使用以下代码文件去除此类区域。
- 文件中小框的阈值设为 0,以确保只去除大框
tools/remove_small_big_boxes.py
CRF on the candidate boxes候选框的 CRF 处理
- 我们使用条件随机场(CRF)确保从运动边界获得的候选框紧密包围无人机
- 脚本路径:
tools/crf/crf_on_labels.py
- 该代码文件接受以下参数:
--frames_folder
:保存 png 格式视频帧的文件夹--labels_mask_folder
:设置为从候选区域中去除无关大框后得到的二值掩码所在的文件夹--output_folder
:设置为应用 CRF 后输出二值掩码的文件夹--save_boxes_as_json
:设为 true 可将应用 CRF 后的框保存为 JSON 文件
执行上述代码后,可使用以下代码文件将以 JSON 格式获得的框转换为下一步中使用的自定义格式:tools/boxes_list_to_patch_information_json.py
Generating cuboids生成立方体(Cuboids)
- 对 NPS 数据集使用固定尺寸的立方体,
- 对 FL-Drones 数据集使用多尺度立方体。
Fixed sized cuboids固定尺寸立方体
可使用以下脚本生成固定尺寸的立方体(用于 NPS 无人机数据集):tools/cuboids/volumes_with_tracking_of_generated_boxes_with_stabilization.py
该脚本将使用上一步生成的 patch 信息 JSON 文件。
Multi sized cuboids多尺寸立方体
可使用以下脚本生成多尺寸立方体(用于 FL-Drones 数据集):tools/cuboids/multi_scale_volumes_with_tracking_and_stabilization_using_masks.py
对于多尺寸立方体,真值、2D 检测结果等会随帧一起变换。这样无需使用逆矩阵将检测结果转换回来即可计算分数。
如果在稳定化方面遇到问题,可尝试降低用于视频稳定化的最大角点数量(在第 218 行)。
I3D features I3D 特征
我们使用 deepmind 的 kinetics I3D 模型(带预训练权重)从生成的立方体中提取特征。
I3D 的仓库地址为:deepmind/kinetics-i3d: 基于 Kinetics 数据集训练的视频分类卷积神经网络模型
我们没有从最后一层获取输出,而是从中间层获取特征(维度为 1x2x14x14x480)。这些特征在第 2 轴上取平均,然后重塑为 14x14x480,再输入到我们提出的时间流水线中。此外,我们只使用了 I3D 的 RGB 流,而非双流网络。
Training
对于时间阶段的训练,在train/temporal/config.py中设置值,然后使用train/temporal/train_model.py开始训练。
时间(3D)模型训练
/root/autodl-tmp/Drone-Detection/train/temporal
- 修改时序模型配置文件
Drone-Detection/train/temporal/config.py
,(主要是 I3D 特征和对应标注)
- images 必须指向I3D 特征文件夹(时序模型依赖视频的时序特征,需提前通过工具提取,如 tools/features/extract_i3d_features.py,若未生成需先执行此步骤)。
- annotations 对应时序标注掩码(需与 I3D 特征的时序顺序匹配)。
# 1. 数据集根路径(根据你的数据位置修改)
my_base_path = "/root/autodl-tmp/NPS-drone" # 你的数据根目录
dataset_base_path = "temporal_data" # 时序数据子目录(可自定义)# 拼接完整路径
dataset_base_path = os.path.join(my_base_path, dataset_base_path)# 2. 模型名称(自定义,用于保存权重文件夹)
model_used = "i3d_pspnet_drone" # 例如:i3d_pspnet_v1# 3. 训练数据路径(关键:指向I3D特征和标注)
dataset_links = {'train': {'annotations': os.path.join(dataset_base_path, 'train', 'ground_truths'), # 时序标注掩码文件夹'images': os.path.join(dataset_base_path, 'train', 'i3d_features'), # I3D特征文件夹(需提前生成)'temp': os.path.join(dataset_base_path, 'temp', model_used, model_used), # 模型权重保存路径},
}# 4. 训练超参数(根据GPU显存调整)
n_classes = 2 # 固定为“背景+无人机”
epochs = 100 # 训练轮次(可根据效果调整)
batch_size = 4 # 批次大小(显存不足则减小,如2)
steps_per_epoch = 1024 # 每轮训练步数# 5. 微调设置(若使用预训练权重)
is_finetune = False # 是否微调(首次训练设为False)
old_weight_path = '' # 若微调,填写之前保存的权重路径
- 运行脚本
cd /root/autodl-tmp/Drone-Detection
python train/temporal/train_model.py
Testing
复制你想要在测试数据上进行评估的权重的绝对路径,将其作为数组元素粘贴到test_model/temporal/checkpoint_paths.py文件中。在test_model/temporal/config.py文件中为现有变量添加配置值,然后运行test_model/temporal/test.py文件对测试数据进行评估。
NMS
使用 I3D 特征从时间阶段生成结果后,将预测结果通过 NMS 阶段:tools/nms/nms_generated_boxes.py
Results generation
可使用以下代码生成结果:test_model/temporal/results_generation.py
也可使用以下文件中的代码计算结果:tools/generate_annotations_and_scores.py
Temporal consistency
我们利用时间一致性去除检测结果中的噪声假阳性。相关代码在以下文件中:tools/video_tubes/remove_noisy_false_positives_by_tracking.py