行人(客流)計數的一些底層技術

概述

自己之前寫過一篇文章介紹自己做的行人(客流)計數的工作:基於檢測和多目標跟蹤的客流統計功能小結

這篇文章利用行人檢測及多目標跟蹤技術,對過邊線的人數進行計數。檢測和多目標跟蹤我就不多說了,這裏說一下如何進行過線檢測。

基本思想

多目標跟蹤會對每一幀的每個檢測框賦予一個持續不變的ID,作爲同一個目標的標識。同時,爲了進行位置判斷,我會把每個檢測框抽象爲一個點,比如將檢測框上邊緣的中心作爲人的位置。

用於計數的邊線我設計了兩條,而且可以在任意位置。這兩條邊線我稱爲A線和B線,做業務的時候可以約定A線在外B線在內,這樣就好統計進出的方向。之所以不叫外線內線,是因爲存在在一些開放區域,比如馬路,無所謂內外。這兩條邊線在界面上由人工來畫的,然後配置到算法中來。下圖界面上我的邊線支持三段折線,後面講的時候就用一根線段來講。實際項目應用中,可以在界面畫多組邊線,每組邊線對應一個路口或門口。邊線的長度及角度都是可以根據場景任意畫的。

有了上面的工作,每個人的在我的算法裏就是一個有着編號的點,每一幀都能知道這個點的位置。同時算法裏還有A\B兩根邊線的端點座標。流量計數就是統計這些點是否跨過了兩根邊線,以及先從哪根線段開始跨的。

image

如何判斷跨線

在有兩條邊線的情況下,我總結了下五種情況,如下圖,只有前兩種是真正算跨線的,這兩種情況纔是算法要檢查的人流量。

在這裏插入圖片描述

先後跨越的情況

上面說了,每個行人都被抽象爲一個帶ID和座標的點,由於多目標跟蹤的緣故,同一個人的ID在多幀畫面間是保持不變的,而座標則隨着人走動而改變。

每兩幀畫面的座標都可以點連成一個線段,用這個線段判斷是否和兩個邊線交叉就可以了。如果一個人先跨越了A邊線,後續又跨越了B邊線,那麼妥妥的A到B的走動方向無疑了。

  • 注意這裏有一個技術:判斷兩個線段是否相交

爲了判斷是否和邊線相交,每個ID的歷史座標要在程序裏存下來,直到這個ID在多目標算法裏消失。

一次跨越兩個邊線

有些情況下兩條邊線距離很窄,或者視頻畫面出現了丟幀,那麼算法檢測到行人一次性跨越了兩條邊線也是正常的。

有了上面提到的判斷線段相交的算法,那麼判跨過兩根邊線就容易了。但是如何判斷方向呢?

  • 這裏用到了第二個技術:計算點和線段的距離

如果起點離A邊線近,離B邊線遠,那麼就是從A到B的跨越,否則就是反過來的。

圖形學判斷

基本思想講完了,下面就剩下如何實現線段交叉和距離的計算。
說明一下,我這裏無論是行人的座標還是邊線端點的座標,用的都是對圖像尺寸做的歸一化。所以我的座標大小都是0~1的小數。

判斷兩個線段是否相交

注意這裏說的是線段相交,不是直線。
下面是一個python實現的判斷線段是否相交的函數,其中(point_aa, point_bb)表示一根線段的兩個端點,( point_cc, point_dd)表示另一個線段的兩個端點。相交返回Ture,否則返回False。

def __intersect( point_aa, point_bb,
                     point_cc, point_dd):
        # this fuction will judge whether two line-segment is intersect
        # from https://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
        s10_x = point_bb.x - point_aa.x
        s10_y = point_bb.y - point_aa.y
        s32_x = point_dd.x - point_cc.x
        s32_y = point_dd.y - point_cc.y

        denom = s10_x * s32_y - s32_x * s10_y
        if (denom == 0):
            return False
        
        denomPositive = denom > 0
        
        s02_x = point_aa.x - point_cc.x
        s02_y = point_aa.y - point_cc.y
        s_numer = s10_x * s02_y - s10_y * s02_x
        if ((s_numer < 0) == denomPositive):
            return False
        t_numer = s32_x * s02_y - s32_y * s02_x
        if ((t_numer < 0) == denomPositive):
            return False

        if (((s_numer > denom) == denomPositive) or ((t_numer > denom) == denomPositive)):
            return False
        
        return True

這個算法參考自網上:https://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect

計算點和線段的距離

下面就是我的一個實現。(x, y)是點的座標, ( x1, y1, x2, y2)是線段兩個端點的座標。

def __distance_to_line( x, y, x1, y1, x2, y2 ):
        # from:http://blog.sina.com.cn/s/blog_5d5c80840101bnhw.html
        cross = (x2 - x1) * (x - x1) + (y2 - y1) * (y - y1)
        if (cross <= 0) :
            return math.sqrt((x - x1) * (x - x1) + (y - y1) * (y - y1))

        d2 = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)
        if (cross >= d2) :
            return math.sqrt((x - x2) * (x - x2) + (y - y2) * (y - y2))

        r = cross / d2
        px = x1 + (x2 - x1) * r
        py = y1 + (y2 - y1) * r

        return math.sqrt( (x - px) * (x - px) + (py - y1) * (py - y1) )

這段算法參考自:http://blog.sina.com.cn/s/blog_5d5c80840101bnhw.html

原始代碼是C#,我給改成python了。

客流計數要考慮的其他問題

  1. 單人反覆跨線。如果本意是爲了檢測門店的進出人數,但是店長或營銷人員來回踱步會增加無意義的客流增加,爲此需要將一直在畫面中來回跨線的情況去掉。
  2. 算法速度。如果檢測和多目標跟蹤算法不能以接近實時的速度運算,一切都沒有意義。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章