以下链接是个人关于DG-Net(行人重识别ReID)所有见解,如有错误欢迎大家指出,我会第一时间纠正。有兴趣的朋友可以加微信:17575010159 相互讨论技术。若是帮助到了你什么,一定要记得点赞!因为这是对我最大的鼓励。 文末附带 \color{blue}{文末附带} 文末附带 公众号 − \color{blue}{公众号 -} 公众号− 海量资源。 \color{blue}{ 海量资源}。 海量资源。
行人重识别0-06:DG-GAN(ReID)-目录-史上最新最全:https://blog.csdn.net/weixin_43013761/article/details/102364512
极度推荐的商业级项目: \color{red}{极度推荐的商业级项目:} 极度推荐的商业级项目:这是本人落地的行为分析项目,主要包含(1.行人检测,2.行人追踪,3.行为识别三大模块):行为分析(商用级别)00-目录-史上最新无死角讲解
训练测试数据来源在上篇博客我们存在疑问:
train_loader_a, train_loader_b, test_loader_a, test_loader_b = get_all_data_loaders(config)
这里四个类别的数据分别来自哪里,有什么作用,先看来源:
def get_all_data_loaders(conf):
batch_size = conf['batch_size']
num_workers = conf['num_workers']
# 重新定义a,b图片的大小
if 'new_size' in conf:
new_size_a= conf['new_size']
new_size_b = conf['new_size']
else:
new_size_a = conf['new_size_a']
new_size_b = conf['new_size_b']
# 截切图片的高和宽
height = conf['crop_image_height']
width = conf['crop_image_width']
if 'data_root' in conf:
# 训练图片a来自于pytorch/train_all
train_loader_a = get_data_loader_folder(os.path.join(conf['data_root'], 'train_all'), batch_size, True,
new_size_a, height, width, num_workers, True)
# 测试图片a来自于pytorch/query
test_loader_a = get_data_loader_folder(os.path.join(conf['data_root'], 'query'), batch_size, False,
new_size_a, height, width, num_workers, False)
# 训练图片b来自于pytorch/train_all
train_loader_b = get_data_loader_folder(os.path.join(conf['data_root'], 'train_all'), batch_size, True,
new_size_b, height, width, num_workers, True)
# 测试图片b来自于pytorch/query
test_loader_b = get_data_loader_folder(os.path.join(conf['data_root'], 'query'), batch_size, False,
new_size_b, height, width, num_workers, False)
else:
train_loader_a = get_data_loader_list(conf['data_folder_train_a'], conf['data_list_train_a'], batch_size, True,
new_size_a, height, width, num_workers, True)
test_loader_a = get_data_loader_list(conf['data_folder_test_a'], conf['data_list_test_a'], batch_size, False,
new_size_a, height, width, num_workers, False)
train_loader_b = get_data_loader_list(conf['data_folder_train_b'], conf['data_list_train_b'], batch_size, True,
new_size_b, height, width, num_workers, True)
test_loader_b = get_data_loader_list(conf['data_folder_test_b'], conf['data_list_test_b'], batch_size, False,
new_size_b, height, width, num_workers, False)
return train_loader_a, train_loader_b, test_loader_a, test_loader_b
pytorch目录显示如下: 这些文件都是通过前面我们执行prepare-market.py生成的, gallery:包含了官方数据集的所有行人,并且已经按ID分类好了,其中有个-1的文件夹,表示识别不出来的行人。gallery分成(复制)了multi-query与train_all两个文件夹。 query:该文件夹中的图片,都是从multi-query中随机挑选而来,每个ID选取了五张照片。 train_all :包含了所有训练的照片,不含有测试照片,该文件被分成验证集train和训练集val。 val :从train_all中每个ID抽取一张照片 train:train_all 删除掉val的照片都作为train(训练集)
上面是个人分析,但是代码中很明显,应该是把train_all当作训练集,query当作是测试集,具体不是很了解,但是从这里我们可以得到的一个结论是,训练的时候,每次需要a,b两张图片,并且来自同一train_all数据集。大概就是为了让这两张图片换衣服吧。测试图片a,b也是来自同一query数据集。既然知道了这个,我们,我去看网络把,其实我也很好奇,他对a,b图片是怎么处理的。
在看代码之前,还要为大家打个预防针,这个地方把我坑得不容易(现在心脏都在隐隐作痛),在传递的时候,一个ID下面,不是传递一张图片,是两张(也就是一起四张),为什么是两张,我们看下面函数在train.py文件中:
# 循环获得训练数据,a,b
for it, ((images_a,labels_a, pos_a), (images_b, labels_b, pos_b)) in enumerate(zip(train_loader_a, train_loader_b)):
if num_gpu>1:
trainer.module.update_learning_rate()
else:
trainer.update_learning_rate()
# images_a[batch_size,3,256,128],images_b[batch_size,3,256,128]
images_a, images_b = images_a.cuda().detach(), images_b.cuda().detach()
# 这个地方注意,后面讲解,反正是图片
# pos_a[batch_size,3,256,128],pos_b[batch_size,3,1024]
pos_a, pos_b = pos_a.cuda().detach(), pos_b.cuda().detach()
# labels_a[batch_size],labels_b[atch_size]
labels_a, labels_b = labels_a.cuda().detach(), labels_b.cuda().detach()
这里的images_a与pos_a同一ID不一样的图片,images_b 与pos_b来自同一ID不一样的图片。labels_a, labels_b分别表示ID类别。
网络框架略朗首先我们回到train.py文件,找到如下代码:
# Setup model and data loader
# 加载,训练数据,进行模型构建和加载
if opts.trainer == 'DGNet':
trainer = DGNet_Trainer(config, gpu_ids)
trainer.cuda()
这里他就把网络框架搭建好了,其中怎么搭建的,我们先不去分析,因为我发现,有的东西需要从细节开始,但是有的东西需要从整体去看待,掌握的全局思路之后再去分析细节,是可以事半功倍的。假设他搭建好了,就要进行前向传播(还是train.py文件):
with Timer("Elapsed time in update: %f"):
# Main training code
# 进行前向传播
x_ab, x_ba, s_a, s_b, f_a, f_b, p_a, p_b, pp_a, pp_b, x_a_recon, x_b_recon, x_a_recon_p, x_b_recon_p = \
trainer.forward(images_a, images_b, pos_a, pos_b)
执行这段代码之后,就进行了前向传播,得到一堆看起来不知道是什么东西的东西,不急,慢慢分析。在pyTrotch中,网络结构习惯用类来搭建,并且继承nn.Module,如trainer.py文件:
class DGNet_Trainer(nn.Module):
......
其中都会存在一个forward函数,是的,就是trainer.forward这里调用了这个函数。现在我们就来分析这个函数。贴图,贴图,不解释,不结合图,真的不好理解:
下面是函数代码注释
# 这里是重点,前向传播的时候,该函数会被调用,这个就是论文的总体思路了
# 大家注意,先打开论文的Figure 2,
# x_a,x_b, xp_a, xp_b[4, 3, 256, 128]
def forward(self, x_a, x_b, xp_a, xp_b):
# 送入x_a,x_b两张图片(来自训练集的不同ID)
# 通过st 编码器,编码成两个stcode:
# s_a[batch,128,64,32]
# s_b[batch,128,64,32]
# single会根据参数设定判断是否转化为灰度图
s_a = self.gen_a.encode(self.single(x_a))
s_b = self.gen_b.encode(self.single(x_b))
# 先把图片进行下采样,图示我们可以看到ap code的体积比st code是要小的,这样会出现一个情况,那么他们是没有办法直接融合的,所以后面有个全链接成把他们统一
# f_a[batch_size,2024*4=8192], p_a[0]=[batch_size, class_num=751], p_a[1]=[batch_size, class_num=751]
# f_b[batch_size,2024*4=8192], p_b[0]=[batch_size, class_num=751], p_b[1]=[batch_size, class_num=751]
# f代表的是经过ap编码器得到的ap code,
# p表示对身份的预测(有两个身份预测,也就是p_a了两个元素,这里不好解释),
# 前面提到过,ap编码器,不仅负责编码,还要负责身份的预测(行人重识别),也是我们落实项目的关键所在
# 这里是第一个重难点,在论文的翻译中提到过,后续详细讲解
f_a, p_a = self.id_a(scale2(x_a))
f_b, p_b = self.id_b(scale2(x_b))
# 进行解码操作,就是Figure 2中的黄色梯形G操作,这里的x_a,与x_b进行衣服互换
# s_b[batch,128,64,32] f_a[batch_size,2024*4=8192] --> x_ba[batch_size,3,256,128]
x_ba = self.gen_a.decode(s_b, f_a)
x_ab = self.gen_b.decode(s_a, f_b)
# 进行重构,可以这样理解,x_a穿上自己的衣服,当然还是自己(图片基本不变)
# x_b也是同理
x_a_recon = self.gen_a.decode(s_a, f_a)
x_b_recon = self.gen_b.decode(s_b, f_b)
# 这里是同一个人,换了一个姿态,p应该表示pos,把姿态经过外貌编码器得到ap code
fp_a, pp_a = self.id_a(scale2(xp_a))
fp_b, pp_b = self.id_b(scale2(xp_b))
# 一个人,穿上另外一个姿态的衣服(衣服相同的),得到初始姿态的图片
x_a_recon_p = self.gen_a.decode(s_a, fp_a)
x_b_recon_p = self.gen_b.decode(s_b, fp_b)
# Random Erasing only effect the ID and PID loss.
# 把图片擦除一些像素,然后进行ap code编码
if self.erasing_p > 0:
# 先把每张图片都擦除一些像素
x_a_re = self.to_re(scale2(x_a.clone()))
x_b_re = self.to_re(scale2(x_b.clone()))
xp_a_re = self.to_re(scale2(xp_a.clone()))
xp_b_re = self.to_re(scale2(xp_b.clone()))
# 然后经过编码成ap code,暂时不知道作用,感觉应该是数据增强
# 类似于,擦除了图片的一些像素,但是已经能够识别出来这些图片是谁
_, p_a = self.id_a(x_a_re)
_, p_b = self.id_b(x_b_re)
# encode the same ID different photo
_, pp_a = self.id_a(xp_a_re)
_, pp_b = self.id_b(xp_b_re)
# 混合合成图片:x_ab[images_a的st,images_b的ap] 混合合成图片x_ba[images_b的st,images_a的ap]
# s_a[输入图片images_a经过Es编码得到的 st code] s_b[输入图片images_b经过Es编码得到的 st code]
# f_a[输入图片images_a经过Ea编码得到的 ap code] f_b[输入图片images_b经过Ea编码得到的 ap code]
# p_a[输入图片images_a经过Ea编码进行身份ID的预测] p_b[输入图片images_b经过Ea编码进行身份ID的预测]
# pp_a[输入图片pos_a经过Ea编码进行身份ID的预测] pp_b[输入图片pos_b经过Ea编码进行身份ID的预测]
# x_a_recon[输入图片images_a(s_a)与自己(f_a)合成的图片,当然和images_a长得一样]
# x_b_recon[输入图片images_b(s_b)与自己(f_b)合成的图片,当然和images_b长得一样]
# x_a_recon_p[输入图片images_a(s_a)与图片pos_a(fp_a)合成的图片,当然和images_a长得一样]
# x_b_recon_p[输入图片images_a(s_a)与图片pos_b(fp_b)合成的图片,当然和images_b长得一样]
return x_ab, x_ba, s_a, s_b, f_a, f_b, p_a, p_b, pp_a, pp_b, x_a_recon, x_b_recon, x_a_recon_p, x_b_recon_p
注释,都是比较简单,当然也是很形象的,首先我们看到forward函数,需要是个参数 x_a, x_b, xp_a, xp_b。这里的四个参数,就是我们前面说的,分别两个ID的两张图片。在上面的图中,我用蓝色的框,框出了3张照片,那么和这四张照片怎么对应起来呢?可以这样理解,对应关系如下: x_a =
x
j
x_j
xj x_b =
x
i
x_i
xi xp_b =
x
t
x_t
xt 那么出现了一个问题,多余出来的xp_a呢?他怎么办?这里我们思考一个一个问题,假设只传送了3张图片: 把上面的网络跑了一遍,当跑下一遍的时候,我们又传递了3张照片将来。也就是说,跑两次网络,消耗了6张图片。现在我们传递4张将来,先拿x_a,x_b,xp_b跑一遍,在拿x_b,x_a,xp_a跑一遍。这样我们4张照片就跑了2次,节约了2张照片。这里就是简单使用了(输入)角色互换的道理。那么我说他是这样的?就要给出证据(大家这里注意一下:xp表示是
x
t
x_t
xt)。 首先代码:
# 送入x_a,x_b两张图片(来自训练集的不同ID)
# 通过st 编码器,编码成两个stcode:
# s_a[batch,128,64,32]
# s_b[batch,128,64,32]
s_a = self.gen_a.encode(self.single(x_a))
s_b = self.gen_b.encode(self.single(x_b))
对应图中的 这里分别对输入的x_a,x_b进行编码,得到对应st code,即代码中s_a,s_b 。红框表示输入,粉框代表输入(后面一样就不重复了)
下面是代码:
# 先把图片进行下采样,图示我们可以看到ap code的体积比st code是要小的,这样会出现一个情况,那么他们是没有办法直接融合的,所以后面可能有个全链接成把他们统一
# f_a[batch_size,2024*4=8192], p_a[0]=[batch_size, class_num=751], p_a[1]=[batch_size, class_num=751]
# f_b[batch_size,2024*4=8192], p_b[0]=[batch_size, class_num=751], p_b[1]=[batch_size, class_num=751]
# f代表的是经过ap编码器得到的ap code, p表示对身份的预测
f_a, p_a = self.id_a(scale2(x_a))
f_b, p_b = self.id_b(scale2(x_b))
首先把x_a,x_b两张图片输入,经过Ea(外观编码器),分别得到对应的ap code,即代码中的f_a,f_b。对于p_a,p_b 我后面讲解,从源码猜测,好像是两个ID类别的概率,如下:
即属于
x
j
i
x_j^i
xji与
x
i
j
x_i^j
xij的概率(暂时是猜测)
下面是代码:
# 进行解码操作,就是Figure 2中的黄色梯形G操作,这里的x_a,与x_b进行衣服互换
# s_b[batch,128,64,32] f_a[batch_size,2024*4=8192] --> x_ba[batch_size,3,256,128]
x_ba = self.gen_a.decode(s_b, f_a)
x_ab = self.gen_b.decode(s_a, f_b)
可以看到,是对应两个输入,但是只有一个输出,这就是结构和衣服融合的过程。self.gen_a.decode表示的是图中的G。
下面是代码:
# 进行重构,可以这样理解,x_a穿上自己的衣服,当然还是自己(图片基本不变)
# x_b也是同理
x_a_recon = self.gen_a.decode(s_a, f_a)
x_b_recon = self.gen_b.decode(s_b, f_b)
可以看到,依旧是两个输入,得到一个合成图片,但是这里是同一个人。他的思想,就是一个人脱了衣服,然后又穿生衣服,这个人还是原来的人,或者说还是同一张图片。
下面是代码:
# 这里是同一个人,换了一个姿态,p应该表示pos,把姿态经过外貌编码器得到ap code
fp_a, pp_a = self.id_a(scale2(xp_a))
fp_b, pp_b = self.id_b(scale2(xp_b))
前面提到,带p的输入图像,表示的是姿态,或者说和另外一张图片不一样的姿态。p就是pose嘛。
下面是:
# 一个人,穿上另外一个姿态的衣服(衣服相同的),得到初始姿态的图片
x_a_recon_p = self.gen_a.decode(s_a, fp_a)
x_b_recon_p = self.gen_b.decode(s_b, fp_b)
这样,我们网络的每个环节都对应起来了。大家要注意recon表示重构建的意思,重点在重不在构,重复构建出相同的照片,就是重构,表示他们没有换衣服,还是自己的衣服。
最开始的时候,我从细节处开始分析,把自己坑太惨了,好些地方想不明白,主要原因还是第一次接触PyTroch框架,很多东西都比较陌生。还有就是,大家不要奇怪,为什么返回那么多的东西,其实都是为了计算损失,后续详细讲解。
就到这里了,下面我们就开始讲解细节了,如编码器,解码器,鉴别器的详细内容了。记得点赞啊,我相信下篇博客还会见面了,毕竟这叫缘分嘛!