密度图的密度估计_基于PaddlePaddle实现的密度估计模型CrowdNet

f5689c6fa483506f44d5ba668e08c89e.png

前言

CrowdNet模型是2016年提出的人流密度估计模型,论文为《CrowdNet: A Deep Convolutional Network for DenseCrowd Counting》,CrowdNet模型主要有深层卷积神经网络和浅层卷积神经组成,通过输入原始图像和高斯滤波器得到的密度图进行训练,最终得到的模型估计图像中的行人的数量。当然这不仅仅可以用于人流密度估计,理论上其他的动物等等的密度估计应该也可以。

项目开源地址: https://github.com/yeyupiaoling/PaddlePaddle-CrowdNet.git

本项目开发环境为:

  • Windows 10

  • Python 3.7

  • PaddlePaddle 2.0.0a0

CrowdNet模型实现

以下是CrowdNet模型的结构图,从结构图中可以看出,CrowdNet模型是深层卷积网络(Deep Network)和浅层卷积网络(Shallow Network)组成,两组网络通过拼接成一个网络,接着输入到一个卷积核数量和大小都是1的卷积层,最后通过插值方式得到一个密度图数据,通过统计这个密度就可以得到估计人数

a6e5e98c01df2ad2a340b6b80cc2c5e5.png

在PaddlePaddle中,通过以下代码判断即可实现上面的CrowdNet模型,在深层卷积网络和浅层卷积网络的卷积层都使用conv_bn卷积层,这个是通过把卷积层和batch_norm组合在一起的。在本项目中,输入的图像大小3, 640, 480,密度图大小为1, 80, 60,所以深层卷积网络输出的shape为512, 80, 60,浅层神经网络的输出为24, 80, 60。两个网络的输出通过fluid.layers.concat()接口进行拼接,拼接后输入到fluid.layers.conv2d(),最后通过fluid.layers.resize_bilinear() 双向性插值法输出一个密度图,最后使用的fluid.layers.reduce_sum()是为了方便在预测时直接输出估计人数。

def deep_network(img):
x = img
x = conv_bn(input=x, num_filters=64, filter_size=3, padding=1, act='relu')
x = conv_bn(input=x, num_filters=64, filter_size=3, padding=1, act='relu')
x = fluid.layers.pool2d(input=x, pool_size=2, pool_stride=2)
x = fluid.layers.dropout(x=x, dropout_prob=0.25)
x = conv_bn(input=x, num_filters=128, filter_size=3, padding=1, act='relu')
x = conv_bn(input=x, num_filters=128, filter_size=3, padding=1, act='relu')
x = fluid.layers.pool2d(input=x, pool_size=2, pool_stride=2)
x = fluid.layers.dropout(x=x, dropout_prob=0.25)
x = conv_bn(input=x, num_filters=256, filter_size=3, padding=1, act='relu')
x = conv_bn(input=x, num_filters=256, filter_size=3, padding=1, act='relu')
x = conv_bn(input=x, num_filters=256, filter_size=3, padding=1, act='relu')
x = fluid.layers.pool2d(input=x, pool_size=2, pool_stride=2)
x = fluid.layers.dropout(x=x, dropout_prob=0.5)
x = conv_bn(input=x, num_filters=512, filter_size=3, padding=1, act='relu')
x = conv_bn(input=x, num_filters=512, filter_size=3, padding=1, act='relu')
x = conv_bn(input=x, num_filters=512, filter_size=3, padding=1, act='relu')
x = fluid.layers.pool2d(input=x, pool_size=3, pool_stride=1, pool_padding=1)
x = conv_bn(input=x, num_filters=512, filter_size=3, padding=1, act='relu')
x = conv_bn(input=x, num_filters=512, filter_size=3, padding=1, act='relu')
x = conv_bn(input=x, num_filters=512, filter_size=3, padding=1)
x = fluid.layers.dropout(x=x, dropout_prob=0.5)return x
def shallow_network(img):
x = img
x = conv_bn(input=x, num_filters=24, filter_size=5, padding=3, act='relu')
x = fluid.layers.pool2d(input=x, pool_size=5, pool_type='avg', pool_stride=2)
x = conv_bn(input=x, num_filters=24, filter_size=5, padding=3, act='relu')
x = fluid.layers.pool2d(input=x, pool_size=5, pool_type='avg', pool_stride=2)
x = conv_bn(input=x, num_filters=24, filter_size=5, padding=4, act='relu')
x = fluid.layers.pool2d(input=x, pool_size=5, pool_type='avg', pool_stride=2)return x
# 创建CrowdNet网络模型
net_out1 = deep_network(images)
net_out2 = shallow_network(images)
concat_out = fluid.layers.concat([net_out1, net_out2], axis=1)
conv_end = fluid.layers.conv2d(input=concat_out, num_filters=1, filter_size=1)
# 双向性插值
map_out = fluid.layers.resize_bilinear(conv_end, out_shape=(80, 60))
# 避开Batch维度求和
sum_ = fluid.layers.reduce_sum(map_out, dim=[1, 2, 3])
sum_ = fluid.layers.reshape(sum_, [-1, 1])

