• 行人重识别reid


    概述:人脸reid可以分为两个大方向:
    一种是和人脸识别一样,多少个id就是多少个类别,训一个分类模型,然后把backbone的输出作为人体特征。可以任选backbone(resnet,轻量化模型(osnet,mobilenet…))+交叉熵损失/triplet loss+交叉熵损失/triplet loss+交叉熵损失/circle loss+交叉熵损失…

    参考:
    https://blog.csdn.net/ctwy291314/article/details/83618646

    基于人体id的识别方法

    常用方法:
    一、用分类的方法
    分类的问题是怎么定义的?在我们数据集像 mark1501 上有 751 个人的照片组成,这个分类相当于一张图片输入这个网络之后,判断这个人是其中某一个人的概率,要把这个图片分类成 751 个 ID 中其中一个的概率,这个地方的 Loss 一般都用 SoftmaxLoss。机器视觉的同学应该非常熟悉这个,这是非常基本的一个Loss,对非机器视觉的同学,这个可能要你们自己去理解,它可以作为分类的实现。

    二、基于TripletLoss 三元损失的 ReID 方案
    TripletLoss计算机视觉里另外一个常用的 Loss。
    它的设计思路是从数据里面选择三个图片,这三个图片由两个人构成,其中两张图片是同一个人,另外一张图片不是同一个人,当这个网络在没有训练的时候,我们假设这同一个人的两张照片距离要大于这个人跟不是同一个人两张图片的距离。

    它强制模型训练,使得同一个人两张图片的距离小于第三张图片。它真正的目的是让同类的距离更近,不同类的距离更远。这是TripletLoss的定义。

    在 ReID 方案里面我给大家介绍一个 Batchhard的策略,因为 TripletLoss 在设计时怎么选这三张图是有很多文章在实现不同算法,我们的文章里用的是 Batchhard算法,就是我们从数据集随机抽取 P 个人,每个人 K 张图片形成一个 Batch,每个人的 K 张图片之间形成一个 K×(K-1)个 ap 对,再在剩下其他人里取一个与该 ap 距离最近的 negtive,组成 apn 组,然后我们这个模型使得 apn 组成的 Loss 尽量小。

    Triplet-loss
    参考:https://zhuanlan.zhihu.com/p/171627918
    用的是三元组进行训练,训练数据包括一个锚点(anchor) 、一个正样本(positive)和一个负样本(negative),它的目的是使得锚点和正样本的距离尽可能小,与负样本的距离尽可能大。
    公式: L=max(d(a, p) - d(a,n)+margin,0)
    a:anchor,锚示例;p:positive,与a是同一类别的样本;n:negative,与a是不同类别的样本;margin是一个大于0的常数。最终的优化目标是拉近a和p的距离,拉远a和n的距离。
    三、
    模型的设计:
    img—>resnet–>(?,2048,1)的特征–>fc–>(?,person_count,1)
    person_count由数据集决定,数据集有多少个人,这里的标签长度就是几。
    2048维的特征去计算triplet loss,(?,person_count,1)标签去计算softmax分类损失。
    代码:https://github.com/KaiyangZhou/deep-person-reid
    triplet.py

    self.model = model
    self.optimizer = optimizer
    self.scheduler = scheduler
    self.register_model('model', model, optimizer, scheduler)
    
    assert weight_t >= 0 and weight_x >= 0
    assert weight_t + weight_x > 0
    self.weight_t = weight_t
    self.weight_x = weight_x
    #####一个TripletLoss,一个交叉熵损失
    self.criterion_t = TripletLoss(margin=margin)
    self.criterion_x = CrossEntropyLoss(
        num_classes=self.datamanager.num_train_pids,
        use_gpu=self.use_gpu,
        label_smooth=label_smooth
    )
    
    
    
    def forward_backward(self, data):
            imgs, pids = self.parse_data_for_train(data)    
    
            if self.use_gpu:
                imgs = imgs.cuda()
                pids = pids.cuda()
    
            outputs, features = self.model(imgs)  ##imgs是(32,3,256,128), outputs是(32,1453),features是(32,2048)
    
            loss = 0
            loss_summary = {}
    
            if self.weight_t > 0:
                loss_t = self.compute_loss(self.criterion_t, features, pids)   ##pids是标签(32,), criterion_t是三元组损失
                loss += self.weight_t * loss_t
                loss_summary['loss_t'] = loss_t.item()
    
            if self.weight_x > 0:
                loss_x = self.compute_loss(self.criterion_x, outputs, pids)##三元组损失使用特征计算,交叉熵损失用输出计算
                loss += self.weight_x * loss_x
                loss_summary['loss_x'] = loss_x.item()
                loss_summary['acc'] = metrics.accuracy(outputs, pids)[0].item()
    
            assert loss_summary
    
            self.optimizer.zero_grad()
            loss.backward()
            self.optimizer.step()
    
            return loss_summary
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    def compute_loss(self, criterion, outputs, targets):
        if isinstance(outputs, (tuple, list)):
            loss = DeepSupervision(criterion, outputs, targets)
        else:
            loss = criterion(outputs, targets)
        return loss
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    标签处理,以market1501为例

    pid_container = set()
    for img_path in img_paths:##img_path=’bounding_box_train/0114_c2s3_071702_01.jpg'
        pid, _ = map(int, pattern.search(img_path).groups()) #得到pid=114
        if pid == -1:
            continue # junk images are just ignored
        pid_container.add(pid)    ##pid_container共751个
    pid2label = {pid: label for label, pid in enumerate(pid_container)}  ##pid_container共751个
    
    data = []
    for img_path in img_paths:   :##img_path=’bounding_box_train/0114_c2s3_071702_01.jpg'
        pid, camid = map(int, pattern.search(img_path).groups())   #得到pid=114,camid=2
        if pid == -1:
            continue # junk images are just ignored
        assert 0 <= pid <= 1501 # pid == 0 means background
        assert 1 <= camid <= 6
        camid -= 1 # index starts from 0
        if relabel:
            pid = pid2label[pid]
        data.append((img_path, pid, camid))
    ####得到的pid是人的标签,camid是相机的id
    return data
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    hard_mine_triplet_loss.py

    def forward(self, inputs, targets):
        """
        Args:
            inputs (torch.Tensor): feature matrix with shape (batch_size, feat_dim).
            targets (torch.LongTensor): ground truth labels with shape (num_classes).
        """
        n = inputs.size(0)
    
        # Compute pairwise distance, replace by the official when merged
        dist = torch.pow(inputs, 2).sum(dim=1, keepdim=True).expand(n, n)
        dist = dist + dist.t()
        dist.addmm_(inputs, inputs.t(), beta=1, alpha=-2)
        dist = dist.clamp(min=1e-12).sqrt() # for numerical stability
    
        # For each anchor, find the hardest positive and negative
        mask = targets.expand(n, n).eq(targets.expand(n, n).t())
        dist_ap, dist_an = [], []
        for i in range(n):
            # print(dist[i])
            dist_ap.append(dist[i][mask[i]].max().unsqueeze(0))
            dist_an.append(dist[i][mask[i] == 0].min().unsqueeze(0))
        dist_ap = torch.cat(dist_ap)
        dist_an = torch.cat(dist_an)
    
        # Compute ranking hinge loss
        y = torch.ones_like(dist_an)
        return self.ranking_loss(dist_an, dist_ap, y)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    具体实现过程
    例如batch size是32
    模型输出的特征值是(32,2048),对应的label人的id大小是(32,)。
    label处理:
    把label处理成32*32的,第一行代表是否等于67,第二行代表是否等于531…
    tensor([ 67, 531, 46, 518, 694, 696, 90, 708, 321, 124, 131, 701, 522, 343,
    359, 390, 629, 580, 575, 541, 160, 750, 336, 416, 408, 101, 666, 565,
    44, 315, 155, 207], device=‘cuda:0’)

    因为这里没有相同的id,所以最后的32*32矩阵只有对角线为true,相当于公式中的正(Positive)示例是自己。
    tensor([[ True, False, False, …, False, False, False],
    [False, True, False, …, False, False, False],
    [False, False, True, …, False, False, False],
    …,
    [False, False, False, …, True, False, False],
    [False, False, False, …, False, True, False],
    [False, False, False, …, False, False, True]], device=‘cuda:0’)
    输出特征处理:
    请添加图片描述
    对于margin的理解:自己的自己最大距离,自己和其他人最小距离之间的差值,要大于margin。
    person1:imgp1_1,imgp1_2,imgp1_3…
    person2:…

    person100:imgp100_1,imgp100_2,imgp100_3…
    imgp1_1与imgp1_*之间取max,imgp1_1与其他人之间的距离算min,两个差值要大于margin。

    四、数据集下载
    参考:https://www.likecs.com/show-204151048.html

    五、问题记录
    训练时候triplet loss等于0,分析发现margin默认值是0.3,批次是32,而人的id都是上千,所以几乎每个批次中每个人都只出现一次,没有重复的,就导致positive是自己,而triplet loss是希望计算a和p距离,a和n距离的差值,现在这样计算的是a和a距离,a和n距离的差值,a和p距离,a和n距离的差值可能设置margin=0.3合理,但是a和a距离,a和n距离的差值都是15+。
    而且这样基本是无法实现类内距离更近的,因为每次只有类间,没有类内。
    改进:
    取每个批次数据时要保证有类内的,例如32张图片中,每个人都要至少有两张图。不能随便取每个批次的数据。但是代码中都是用内置的dataloader随机取的数据,不太好改,于是又找了另一个工程:https://github.com/layumi/Person_reID_baseline_pytorch。

    if opt.triplet:
        miner = miners.MultiSimilarityMiner()
        criterion_triplet = losses.TripletMarginLoss(margin=0.3)
    if opt.circle:
        loss +=  criterion_circle(*convert_label_to_similarity(ff,labels))/now_batch_size
    if opt.triplet:
        hard_pairs = miner(ff, labels)
        loss +=  criterion_triplet(ff, labels, hard_pairs) #/now_batch_size
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    和上一个工程一样,都有同样的问题,hard_pairs困难样本 1/3是0,criterion_triplet(ff, labels, hard_pairs)也1/3是0,比上边那个工程好一点。再
    不知道这样用是否正确,按triplet loss设计的本意不应该这样用啊。

    circle loss:在triplet loss基础上进行改进的。

  • 相关阅读:
    042:mapboxGL点击某feature点,使其为中心点
    前端环境变量及vite中本地环境配置实践
    2-Docker进阶
    MySQL报错:unknown collation: ‘utf8mb4_0900_ai_ci‘
    Linux运维Centos7_创建虚拟机 安装操作系统
    机器人过程自动化(RPA)入门 6. 通过插件和扩展易于控制应用程序
    中文人物关系知识图谱(含码源):中文人物关系图谱构建、数据回标、基于远程监督人物关系抽取、知识问答等应用.
    使用Ascend八卡训练报错,len to make them match
    两化融合的基本知识
    Python多重高斯分布数据可视化
  • 原文地址:https://blog.csdn.net/weixin_41012399/article/details/125891478