概述
自己之前写过一篇文章介绍自己做的行人(客流)计数的工作:基于检测和多目标跟踪的客流统计功能小结
这篇文章利用行人检测及多目标跟踪技术,对过边线的人数进行计数。检测和多目标跟踪我就不多说了,这里说一下如何进行过线检测。
基本思想
多目标跟踪会对每一帧的每个检测框赋予一个持续不变的ID,作为同一个目标的标识。同时,为了进行位置判断,我会把每个检测框抽象为一个点,比如将检测框上边缘的中心作为人的位置。
用于计数的边线我设计了两条,而且可以在任意位置。这两条边线我称为A线和B线,做业务的时候可以约定A线在外B线在内,这样就好统计进出的方向。之所以不叫外线内线,是因为存在在一些开放区域,比如马路,无所谓内外。这两条边线在界面上由人工来画的,然后配置到算法中来。下图界面上我的边线支持三段折线,后面讲的时候就用一根线段来讲。实际项目应用中,可以在界面画多组边线,每组边线对应一个路口或门口。边线的长度及角度都是可以根据场景任意画的。
有了上面的工作,每个人的在我的算法里就是一个有着编号的点,每一帧都能知道这个点的位置。同时算法里还有A\B两根边线的端点坐标。流量计数就是统计这些点是否跨过了两根边线,以及先从哪根线段开始跨的。
如何判断跨线
在有两条边线的情况下,我总结了下五种情况,如下图,只有前两种是真正算跨线的,这两种情况才是算法要检查的人流量。
先后跨越的情况
上面说了,每个行人都被抽象为一个带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了。
客流计数要考虑的其他问题
- 单人反复跨线。如果本意是为了检测门店的进出人数,但是店长或营销人员来回踱步会增加无意义的客流增加,为此需要将一直在画面中来回跨线的情况去掉。
- 算法速度。如果检测和多目标跟踪算法不能以接近实时的速度运算,一切都没有意义。