
简介这份资源提供了基于TensorFlow搭建Faster R-CNN完成目标检测任务的完整工程包含可直接运行的Python代码与配套数据适合具备一定深度学习基础与Python编程能力、希望快速复现经典检测模型的学习者或研究者也可作为课程设计、毕业设计或算法对比实验的起点。压缩包内共有11443个文件其中9963个XML标注文件与1264个PNG图像构成训练/验证数据集另有29个PY脚本、30个PYC编译文件、23个PKL缓存及VGG16预训练权重CKPT整体约502.91MB目录结构清晰便于按模块阅读和调试。目前已有2116人学习下载说明该资源在同类教程中具有一定参考价值。下载后可直接按脚本顺序运行快速体验从数据读取、RPN区域提议到检测结果输出的完整流程也可基于已有代码更换自定义数据集便于理解Faster R-CNN的各个环节并开展二次开发。1. 为什么是这份Faster R-CNN一个能直接跑通的目标检测起点做目标检测的人十有八九卡在第一步论文看完了算法思路也懂了一动手发现环境装不上、数据集格式对不上、训练脚本跑不起来。TensorFlow的Faster R-CNN更是出了名的依赖重灾户——版本对不对、CUDA能不能用、VOC标注怎么转TFRecord任何一个环节翻车半天就没了。这份资源把这些问题一步跨过去了自带完整工程代码自带可直接训练的VOC格式数据集解压配置好环境就能跑通整个流程。它不教你重新发明轮子而是给你一个能改、能调、能换数据的基准实现。适合两类人刚入门目标检测、需要一份能出图能出指标的参考代码的学生以及要在新数据集上快速验证Faster R-CNN效果、不想从零写网络结构的工程师。它最大的价值不是算法多新而是把训练到推理的链路给你补齐了。2. 先看懂Faster R-CNN的骨架RPN与RoI头各自在做什么2.1 双阶段检测为什么在精度上更稳Faster R-CNN是双阶段检测器的代表第一阶段用Region Proposal NetworkRPN从特征图上生成候选框第二阶段把这些候选框对应的特征区域送入RoI头做分类和回归。两个阶段各司其职RPN解决「哪里有目标」RoI头解决「是什么、框多准」。对比单阶段的YOLO和SSDFaster R-CNN的优势在后半段——每个候选框都单独过一遍RoI Pooling再做精细分类和边框回归。单阶段算法在密集小目标场景下容易出现漏检而Faster R-CNN这种「先粗筛再细判」的思路在VOC这类中等密度数据集上跑出来的mAP通常更稳。代价是速度慢GPU上一张图大约100-200ms但换来的是训练收敛时的定位精度和类别置信度更可靠。如果你的场景允许离线推理、对精度要求高于实时性选它是对的。2.2 这份代码的工程结构从train.py到model层解压资源后典型目录结构长这样faster_rcnn_tf/ ├── train.py # 训练入口 ├── test.py # 推理评估入口 ├── demo.py # 单图可视化推理 ├── data/ │ ├── VOCdevkit/ # 自带VOC格式数据集 │ ├── pretrained_model/ # 预训练权重VGG16 backbone │ └── tfrecords/ # 转换后的TFRecord存放目录 ├── lib/ │ ├── networks/ # 网络结构定义 │ ├── rpn/ # RPN相关实现 │ ├── roi_pooling/ # RoI Pooling层 │ ├── datasets/ # 数据集读取与格式转换 │ └── utils/ # 框处理、NMS等工具 └── tools/ ├── create_pascal_tf_record.py # VOC转TFRecord └── eval.py # 计算mAP训练入口是train.py它负责把配置文件里的超参数读进来初始化网络和Session然后进入迭代循环。lib/networks/里是VGG16的卷积层特征提取部分和后续的分类回归头lib/rpn/里实现了anchor生成、RPN分类回归、候选框生成proposal_layer。数据链路从lib/datasets/开始它读取TFRecord并做数据增强。这套结构是最经典的TensorFlow Faster R-CNN实现方式——不依赖tf.contrib的高层抽象网络层都用底层API手动拼好处是你可以精确看到每个张量的形状变化调试时能打印出每一个中间结果。对于想搞懂网络细节的人这份代码比封装好的TensorFlow Object Detection API更值得读。2.3 输入输出与anchors参数网络输入是固定尺寸的图片常见设置是短边600像素、长边不超过1000像素。这份代码里数据预处理阶段会把图片resize到这个范围同时保持宽高比不变。输出端有两个分支分类分支输出每个候选框在N个类别上的概率分布回归分支输出4个坐标偏移量dx, dy, dw, dh对应候选框到真实框的映射。anchor的默认配置直接写在lib/rpn/generate_anchors.py里def generate_anchors(base_size16, ratios[0.5, 1, 2], scales[8, 16, 32]): 生成基础的9个anchor base_size: 基础边长16px对应VGG16下采样16倍 ratios: 宽高比0.5表示高瘦框2表示矮胖框 scales: 缩放倍数 num_anchors len(ratios) * len(scales) # 每个anchor用[x1, y1, x2, y2]表示左上角和右下角坐标 anchors np.zeros((num_anchors, 4)) # ... 具体生成逻辑 return anchors.astype(np.float32)这9个anchor是在16x16的基础框上做缩放变换得到的覆盖了三种宽高比和三种尺度。RPN会对特征图上每个位置输出9个anchor的类别概率前景/背景和4个坐标回归值。如果你要检测的目标特别瘦长比如人形或护栏把ratios改为[0.2, 0.5, 1, 2, 5]这类更细的档位会有效果如果目标特别小scales里要加4或2这种小尺度。这个参数是精度调优的第一个顺手点。3. 把环境先立住TensorFlow版本、CUDA与依赖的匹配3.1 版本矩阵TensorFlow 1.x还是2.x怎么选这份工程代码基于TensorFlow 1.x的API风格编写训练代码里大量使用tf.Session()、tf.placeholder、tf.train.MomentumOptimizer这类动态图机制。如果直接装TensorFlow 2.x裸版本跑起来会报各种AttributeError: module tensorflow has no attribute Session这类错误。这不是代码有问题是API迁移带来的断代。我建议先看代码里用没用tf.compat.v1的兼容层。这份工程完整保留了原生的TF 1.x写法所以最稳妥的方案是装TensorFlow 1.15版本——1.15是官方最后一个完美支持tf.Session的版本之后2.x主推Keras和Eager Execution。如果你是NVIDIA显卡CUDA和cuDNN的版本匹配是最大的坑TensorFlow版本CUDA版本cuDNN版本备注1.1310.07.4老显卡兼容性好1.1410.07.4支持Python 3.71.1510.07.5推荐生态最全3.2 安装步骤与验证脚本推荐用conda隔离环境不污染系统Pythonconda create -n tf15 python3.7 source activate tf15 pip install tensorflow-gpu1.15安装后强制验证GPU可用这一步不能跳过。很多人的训练其实没跑到GPU上用CPU硬算了一下午Loss还一直在涨——白白浪费时间。import tensorflow as tf # 验证GPU设备是否被正确识别 sess tf.Session(configtf.ConfigProto(allow_soft_placementTrue)) print(GPU支持检测:, tf.test.is_gpu_available(cuda_onlyTrue)) # 跑一个简单矩阵乘确认计算发生在GPU上 a tf.constant(1.0, shape[1000, 1000]) b tf.constant(2.0, shape[1000, 1000]) c a * b print(矩阵计算结果:, sess.run(c[0, :3]))这段代码同时验证了三个关键点Session能不能正常创建、GPU设备是否被CUDA识别、最基本的算子能否执行。如果输出里没有出现GPU设备信息说明要么CUDA环境变量没配好要么TensorFlow找不到libcudart.so动态库。此时先别急着跑训练把nvcc -V和nvidia-smi两个命令打印出来的版本对齐再装一遍TensorFlow。3.3 依赖不一致时看什么报错版本不对最常见的错误是ImportError现象是加载tensorflow时报libcudart.so.10.0: cannot open shared object file这说明TensorFlow 1.15默认找CUDA 10.0的运行时库你的机器装的是CUDA 11.x。解决方法是装对应的CUDA版本而不是在网上搜「环境变量怎么配」——环境变量只能让驱动找到库解决不了库版本本身缺失的问题。还有一类报错是Could not create cudnn handle或者failed to get convolution algorithm。这是显存碎片或cuDNN版本不匹配的表象。优先看cuDNN是不是7.5以上且和CUDA 10.0对应其次降低batch_size释放显存。我一般习惯在代码里加一句config.gpu_options.allow_growth True让TensorFlow按需申请显存而不是启动时占满全部——多卡机器上这个设置能避免和其它进程抢显存。4. 数据准备与格式转换VOC标注怎么变成TFRecord4.1 资源里的数据目录结构Faster R-CNN训练不能直接读VOC的XML标注文件输入层要的是TFRecord这种二进制格式读取速度快、支持多线程shuffle。资源自带的数据集已经按VOC标准目录组织好了VOCdevkit/ └── VOC2007/ ├── JPEGImages/ # 所有训练和测试原图 ├── Annotations/ # 每个图片对应的XML标注 ├── ImageSets/ │ ├── Main/ │ │ ├── train.txt # 训练集图片文件名列表 │ │ ├── val.txt # 验证集图片文件名列表 │ │ └── test.txt # 测试集图片文件名列表 └── Labels/ # 部分版本会预生成txt格式标签JPEGImages里是原始图片Annotations里是PASCAL VOC格式的XML文件。每个XML里包含图片尺寸信息width、height和若干个object节点每个object节点里有name标签名和bndbox标注框的四个坐标。ImageSets/Main里的txt文件决定了哪些图片进入训练、哪些进入验证——这份资源已经帮你划分好了不用自己重新切片。4.2 转换脚本的用法tools/create_pascal_tf_record.py就是干这件事的。它读取VOC目录把图片和标注打包成TFRecord文件。基本调用方式python tools/create_pascal_tf_record.py \ --data_dirdata/VOCdevkit \ --year2007 \ --settrainval \ --output_pathdata/tfrecords/voc_2007_trainval.tfrecord参数说明--year指定VOC版本--set指定要转换的数据子集trainval表示训练和验证合并如果你只想快速看效果可以单独--settrain--output_path是TFRecord输出位置。转换过程会在终端打印每个图片的处理进度跑完后在data/tfrecords/目录下能看到生成的二进制文件。转换脚本内部做了四件事读取图片文件并解码为字节数据、解析XML里的标注框和类别、把坐标转换为相对值除以图片宽高、将所有信息封装成tf.train.Example协议缓冲区写入文件。这里注意坐标转相对值是必须的——网络最终输出的是0~1之间的归一化坐标偏移输入的数据也要归一化否则Loss会先崩一波。4.3 标注格式与标签映射XML里每个object节点的典型结构长这样annotation filename000001.jpg/filename size width375/width height500/height /size object nameperson/name bndbox xmin101/xmin ymin160/ymin xmax233/xmax ymax413/ymax /bndbox /object /annotation转换脚本解析时会维护一个类别字典把字符串类别映射为整数索引比如{background: 0, aeroplane: 1, bicycle: 2, ...}。这个映射表必须和模型输出层的类别数一致。如果数据集里新增了一个类别要同步改三处类别字典、模型配置里的num_classes、lib/datasets/pascal_voc.py里的self._classes列表。漏改任何一处都会训练时报维度不匹配。还有一个容易被忽略的点bbox坐标里xmin和xmax如果相等或者超出图片边界转换脚本会直接跳过这个标注或报错。混入脏数据会导致训练中途崩溃报错信息又长又怪最后排查到源头发现是某张图标注框越界。我处理的时候会在转换脚本里加一个校验函数过滤掉非法框——这也是这份资源本身没做但实战必备的一个补强。4.4 生成TFRecord后的验收不要急着训练先写几行代码验证TFRecord能正常读回来import tensorflow as tf # 从TFRecord读取一条样本解析 def parse_example(serialized_example): features tf.parse_single_example( serialized_example, features{ image: tf.FixedLenFeature([], tf.string), height: tf.FixedLenFeature([], tf.int64), width: tf.FixedLenFeature([], tf.int64), bboxes: tf.FixedLenFeature([None], tf.float32), labels: tf.FixedLenFeature([None], tf.int64), } ) return features # 验证能读到数据并打印形状 read_files tf.data.TFRecordDataset(data/tfrecords/voc_2007_trainval.tfrecord) for serialized_example in read_files.take(5): features parse_example(serialized_example) print(图片尺寸:, features[height], x, features[width]) print(标注框数量:, len(features[bboxes]))如果这一步能正常读出5条样本说明数据链路是通的此时再进入训练环节。如果读出来是空数据集多半是转换时ImageSets/Main里的txt文件名列表和JPEGImages实际文件名对不上——检查txt里有没有多余的空行或后缀不一致。5. 训练与踩坑排查参数怎么调、Loss不降与显存溢出5.1 训练启动与关键超参环境OK、数据OK就可以启动训练了。这份代码用train.py作为入口最简启动方式python train.py \ --dataset_dirdata/tfrecords \ --weights_pathdata/pretrained_model/vgg16.ckpt \ --max_iters50000 \ --batch_size16 \ --learning_rate0.001 \ --num_classes21先解释参数含义这套参数是VOC 20类背景的标配参数推荐值说明max_iters50000迭代次数显存小的卡先设10000试通链路batch_size16单卡16是VGG16 backbone的调度上限显存12G以下降到8learning_rate0.001训练初期用这个值30000步后衰减到0.0001num_classes2120个VOC类别 1个背景类训练过程会每20步打印一次Loss信息每10000步保存一个checkpoint到output/目录。第一次跑建议把max_iters设为5000先确认整条链路能走通再跑完整训练——直接拉50000步如果中途因为某层输出维度不匹配崩掉调试成本高得多。5.2 Loss曲线怎么看训练日志里显示的Loss是一个总体值实际上是多个Loss的加权和。以这份代码为例iter: 20, loss: 2.857, rpn_cls_loss: 1.234, rpn_reg_loss: 0.431, roi_cls_loss: 0.858, roi_reg_loss: 0.334四个分量分别对应RPN的分类Loss、RPN的回归Loss、RoI头的分类Loss、RoI头的回归Loss。它们的含义rpn_cls_lossRPN判断anchor里有没有目标的损失。正常应该在1左右浮动如果一直在2以上降不下去说明anchor设置和数据集目标尺度严重不匹配rpn_reg_lossRPN预测anchor偏移量的损失。这个值通常比分类Loss小一个量级因为它只在正样本anchor上计算roi_cls_lossRoI头最终判断候选框类别的损失。这个下降最直观——从一开始的3左右降到0.2以下说明分类学出来了roi_reg_lossRoI头精调候选框坐标的损失。训练后期这个值会变得很小但不会归零因为框坐标永远有噪声整体Loss的下降曲线应该是平滑的阶梯式——每段平台都是学习率在起作用。如果你的Loss在前2000步内降到接近0.5以下大概率是过拟合了数据集太小正常VOC训练前5000步Loss应该还在2附近徘徊。5.3 Faster R-CNN训练常见问题排查这部分的每个问题都是实际跑工程时遇到过的真坑按「现象→原因→解决」梳理。现象1训练启动时报OutOfMemoryError显存直接被占满。原因默认配置里config.gpu_options.per_process_gpu_memory_fraction1.0TensorFlow启动时会试图一次性占满整张卡的显存。如果卡上同时跑了其它进程必然OOM。解决训练脚本开头加上config tf.ConfigProto() config.gpu_options.allow_growth True # 按需申请显存 config.gpu_options.per_process_gpu_memory_fraction 0.8 # 最多使用80% sess tf.Session(configconfig)把allow_growth设为True后TensorFlow只在每次需要时申请显存增量实际占用一般会比显存总量小30%左右。batch_size也要同步动调12G显存配16batch跑VGG16会有压力降到8最稳。现象2Loss在训练初期突然变成NaN。原因最常见的是学习率设置太高梯度在回传过程中溢出其次是TFRecord坐标归一化时出现了NaN值——比如标注里出现了width0导致的除零操作。解决先检查TFRecord里的bbox坐标是否有0再用小学习率复位。这里给出一个稳妥的做法——在配置里加梯度裁剪optimizer tf.train.MomentumOptimizer(learning_ratelr, momentum0.9) # 梯度裁剪防止更新量过大炸掉Loss grads, vars zip(*optimizer.compute_gradients(loss)) grads, _ tf.clip_by_global_norm(grads, 5.0) train_op optimizer.apply_gradients(zip(grads, vars))梯度裁剪强制把所有梯度的全局L2范数限制在5.0以内即使某一步出现了异常梯度更新量也不会突变到把权重推飞。现象3Loss一直下降但推理时框全部偏移检测位置错得离谱。原因RPN收敛了但RoI回归没有对齐。通常是没有加载预训练权重或者VGG16的网络层命名空间与预训练权重里的变量名不一致——加载后有很多变量是随机初始化的。解决加载预训练权重后做一次变量匹配验证把不匹配的变量名打印出来。然后在train.py中初始化函数里强制展开所有变量# 加载预训练模型时排除分类层和回归头这两层要随机初始化重新学 variables_to_restore tf.contrib.slim.get_variables_to_restore() variables_to_restore [ v for v in variables_to_restore if not (cls in v.name or reg in v.name) ] saver tf.train.Saver(variables_to_restore)排除掉分类和回归层的用意是VGG16的卷积层提取的特征是通用的可以复用预训练权重但最终的分类头和回归头通道数取决于你手头的类别数加载预训练权重里的对应参数反而会扰乱训练起点。现象4训练到20000步后mAP停滞在60%左右上不去。原因这个阶段一般不是代码问题是anchor设置和数据集目标尺度不对齐。VOC数据集中小目标占比较高默认anchors里最小scale8即16x8128px的目标覆盖不了更小的目标。解决先统计数据集中所有标注框的宽高分布再改generate_anchors.py里的参数。打印标注框尺寸分布的快捷方法import xml.etree.ElementTree as ET import glob sizes [] for xml_file in glob.glob(data/VOCdevkit/VOC2007/Annotations/*.xml): root ET.parse(xml_file).getroot() for obj in root.iter(object): box obj.find(bndbox) w int(box.find(xmax).text) - int(box.find(xmin).text) h int(box.find(ymax).text) - int(box.find(ymin).text) sizes.append((w, h)) print(平均宽高:, np.mean(sizes, axis0)) print(最大宽高:, np.max(sizes, axis0))如果算出来平均宽高在80px以下默认anchor就偏大了。把scales参数改为[4, 8, 16]让RoI头能处理更小的目标。现象5Train和Val表现差距极大train的准确率很高val一塌糊涂。原因这是典型的过拟合但很少有人想到一个细节——数据增强。这份代码的默认配置里只做了水平翻转没有随机裁剪和色彩抖动。VOC图片数量只有5000张模型很容易把背景和目标的强关联记下来。解决如果不想改太多代码先在lib/datasets/pascal_voc.py里加上随机裁剪增强——从原图中随机裁一块包含部分标注框的图片参与训练相当于变相扩大了有效样本数。核心逻辑是把标注框坐标同步做平移和缩放def random_crop(img, boxes, labels): h, w img.shape[:2] # 随机裁剪区域为新图片的0.7~1.0倍 crop_ratio np.random.uniform(0.7, 1.0) crop_h, crop_w int(h * crop_ratio), int(w * crop_ratio) x_off np.random.randint(0, w - crop_w) y_off np.random.randint(0, h - crop_h) crop_img img[y_off:y_offcrop_h, x_off:x_offcrop_w] # 标注框同步裁剪,并过滤掉裁剪后面积过小的框 new_boxes [] for box in boxes: x1, y1, x2, y2 box if x2 x_off or y2 y_off or x1 x_offcrop_w or y1 y_offcrop_h: continue # 这个框完全在裁剪区域外 new_box [ max(x1-x_off, 0), max(y1-y_off, 0), min(x2-x_off, crop_w), min(y2-y_off, crop_h) ] if new_box[2]-new_box[0] 20 or new_box[3]-new_box[1] 20: continue # 太小的框容易引入噪声,直接丢弃 new_boxes.append(new_box) return crop_img, new_boxes, labels注意这个增强不影响TFRecord的读取——它发生在数据加载到内存后、进网络前。加了这一步之后等于训练集里每张图每次迭代用的都是随机裁剪版本能有效缓解过拟合。6. 推理与验证mAP计算、置信度阈值与迁移到新数据集6.1 用训练好的权重跑通推理训练完成后output/目录下会保存最终的checkpoint文件。推理入口是test.py它会读取checkpoint、遍历测试集图片、输出检测结果python test.py \ --checkpoint_pathoutput/vgg16_faster_rcnn.ckpt \ --test_dirdata/VOCdevkit/VOC2007/ImageSets/Main/test.txt \ --output_diroutput/detection_results推理脚本的核心工作是逐图前向计算然后通过lib/utils/nms.py里的NMS后处理去掉重叠框。NMS的关键参数是nms_threshold0.3和score_threshold0.7——前者控制两个重叠度多少时保留高置信度的框后者控制置信度低于多少直接丢弃。如果你的场景里漏检比误检更不可接受把score_threshold降到0.5如果追求精确率升到0.8。6.2 mAP计算与调优基线评估目标检测模型不能只看单个精度的数字要用VOC官方的mAP指标。这份代码里的tools/eval.py会遍历所有图片收集每个类别的precision-recall曲线然后计算AP的平均值。跑完一轮评估输出会类似AP for aeroplane 0.756 AP for bicycle 0.812 AP for person 0.753 ... Mean AP 0.701不同类别的AP差异能直接告诉你模型在哪类目标上弱——如果person的AP远低于均值大概率是训练集中人体目标太小或遮挡严重此时调整anchor和随机裁剪参数比调整学习率更有效。6.3 一套通用的数据迁移清单如果你要把它用到自己的数据集上按这个顺序改动即可先把标注转成VOC XML格式 → 在lib/datasets/pascal_voc.py里更新类别列表 → 重新生成TFRecord → 修改训练脚本的num_classes参数 → 重新训练。整个过程不需要动网络结构Faster R-CNN的骨架是数据无关的。有一个小坑必须提前说如果你的数据集中类别数和VOC完全不同且图片尺寸普遍较大比如遥感影像VGG16 backbone的最后一个池化层输出的特征图尺寸会偏大。这不一定带来精度提升反而会让RPN跑得极慢并占用更多显存。此时需要在train.py里调整输入图片的resize尺寸把长边从1000降到800逼着模型学习更紧凑的特征表达——这个调整对遥感这类大图场景作用非常明显。我最早在这份代码上跑自己的数据集时上来就是全默认参数结果训练了4小时Loss不降、mAP不到20%。后来按上面的思路挨个排查查TFRecord有没有问题、查类别数对不对、查anchor尺度合不合理最后发现是我在转换XML时把name标签的值弄错了模型把猫都学成了dog。那以后我每次换数据集都强制走一遍「打印10张图检测结果按类别统计AP」的验收流程确认每个类别都出图了再谈调优。希望帮到你。本文还有配套的精品资源点击获取