简介基于TensorFlow实现的SqueezeDet卷积神经网络对象检测设计源码面向深度学习开发者与研究人员适合实时目标检测与轻量级部署场景。模型融合SqueezeNet的低参数量设计和YOLO的高效检测框架在保证精度的同时降低计算复杂度。压缩包共41个文件包含26个Python脚本覆盖数据预处理、模型构建、训练评估与结果可视化另有C与C语言文件用于底层加速5张PNG图片展示检测效果2个Shell脚本支持自动化流程并附带配置、说明、许可证等文件整体大小约2.53MB。项目目录清晰便于按模块阅读和二次开发。截至目前已有322人学习下载可用于智能交通、工业自动化、安防监控等对象检测任务。研读源码有助于理解SqueezeDet的网络结构、候选框生成与损失函数设计并掌握TensorFlow下的完整模型实践流程。1. 为什么SqueezeDet能同时跑在GPU和嵌入式设备上当你把YOLOv2在KITTI上跑到80% mAP时模型文件轻松超过200MB前向推理在Jetson Nano上只能跑到5 FPS。SqueezeDet就是冲着这个痛点来的它用SqueezeNet的Fire模块搭起卷积主干再用YOLO风格的检测头做类别和框回归整体是一个轻量级卷积神经网络模型权重不到10MB在TensorFlow环境下可以端到端训练。这个源码包完整覆盖数据加载、训练、评估和demo推理适合正在做嵌入式对象检测选型、或者想在KITTI上快速跑通一个轻量级检测模型的工程师。接下来我会按“源码结构 - 配置 - 训练评估 - demo”的顺序带你把它跑起来并调出自己的模型。2. SqueezeDet源码结构从config到eval的训练闭环拿到源码包不要急着跑train.sh先花十分钟把src下的目录关系理清。这个项目的组织方式很接近TensorFlow模型的典型工程化布局config放超参数nets放网络结构dataset放数据接口utils放公共工具根目录下train.py/eval.py/demo.py是三个入口。理解了谁调用谁后面改代码才不会把配置路径写错。2.1 目录级文件对比哪些是入口哪些是支撑先看src目录下的核心文件分布路径职责运行方式train.py训练入口命令行直接运行eval.py评估入口命令行直接运行demo.py推理入口命令行直接运行config/config.py配置基类被各模型配置继承config/kitti_squeezeDet_config.pySqueezeDet在KITTI上的配置训练时指定config/kitti_squeezeDetPlus_config.pyPlus版本配置训练时指定config/kitti_res50_config.pyResNet50骨干配置训练时指定config/kitti_vgg16_config.pyVGG16骨干配置训练时指定nets/squeezeDet.py模型定义被train/eval调用dataset/kitti.pyKITTI数据接口数据加载dataset/pascal_voc.pyPASCAL VOC数据接口数据加载utils/caffemodel2pkl.py预训练权重转换命令行直接运行这个项目虽然核心是Python脚本但像随机裁剪、数据增广和部分IO处理会依赖C编译的扩展模块这是深度学习工程里常见的性能分工。Python侧负责控制流和模型组装C/C侧负责高频像素操作两者通过TensorFlow的op机制衔接。你不需要改这些底层代码但要知道训练时如果出现C符号未定义大概率是编译环境没有匹配上当前的Python或TensorFlow版本。在train.py中第一步就是读取--config指向的配置文件然后根据配置里的DATASET去实例化dataset/kitti.py或dataset/pascal_voc.py。配置文件里的参数会在训练过程中被不断引用比如BATCH_SIZE、NUM_CLASSES、ANCHORS这些都是直接决定模型能否跑通的关键。2.2 从config到model参数是怎么流进网络结构的config目录下的config.py定义了ModelConfig基类所有模型配置都继承它。基类里常见字段包括BATCH_SIZE、LEARNING_RATE、MAX_GRAD_NORM、NUM_CLASSES、ANCHORS、ANCHOR_PER_GRID、HEIGHT、WIDTH等。在kitti_squeezeDet_config.py中典型配置是这样from config.config import ModelConfig class kitti_squeezeDet_config(ModelConfig): def __init__(self): super(kitti_squeezeDet_config, self).__init__() self.NUM_CLASSES 3 self.ANCHOR_PER_GRID 9 self.HEIGHT 384 self.WIDTH 1248 self.BATCH_SIZE 16 self.LEARNING_RATE 0.001 self.DECAY_STEPS 10000 self.DECAY_RATE 0.1 self.LOSS_COEF_BBOX 5.0 self.LOSS_COEF_CONF_POS 75.0 self.LOSS_COEF_CONF_NEG 100.0 self.LOSS_COEF_CLASS 1.0这段代码的逻辑是通过继承ModelConfig子类只覆写需要改动的字段其他默认值会保留。比如MAX_GRAD_NORM如果不在子类中定义就会沿用基类里的值。这样的好处是切到kitti_squeezeDetPlus_config.py时你只需要关注HEIGHT、WIDTH和网络结构相关的字段损失权重这些训练超参数不会因为切换模型配置而丢失。参数说明LOSS_COEF_CONF_POS和LOSS_COEF_CONF_NEG是SqueezeDet损失函数中的置信度正负样本权重它们之间的比例直接影响模型对前景和背景的敏感度。如果网络预测的框有大量误检可以把LOSS_COEF_CONF_POS调高一点如果召回率偏低则需要调低它。DECAY_STEPS和DECAY_RATE共同决定了学习率按什么节奏下降训练时如果发现loss在某个区间来回震荡可以适当把DECAY_STEPS拉大。2.3 数据加载kitti.py 与 pascal_voc.py 的差异KITTI数据集的标注是每个文件一行格式为类别、截断、遮挡、角度、bbox等。dataset/kitti.py做了两件事一是读取标注并转成TensorFlow的Example协议格式二是为模型输出生成ground truth标签。PASCAL VOC的标注是XML格式所以项目中分别实现两个类。如果你要换自己的数据集只需要参照kitti.py实现一个能返回图片路径和标注框列表的类。看dataset/kitti.py中读取标注的常见实现def _load_kitti_annotations(self, index): label_file os.path.join(self._data_path, label_2, index .txt) boxes [] with open(label_file, r) as f: for line in f: parts line.strip().split( ) cls parts[0] if cls in self._classes: x1 int(parts[4]) y1 int(parts[5]) x2 int(parts[6]) y2 int(parts[7]) boxes.append([x1, y1, x2, y2, self._classes.index(cls)]) return boxes这段代码读取KITTI的label_2目录只保留在_classes列表中出现过的类别并把类别名称转成索引。注意KITTI原始标注里x1、y1、x2、y2是浮点数后续在损失函数里会被当作归一化坐标处理这里保持原样读出来训练时再除图像宽和高。_classes来自于配置中的CLASSES字段所以如果你要检测自己的类别比如交通标志就需要改kitti_squeezeDet_config.py中的CLASSES并确保数据标注格式与KITTI一致。2.4 模型定义squeezeDet.py 与 squeezeDetPlus.py 的边界nets目录下同时存在vgg16_convDet.py、resnet50_convDet.py、squeezeDet.py和squeezeDetPlus.py。它们都实现conv_det方法最后输出的特征图通过一个1x1卷积生成包含类别、位置、置信度的三部分预测张量。在squeezeDet.py中核心是SqueezeNet的fire模块通常写成这样def fire_module(inputs, squeeze_depth, expand_depth): with tf.variable_scope(fire): squeeze tf.layers.conv2d( inputs, squeeze_depth, [1, 1], activationtf.nn.relu) expand1 tf.layers.conv2d( squeeze, expand_depth, [1, 1], activationtf.nn.relu) expand3 tf.layers.conv2d( squeeze, expand_depth, [3, 3], paddingsame, activationtf.nn.relu) return tf.concat([expand1, expand3], axis3)fire_module将squeeze层降维后再并行做1x1和3x3卷积最后拼接在一起。相比VGG那样连续堆3x3卷积它在同样感受野下大幅减少了参数量。squeezeDetPlus.py则是在此基础上增加了多层特征融合输入分辨率更高检测小物体的能力更强但推理速度会下降。所以你如果是在移动设备上部署选kitti_squeezeDet_config.py如果追求精度且GPU显存足够再考虑Plus版。3. 配置KITTI数据集与模型参数训练前必须改的配置项KITTI数据集是SqueezeDet最常见的实验基准但很多人下载完源码后直接运行train.sh结果数据路径不对、类别不匹配训练几步就崩。这里把配置环节单独拆开讲是因为后面所有工作都建立在这个环节正确的基础上。3.1 数据目录规范images、labels和ImageSets怎么放KITTI数据集的原始目录结构是training/image_2和training/label_2。项目里data目录下需要软链或拷贝成如下布局data/kitti/ ├── training/ │ ├── image_2/*.png │ └── label_2/*.txt └── ImageSets/ ├── train.txt └── val.txtImageSets下的train.txt和val.txt每行是一个不带扩展名的图像编号kitti.py会依据它们切分训练和验证集。常见做法是执行项目中的data/random_split_train_val.py来生成python data/random_split_train_val.py \ --data_dir data/kitti/training \ --ratio 0.8这个脚本会按比例把样本随机分成训练和验证并写入ImageSets目录。参数--ratio 0.8表示80%做训练集20%做验证集。如果你只有100张标注图我建议把比例调到0.9验证集太少会让评估指标抖动很大甚至出现某些类别的AP直接为0。3.2 类别、Anchor和输入尺寸的修改点在kitti_squeezeDet_config.py中最常改的三个位置是NUM_CLASSES、CLASSES和ANCHOR_PER_GRID。假设你要从KITTI的3类改成自己的1类person配置改动如下self.NUM_CLASSES 1 self.CLASSES [person] self.ANCHOR_PER_GRID 5 self.HEIGHT 512 self.WIDTH 768修改后模型输出的卷积层通道数会自动根据NUM_CLASSES调整。检测头的通道数公式是ANCHOR_PER_GRID * (NUM_CLASSES 5)其中5是t_x、t_y、t_w、t_h和confidence。如果这个数值与预训练anchor的维度不匹配训练会在损失计算阶段报形状错误。这里的ANCHOR_PER_GRID表示特征图每个位置预测的anchor数量值越大对形状变化更敏感但计算量也线性增加。这里整理一个常用配置项的对照表参数KITTI默认值换成自己的数据时NUM_CLASSES3改为实际类别数CLASSEScar/pedestrian/cyclist改为自己的类别名列表ANCHOR_PER_GRID9根据目标尺寸分布调整HEIGHT/WIDTH384/1248改为32的倍数BATCH_SIZE16按显存大小调整需要强调HEIGHT和WIDTH必须能被下采样因子整除。SqueezeDet的卷积步长总倍数通常是32所以输入尺寸推荐是32的倍数比如384x1248或512x768。使用不匹配的尺寸会导致最后特征图宽度变成小数TensorFlow会直接报Negative dimension size错误。3.3 预训练权重从caffemodel转换的必要性SqueezeNet发布的预训练权重是Caffe格式项目用utils/caffemodel2pkl.py转换成pickle再通过_load_pretrained_weights加载。如果你跳过这一步从随机初始化开始训练收敛会慢很多尤其在样本量小的数据集上容易过拟合。转换命令常见是这样的python src/utils/caffemodel2pkl.py \ --caffemodel data/squeezenet.caffemodel \ --output data/squeezenet.pkl转换完成后把kitti_squeezeDet_config.py中的PRETRAINED_MODEL指向data/squeezenet.pkl。如果没有Caffe环境也可以下载别人转好的.pkl但要注意通道顺序是否与TensorFlow一致。Caffe默认是CHWTensorFlow是HWC不一致时需要在加载后做一次维度转置否则第一层卷积的特征分布会完全错乱。4. 训练与评估实战train.sh/eval.sh 与TensorBoard监控配置改完后下一步是真正把训练跑起来。这里最容易翻车的是TensorFlow版本问题因为源码基于TensorFlow 1.x编写代码中的tf.layers、tf.contrib在TensorFlow 2.x会被移除。所以环境准备这一步不能省。4.1 环境准备TensorFlow版本与依赖我建议用Anaconda建一个独立环境安装TensorFlow GPU 1.14.0conda create -n squeezedet python3.6 conda activate squeezedet pip install tensorflow-gpu1.14.0 pip install opencv-python pillow matplotlib注意不要装TensorFlow 2.x否则train.py会报tf.contrib不存在。如果只有TF 2.x环境也可以用tf.compat.v1兼容模式但要改很多代码不推荐。此外NVIDIA驱动版本、CUDA 10.0和cuDNN 7.6是TensorFlow 1.14官方支持组合安装时优先核对这个对应关系。通过conda创建环境是最省事的直接一条命令就把Python版本和包依赖隔离了。提示TensorFlow 1.x与2.x的原生API变化较大不要尝试在2.x环境直接运行本项目训练脚本否则你会被tf.Session()不存在这种低级错误卡住。4.2 训练启动命令与参数项目根目录下的scripts/train.sh封装了训练入口去掉注释后核心命令是cd src python train.py \ --config config/kitti_squeezeDet_config.py \ --dataset KITTI \ --data_path ../data/kitti/training \ --log_dir ../logs/train \ --pretrained_model ../data/squeezenet.pkl参数说明--config指定模型配置文件切换kitti_squeezeDetPlus_config.py会启用Plus版本。--dataset告诉数据加载模块使用KITTI还是PASCAL VOC接口。--data_path指向training目录脚本会据此拼接image_2和label_2。--log_dir保存训练checkpoint和TensorBoard事件文件。--pretrained_model指向第3章转换出的预训练权重。启动后大约几分钟会在终端打印每步loss并写checkpoint到log_dir/train/model.ckpt-*。如果你想实时看loss曲线另开一个终端运行tensorboard --logdir ../logs/train浏览器打开6006端口就能看到scalar图。如果你想改学习率衰减策略在config里调DECAY_STEPS和DECAY_RATE两个参数控制step增长时学习率下降的速率。我一般在数据量小于5000时把DECAY_STEPS翻倍避免学习率下降太快导致后期精调不足。4.3 评估用eval.py输出mAP和PR曲线用同一份训练好的checkpoint跑验证集命令是cd src python eval.py \ --config config/kitti_squeezeDet_config.py \ --dataset KITTI \ --data_path ../data/kitti/training \ --checkpoint ../logs/train/model.ckpt-10000 \ --eval_dir ../logs/eval \ --split val这里的--split val告诉评估器只加载ImageSets/val.txt里的样本。评估过程中模型会输出每张图的检测框和置信度然后调用dataset/voc_eval.py计算mAP。KITTI评估脚本在dataset/kitti-eval目录下与PASCAL的AP计算方式略有不同主要体现在IOU阈值和官方允许的难以样本处理上。通常跑完看到类似AP for car 0.82这样的输出就说明流程通了。如果AP很低先看是不是配置文件里CLASSES顺序和数据标注不一致类别索引错位会导致ground truth匹配完全失效。4.4 训练与评估中的典型报错下面把最容易踩的坑整理成表格便于对照排查报错特征最可能原因检查项Negative dimension size输入尺寸不是32的倍数HEIGHT/WIDTH调整到32的倍数No module named tf.contribTensorFlow版本太高降到1.14或1.13ValueError: cannot reshape类别数或anchor数与配置不一致核对NUM_CLASSES和ANCHOR_PER_GRIDOutOfMemoryErrorbatch_size太大或图像太大降低BATCH_SIZE或缩放入口尺寸FileNotFoundError: label_2数据路径不对或ImageSets缺失确认训练时--data_path指向training遇到OutOfMemoryError时先看是在哪一步爆掉。如果是加载预训练时爆可能是图片被无限制放大如果训练中途爆我一般会把BATCH_SIZE减半同时把MAX_GRAD_NORM保留默认值防止梯度爆炸和显存抖动叠加。还有一个容易被忽略的点eval.py默认也会占用一定显存如果训练时已经接近显存上限评估前先kill掉训练进程。5. 用demo.py做实时检测单张图片与性能调优的坑训练完模型后用demo.py做单张推理是最直接的验证方法。这章我会把从推理到部署的几个关键技巧一次说清。5.1 单张图片推理demo.py最少参数跑通命令如下cd src python demo.py \ --config config/kitti_squeezeDet_config.py \ --checkpoint ../logs/train/model.ckpt-10000 \ --image_path ../data/demo.jpg \ --output_path ../outputs/demo_result.jpg它读取一张图前向推理一次把检测框和类别画在图上。注意--config中的HEIGHT和WIDTH会决定图片缩放方式如果原图宽高比与配置差异很大物体框位置会出现偏移。常见做法是在demo.py中先按原图比例居中填充而不是直接拉伸。如果你发现框的位置偏了先确认预处理是否用了letterbox。5.2 导出为冻结模型去掉训练节点的关键步骤如果要把训练好的模型部署到Jetson或手机需要导出为冻结的.pb文件。常见做法是from tensorflow.python.framework import graph_io with tf.Session() as sess: saver.restore(sess, ../logs/train/model.ckpt-10000) graph_def tf.get_default_graph().as_graph_def() output_names [squeezedet/conv_det/class/scores, squeezedet/conv_det/bbox/bbox_coord] graph_io.write_graph(graph_def, ../logs/frozen, squeezedet.pb, as_textFalse)这里的output_names需要在实际网络变量名中确认常见做法是在nets/squeezeDet.py最后层打印tf.get_default_graph().get_operations()来核对。导出时一定要用graph_io.write_graph保存为二进制文本格式的pb文件体积更大加载也更慢。冻结完后可以用tf.graph_util.convert_variables_to_constants把权重固化进去这样部署端不需要checkpoint文件。5.3 推理提速NMS阈值和输入尺寸的组合调优SqueezeDet后处理用的是普通NMS不是Fast NMS。在demo模式下调低NMS的IOU阈值可以减少重框但也会让同一物体的多个候选框难以合并导致召回率下降。输入尺寸是一个更直接的杠杆HEIGHT降到256、WIDTH降到800在不太损失小物体精度的前提下推理时间能缩短约30%。这个收益比换NMS实现更稳定。把256x800配置下的检测结果和384x1248的叠在一起看你会发现召回率下降主要出现在远处行人上这就是嵌入式部署前需要权衡的那部分精度。本文还有配套的精品资源点击获取