终于到了在家躺着都是给国家做贡献的时候(手动捂脸),对人员密集场所的监控和自动报警也算是对疫情的一份贡献吧,哈哈!项目地址:https://github.com/zzubqh/CrowdCount,如果你感觉有点用就帮忙点个start吧
概述和生成人群密度图
基于MSCNN的人群密度估计:
- 概述和生成人群密度图
- 数据集制作和数据生成器
- 密度等级分类网络
- MSCNN的训练和预测
人群密度估计算法
- 传统的人群计数方法
- 深度学习的方法
深度学习的方法在目标检测、识别、分割相比传统的方法都取得了很大的进步,同样在人群密度估计上也比传统的方法效果要好的多。
基于深度学习的人群密度估计方法概述
- 生成数据集中图片的密度图,生成方法见密度图生成算法,此密度图作为ground truth。
- 利用cnn做特征提取,这里就是每种算法最大差异的地方。原始图片通过精心构造的网络提取特征后,生成一副同密度图具有相同大小的图片,用第一步生成的密度图作为ground truth 进行loss计算。在计算loss的时候,简单期间可以直接使用mse,也可以使用一些论文中提出的专门针对密度图的loss。
- 对训练好的网络输出,直接求和(原因见密度图生成算法)即为图片中包含的人数。
项目概述
- 由于本项目对于人数大于100的统统记为100,不需要对人群密度过大的做出人数预测,所以选了一个网络结构相对简单的mscnn。
- 检测的图片中包括:不包含任何人的图片、只有有限数量人的图片和密度很高的图片,所以先利用一个密度分类网络将图片的密度等级分成了三个等级,分别为0:不包含任何人的图片;1:包含的人员数小于100;2:包含的人员数大于100。
- 在项目中,实现了一个使用vgg16作为特征提取的密度等级分类网络;一个mscnn网络用于人数估计;一个使用c#编写的对密度等级打标签的小工具。
- 最终的预测结果:
密度图生成方法
- 原理
- 如果一个标注点的位置为 x i x_{i} xi ,则改点可以表示为 δ ( x − x i ) \delta(x-x_i) δ(x−xi) ,其中 δ ( x − x i ) \delta(x-x_i) δ(x−xi)为冲击函数。因此具有N个人头的标签可以表示为:
H ( x ) = ∑ i = 0 N δ ( x − x i ) H(x) = \sum_{i=0}^{N} \delta(x-x_i) H(x)=i=0∑Nδ(x−xi)
上式说人话就是:一副图具有N个人头的图片的密度图,人头位于图片的(i,j)处,则在该处像素值为1,即 p(i,j)=1 其余所有的地方像素值为0。
- 上式就是密度图了,但是这样生成的密度图存在一个很严重的问题,会造成生成的密度图很稀疏导致在网络计算loss的时候整体输出趋近于0,而且不利于统计人群密度大的时候的人数。故,可以使用高斯函数对上式进行卷积,将标记为人头的位置变成该区域的密度函数,这样即在一定程度上解决了图片的稀疏问题,又不改变图片中人数的计数方式,依然只对密度图求和即可!
假设密度图上在[1,1]处标记出一个人头,则有如下数组:
array([[0., 0., 0.],
[0., 1., 0.],
[0., 0., 0.]])
取高斯核的 σ = 0.75 \sigma=0.75 σ=0.75滤波后得:
array([[0.05469418, 0.12447951, 0.05469418],
[0.12447951, 0.28330525, 0.12447951],
[0.05469418, 0.12447951, 0.05469418]])
可知,两个数组求和后值都为1,但消除了稀疏矩阵,更利于网络的loss计算!
- 高斯核卷积核选择。在真实场景下特别是人群密度很高的时候,每个 x i x_{i} xi 的位置并不是独立的,由于图片存在着透视失真,导致像素与周边样本在不同场景区域尺度不一致。因此为了精确估计群体密度函数需要考虑透视变换,假设每个头部周围的人群是均匀分布的,那么头部与最近k个邻居之间的图像中的平均距离可以给出几何失真的合理估计,即根据图像中k个头部间的距离来确定参数 σ \sigma σ。对于每个人头 x i x_{i} xi ,给出 k 个近邻头部距离{ x 1 i , x 2 i , . . . , x m i x_{1}^{i},x_{2}^{i},...,x_{m}^{i} x1i,x2i,...,xmi},计算平均距离 d i ‾ = 1 m ∑ j = 1 m d j i \overline{d_{i}}=\frac{1}{m}\sum_{j=1}^{m}d_{j}^{i} di=m1∑j=1mdji, x i x_{i} xi在图片上所在的位置对应一个区域,该区域内的人群密度与 d i ‾ \overline{d_{i}} di成比例,所以使用自适应高斯核进行卷积,高斯核的 σ i \sigma_{i} σi可变且与 d i ‾ \overline{d_{i}} di成比例
F ( x ) = ∑ i = 0 N δ ( x − x i ) ⋅ G σ i ( x ) F(x) = \sum_{i=0}^{N} \delta(x-x_i)\cdot G_{\sigma_{i}}(x) F(x)=i=0∑Nδ(x−xi)⋅Gσi(x) 其中 σ i ( x ) = β d i ‾ \sigma_{i}(x)=\beta\overline{d_{i}} σi(x)=βdi,这里的 β \beta β 取0.3。体现在代码中,就是先计算N个头部间的距离,然后计算出每个头部对应的平均距离,我在代码中K取1,因为训练集中存在大量只包含一个人或者两个人的图片。然后使用对应的 d i ‾ \overline{d_{i}} di计算高斯核的 σ \sigma σ
- 密度图生成代码
def get_densemap(self, img, img_index, size):
"""
生成密度图,准备输入神经网络
:param img
:param img_index
:param positions
:param size 神经网络输入层图片大小
"""
h, w = img.shape[:-1]
proportion_h, proportion_w = size / h, size / w
gts = self.positions[0][img_index].copy()
for i, p in enumerate(self.positions[0][img_index]):
now_x, now_y = int(p[0] * proportion_w), int(p[1] * proportion_h)
gts[i][0] = now_x
gts[i][1] = now_y
res = np.zeros(shape=[size, size])
bool_res = (gts[:, 0] < size) & (gts[:, 1] < size)
for k in range(len(gts)):
gt = gts[k]
if bool_res[k] == True:
res[int(gt[1])][int(gt[0])] = 1
pts = np.array(list(zip(np.nonzero(res)[1], np.nonzero(res)[0])))
map_shape = [size, size]
density = np.zeros(shape=map_shape, dtype=np.float32)
if len(pts) == 1:
sigmas = [0]
else:
neighbors = NearestNeighbors(n_neighbors=1, algorithm='kd_tree', leaf_size=1200)
neighbors.fit(pts.copy())
distances, _ = neighbors.kneighbors()
sigmas = distances.sum(axis=1) * 0.3
for i in range(len(pts)):
pt = pts[i]
pt2d = np.zeros(shape=map_shape, dtype=np.float32)
pt2d[pt[1]][pt[0]] = 1
density += cv2.GaussianBlur(pt2d, (3, 3), sigmas[i])
return density
生成的密度图如下所示: