終於到了在家躺着都是給國家做貢獻的時候(手動捂臉),對人員密集場所的監控和自動報警也算是對疫情的一份貢獻吧,哈哈!項目地址:https://github.com/zzubqh/CrowdCount,如果你感覺有點用就幫忙點個start吧 😃
概述和生成人羣密度圖
基於MSCNN的人羣密度估計:
人羣密度估計算法
- 傳統的人羣計數方法
- 基於檢測和迴歸的方法
- 基於密度圖的方法
- 深度學習的方法
深度學習的方法在目標檢測、識別、分割相比傳統的方法都取得了很大的進步,同樣在人羣密度估計上也比傳統的方法效果要好的多。
基於深度學習的人羣密度估計方法概述
- 生成數據集中圖片的密度圖,生成方法見密度圖生成算法,此密度圖作爲ground truth。
- 利用cnn做特徵提取,這裏就是每種算法最大差異的地方。原始圖片通過精心構造的網絡提取特徵後,生成一副同密度圖具有相同大小的圖片,用第一步生成的密度圖作爲ground truth 進行loss計算。在計算loss的時候,簡單期間可以直接使用mse,也可以使用一些論文中提出的專門針對密度圖的loss。
- 對訓練好的網絡輸出,直接求和(原因見密度圖生成算法)即爲圖片中包含的人數。
項目概述
- 由於本項目對於人數大於100的統統記爲100,不需要對人羣密度過大的做出人數預測,所以選了一個網絡結構相對簡單的mscnn。
- 檢測的圖片中包括:不包含任何人的圖片、只有有限數量人的圖片和密度很高的圖片,所以先利用一個密度分類網絡將圖片的密度等級分成了三個等級,分別爲0:不包含任何人的圖片;1:包含的人員數小於100;2:包含的人員數大於100。
- 在項目中,實現了一個使用vgg16作爲特徵提取的密度等級分類網絡;一個mscnn網絡用於人數估計;一個使用c#編寫的對密度等級打標籤的小工具。
- 最終的預測結果:
密度圖生成方法
- 原理
- 如果一個標註點的位置爲 ,則改點可以表示爲 ,其中爲衝擊函數。因此具有N個人頭的標籤可以表示爲:
上式說人話就是:一副圖具有N個人頭的圖片的密度圖,人頭位於圖片的(i,j)處,則在該處像素值爲1,即 p(i,j)=1 其餘所有的地方像素值爲0。 - 上式就是密度圖了,但是這樣生成的密度圖存在一個很嚴重的問題,會造成生成的密度圖很稀疏導致在網絡計算loss的時候整體輸出趨近於0,而且不利於統計人羣密度大的時候的人數。故,可以使用高斯函數對上式進行卷積,將標記爲人頭的位置變成該區域的密度函數,這樣即在一定程度上解決了圖片的稀疏問題,又不改變圖片中人數的計數方式,依然只對密度圖求和即可!
假設密度圖上在[1,1]處標記出一個人頭,則有如下數組:
array([[0., 0., 0.],
[0., 1., 0.],
[0., 0., 0.]])
取高斯核的濾波後得:
array([[0.05469418, 0.12447951, 0.05469418],
[0.12447951, 0.28330525, 0.12447951],
[0.05469418, 0.12447951, 0.05469418]])
可知,兩個數組求和後值都爲1,但消除了稀疏矩陣,更利於網絡的loss計算! - 高斯核卷積核選擇。在真實場景下特別是人羣密度很高的時候,每個 的位置並不是獨立的,由於圖片存在着透視失真,導致像素與周邊樣本在不同場景區域尺度不一致。因此爲了精確估計羣體密度函數需要考慮透視變換,假設每個頭部周圍的人羣是均勻分佈的,那麼頭部與最近k個鄰居之間的圖像中的平均距離可以給出幾何失真的合理估計,即根據圖像中k個頭部間的距離來確定參數 。對於每個人頭 ,給出 k 個近鄰頭部距離{},計算平均距離,在圖片上所在的位置對應一個區域,該區域內的人羣密度與成比例,所以使用自適應高斯核進行卷積,高斯核的可變且與成比例
其中 ,這裏的 取0.3。體現在代碼中,就是先計算N個頭部間的距離,然後計算出每個頭部對應的平均距離,我在代碼中K取1,因爲訓練集中存在大量只包含一個人或者兩個人的圖片。然後使用對應的計算高斯核的
- 如果一個標註點的位置爲 ,則改點可以表示爲 ,其中爲衝擊函數。因此具有N個人頭的標籤可以表示爲:
- 密度圖生成代碼
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])
# density += filters.gaussian_filter(pt2d, sigmas[i], mode='constant')
return density
生成的密度圖如下所示: