高空抛物流程很简单,只需要分为两步,第一步为动态目标检测,第二步为动态目标跟踪。在此之后的操作都是应用层的逻辑,比如如何判断目标是否为干扰等。
1. 动态目标检测
说到动态目标检测,大多数人都可以瞬间想到帧差法或者光流检测。但是这都是最初级的方法,因为有许多局限性,比如帧差法无法避免对树叶的误检,在摄像头有轻微摇晃的情况下也会有很多误检,也无法适应光线变化等;光流法也是相同的问题,而且光流法还有另外一个最大的问题是其基于稀疏特征点匹配的算法,因此实际上没有很好的办法将整张图的特征点分为不同的目标——虽然有稠密光流检测算法,但是耗时较长,且还有其他问题,不用考虑。
基于以上原因,出现了“背景建模”这一概念。
背景建模在opencv中实现了两类,一类是高斯建模,一类是KNN建模,都是对像素点进行建模。具体原理可以参考:https://blog.csdn.net/lwx309025167/article/details/78573152。因为其中内容较多,这里就不展开讨论,这篇博文写得非常详细了。如果只考虑工程应用,那么只需要知道opencv实现的算法怎么用就可以了。
这里使用的是KNN建模,速度比MOG2稍微快一些,效果差不多。
class knnDetector:
def __init__(self, history, dist2Threshold, minArea):
self.minArea = minArea
self.detector = cv2.createBackgroundSubtractorKNN(history, dist2Threshold, False)
self.kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
def detectOneFrame(self, frame):
if frame is None:
return None
start = time.time()
mask = self.detector.apply(frame)
stop = time.time()
print("detect cast {} ms".format(stop - start))
start = time.time()
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, self.kernel)
stop = time.time()
print("open contours cast {} ms".format(stop - start))
start = time.time()
image, contours, hierarchy = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
stop = time.time()
print("find contours cast {} ms".format(stop - start))
i = 0
bboxs = []
start = time.time()
for c in contours:
i += 1
if cv2.arcLength(c, True) < 20:
continue
area = cv2.contourArea(c)
if area < self.minArea:
continue
bboxs.append(cv2.boundingRect(c))
stop = time.time()
print("select cast {} ms".format(stop - start))
return mask, bboxs
开运算只是为了减少findContours的时间,然后根据mask的面积筛选,当然,这里可以有自己的阈值,或者可以自己设置筛选的条件,比如长宽比。
2. 轨迹跟踪
虽然背景建模可以剔除掉很多有规律的动态目标(比如树叶或者衣服的晃动),但是不可避免的还是有一些误检,因此需要将真正的抛物与误检分开。这里采用的是根据目标的移动距离来判断,也就是说,如果一个目标移动的距离大于一定的阈值,那么就认为是“抛物”(当然,也可以根据其他的条件,比如其轨迹必须是往下的,或者其轨迹线需要是抛物线等,条件越苛刻,误检越少,相应的有可能会发生漏检)。
但是有一个问题,因为动态目标检测出来的是一个个的目标块,这个时候根本不知道上一帧与下一帧的目标对应关系,也就谈不上移动距离。因此需要跟踪算法,将目标一一对应起来。
传统的跟踪算法,要么是根据HOG特征,要么是根据特征点来进行跟踪。但是因为高空抛物目标很小,而且移动速度较快,所以不能用传统的基于特征的方式进行跟踪。我们唯一知道的就是在一帧里面的许多目标框,能不能只依据坐标来跟踪多个目标呢?答案是有,那就是SORT算法(注意,不是DeepSort,DeepSort依旧会用到特征,在这里不适用)。
SORT是 SIMPLE ONLINE AND REALTIME TRACKING 的简写,并不是什么排序算法。
其核心算法是匈牙利算法+卡尔曼滤波。SORT算法没有用到特征跟踪,其本质实际上是根据观测的位置预测下一帧出现的位置,而我们预测的高空抛物实际上是有很强的规律的(重物规律强,较轻的物体如塑料袋或者纸板等,不是很规律,但是其速度不快,在每一帧之间基本上都有IOU重叠,因此也不会漏检),所以完全可以用此算法。
卡尔曼滤波之所以命名为“滤波”,是因为此算法最开始提出是为了“平滑”信号噪声,其后这个算法才用到图像跟踪上。
3. 代码下载
原理其实也就是那样,没啥好说的。代码包括自己拍摄的一段小的高空抛物视频,需要的自己取吧:高空抛物检测演示代码
ps:不建议自己去拍,我们当时即便是抛的很轻的东西(诸如塑料袋以及卷纸之类的),依旧做贼一样,而且还被保安教训了