天天看点

【reID学习记录】Person_reID_baseline_pytorch学习

  可以将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

    【reID学习记录】Person_reID_baseline_pytorch学习
  • 进入需下载代码的目录中

    cd /home/jychen/work/ReID

    ,在linux命令行中使用

    git clone https://github.com/layumi/Person_reID_baseline_pytorch.git

    ,对该项目源码clone到本地,等待clone完成
  • 进入需下载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 表示手工标注框

  • conda activate pytorch

    进入先前配好的pytorch环境,

    conda list

    查看环境里都有什么东西和它们的版本,

    bash pip install torchvision

    下载torchvision,我的pytorch环境里有python 3.6.2(后来发现要3.6.6以上版本,后文更新了),pytorch 1.7.1,torchvision 0.8.2
  • 打开Person_reID_baseline_pytorch里的prepare.py,替换download_path路径为刚下载的数据路径,然后保存,进入Person_reID_baseline_pytorch路径,

    python prepare.py

    【reID学习记录】Person_reID_baseline_pytorch学习

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每个类里除了第一张以外的图片

  • 运行结果是在数据集里对图片分类如下,
    【reID学习记录】Person_reID_baseline_pytorch学习
  • 查看model.py文件,通过

    from torchvision import models

    model = models.resnet50(pretrained=True)

    可以很方便地调用常用模型。此处对ResNet模型做了修改,因为Market-1501中有751个分类,而ResNet是在ImageNet中训练的,有1,000个分类。
插班生补课:了解卷积神经网络的初学者指南

深度残差网络(Deep residual network, ResNet)

深度网络出现了退化问题(Degradation problem):网络深度增加时,网络准确度出现饱和,甚至出现下降。

深度残差网络的一个堆积层结构有输入特征连接到输出特征,解决退化问题

【reID学习记录】Person_reID_baseline_pytorch学习
【reID学习记录】Person_reID_baseline_pytorch学习
参考:https://zhuanlan.zhihu.com/p/31852747

__init__(self)

函数中对要进行的计算进行初始化,即设置每层输入输出的数量,每层的计算类型。

__forward__(self,x)

函数中定义具体的计算关系,即每层之间如何连接,是否要pool,relu还是sigmoid等

nn.AdaptiveAvgPool2d() 与 nn.AvgPool2d()

两者都是做二维的平均池化,但是它们关注的参数却不一样

nn.AvgPool2d()使用时一般关注 kernel_size 、stride 与 padding 三个参数

  • kernel_size为池化窗口大小
  • stride为max pooling的窗口移动步长
  • padding为输入的每一条边补充0的层数
  • 最后输出的尺寸由以上三个参数决定
nn.AdaptiveAvgPool2d()的特点是自适应,只需要关注输出维度的大小output_size,具体的实现过程和参数选择自动帮你确定了,更方便使用
ft_net模型由model_ft和classifier组成。有两种初始化参数方法。对于model_ft,设置参数

pretrained = True

,该参数设置为ImageNet上预训练的模型的参数。对于classifier,在ClassBlock中使用

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/

      ,输入

      wget -c https://repo.anaconda.com/pkgs/main/linux-64/python-3.6.12-hcff3b4d_2.conda

      将它下载到本地(不知道为啥这样就下的很快),进入pytorch环境,

      conda install python-3.6.12-hcff3b4d_2.conda

      安装新的python版本,输入

      conda list

      可以看到python版本覆盖为3.6.12了
  • 重新跑train.py,发现警告
    • /home/jychen/anaconda3/envs/pytorch/lib/python3.6/site-packages/torch/optim/lr_scheduler.py:136: UserWarning: Detected call of

      lr_scheduler.step()

      before

      optimizer.step()

      . In PyTorch 1.1.0 and later, you should call them in the opposite order:

      optimizer.step()

      before

      lr_scheduler.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)
    • 因为我用的是pytorch 1.7.1,Warning说代码第233行的

      optimizer.step()

      要在代码第170行的

      scheduler.step()

      执行之前,但我发现还是不改动跑的效果好
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现在用新用法了
    【reID学习记录】Person_reID_baseline_pytorch学习
    加入Loader=yaml.FullLoader
    【reID学习记录】Person_reID_baseline_pytorch学习
  • 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 数据集路径