YOLOv3与行人重识别:从检测到特征匹配的实战入门 简介基于YOLOv3与行人重识别ReID技术的行人搜索完整实现适合人工智能、图像识别方向开发者学习实践特别面向需要完成“检测跨镜头匹配”任务的算法工程师与学生。项目先由YOLOv3完成行人检测生成候选框再借助重识别模型提取特征并跨视角匹配从而定位特定行人。资源包共49个文件包含27个Python脚本模型定义、检测推理、查询接口等、19张示例图片、YOLO配置文件、类别名称文件与数据配置大小仅5.51MB轻量易部署。目录结构清晰涵盖训练与推理脚本、预处理工具和测试图片便于快速复现通过研读核心代码可理解特征提取与相似度匹配的具体实现并支持在自有数据上调整模型。目前已有1359人学习适合希望掌握目标检测与重识别串联流程的开发者可为智能监控、交通分析等场景提供技术基础。1. YOLOv3 行人重识别一个能“找特定行人”的入门demo做图像识别项目绕不开一个真实需求模型告诉你画面里有一群人还不够你真正想找的是“那个穿红衣服的人还在不在别的镜头里”。这类任务靠单一目标检测无法完成需要把 YOLOv3 的行人检测和行人重识别ReID串成一条流水线。person_search_demo-master 正是这套思路的落地demoYOLOv3 先在街景或监控画面中圈出所有行人ReID 模型再为每个检测框提取特征向量拿你的查询图片去比对最后按相似度排序输出“最可能是目标行人”的结果。它不是生产系统精度和速度都有优化空间但作为毕设、课程设计或入门实战的骨架非常合适能让你在一台普通电脑上看到完整链路跑通。2. 先看懂两个模型的分工YOLOv3 的检测框和 ReID 的特征向量怎么配合2.1 YOLOv3 的多尺度检测为什么它在这里是候选框生成器YOLOv3 最核心的思路是把输入图片划分成 S×S 的网格每个网格负责预测若干个边界框以及这些框的类别概率。它用了三个不同尺度的 feature map分别偏重检测大、中、小目标。这个特性对行人搜索很重要监控画面里行人距离镜头远近差异极大有的行人只占几十个像素如果没有多尺度预测远处的小目标很容易漏掉。不少人会有个误区觉得行人搜索场景里YOLOv3 扮演的角色就是“最后结果”。实际不是。在这个项目中YOLOv3 更像是一个候选框生成器它输出的每个检测框都只是给后续 ReID 提供裁剪区域。我一般会先看它的输出置信度再把框完整截取出来。这里有个血泪经验如果检测框把行人肩膀或者脚踝截断了ReID 特征会很飘因为重识别模型的训练数据大多是包含完整人体的图片输入的“半截人”超出了它见过的分布。YOLOv3 是单阶段检测器处理速度比两阶段的 Faster R-CNN 快这也是很多行人搜索项目用它开头的原因。对于这套 demo 来说速度不用追求极限更重要的是它的结构足够直白models.py里能直接看到网络定义cfg/yolov3.cfg里能逐层看到卷积、残差块和上采样结构想调试时不会陷入“黑匣子”困境。2.2 ReID 模型特征向量怎么描述“同一个人”行人重识别ReID本质上是度量学习不是分类。它不会直接告诉网络“这是001号行人”而是训练网络输出一个高维特征向量让同一个人的不同照片特征距离近、不同人的照片特征距离远。在 person_search_demo 的reid目录下modeling、datasets.py、config等文件共同组成了一套完整的 ReID 结构。常见做法是先用 CNN 对输入图像做卷积最后用全连接层或全局池化输出一个固定长度的向量比如 1024 维或 2048 维。推理阶段检测框内的行人会被 resize 到 ReID 模型要求的输入尺寸通常是 256×128 或 128×256具体以代码里的配置为准。比较特征时常见度量有两种余弦相似度和欧氏距离。余弦相似度把两个向量归一化后计算夹角余弦值越接近 1 表示越相似欧氏距离则是算向量之间的直线距离值越小越相似。两个度量在数学上是相通的但实际代码里用哪个、阈值怎么定会直接影响搜索结果。很多翻车现场就是这里埋下的后面避坑章会专门展开。2.3 串联推理从一张街景图到一份行人排序结果整套流程可以拆成五步检测、过滤、裁剪、提特征、比对。用 Python 伪代码看更加直观import cv2 import numpy as np # 1. YOLOv3 检测整张图, conf_thres 控制置信度阈值 boxes yolov3.detect(image, conf_thres0.4, nms_thres0.5) # 2. 只保留类别为 person 的检测框 person_boxes [b for b in boxes if b.cls person] # 3. 逐个裁剪并 resize 到 ReID 指定尺寸 features [] for box in person_boxes: crop image[box.y1:box.y2, box.x1:box.x2] crop cv2.resize(crop, (128, 256)) # 常用 ReID 输入尺寸 crop normalize(crop) # 像素归一化到 [0,1] 区间 # 4. ReID 前向推理, 得到特征向量 feat reid_model(crop) features.append(feat) # 5. 将 query 特征与所有 gallery 特征做余弦相似度比对 scores cosine_similarity(query_feat, np.stack(features))逻辑上要注意两点yolov3.detect是封装后的方法真实项目里可能叫search.py里的具体函数参数conf_thres和nms_thres通常暴露在配置里不要写死在网络结构里。normalize这一步也不能省模型的训练和推理必须使用相同的预处理方式像素范围、通道顺序RGB 还是 BGR、均值减法都必须一致否则特征会整体偏移匹配效果断崖式下降。这套串联逻辑看起来简单但一旦某个环节输入分布变了后面所有结果都会跑偏。所以我建议先在一两张图上手动画框验证确认裁剪和 resize 没问题后再跑批量否则你拿到的可能是一堆“看似排序正确但其实是随机噪声”的输出。3. 把 person_search_demo 跑起来目录结构、环境依赖和两条推理路径3.1 解压后的目录结构先搞清楚每个文件干什么解压person_search_demo-master.zip后第一眼看上去文件不少但真正需要手动改动的其实没几个。下面这张表是我拆过之后总结的职责分工文件/目录作用models.pyYOLOv3 网络结构定义用 PyTorch 实现search.py主搜索入口串联检测、提特征、匹配全过程cfg/yolov3.cfgYOLOv3 网络结构配置文件层参数都在这里data/coco.namesCOCO 数据集的 80 个类别名称需要从里面筛出 persondata/coco.data类别数、权重路径、验证集路径等整套配置samples/待检索的 gallery 图库图片query/查询图片自带两个编号对应的 jpgreid/config/ReID 模型参数配置reid/modeling/ReID 网络结构代码reid/detect.py对检测框批量提取 ReID 特征reid/query_get.py生成查询图片的特征向量reid/datasets.pyReID 数据集的读取和预处理reid/utils.py距离计算、可视化等工具函数reid/parse_config.py解析 ReID 配置文件的工具show.jpg结果可视化示例图在动手跑之前先打开data/coco.names确认第一个人名是不是 person因为 YOLOv3 输出的类别索引是从 0 开始的如果类别顺序不一致过滤条件会完全失效。项目里常见的过滤写法是class_ids 0对应 COCO 的 person 类别。一旦你换了自定义数据集这个索引必须同步改。3.2 环境配置PyTorch OpenCV 是底线这个项目是较早期的 YOLOv3 实现依赖并不复杂核心是 PyTorch、OpenCV 和 NumPy。我一般会在 Python 3.8 的环境里安装 PyTorch 1.8 或 1.10 的版本跑起来最省心。如果你用的是 PyTorch 2.x大概率也能跑但可能会遇到个别接口改名比如某些torchvision.transforms的写法需要兼容调整。pip install torch torchvision opencv-python numpy安装完之后验证一下 import 是否正常python -c import torch, cv2, numpy; print(torch.__version__, cv2.__version__)这里有个细节如果要用 GPU 加速PyTorch 的安装包必须和你的 CUDA 版本匹配。你可以先跑nvidia-smi看驱动支持的最高 CUDA 版本再去 PyTorch 官网选对应版本。没有独立显卡也不用慌这个 demo 的检测图和 query 图都不算大CPU 也能跑通只是速度慢一些。3.3 运行 search.py 和 query_get.py先看输出再调参项目里比较自然的运行顺序是先进入reid目录生成查询特征再回到根目录执行主搜索。因为search.py需要用到查询图片的特征如果这个特征还没生成后面比对就没有参照物。cd person_search_demo-master/reid python query_get.pyquery_get.py会读取query/目录下的两张 jpg调用 ReID 网络提取特征向量并把这些特征保存下来。运行成功后终端一般会打印出特征维度或保存路径。如果看不到任何输出检查query目录里是否有图片以及reid/config里的模型路径是否正确。接下来回到项目根目录跑主搜索脚本cd .. python search.pysearch.py的职责是遍历samples/目录里的图片对每张图先用 YOLOv3 检测行人再用 ReID 提取特征最后和刚才生成的 query 特征做相似度比对按得分从高到低排序把最匹配的结果可视化到show.jpg。运行结束后屏幕上会打印每个候选框的相似度得分同时show.jpg里会标出检测框和匹配到的目标行人。如果你拿到的代码版本没有把参数写成命令行形式只能靠硬编码路径那就在search.py顶部找query_dir、gallery_dir、output_path这类变量改成你自己的路径。# search.py 中常见的可配置变量 conf_thres 0.4 nms_thres 0.5 query_dir query gallery_dir samples output_path show.jpgconf_thres越低yolov3 会输出越多的检测框漏检更少但误检也更多nms_thres控制重叠框的合并力度设太高会出现一堆框叠在同一个人身上设太低会把两个挨得近的行人框强行合并。这两个参数是后续调优的主战场。4. 调参实战conf_thres、nms_thres 和 ReID 相似度阈值的取舍4.1 检测阈值怎么设conf_thres 和 nms_thres检测环节有两个阈值直接决定整套系统的输入质量。conf_thres是置信度阈值低于该值的边界框会被丢弃。行人检测场景下我一般从 0.4 开始试。如果测试图是中远景人群0.4 可能会漏掉几个像素很小的远处行人这时往下降到 0.3 能看到明显召回提升但要接受更多背景误检框如果测试图是近景特写0.5 都足够因为近处行人置信度通常很高。nms_thres是 Non-Maximum Suppression 的阈值作用是去掉重叠度高的重复框。行人场景有一个特殊性人的姿态各异站在一起时两个框的重叠区域可能超过普通阈值。设 0.4 时两个人紧挨着可能被合并成一个人导致 ReID 拿到的是一个“双人混合框”特征完全混乱设 0.6 时同一个行人身上可能出现多个冗余框ReID 会对同一人多算好几次结果排序里同一身份占好几行。参数建议值表现conf_thres0.35召回和误检平衡点适合中远景人群conf_thres0.5高质量近景误检明显减少nms_thres0.45行人密度中等时比较稳nms_thres0.6行人重叠严重时需要但要注意重复框调整时不要只看单张图我习惯把检测框先画出来看一眼如果框的边缘正好贴合行人两侧说明参数合理如果框里带着大量背景或者框明显偏小先别急着调 ReID回头改检测参数更有效。4.2 ReID 相似度度量用余弦距离还是欧氏距离ReID 阶段最核心的参数是相似度阈值。使用余弦相似度时代码逻辑是“得分越高越相似”使用欧氏距离时是“距离越小越相似”。这两个方向反着最容易搞混。项目里reid/utils.py通常会封装好距离计算函数找到它确认内部用的公式再去解释结果。实际经验是同一个人的两帧图余弦相似度通常在 0.5 到 0.8 之间不同人一般在 0.2 以下。但这个分布受 ReID 训练数据集影响很大不能死套阈值。我一般会先取 5 组“同人”和 5 组“异人”样本分别算一遍相似度画个简单分布再选一个让两组不重叠的临界值作为最终阈值。有一点必须强调特征向量如果不做归一化余弦相似度算出来是失真的。常见做法是在比对前执行feat feat / np.linalg.norm(feat)确保每个向量模长为 1。这一步看起来无关紧要实际影响非常大很多“同一个人搜不出来”的问题都出在这里。4.3 换自己的查询图尺寸、裁剪和预处理对齐很多人会直接用自己的图片替换query/目录里的文件然后发现结果很差。问题往往不在模型而在查询图的形态。如果查询图是整张街景图里面既有行人也有大量背景那就必须走“先检测后裁剪”的流程如果查询图已经是裁剪好的行人半身图或全身图再走检测反而会把整张图当作一个大框 resize等于把背景也当成行人特征。常见做法是给query_get.py加一个开关比如pre_crop参数来控制是否需要先做 YOLOv3 检测。pre_crop True # 查询图是整张街景时设为 True if pre_crop: box yolov3.detect(query_img)[0] # 取置信度最高的行人框 query_img crop_resize(query_img, box) else: query_img cv2.resize(query_img, (128, 256))参数说明取置信度最高的框是默认策略因为查询图里通常目标行人占主体如果你的查询图里有多个人那就换成“面积最大的框”或者手动指定坐标。resize 尺寸 128×256 对应常见的 ReID 输入不要一边这里用 128×256另一边datasets.py里用的是 256×128尺寸不一致会直接导致特征分布错位。5. 避坑记录YOLOv3 ReID 串联最容易翻车的四个问题5.1 检测框很多结果却搜不到同一个人现象YOLOv3 画出了大量行人框但搜出来的 top10 里没有一个是查询目标相似度分数普遍很低。原因最常见的是 ReID 特征没有归一化或者 query 特征和 gallery 特征使用了不同的预处理方式。另一个常见原因是检测框裁剪得太随意把行人的头或脚截掉了一部分。解决比对前强制对特征向量做 L2 归一化检查reid/datasets.py里的 transform 是否和推理代码一致尤其是像素范围0~255 还是 0~1、通道顺序RGB/BGR和归一化均值。5.2 权重加载报错KeyError 与 size mismatch现象加载 YOLOv3 预训练权重时终端报KeyError或者size mismatch for conv.53.weight。原因yolov3.cfg里的网络层数和权重文件不匹配常见于手动改了cfg里的filters数量或者权重文件本身是另一个数据集的版本。解决从models.py里按官方默认结构加载不要动yolov3.cfg最后几个卷积层的filters如果需要换自定义类别数必须同步修改权重文件和 cfg 的层输出维度而不是只改一个文件。5.3 cv2.imread 读中文路径返回 None现象图片明明在目录里但cv2.imread(path)返回 None程序安静地跳过导致后面检测不到任何东西。原因OpenCV 的老版本对中文路径支持不好路径里只要出现中文或特殊字符imread 就会静默失败。解决用cv2.imdecode配合np.fromfile读取import cv2 import numpy as np img cv2.imdecode(np.fromfile(path, dtypenp.uint8), cv2.IMREAD_COLOR)注意这张图是 BGR 通道顺序后面输入模型前如果预期是 RGB需要先做cv2.cvtColor(img, cv2.COLOR_BGR2RGB)。5.4 CUDA out of memory批量推理显存炸了现象跑单张图正常跑samples目录里几十张图时中途报CUDA out of memory。原因批量推理时YOLOv3 的检测框全堆在显存里ReID 又把每张裁剪图当作一个 batch累积下去显存很快就满了。还有一个常见坑推理代码没有写torch.no_grad()导致模型保留了梯度图显存占用直接翻倍。解决把所有前向推理包在with torch.no_grad():里把批量处理改成逐张图处理YOLOv3 的输入尺寸从 608 降到 416 也能显著减少显存占用代价是远处的小行人更容易漏检。6. 进阶用 query_get.py 做批量检索和效果验证6.1 批量生成查询特征把 query_get.py 改造成特征库默认的query_get.py只处理两张查询图实际使用时远远不够。你可以把它改造成批量提取特征的工具把所有查询图片的特征保存到一个字典文件里后续搜索时直接加载省去每次重复前向推理的时间。import glob import numpy as np feat_dict {} for img_path in glob.glob(query/*.jpg): feat extract_feature(img_path) # 复用 detect.py 里的推理逻辑 feat feat / np.linalg.norm(feat) # 比对前归一化 feat_dict[img_path] feat print(processed:, img_path) np.save(query_feats.npy, feat_dict)逻辑说明这段代码把每个查询图片的路径作为 key特征向量作为 value最后统一存成 npy 文件。extract_feature是模拟函数实际对应reid/detect.py里对单张图片检测加提特征的完整流程。保存成字典而不是数组的好处是运行时可以按文件名直接取特征不用自己维护顺序映射。6.2 怎么验证结果可靠rank-1 和 mAP 的自测方法跑通 demo 之后不要只看一张效果图就完事还需要验证指标。对于行人搜索最直观的是 rank-1 准确率查询图在 gallery 里匹配到的第一个结果是否就是目标本人。你在自己的测试集上准备 10 组已知身份的照片每组一张查询图、若干张图库图统计第一命中率即可。mAP 更复杂一些需要给每张 gallery 图标出真实身份标签再按相似度排序算平均精度均值。我自己的教训是这类串联模型一旦指标不对先检查数据预处理和归一化再怀疑模型结构。Good 的检测框、对齐的 ReID 输入、归一化的特征这三个条件同时满足结果通常不会差。从那以后我每次跑行人检测加匹配的流程都会先跑一遍批量自测确认阈值分布稳定后才继续调结构。希望帮到你。本文还有配套的精品资源点击获取