在之前的博客中我有详细介绍过人群计数领域中密度图的生成方法,还有一篇CSRNet的论文学习笔记。 人群计数之生成密度图 论文学习笔记:CSRNet: Dilated Convolutional Neural Networks for Understanding the Highly Congested Scenes 接下来我使用ShanghaiTech数据集对论文中提出的CSRNet模型进行了复现,其中关于数据增强等一些细节进行了省略,以方便初学者更快地了解人群计数相关的方法。
本文使用ShanghaiTech part_B_final部分数据集对进行复现,由于没有对数据进行预处理和数据增强等操作,而part_A_final部分的图片尺寸大小不一样,所以在进行训练的时候只能设置批处理大小为1(batch_size=1)。 关于数据预处理部分的代码请参考:人群计数之生成密度图
运行代码之后我们可以看到在原来存放标注文件的文件夹生成每张图片对应的ground_truth密度图(h5文件)。
经过CSRNet网络模型之后输入图片的尺寸变为原来的1/8,所以生成的真实密度图大小为原图片尺寸的1/8,如果做了数据增强处理(例如裁剪、resize()操作等),需要重新生成一次真实密度图。
CSRnet网络模型主要分为前端和后端网络,前端网络采用的是剔除了全连接层的VGG-16,后端网络采用了空洞卷积的操作,论文中为了对比不同模型的效果,也是搭建了四种空洞率不同的后端网络。网络结构图如下所示: 关于VGG网络的详细介绍可以参考博客:使用pytorch搭建自己的网络之VGG
class CSRNet(nn.Module): def __init__(self, load_weights=True): super(CSRNet, self).__init__() self.frontend_feat = [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512] self.backend_feat = [512, 512, 512,256,128,64] self.frontend = make_layers(self.frontend_feat) self.backend = make_layers(self.backend_feat,in_channels = 512,dilation = True) self.output_layer = nn.Conv2d(64, 1, kernel_size=1) if load_weights: #加载VGG16的预训练模型 vgg16 = models.vgg16(pretrained = True) self._initialize_weights() #初始化权重 #加载torchvision中的预训练模型和参数后通过state_dict()方法提取参数 for i in range(len(self.frontend.state_dict().items())): list(self.frontend.state_dict().items())[i][1].data[:] = list(vgg16.state_dict().items())[i][1].data[:] def forward(self,x): x = self.frontend(x) x = self.backend(x) x = self.output_layer(x) return x def _initialize_weights(self): for m in self.modules(): if isinstance(m, nn.Conv2d): nn.init.normal_(m.weight, std=0.01) if m.bias is not None: nn.init.constant_(m.bias, 0) elif isinstance(m, nn.BatchNorm2d): nn.init.constant_(m.weight, 1) nn.init.constant_(m.bias, 0)这里为了方便起见,直接使用了预训练的VGG-16模型和参数。在初始化函数__init__中,首先调用_initialize_weights()函数对所有层的参数进行初始化,然后使用VGG16预训练好的参数对其中属于VGG的层初始化。
最后补上make_layers()函数:
def make_layers(cfg, in_channels = 3, batch_norm=False, dilation = False): if dilation: d_rate = 2 else: d_rate = 1 layers = [] for v in cfg: if v == 'M': layers += [nn.MaxPool2d(kernel_size=2, stride=2)] else: conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=d_rate,dilation = d_rate) if batch_norm: layers += [conv2d, nn.BatchNorm2d(v), nn.ReLU(inplace=True)] else: layers += [conv2d, nn.ReLU(inplace=True)] in_channels = v return nn.Sequential(*layers)论文中经过对比发现CSRNet-B的效果最好,所以后面的研究中均使用了结构B。这里要修改的话也很简单,注意修改d_rate 的值就好。
首先定义相关参数,然后调用load_local_dataset()函数加载数据集,最后进行训练和测试,并保存模型。由于博主有多块GPU,所以使用了多块GPU共同训练以增加训练速度。代码片段是torch.nn.DataParallel(model.CSRNet()),如果使用此行代码,在保存训练模型并调用模型的时候要注意,使用其他方法可能会报错。
继承dataset类并重写该函数,然后定义load_local_dataset()函数来加载数据集,返回的是数据集的迭代器和数据集大小。
def load_local_dataset(path_sets, batch_size=8): img_paths = [] path_sets=[path_sets] for path in path_sets: for img_path in glob.glob(os.path.join(path, '*.jpg')): img_paths.append(img_path) # 加载数据集 datasets =MyDataset(img_paths, transform=transform) # 调用DataLoader来创建数据集的迭代器 dataset_iter = DataLoader(dataset=datasets, batch_size=batch_size, shuffle=True) return dataset_iter,len(img_paths)附上图像的初始化操作代码:
# 图像的初始化操作 transform = transforms.Compose([ transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ])这里没有什么复杂的操作,唯一要注意的是target通过torch.unsqueeze操作进行维度扩充,使得target和output的维度相同,能够计算损失函数。
为了追求代码的极致简单,这里也只计算了MAE和MSE。
1、在训练代码中均使用了GPU进行训练,如果你想使用CPU训练的话,修改一下代码net = torch.nn.DataParallel(model.CSRNet()).cuda()并去掉所有.cuda()后缀即可。 2、此blog仅提供部分代码和相关讲解,如果想要获取全部资源可以通过下面链接: https://download.csdn.net/download/qq_40356092/12811426 博主也会提供复现代码的所有指导。
以下内容的代码请下载完整资源包:CSRNet-pytorch
原始图片: 真实密度图: 预测的密度图: 预测人数为: 19.395416 真实人数为: 21.089602
由于在复现的时候参考了大量的代码,所以和其他博主/教程中提供的代码可能有相似之处,但是因为时间久远,所以很难找到相应的参考链接,在这里就不提供了~
如果你对以上内容有任何疑问,均可以在留言区进行留言评论,博主看到了一定会及时回复。
