ReID?
ReID是图像检索的子任务,它主要的目的是:利用计算机视觉技术对特定行人进行跨视域匹配和检索。所谓跨视域即是图片来自于不同的摄像头,这样可以用于智能视频监控(如无人超市)、刑侦(追捕嫌疑人)、交管(追踪车辆等)等等应用场景。如上图,需要检索到同一个人(查询图像query)在各个摄像头下图片集(候选行人库gallery)的相关图片。
困难点
困难点主要在于ReID任务的跨视域特性有:
ReID的实现流程图
时间中需要完成包括行人检测和再识别两部分任务,即下图的person detection和后面的person reID,研究上主要侧重于识别的部分(查询图像query集和候选行人库gallery集都是经过detector裁剪好的行人框)。
识别部分分为feature extraction和similarity measurement,即特征提取和相似度度量。
依据流程,数据集实际分为了训练集、验证集、Query、Gallery四个部分。即在训练集上进行模型的训练,得到模型后对Query与Gallery中的图片提取特征计算相似度,对于每个Query在Gallery中找出前N个与其相似的图片。
├── Market/
│ ├── bounding_box_test/ /* Files for testing (candidate images pool),19732张
│ ├── bounding_box_train/ /* Files for training,12936张
│ ├── gt_bbox/ /* Files for multiple query testing,3368张,对应query,在评估时使用
│ ├── gt_query/ /* We do not use it
│ ├── query/ /* Files for testing (query images),3368张查询,会到test中寻找
数据集实例如上,值得注意的是训练集的人和测试集的人没有重复的。比如market里面有751名个人用于训练,另外有750个人用于测试。
如果Gallery中没有Query怎么办?
这种情况实际中很可能会存在,但或许ReID的目的不是为了精确的找到Query,而是返回一个候选列表帮助人工筛选,这样不存在也可能很快发现。
single shot 和muti shot
single shot是指gallery中每个人的图像为一张(N=1),而muti shot是指gallery中每个人的图像为N>1张图像,同样的Rank-1下,一般N越大,得到的识别率越高。
query = qf.view(-1,1)
# print(query.shape)
score = torch.mm(gf,query) # Cosine Distance
score = score.squeeze(1).cpu()
score = score.numpy()
# predict index
index = np.argsort(score) #from small to large
index = index[::-1]
主要流行的解决方法
主要有三种,基于表征,加入局部信息,引入生成对抗。
来自中文综述论文
卢健,陈旭,罗毛欣,王航英. 深度学习行人再识别研究综述.
实际上应该还有很多其他的方法,比如人体属性,骨架Graph等等,还有涉及无监督和跨模态的任务等等。
当前一些sota方法的实现与表现
多蹲蹲顶会和开源就好…
简单注释一下基模型的代码:
class ClassBlock(nn.Module):
def __init__(self, input_dim, class_num, droprate, relu=False, bnorm=True, num_bottleneck=512, linear=True, return_f = False):
super(ClassBlock, self).__init__()
self.return_f = return_f
add_block = [] #图像特征抽象
if linear: #多个mlp
add_block += [nn.Linear(input_dim, num_bottleneck)]
else:
num_bottleneck = input_dim
if bnorm: #bn层
add_block += [nn.BatchNorm1d(num_bottleneck)]
if relu: #激活函数
add_block += [nn.LeakyReLU(0.1)]
if droprate>0: #dropout
add_block += [nn.Dropout(p=droprate)]
add_block = nn.Sequential(*add_block) #把多个mlp串起来
add_block.apply(weights_init_kaiming) #然后应用kaiming初始化
classifier = [] #分类器网络
classifier += [nn.Linear(num_bottleneck, class_num)] #也是mlp得到分类ID数目
classifier = nn.Sequential(*classifier)
classifier.apply(weights_init_classifier)
self.add_block = add_block #特征抽取
self.classifier = classifier #映射到ID
def forward(self, x):
x = self.add_block(x) #先得到特征
if self.return_f: #再分类
f = x
x = self.classifier(x)
return x,f
else:
x = self.classifier(x)
return x
这个基方案可以搭得更深就是了如 AlexNet, VGG16, ResNet and DenseNet等等,还想注释一下PCB。
PCB(Part-Based Convolutional Baseline)将行人图片均匀划分为6个部件并提取特征,用6个分类的损失函数进行模型训练。这么做的意义是能够基于行人结构分割的先验知识驱使,比如part1就是行人的头部。
class PCB(nn.Module):
def __init__(self, class_num ):
super(PCB, self).__init__()
self.part = 6 # 切成6份
model_ft = models.resnet50(pretrained=True)#用resnet50提特征
self.model = model_ft
self.avgpool = nn.AdaptiveAvgPool2d((self.part,1))
self.dropout = nn.Dropout(p=0.5)
# remove the final downsample
self.model.layer4[0].downsample[0].stride = (1,1)
self.model.layer4[0].conv2.stride = (1,1)
# 定义6分类
for i in range(self.part):
name = 'classifier'+str(i)
setattr(self, name, ClassBlock(2048, class_num, droprate=0.5, relu=False, bnorm=True, num_bottleneck=256))
def forward(self, x): #逐层前向就行
x = self.model.conv1(x)
x = self.model.bn1(x)
x = self.model.relu(x)
x = self.model.maxpool(x)
x = self.model.layer1(x)
x = self.model.layer2(x)
x = self.model.layer3(x)
x = self.model.layer4(x)
x = self.avgpool(x)
x = self.dropout(x)
part = {
}
predict = {
}
# 6个部分的特征 batchsize*2048*6
for i in range(self.part):
part[i] = torch.squeeze(x[:,:,i])
name = 'classifier'+str(i)
c = getattr(self,name)
predict[i] = c(part[i])
# sum prediction
#y = predict[0]
#for i in range(self.part-1):
# y += predict[i+1]
y = []
for i in range(self.part):
y.append(predict[i])
return y
当然还有一些更复杂的模型咯,更多的论文都可以参考上面的那个开源。
评价指标
一般使用rank-n,即搜索结果中最靠前(置信度分数最高)的n张图有正确结果的概率。
例如: lable ID为行人0001,在100个样本中搜索。
怎么真实应用?
真实场景中的情况,和公共数据集的数据是不一样的,往往是需要从原视频/图像的一整张大图中,先做检测找出小人,然后才在所建立的gallery里面进行查询和检索的。