作者:欧新宇(Xinyu OU)
当前版本:Release v1.1
开发平台:Paddle 2.3.2
运行环境:Intel Core i7-7700K CPU 4.2GHz, nVidia GeForce GTX 1080 Ti
本教案所涉及的数据集仅用于教学和交流使用,请勿用作商用。
最后更新:2022年2月4日
当前摔倒检测是智能家居的重要研究方向,它可以有效地降低家庭成员因独自摔倒后因救治不及时而导致的死亡率。本项目所用数据集为网上收集而得,包含7782张图片,举例如下:
该数据集的部分图片和标签存在问题,无法正确读取,需要经过数据清洗和标签数据重写后才能使用。其中不可用图像包含9个,可用图像数据为7773个,按照7:1:2的比例进行划分, 其中训练验证集样本6217(80%),训练集样本5439个(70%), 验证集样本778个(10%), 测试集样本1556个(20%)。数据集提供了voc格式的标签,数据类型为 *.xml
,可用类别为:person和Down,但xml标签文件中还存在两个无用的类别“10+”和“dog”,在训练时需要进行合适的处理。
PaddleDetection环境的安装只需要执行一次,确认服务器或本地环境已经具备相关依赖文件后,可跳过该步。
解压 PaddleDetection-release-2.3
工具包到项目文件夹,实现方法有两种:
文件夹名
改为 DetectionRoadSign
(便于项目管理)或直接使用带版本的命名 PaddleDetection-release-2.3
(便于版本管理)import os import zipfile # 设置工作路径 project_name = 'DetectionRoadSign' workspace = 'D:\\Workspace\\MyProjects' if not os.path.exists(workspace): os.makedirs(workspace) os.chdir(workspace) # 将PaddleDetection解压到工作路径的更目录 f = zipfile.ZipFile('D:\Workspace\安装包\Paddle\PaddleDetection-release-2.3.zip', 'r') for file in f.namelist(): f.extract(file, './') f.close() # 修改文件名为PaddleDetection(如果需要) # os.rename('PaddleDetection-release-2.3', project_name)
>> python ppdet/modeling/tests/test_architectures.py
验证通过将得到如下提示:
Ran 7 tests in 12.816s
OK
本项目数据集已上传至百度AIStudio,下载地址为: https://aistudio.baidu.com/aistudio/datasetdetail/127208 。
初次训练时,系统会提示部分图片/标签无法读取,从而因报错而导致训练停止,经观察发现收集到的数据集有如下问题:
<folder><filename><path>
可能存在中文信息;2.标签 <width><height>
值不存在(=0)。<annotation> <folder>高雅</folder> <filename>people(10).jpg</filename> <path>C:\Users\ehualu\Desktop\高雅\people(10).jpg</path> <source> <database>Unknown</database> </source> <size> <width>0</width> <height>0</height> <depth>3</depth> </size> <segmented>0</segmented> <object> <name>down</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>371</xmin> <ymin>312</ymin> <xmax>578</xmax> <ymax>380</ymax> </bndbox> </object> </annotation>
对于以上三个问题,我们给出了如下的数据清洗方案(注意每个数据集存在的问题不尽相同,因此数据清洗工程性很强,需要针对性设置)。
################################################################################## # 数据清洗 # 作者: Xinyu Ou (http://ouxinyu.cn) # 数据集名称:摔倒检测(FallDown) # 本程序功能: # 1. 对图像坏样本,进行检测,并保存到bad.txt中 # 2. 对xml标签文件中缺失(width, height)及错误信息(folder,filename,path)进行重新写入 ################################################################################### import os import cv2 import codecs from xml.dom.minidom import parse import xml.dom.minidom # 1. 定义数据集路径和数据统计信息 dataset_name = 'FallDown' dataset_path = 'D:\\Workspace\\ExpDatasets\\' dataset_root_path = os.path.join(dataset_path, dataset_name) excluded = ['.DS_Store'] # 被排除的文件 num_bad = 0 num_good = 0 # 检测坏数据列表是否存在,如果存在则先删除。 bad_list = os.path.join(dataset_root_path, 'badfile_list.txt') if os.path.exists(bad_list): os.remove(bad_list) # 执行数据清洗 count = 0 with codecs.open(bad_list, 'a', 'utf-8') as f_bad: images = os.listdir(os.path.join(dataset_root_path, 'JPEGImages')) # 获取图像路径 for image in images: # 循环遍历图像文件夹中的所有图像 file_name, ext_name = os.path.splitext(image) img_path = os.path.join(dataset_root_path, 'JPEGImages', image) xml_path = os.path.join(dataset_root_path, 'Annotations', file_name+'.xml') try: img = cv2.imread(img_path, 1) img_shape = img.shape # 获取图像的形态[WHC] DOMTree = xml.dom.minidom.parse(xml_path) # 使用Dom解析xml文件 collection = DOMTree.documentElement # 将解析后的信息保存到变量collection中 width = collection.getElementsByTagName('width') # 获取xml文件中width标签的值 height = collection.getElementsByTagName('height') # 获取xml文件中height标签的值 folder = collection.getElementsByTagName('folder') filename = collection.getElementsByTagName('filename') path = collection.getElementsByTagName('path') width[0].firstChild.data = img_shape[1] # 将图像的宽的值赋值给xml文件的width标签 height[0].firstChild.data = img_shape[0] # 将图像的宽的值赋值给xml文件的height标签 folder[0].firstChild.data = 'JPEGImages' # 将xml文件的folder标签赋值为: JPEGImages filename[0].firstChild.data = image path[0].firstChild.data = image with open(xml_path, 'w') as f_xml: # 保存修改后的xml信息到xml标签文件中 DOMTree.writexml(f_xml) # cv2.imwrite(img_path, img) num_good += 1 pass except: f_bad.write('{}\n'.format(image)) num_bad += 1 if count % 10 == 0: print('\r 当前清洗进度:{}/{},移除损坏文件{}个。'.format(count, len(images), num_bad), end='') # 输出清洗过程信息 count += 1 print('数据集清洗完成, 损坏文件{}个, 正常文件{}.'.format(num_bad, num_good)) # 输出统计信息
当前清洗进度:7780/7782,移除损坏文件9个。数据集清洗完成, 损坏文件9个, 正常文件7773.
本项目以 faster_rcnn_r50_fpn_1x_voc
模型为例,该模型以ResNet50为主干模型构建fasterRCNN,使用了特征金字塔FPN结构实现特征提取。该模型主要涉及6个配置文件,相互关系如下图所示:
本项目在配置模型超参数时,需要先需要复制/创建如下文件:
./configs
目录下创建 FallDown
文件夹,并从 configs/faster_rcnn
文件夹中拷贝 faster_rcnn_r50_fpn_1x_voc
文件至 configs/FallDown
文件夹中。./configs/FallDown
下创建 _base_
文件夹,并从 configs/faster_rcnn/_base_
文件夹中拷贝如下文件至 configs/FallDown/_base_
文件夹中,包括: optimizer_12e.yml
, faster_fpn_reader.yml
, faster_rcnn_r50_fpn.yml
,。./configs/datasets
文件夹中复制 voc.yml
为 FallDown_voc.yml
。(*.yml)
(faster_rcnn_r50_fpn_1x_coco.yml)
_BASE_: [ # 需要继承的相关配置文件 '../datasets/FallDown_voc.yml', # 路标数据集配置文件 '../runtime.yml', # 全局配置文件 '_base_/optimizer_12e.yml', # 优化器配置文件 '_base_/faster_rcnn_r50_fpn.yml', # 主干网络配置文件 '_base_/faster_fpn_reader.yml', # 数据读取器配置文件 ] weights: output/FallDown/faster_rcnn_r50_fpn_1x_voc/best_model # 最终模型保存的位置 save_dir: output/FallDown # 模型的输出文件夹
(configs/runtime.yml)
use_gpu: true # 根据硬件选择是否使用GPU log_iter: 10 # 设置多少次迭代显示一次训练日志 save_dir: output # 模型保存的文件夹 snapshot_epoch: 1 # 每隔多少个周期保存一次模型 print_flops: true # 是否显示计算吞吐量,注意启用该选项需要安装paddleslim
configs/datasets/roadsign_coco.yml
)metric: VOC # 数据集的结构形态:MSCOCO map_type: integral # [11points|integral],前者为VOC2010以前的版本,计算[0,0.1,..1] 11个点的mAP,integral为VOC2010后的版本,计算每个Recall的mAP num_classes: 3 # 类别数量 TrainDataset: # 训练集 !VOCDataSet dataset_dir: D:/Workspace/ExpDatasets/FallDown # 图像的根目录 anno_path: trainval.txt # 图像的标注文件列表,格式类似分类任务,如:img1.png img1.xml label_list: label_list.txt # label的列表按照自然数进行排序,每个类别名称一行,例如:car, person, table,其中预测索引0表示car,索引1表示person,索引2表示table data_fields: ['image', 'gt_bbox', 'gt_class', 'difficult'] # xml文件的关键数据域,包括[图像,groundTurth边界框坐标,groundTurth类别名称] EvalDataset: # 验证集 !VOCDataSet dataset_dir: D:/WorkSpace/ExpDatasets/FallDown anno_path: test.txt label_list: label_list.txt data_fields: ['image', 'gt_bbox', 'gt_class', 'difficult'] TestDataset: # 测试集 !ImageFolder dataset_dir: D:/WorkSpace/ExpDatasets/FallDown anno_path: label_list.txt
(_base_/faster_rcnn_r50_fpn.yml)
architecture: FasterRCNN # 模型架构 FasterRCNN pretrain_weights: https://paddledet.bj.bcebos.com/models/pretrained/ResNet50_cos_pretrained.pdparams FasterRCNN: # 定义FasterRCNN各模块名称 backbone: ResNet # 主干网络:ResNet neck: FPN # neck模块(特征提取):FPN特征金字塔 rpn_head: RPNHead # RPN头:区域建议网络 bbox_head: BBoxHead # BBox头:BoundingBox 区域回归模块 # post process bbox_post_process: BBoxPostProcess # 后处理模块 ResNet: # index 0 stands for res2 depth: 50 # 模型深度,此处标识为ResNet50 norm_type: bn # 设置是否启用batch-norm, bn正则化 freeze_at: 0 # 微调训练时冻结模块的起点,index=0表示的是res2,因此,第一组卷积在微调时将永久冻结 (fastRCNN) return_idx: [0,1,2,3] # 实际参与微调的层为[2,3,4,5] num_stages: 4 FPN: out_channel: 256 # FPN输出的通道数为 256 RPNHead: anchor_generator: ## Anchor采样的基本原则 aspect_ratios: [0.5, 1.0, 2.0] # 采样长宽比例 anchor_sizes: [[32], [64], [128], [256], [512]] # FPN采样基准尺度 strides: [4, 8, 16, 32, 64] # 采样步长 rpn_target_assign: ## RPN采样规则 batch_size_per_im: 256 # 共采样256个Anchor fg_fraction: 0.5 # 正样本比例为 0.5,即 512*0.5=256个 negative_overlap: 0.3 # 与所有真实框的IoU<0.3的Anchor为负样本 positive_overlap: 0.7 # 与任意真实框的IoU>0.7的Anchor为正样本 use_random: True # 使用随机方式进行采样 train_proposal: min_size: 0.0 nms_thresh: 0.7 # nms阈值 0.7 pre_nms_top_n: 2000 # 使用RPN采样2000个RoIs用于后续的处理 post_nms_top_n: 1000 # 经过对象性排序后保留1000个样本 topk_after_collect: True test_proposal: min_size: 0.0 nms_thresh: 0.7 # nms阈值 0.7 pre_nms_top_n: 1000 # 使用RPN采样1000个RoIs用于后续的处理 post_nms_top_n: 1000 # 经过对象性排序后保留1000个样本,原作为300个 BBoxHead: ## BBox头的超参数 head: TwoFCHead # 输出头为TwoFCHead,通道数为 1024 roi_extractor: resolution: 7 # RoI的输出特征为 7×7,也即输入BBox的特征为7×7 sampling_ratio: 0 aligned: True # 启用RoI Align模式 bbox_assigner: BBoxAssigner BBoxAssigner: ## BBox回归的超参数 batch_size_per_im: 512 # 采样总数为 512 bg_thresh: 0.5 # IoU<0.5为负样本 fg_thresh: 0.5 # IoU>0.5为正样本 fg_fraction: 0.25 # 正样本比率为0.25,即 512*0.25=128个 use_random: True # 使用随机方式进行采样 TwoFCHead: out_channel: 1024 # 输入BBoxHead的特征通道数 BBoxPostProcess: ## 后处理方法为:多类非极大抑制NMS decode: RCNNBox nms: name: MultiClassNMS keep_top_k: 100 # 保留评分最高的100个patch(Anchor) score_threshold: 0.05 # 评分最低阈值 nms_threshold: 0.5 # nms阈值
(_base_/faster_fpn_reader.yml)
worker_num: 2 TrainReader: # 训练阶段的数据读取器 sample_transforms: - Decode: {} # 随机裁剪,训练时采用多尺度进行训练,验证和测试时通常使用固定尺寸/比例进行测试 - RandomResize: {target_size: [[640, 1333], [672, 1333], [704, 1333], [736, 1333], [768, 1333], [800, 1333]], interp: 2, keep_ratio: True} - RandomFlip: {prob: 0.5} # 按照0.5的概率随机水平翻转 - NormalizeImage: {is_scale: true, mean: [0.485,0.456,0.406], std: [0.229, 0.224,0.225]} # 数据标准化:减均值、去方差 - Permute: {} batch_transforms: - PadBatch: {pad_to_stride: 32} batch_size: 1 # 每批次训练样本的数量 shuffle: true # 是否随机打乱数据,通常在训练过程中需要随机打乱 drop_last: true # epoch/batch_size有余数时,是否将末尾的数据进行丢弃,训练中通常进行丢弃 collate_batch: false EvalReader: # 验证阶段的数据读取器 sample_transforms: - Decode: {} - Resize: {interp: 2, target_size: [800, 1333], keep_ratio: True} - NormalizeImage: {is_scale: true, mean: [0.485,0.456,0.406], std: [0.229, 0.224,0.225]} - Permute: {} batch_transforms: - PadBatch: {pad_to_stride: 32} batch_size: 1 shuffle: false drop_last: false TestReader: # 测试阶段的数据读取器 sample_transforms: - Decode: {} - Resize: {interp: 2, target_size: [800, 1333], keep_ratio: True} - NormalizeImage: {is_scale: true, mean: [0.485,0.456,0.406], std: [0.229, 0.224,0.225]} - Permute: {} batch_transforms: - PadBatch: {pad_to_stride: 32} batch_size: 1 shuffle: false drop_last: false
(_base_/optimizer_1x.yml)
epoch: 12 # 迭代训练的周期数,默认为12个周期 LearningRate: # 学习率超参数 base_lr: 0.005 # 初始学习率 schedulers: - !PiecewiseDecay # 学习率策略:分段学习率 gamma: 0.1 # 学习率衰减率超参数:0.1 milestones: [8, 11] # 学习率衰减的时刻点,第8个epoch和第11个epoch - !LinearWarmup # 线性热身(学习率慢启动),避免较大的初始学习率引起损失过分振荡 start_factor: 0.1 # 衰减率为 0.1 steps: 2000 # 热身周期为 1000次迭代 OptimizerBuilder: # 优化方法设置 optimizer: momentum: 0.9 # 动量值 0.9 type: Momentum # 优化方法:基于动量的随机梯度下降 regularizer: factor: 0.0001 # 正则化因子 0.0005 type: L2 # 正则化方法 L2正则化
下面给出训练模型的基本命令,值得注意的是,在Notebook中运行训练进程,无法实时打印出日志文件(会在运行完一次性打印出所有日志)。鉴于训练时间通常比较长,为了便于观察训练过程,建议使用Anaconda终端的命令行进行执行。PaddleDetection训练过程中若开启了--eval
,会将所有checkpoint
中评估结果最好的checkpoint
保存为best_model
。
##################### JupyterLab ###################################### import os workspace = 'D:\\Workspace\\MyProjects\\PaddleDetection-release-2.3' os.chdir(workspace) model_template = 'faster_rcnn_r50_fpn_1x_voc' !python -u tools/train.py -c configs/FallDown/{model_template}.yml --eval ##################### 命令行 ###################################### # 命令行执行路径为项目文件夹:D:\\Workspace\\MyProjects\\PaddleDetection-release-2.3 # python -u tools/train.py -c configs/FallDown/faster_rcnn_r50_fpn_1x_voc.yml --eval
PaddleDetection提供了tools/eval.py
脚本用于评估模型,该命令可以使用模型的默认参数 weight
所指定的模型进行评估(建议),若使用预先下载的模型也可以通过 -o weights=
命令进行指定。参数 --classwise
可以打印出每个类别的精确度。
##################### JupyterLab ###################################### import os workspace = 'D:\\Workspace\\MyProjects\\PaddleDetection-release-2.3' os.chdir(workspace) model_template = 'faster_rcnn_r50_fpn_1x_voc' !python -u tools/eval.py -c configs/FallDown/{model_template}.yml --classwise ##################### 命令行 ###################################### # 命令行执行路径为项目文件夹:D:\\Workspace\\MyProjects\\PaddleDetection-release-2.3 # python -u tools/eval.py -c configs/FallDown/faster_rcnn_r50_fpn_1x_voc.yml --classwise
[02/04 23:50:06] ppdet.utils.checkpoint INFO: Finish loading model weights: output/FallDown/faster_rcnn_r50_fpn_1x_voc/best_model.pdparams [02/04 23:50:07] ppdet.engine INFO: Model FLOPs : 163.633649G. (image shape is [1, 3, 800, 1088]) [02/04 23:50:08] ppdet.engine INFO: Eval iter: 0 [02/04 23:50:17] ppdet.engine INFO: Eval iter: 100 [02/04 23:50:25] ppdet.engine INFO: Eval iter: 200 [02/04 23:50:34] ppdet.engine INFO: Eval iter: 300 [02/04 23:50:42] ppdet.engine INFO: Eval iter: 400 [02/04 23:50:51] ppdet.engine INFO: Eval iter: 500 [02/04 23:51:00] ppdet.engine INFO: Eval iter: 600 [02/04 23:51:08] ppdet.engine INFO: Eval iter: 700 [02/04 23:51:17] ppdet.engine INFO: Eval iter: 800 [02/04 23:51:27] ppdet.engine INFO: Eval iter: 900 [02/04 23:51:38] ppdet.engine INFO: Eval iter: 1000 [02/04 23:51:47] ppdet.engine INFO: Eval iter: 1100 [02/04 23:51:56] ppdet.engine INFO: Eval iter: 1200 [02/04 23:52:04] ppdet.engine INFO: Eval iter: 1300 [02/04 23:52:13] ppdet.engine INFO: Eval iter: 1400 [02/04 23:52:21] ppdet.engine INFO: Eval iter: 1500 [02/04 23:52:26] ppdet.metrics.metrics INFO: Accumulating evaluatation results... [02/04 23:52:27] ppdet.metrics.map_utils INFO: Per-category of VOC AP: +----------+-------+----------+-------+----------+-------+ | category | AP | category | AP | category | AP | +----------+-------+----------+-------+----------+-------+ | person | 0.558 | down | 0.752 | 10+ | 0.710 | +----------+-------+----------+-------+----------+-------+ [02/04 23:52:27] ppdet.metrics.map_utils INFO: per-category PR curve has output to voc_pr_curve folder. [02/04 23:52:27] ppdet.metrics.metrics INFO: mAP(0.50, integral) = 67.31% [02/04 23:52:27] ppdet.engine INFO: Total sample number: 1556, averge FPS: 11.08072138936038 W0204 23:50:00.861847 1420 device_context.cc:447] Please NOTE: device: 0, GPU Compute Capability: 6.1, Driver API Version: 11.1, Runtime API Version: 10.2 W0204 23:50:00.951114 1420 device_context.cc:465] device: 0, cuDNN Version: 7.6. libpng warning: iCCP: known incorrect sRGB profile
PaddleDetection提供了tools/infer.py预测工具,可以使用训练好的模型预测图像并可视化,也可以通过-o weights=指定加载训练过程中保存的权重;参数 --infer_img={img_path}
用于指定待预测图片。
####### 预测 import os import matplotlib.pyplot as plt import cv2 # 0. 设置工作路径 workspace = 'D:\\Workspace\\MyProjects\\PaddleDetection-release-2.3' os.chdir(workspace) ####### 1. 执行预测Inference # 1.1 设置图片路径 img_root = r'../../Data/Projects/Project016FallDownDetection/people(62).jpg' img_file = 'people(62).jpg' img_path = os.path.join(img_root, img_file) # 1.2 设置模型路径 model_template = 'faster_rcnn_r50_fpn_1x_voc' # 1.3 设置Inference图像的输出和读取路径 infer_path = os.path.join(workspace, 'output', 'FallDown') # 1.4 执行预测脚本 !python tools/infer.py -c configs/FallDown/{model_template}.yml -o --infer_img={img_path} --output_dir={infer_path} ####### 2. 可视化Inference结果 plt.figure(figsize=(10, 6)) infer_img_path = os.path.join(infer_path, img_file) infer_img = cv2.imread(infer_img_path, 1) plt.imshow(cv2.cvtColor(infer_img, cv2.COLOR_BGR2RGB)) plt.show()
[02/05 00:37:49] ppdet.utils.checkpoint INFO: Finish loading model weights: output/FallDown/faster_rcnn_r50_fpn_1x_voc/best_model.pdparams [02/05 00:37:51] ppdet.engine INFO: Model FLOPs : 182.884425G. (image shape is [1, 3, 800, 1216]) [02/05 00:37:51] ppdet.engine INFO: Detection bbox results save in D:\Workspace\MyProjects\PaddleDetection-release-2.3\output\FallDown\people(62).jpg W0205 00:37:48.273064 3100 device_context.cc:447] Please NOTE: device: 0, GPU Compute Capability: 6.1, Driver API Version: 11.1, Runtime API Version: 10.2 W0205 00:37:48.288024 3100 device_context.cc:465] device: 0, cuDNN Version: 7.6.