通过上面实现的CrowdNet模型,它的结构如下图所示:

a05c227e18f61f7c023a59c1bc2dec93.png

训练模型

本项目使用的是百度公开的一个人流密度数据集,数据集下载链接:https://aistudio.baidu.com/aistudio/datasetdetail/1917,下载之后,执行下面操作:

  • train.json文件存放在data目录

  • test_new.zip解压到data目录

  • train_new.zip解压到data目录

本项目提供了一个脚本create_list.py可以把百度公开的数据集数据标准文件生成本项目所需要的标注格式,通过执行脚本可以生成类似以下格式的数据列表,每一行的前面是图像路径,后面的是人的坐标点,中间用制表符\t分开。如果开发者要训练自己的数据集,将图像标注数据生成以下格式即可。

data/train/4c93da45f7dc854a31a4f75b1ee30056.jpg	[(171, 200), (365, 144), (306, 155), (451, 204), (436, 252), (600, 235)]
data/train/3a8c1ed636145f23e2c5eafce3863bb2.jpg [(788, 205), (408, 250), (115, 233), (160, 261), (226, 225), (329, 161)]
data/train/075ed038030094f43f5e7b902d41d223.jpg [(892, 646), (826, 763), (845, 75), (896, 260), (773, 752)]

模型的输入标签是一个密度图,那么如何通过标注数据生成一个密度图的,下面就来简单介绍一下。其实就是一些不同核的高斯滤波器生成的,得到的一个比输入图像小8倍的密度图。

import jsonimport numpy as npimport scipyfrom PIL import Imageimport matplotlib.pyplot as pltfrom matplotlib import cm as CMimport scipyimport scipy.spatialfrom PIL import Imagefrom scipy.ndimage.filters import gaussian_filterimport os
# 图片预处理
def picture_opt(img, ann):
# 缩放的图像大小
train_img_size = (640, 480)
gt = []
size_x, size_y = img.size
img = img.resize(train_img_size, Image.ANTIALIAS)for b_l in range(len(ann)):
x = ann[b_l][0]
y = ann[b_l][1]
x = (x * train_img_size[0] / size_x) / 8
y = (y * train_img_size[1] / size_y) / 8
gt.append((x, y))
img = np.array(img) / 255.0return img, gt
# 高斯滤波
def gaussian_filter_density(gt):
density = np.zeros(gt.shape, dtype=np.float32)
gt_count = np.count_nonzero(gt)if gt_count == 0:return density
pts = np.array(list(zip(np.nonzero(gt)[1].ravel(), np.nonzero(gt)[0].ravel())))
tree = scipy.spatial.KDTree(pts.copy(), leafsize=2048)
distances, locations = tree.query(pts, k=4)for i, pt in enumerate(pts):
pt2d = np.zeros(gt.shape, dtype=np.float32)
pt2d[pt[1], pt[0]] = 1.if gt_count > 1:
sigma = (distances[i][1] + distances[i][2] + distances[i][3]) * 0.1else:
sigma = np.average(np.array(gt.shape)) / 2. / 2.
density += scipy.ndimage.filters.gaussian_filter(pt2d, sigma, mode='constant')return density
# 密度图处理
def ground(img, gt):
imgs = img
x = imgs.shape[0] / 8
y = imgs.shape[1] / 8
k = np.zeros((int(x), int(y)))for i in range(0, len(gt)):if int(gt[i][1]) < int(x) and int(gt[i][0]) < int(y):
k[int(gt[i][1]), int(gt[i][0])] = 1
img_sum = np.sum(k)
k = gaussian_filter_density(k)return k, img_sum

读取一张图片,并经过缩放预处理,在这里图像没有经过装置,但是在训练过程中需要对图像执行装置im.transpose()操作,这样才符合PaddlePaddle的输入格式。

# 读取数据列表with open('data/data_list.txt', 'r', encoding='utf-8') as f:
lines = f.readlines()
line = lines[50]
img_path, gt = line.replace('\n', '').split('\t')
gt = eval(gt)
img = Image.open(img_path)
im, gt = picture_opt(img, gt)print(im.shape)
plt.imshow(im)

21e2d089cd4194b4d607cd582fa442f9.png

通过ground()函数将上面的图片生成一个密度图,密度图结果如下图所示。注意在输入PaddlePaddle的密度图是要经过装置的,因为图像的数据的输入是装置的,所以密度图也得装置。

