可以将Person re-ID视为图像检索问题。给定摄像机A中的一个查询图像(query),我们需要查找其他摄像机(gallery)中同一个人的图像。Person re-ID的关键是找到该人员的区别性表示形式。
本文参考链接:https://github.com/layumi/Person_reID_baseline_pytorch/tree/master/tutorial
- 首先在linux命令行中新建一个目录
(名字可以按你项目名定义),之后敲mkdir reID
命令,确认git工具已经安装好了,没有的话就git
sudo apt install git
-
在github查看你所需要使用下载源码项目的URL地址
代码链接:https://github.com/layumi/Person_reID_baseline_pytorch
- 进入需下载代码的目录中
,在linux命令行中使用cd /home/jychen/work/ReID
,对该项目源码clone到本地,等待clone完成git clone https://github.com/layumi/Person_reID_baseline_pytorch.git
- 进入需下载Market-1501数据集的目录中
,输入cd /home/jychen/work/ReID/data
wget -c http://188.138.127.15:81/Datasets/Market-1501-v15.09.15.zip
下载数据集
数据集链接:http://188.138.127.15:81/Datasets/Market-1501-v15.09.15.zip
Market-1501 数据集在清华大学校园中采集,夏天拍摄,在 2015 年构建并公开。它包括由6个摄像头(其中5个高清摄像头和1个低清摄像头)拍摄到的 1501 个行人、32668 个检测到的行人矩形框。每个行人至少由2个摄像头捕获到,并且在一个摄像头中可能具有多张图像。训练集有 751 人,包含 12,936 张图像,平均每个人有 17.2 张训练数据;测试集有 750 人,包含 19,732 张图像,平均每个人有 26.3 张测试数据。3368 张查询图像的行人检测矩形框是人工绘制的,而 gallery 中的行人检测矩形框则是使用DPM检测器检测得到的。该数据集提供的固定数量的训练集和测试集均可以在single-shot或multi-shot测试设置下使用。
目录结构
Market-1501
├── bounding_box_test
├── 0000_c1s1_000151_01.jpg
├── 0000_c1s1_000376_03.jpg
├── 0000_c1s1_001051_02.jpg
├── bounding_box_train
├── 0002_c1s1_000451_03.jpg
├── 0002_c1s1_000551_01.jpg
├── 0002_c1s1_000801_01.jpg
├── gt_bbox
├── 0001_c1s1_001051_00.jpg
├── 0001_c1s1_009376_00.jpg
├── 0001_c2s1_001976_00.jpg
├── gt_query
├── 0001_c1s1_001051_00_good.mat
├── 0001_c1s1_001051_00_junk.mat
├── query
├── 0001_c1s1_001051_00.jpg
├── 0001_c2s1_000301_00.jpg
├── 0001_c3s1_000551_00.jpg
└── readme.txt
目录介绍
1) “bounding_box_test”——用于测试集的 750 人,包含 19,732 张图像,前缀为 0000 表示在提取这 750 人的过程中DPM检测错的图(可能与query是同一个人),-1 表示检测出来其他人的图(不在这 750 人中)
2) “bounding_box_train”——用于训练集的 751 人,包含 12,936 张图像
3) “query”——为 750 人在每个摄像头中随机选择一张图像作为query,因此一个人的query最多有 6 个,共有 3,368 张图像
4) “gt_query”——matlab格式,用于判断一个query的哪些图片是好的匹配(同一个人不同摄像头的图像)和不好的匹配(同一个人同一个摄像头的图像或非同一个人的图像)
5) “gt_bbox”——手工标注的bounding box,用于判断DPM检测的bounding box是不是一个好的box
命名规则
以 0001_c1s1_000151_01.jpg 为例
1) 0001 表示每个人的标签编号,从0001到1501;
2) c1 表示第一个摄像头(camera1),共有6个摄像头;
3) s1 表示第一个录像片段(sequece1),每个摄像机都有数个录像段;
4) 000151 表示 c1s1 的第000151帧图片,视频帧率25fps;
5) 01 表示 c1s1_001051 这一帧上的第1个检测框,由于采用DPM检测器,对于每一帧上的行人可能会框出好几个bbox。00 表示手工标注框
-
进入先前配好的pytorch环境,conda activate pytorch
查看环境里都有什么东西和它们的版本,conda list
下载torchvision,我的pytorch环境里有python 3.6.2(后来发现要3.6.6以上版本,后文更新了),pytorch 1.7.1,torchvision 0.8.2bash pip install torchvision
- 打开Person_reID_baseline_pytorch里的prepare.py,替换download_path路径为刚下载的数据路径,然后保存,进入Person_reID_baseline_pytorch路径,
python prepare.py
prepare.py的目的是将具有相同ID的图像放在一个文件夹中。
os.walk函数
os.walk(top[, topdown=True[, οnerrοr=None[, followlinks=False]]])
- top – 是你所要遍历的目录的地址, 返回的是一个三元组(root,dirs,files)。
- root 所指的是当前正在遍历的这个文件夹的本身的地址
- dirs 是一个 list ,内容是该文件夹中所有的目录的名字(不包括子目录)
- files 同样是 list , 内容是该文件夹中所有的文件(不包括子目录)
- topdown --可选,为 True,则优先遍历 top 目录,否则优先遍历 top 的子目录(默认为开启)。如果 topdown 参数为 True,walk 会遍历top文件夹,与top文件夹中每一个子目录。
- onerror – 可选,需要一个 callable 对象,当 walk 需要异常时,会调用。
- followlinks – 可选,如果为 True,则会遍历目录下的快捷方式(linux 下是软连接 symbolic link> )实际所指的目录(默认关闭),如果为 False,则优先遍历 top 的子目录。
遍历后将后缀名为jpg的图片文件提取名字第一个“_”前的字为类名,根据类名创建文件夹,再复制图片到相应分类文件夹里。
query为query里的图片分类
multi-query为gt_bbox里的图片分类
gallery为bounding_box_test里的图片分类
train_all为bounding_box_train里的图片分类
val为bounding_box_train里每个类的第一张图片
test为bounding_box_train每个类里除了第一张以外的图片
- 运行结果是在数据集里对图片分类如下,
- 查看model.py文件,通过
和from torchvision import models
可以很方便地调用常用模型。此处对ResNet模型做了修改,因为Market-1501中有751个分类,而ResNet是在ImageNet中训练的,有1,000个分类。model = models.resnet50(pretrained=True)
插班生补课:了解卷积神经网络的初学者指南
深度残差网络(Deep residual network, ResNet)
深度网络出现了退化问题(Degradation problem):网络深度增加时,网络准确度出现饱和,甚至出现下降。
深度残差网络的一个堆积层结构有输入特征连接到输出特征,解决退化问题
参考:https://zhuanlan.zhihu.com/p/31852747
函数中对要进行的计算进行初始化,即设置每层输入输出的数量,每层的计算类型。
__init__(self)
函数中定义具体的计算关系,即每层之间如何连接,是否要pool,relu还是sigmoid等
__forward__(self,x)
nn.AdaptiveAvgPool2d() 与 nn.AvgPool2d()
两者都是做二维的平均池化,但是它们关注的参数却不一样
nn.AvgPool2d()使用时一般关注 kernel_size 、stride 与 padding 三个参数
nn.AdaptiveAvgPool2d()的特点是自适应,只需要关注输出维度的大小output_size,具体的实现过程和参数选择自动帮你确定了,更方便使用
- kernel_size为池化窗口大小
- stride为max pooling的窗口移动步长
- padding为输入的每一条边补充0的层数
- 最后输出的尺寸由以上三个参数决定
ft_net模型由model_ft和classifier组成。有两种初始化参数方法。对于model_ft,设置参数,该参数设置为ImageNet上预训练的模型的参数。对于classifier,在ClassBlock中使用
pretrained = True
。
weights_init_kaiming
- 打开train.py,输入
可以跑一跑训练。python train.py --gpu_ids 0 --name ft_ResNet50 --train_all --batchsize 32 --data_dir your_data_path
- –gpu_ids 要运行哪个GPU。
- –name 模型的名称。
- –data_dir 训练数据的路径。
- –train_all 使用所有图像进行训练。
- –batchsize 批量大小。
- –erasing_p 随机擦除概率
- 此时发现了问题,ValueError: signal number 32 out of range,查找发现这是Python中的错误( https://bugs.python.org/issue33329 )。
- 对于Python 3.6 ,它已在2019年7月左右发布的3.6.6中修复。但我用的是python3.6.2,为什么我的版本这么低呢,因为我用的清华源,里头的conda install python=3.6 只能安装到3.6.2版本,没找到什么好办法,只能换回默认源,试图重新创建一个pytorch环境,新环境为python3.6.12,但是下载非常慢。
- 又出现了CondaHTTPError: HTTP 000 CONNECTION FAILED for url https://repo.anaconda.com/pkgs/main/linux-64/python-3.6.12-hcff3b4d_2.conda,直接cd进入路径
,输入/home/jychen/anaconda3/pkgs/
将它下载到本地(不知道为啥这样就下的很快),进入pytorch环境,wget -c https://repo.anaconda.com/pkgs/main/linux-64/python-3.6.12-hcff3b4d_2.conda
安装新的python版本,输入conda install python-3.6.12-hcff3b4d_2.conda
可以看到python版本覆盖为3.6.12了conda list
- 重新跑train.py,发现警告
- /home/jychen/anaconda3/envs/pytorch/lib/python3.6/site-packages/torch/optim/lr_scheduler.py:136: UserWarning: Detected call of
beforelr_scheduler.step()
. In PyTorch 1.1.0 and later, you should call them in the opposite order:optimizer.step()
beforeoptimizer.step()
. Failure to do this will result in PyTorch skipping the first value of the learning rate schedule. See more details at https://pytorch.org/docs/stable/optim.html#how-to-adjust-learning-rate)lr_scheduler.step()
- 因为我用的是pytorch 1.7.1,Warning说代码第233行的
要在代码第170行的optimizer.step()
执行之前,但我发现还是不改动跑的效果好scheduler.step()
- /home/jychen/anaconda3/envs/pytorch/lib/python3.6/site-packages/torch/optim/lr_scheduler.py:136: UserWarning: Detected call of
train.py理解
- 使用了命令行工具argparse,使运行代码时能额外传入参数,参数名字前带“–”可以将参数变为可选参数,就不用按顺序传参,可以通过名字传参。action='store_true’的意思为:如果不触发,默认为FALSE的,触发才置为TRUE。
- cudnn.benchmark = True,对模型里的卷积层进行预先的优化,可以提升运行速度,要求网络模型和输入大小不变
transform_train_list = [
transforms.Resize((256,128), interpolation=3), #重置图像大小(分辨率),interpolation为插值方法
transforms.Pad(10), #填充
transforms.RandomCrop((256,128)), #按指定尺寸随机裁剪图像(中心坐标随机)
transforms.RandomHorizontalFlip(), #以0.5概率使图像随机水平翻转 (这些都是增强数据的实际效果,泛化性等)
transforms.ToTensor(), #将数据归一化到[0,1]
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) #数据归一化到[-1,1]
- erasing_p随机擦除概率,color_jitter对颜色的数据增强:图像亮度、饱和度、对比度变化。
- ImageFolder 数据加载器,指定路径下加载并执行组合好的transforms操作
torch.utils.data.DataLoader:
该接口主要用来将自定义的数据读取接口的输出或者PyTorch已有的数据读取接口的输入按照batch size封装成Tensor,后续只需要再包装成Variable即可作为模型的输入
shuffle:是否将图片打乱
num_workers:使用多少个子进程来导入数据
pin_memory: 在数据返回前,是否将数据复制到CUDA内存中
- optimizer.zero_grad(),是将变量的梯度设置为零。在每一次迭代中都使用梯度来更新网络,所以更新后需要清除梯度。否则,梯度将累积。
- with torch.no_grad()中的数据不需要计算梯度,也不会进行反向传播
核心步骤:前向传播,计算loss,反向传播(计算所有参数的梯度),更新参数
outputs = model(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
optim.SGD神经网络优化器
params (iterable) – 待优化参数的iterable或者是定义了参数组的dict
lr (float) – 学习率
momentum (float, 可选) – 动量因子(默认:0)
weight_decay (float, 可选) – 权重衰减(L2惩罚)(默认:0)
dampening (float, 可选) – 动量的抑制因子(默认:0)
nesterov (bool, 可选) – 使用Nesterov动量(默认:False)
- 转到test.py,
,加载我们刚刚训练过的网络权重以提取每个图像的视觉特征。python test.py --gpu_ids 0 --name ft_ResNet50 --test_dir your_data_path --batchsize 32 --which_epoch 59
- –gpu_ids 要运行哪个GPU
- –name 训练模型的目录名称
- –batchsize 批量大小
- –which_epoch 选择第i个模型
- –test_dir 测试数据的路径
multiple_scale将图片进行不同尺度的缩放,得到图像金字塔,然后对每层图片提取不同尺度的特征,得到特征图。最后对每个尺度的特征都进行单独的预测。
特点:不同尺度的特征都可以包含很丰富的语义信息,精度高 ,但 速度慢。
- 出现Warning,yaml现在用新用法了 加入Loader=yaml.FullLoader
- test.py里的os.system(‘python evaluate_gpu.py | tee -a %s’%result)调用了evaluate_gpu.py 并将结果写入result.txt中
- rank-n:搜索结果中最靠前(置信度最高)的n张图有正确结果的概率
- 平均精度均值(mAP):对于一些数据集来说,一张probe图像在gallery中可能有多张相匹配的图像,而mAP则是同时考虑了准确率和召回率,更能客观反映模型的性能。
- torch.mul(a, b)是矩阵a和b对应位相乘,a和b的维度必须相等,比如a的维度是(1, 2),b的维度是(1, 2),返回的仍是(1, 2)的矩阵
- torch.mm(a, b)是矩阵a和b矩阵相乘,比如a的维度是(1, 2),b的维度是(2, 3),返回的就是(1, 3)的矩阵
- squeeze()实现降维
- 转到demo.py,
python demo.py --query_index 777 --test_dir your_dir
–query_index 要查询的索引,可以选择0~3367之间的数字
–test_dir 数据集路径