行人重识别reid

概述:人体reid可以分为三个大方向:
一种是和人脸识别一样,多少个id就是多少个类别,训一个分类模型,然后把backbone的输出作为人体特征。可以任选backbone(resnet,轻量化模型(osnet,mobilenet…))+交叉熵损失/triplet loss+交叉熵损失/triplet loss+交叉熵损失/circle loss+交叉熵损失…
github:
第二种是基于视频的人体reid,和第一种方法类似,只是加上了时序信息,用到了rnn等时序模型。
github链接:
https://github.com/cvlab-yonsei/STMN
https://github.com/jiyanggao/Video-Person-ReID
第三种是基于属性的。论文有一些,但是没有找到开源工程。叫做利用行人属性辅助视频行人重识别。
论文1:
https://blog.csdn.net/weixin_42643931/article/details/102795636
参考博客:https://zhuanlan.zhihu.com/p/143552995
代码1:
https://github.com/yuange250/Video-based-person-ReID-with-Attribute-information

参考:
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


def compute_loss(self, criterion, outputs, targets):
    if isinstance(outputs, (tuple, list)):
        loss = DeepSupervision(criterion, outputs, targets)
    else:
        loss = criterion(outputs, targets)
    return loss

标签处理,以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

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)

具体实现过程
例如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’)
输出特征处理:
行人重识别reid_第1张图片
对于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

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

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

问题记录:
python训练输出预处理用的pil和transfrom库,板端部署时c++用的opencv,处理结果都不一样,只能改训练预处理代码,但是又不太好改。只能大致一样,记得把板端的brg转成rgb

你可能感兴趣的:(人工智能,深度学习)