k, img_sum = ground(im, gt)
groundtruth = np.asarray(k)
groundtruth = groundtruth.astype('float32')print("实际人数:", img_sum)print("密度图人数:", np.sum(groundtruth))print("密度图大小:", groundtruth.shape)
plt.imshow(groundtruth,cmap=CM.jet)

c8ceb07ba5a8c677c4e609264301d7ef.png

训练程序

以下为train.py的代码,在训练中使用了平方差损失函数,其中损失值乘以6e5是为了不让输出的损失值太小。

loss = fluid.layers.square_error_cost(input=map_out, label=label) * 6e5
loss = fluid.layers.mean(loss)

为了加快数据的读取,这里使用了异步数据读取方式,可以一边训练一边读取下一步batch的数据。

py_reader = fluid.io.PyReader(feed_list=[images, label, img_num],
capacity=32,
iterable=True,
return_list=False)
py_reader.decorate_sample_list_generator(paddle.batch(reader.train_reader(data_list_file), batch_size=BATCH_SIZE),
places=fluid.core.CPUPlace())

在训练前加上一个加载预训练模型的方法,如果之前的模型存在,就加载该模型,接着上一次的训练结果继续训练。

if PERSISTABLES_MODEL_PATH is not None and os.path.exists(PERSISTABLES_MODEL_PATH):
def if_exist(var):if os.path.exists(os.path.join(PERSISTABLES_MODEL_PATH, var.name)):print('loaded: %s' % var.name)return os.path.exists(os.path.join(PERSISTABLES_MODEL_PATH, var.name))
fluid.io.load_vars(exe, PERSISTABLES_MODEL_PATH, main_program=fluid.default_main_program(), predicate=if_exist)

在执行训练前需要留意以下几个参数,需要根据自己的实际情况修改。当然如果开发者都是按照上面的操作,这里基本上不需要修改,但是BATCH_SIZE可能要修改一下,因为这个模型比较大,如何显存小的可能还有修改,以下是笔者在8G显存的环境下设置的。

# 是否使用GPU
USE_CUDA = True
# 模型参数保存路径
PERSISTABLES_MODEL_PATH = 'persistables_model/'
# 预测模型保存路径
INFER_MODEL = 'infer_model/'
# 训练轮数
EPOCHS_SUM = 800
# Batch大小
BATCH_SIZE = 6
# 图像列表路径
data_list_file = 'data/data_list.txt'

最后执行python train.py开始训练模型。

预测

最通过执行infer.py可以把data/test/目录下的图像都进行预测,结果写入到results.csv文件中。

下面介绍预测的大概方式,通过加载训练过程中保存的预测模型,得到一个预测程序。

import matplotlib.pyplot as pltfrom matplotlib import cm as CMimport osimport numpy as npimport paddle.fluid as fluidfrom PIL import Image
# 是否使用GPU
USE_CUDA = True
INFER_MODEL = 'infer_model/'
place = fluid.CUDAPlace(0) if USE_CUDA else fluid.CPUPlace()
exe = fluid.Executor(place)[inference_program,
feed_target_names,
fetch_targets] = fluid.io.load_inference_model(INFER_MODEL, exe)

读取一张待预测的图片。

image_path = "data/test/00bdc7546131db72333c3e0ac9cf5478.jpg"
test_img = Image.open(image_path)
plt.imshow(test_img)

2f78a2d5988c4cc5caa1134d65ceab03.png

通过对图像进行预处理,输入到预测程序中,预测的结果有两个,第一个是密度图,第二个是估计人数,因为输出的估计是估计人数是一个带小数的值,所以要进行四舍五入。其实对密度图求和也是能够得到估计人数的。因为PaddlePaddle输出的密度图是经过转置的,所以在显示时需要再一次执行转置才能正常显示。

test_img = test_img.resize((640, 480), Image.ANTIALIAS)
test_im = np.array(test_img) / 255.0
test_im = test_im.transpose().reshape(1, 3, 640, 480).astype('float32')
results = exe.run(program=inference_program,
feed={feed_target_names[0]: test_im},
fetch_list=fetch_targets)
density, quantity = results[0], results[1]
q = int(abs(quantity) + 0.5)print("预测人数:", q)
plt.imshow(density[0][0].T,cmap=CM.jet)

5e338d14ee77b9be20a425cf23a7ceae.png

AI Studio平台体验地址:

https://aistudio.baidu.com/aistudio/projectdetail/519178

END -

621cd20afe4f434bc6a80bfbeb3b7c38.png

7adeb011e2a14c351f7f59a9fbb6ec0e.png

33a1814f52d51e8b15b42b55849d21fc.png

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值