基于方向梯度直方图的疫情人流密度检测系统搭建
1 任务目标
- 了解人流密度检测的重要性
- 了解基于HOG的人流密度检测系统
- 掌握HOG的原理
- 掌握OpenCV在图像处理的应用
- 利用OpenCV使用HOG搭建简单的人流检测系统
2 任务描述
2.1 人流密度检测
COVID-19是一种由严重急性呼吸系统综合征冠状病毒引发的传染病,该疾病具有人传人的能力,故为了减少疾病的传播速度与范围,国内各个地区都相应推出了限制人流密度的政策。
传统的方法是通过管理员凭借经验来定性判断某个区域中人流密度的大小,但随着计算机图像处理的迅速发展,这一工作已经可以由计算机通过摄像头完成。
2.2 人流密度检测的历史
2001年,Paul Viola和Michael Jones在他们的论文中提出了Haar级联检测,这一方法不久便在人流密度检测中得到了广泛的应用。
2005年,方向梯度直方图(HOG)的出现使得人流密度检测的精度大大提高。
在2012年,卷积神经网络在图像分类任务中取得了惊人的成绩,卷积神经网络开始被广泛地使用在计算机视觉的各个领域之中。在包含人流密度检测在内的目标检测任务中也出现了大量优秀的卷积神经网络如R-CNN系列与YOLO系列。
基于卷积神经网络的人流密度检测方法往往能够得到很高的正确率,但大多数算法所需要的算力要远远多于传统的Haar与HOG,因此这两种方法直到现在也依然被广泛使用。
3 知识准备
为了更好地完成任务,我们需要掌握一些基础知识。
3.1 行人检测
在计算机视觉领域中,人流密度检测需要使用到行人检测的方法来完成。
行人检测算法的作用是:找出图像或视频中所有的行人,包括其位置和大小,使用矩形框来表示。
3.2 方向梯度直方图
方向梯度直方图(HOG)能够通过提取图像的有用信息,来将三通道的彩色图像转换成一个特征向量。我们使用这个特征向量来完成行人检测任务。
对于内容图像,我们首先得对图像进行必要的预处理来增强图像的特征,最常用的便是调整图像的大小与将图像转换为灰度图,这两种方法都能够在提高运算速度的同时增强图像的特征。
预处理完后的图像,我们可以通过使用不同形状的内核来得到图像的梯度图,常用的内核如下图两种。
随后将整个图像划分为若干个8$*$8的小单元(每个单元内共有128个值,其中64个表示梯度大小,64个表示方向)。
在每个小单元中,所有的梯度值都会映射在一个长度为9的张量上,这个张量便是梯度直方图。
最后,我们通过区间归一化的形式来降低特征与图片光线之间的敏感程度,通常的做法是使用一个16 ∗ * ∗ 16 的块来将四个单元组合成一个36 ∗ * ∗ 1的张量。
最后一步,则是进一步地将这若干个36$*$1的张量组合为一个张量,这个张量就是作为后续检测的输入。
以上步骤都整合在OpenCV中,我们可以直接使用 HOGDescriptor() 函数来完成。
3.3 支持向量机
在2005年,Navneet Dalal和Bill Triggs通过大量测试发现,在使用HOG作为特征提取的前提下,使用线性支持向量机(SVM)作为分类器能够在速度和效果综合性能中取得更佳的结果。我们的任务也会利用支持向量机来作为分类器。
支持向量机是一种二分类模型,它的基本模型为定义在特征空间中的间隔最大线性分类器,即求解出一个能够正确划分训练数据集并且几何间隔最大的分离超平面。
通俗地说,就是找一个超平面来尽可能地分开样本。
这种间隔最大化的学习策略使得支持向量机与感知机(可以理解为只有一层的神经网络)不同,同时也可以将训练支持向量机转化为二次规划的问题。
在OpenCV中已经整合了HOG+SVM的检测器,因此我们只需要直接调用便可。
3.4 OpenCV
OpenCV全称是Open Source Computer Vision Library,是一个跨平台的计算机视觉库,可以在商业和研究领域中免费试用。
OpenCV可以用于开发实时的图像处理、计算机视觉以及模式识别程序,因此它也成为了目前使用的最为广泛的计算机视觉库之一。许多深度学习程序都会使用OpenCV来进行图像的预处理。
3.5 基于OpenCV的视频读取
OpenCV内置了视频读取的对象,我们可以通过构建该对象来按帧读取视频。
创建摄像头对象的函数为
cap = cv2.VideoCapture(path)
其中path表示视频路径,返回的cap便是摄像头对象。对于摄像头对象,我们可以使用以下方法来读取视频中的一帧。
ret,frame = cap.read()
该方法返回两个变量:前者判断对象是否还有帧图像,后者则是该帧图像的张量形式(假如有的话),当使用一次 read() 方法后对象将会取出一帧,再次使用则会取出下一帧(假如有的话)。
通常,我们都是搭配循环函数来不断地提取视频的帧图像。
while True:
ret,frame = cap.read()
###任意代码###
#当视频已经读取完毕则退出
if not ret:
break
最后,我们通过调用 release() 方法来释放摄像头的空间,以防止意外情况(如程序卡死)。
cap.release()
4 项目实施
基于任务描述与知识准备的内容,我们已经具有了初步的任务完成能力。以下我们将会使用OpenCV来完成任务。
4.1 实施思路
对于人流密度统计,我们可以使用以下方法:当区域中行人数量在阈值以下时使用绿色边框划分所有人的位置;当超过阈值则使用红色边框。
实施步骤:
- 导入实验所需库
- 读取视频资源与初始化
- 图片预处理
- 搭建整体系统
4.2 数据预处理实施步骤
步骤1:导入实验所需库
本次实验所需的库为:
import cv2
OpenCV包括了我们本次任务所需要的HOG和SVM分类器。
步骤2:读取视频资源与初始化
首先,作为一个疫情人流密度统计系统,我们需要设定一个人数值来作为警告的阈值,当图像中的人数超过该阈值后,就会触发警告(在该任务中便是边框变红)。
#设置疫情警告人数
LIMIT = 10
随后,为了能够让我们能够分析该系统的效果,我们使用了OXFORD TOWN CENTRE数据集来作为测试数据,我们构建一个摄像头对象,来方便后续的调用
#构建摄像头对象
cap = cv2.VideoCapture('./data/data.flv')
随后,我们根据上文中提到过的方法来直接使用OpenCV创建一个HOG+SVM检测器,来帮助我们搭建这个系统。
#初始化HOG+SVM检测器
hog = cv2.HOGDescriptor()
在这里使用的是OpenCV已经训练好的SVM分类模型,该模型能够对行人进行分割,直接加载这个模型的方法如下。
hog.setSVMDetector(cv2.HOGDescriptor_getDefaultPeopleDetector())
至此,初始化工作已经完成,接下来便是对输入的图像进行处理。
步骤3:图片预处理
直接使用原始图像效果往往不如人意,因此我们需要对图像进行适当的处理,来提高HOG的特征提取效果,进而使得SVM分类器能够更好地找出行人位置。
首先,我们需要通过修正图像的大小,来减少计算量。
img = cv2.resize(img,(1280, 720))
随后,考虑到HOG更适合使用灰度图来进行处理,因此有必要将图像转换为灰度图。这个步骤可以通过 cvtColor() 函数来实现。
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
最后便是将以上两个功能封装成一个函数,这样能够使得程序更加清晰易懂,也更容易进行维护。
def preProcessing(img):
"""预处理图片"""
#修正图像大小
img = cv2.resize(img,(1280, 720))
#得到图像的灰度图
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
return img,gray
值得注意的是,函数的返回值中除了已经处理完的图像外,还包含了修正大小后的原始图像,这样做可以方便后续的调用。
步骤4:搭建整体系统
系统的整体思路为使用一个大循环不断地提取视频的帧图像,在进行完预处理后使用HOG+SVM模型来检测行人。
为此,我们首先根据3.5中提到的方法,创建一个循环程序来读取视频。
#进入主循环部分
while(True):
#读取视频下一帧率
ret, frame = cap.read()
###用作处理图像的部分###
随后,我们可以通过OpenCV的 detectMultiScale 方法来使用HOG+SVM模型来进行检测,该方法返回两个数组:所有行人的边框信息(x,y,长,宽)以及这个边框的权重(即置信度,越高代表框内是行人的概率越大)。
#进入主循环部分
while(True):
#读取视频下一帧率
ret, frame = cap.read()
frame,gray = preProcessing(frame)
#进行扫描
rects, weights = hog.detectMultiScale(gray)
单纯有信息不能直观地反映出系统的处理效果,因此我们需要使用到 rectangle() 函数,来在图像上绘画边框。
#进入主循环部分
while(True):
#读取视频下一帧率
ret, frame = cap.read()
frame,gray = preProcessing(frame)
#进行扫描
rects, weights = hog.detectMultiScale(gray)
#绘图部分
for i, (x, y, w, h) in enumerate(rects):
#参数分别为输入图像,左下角坐标,右上角坐标,RGB颜色及边框粗细
cv2.rectangle(frame, (x,y), (x + w,y+ h),(255,255,255),2)
#显示该帧
cv2.imshow("process", frame)
注意到该任务的需求是 根据阈值 来确定边框的颜色,为此我们得根据这个条件来设置颜色。
#进入主循环部分
while(True):
#读取视频下一帧率
ret, frame = cap.read()
frame,gray = preProcessing(frame)
#进行扫描
rects, weights = hog.detectMultiScale(gray)
#设置边框颜色,当超过限制时边框变为红色
color = (0,0,255) if len(rects) > LIMIT else (0,255,0)
#绘图部分
for i, (x, y, w, h) in enumerate(rects):
cv2.rectangle(frame, (x,y), (x + w,y+ h),color,2)
#显示该帧
cv2.imshow("process", frame)
此时视频能够正常绘画,但是当视频结束后会出现报错情况。为了解决这个问题,我们需要检测视频是否读取完毕或出现异常情况。
同时考虑到主循环有终止的情况,因此使用 release() 来释放内存 destroyAllWindows() 来关闭已打开窗口。
#进入主循环部分
while(True):
#读取视频下一帧率
ret, frame = cap.read()
#判断下一帧是否存在
if ret:
#修正图像大小
frame,gray = preProcessing(frame)
#进行扫描
rects, weights = hog.detectMultiScale(gray)
#设置边框颜色,当超过限制时边框变为红色
color = (0,0,255) if len(rects) > LIMIT else (0,255,0)
#绘图部分
for i, (x, y, w, h) in enumerate(rects):
cv2.rectangle(frame, (x,y), (x + w,y+ h),color,2)
#显示该帧
cv2.imshow("process", frame)
else:
print("视频已结束或遇到未知错误")
break
#释放内存并关闭窗口
cap.release()
cv2.destroyAllWindows()
基本的部分已经完成,但是在视频进行的过程中发现有的情况会出现边框的重叠现象,我们可以通过加入权重检测来判断这个框是否需要绘画。
#进入主循环部分
while(True):
#读取视频下一帧率
ret, frame = cap.read()
#判断下一帧是否存在
if ret:
#修正图像大小
frame,gray = preProcessing(frame)
#进行扫描
rects, weights = hog.detectMultiScale(gray)
#设置边框颜色,当超过限制时边框变为红色
color = (0,0,255) if len(rects) > LIMIT else (0,255,0)
#绘图部分
for i, (x, y, w, h) in enumerate(rects):
#如果该框的权重较小,则不进行绘画
if weights[i] < 0.7:
continue
cv2.rectangle(frame, (x,y), (x + w,y+ h),color,2)
#显示该帧
cv2.imshow("process", frame)
else:
print("视频已结束或遇到未知错误")
break
#释放内存并关闭窗口
cap.release()
cv2.destroyAllWindows()
最后,为了可以使操作者提前退出循环,我们加入了按键检测功能来判断是否提前结束。
#进入主循环部分
while(True):
#读取视频下一帧率
ret, frame = cap.read()
#判断下一帧是否存在
if ret:
#修正图像大小
frame,gray = preProcessing(frame)
#进行扫描
rects, weights = hog.detectMultiScale(gray)
#设置边框颜色,当超过限制时边框变为红色
color = (0,0,255) if len(rects) > LIMIT else (0,255,0)
#绘图部分
for i, (x, y, w, h) in enumerate(rects):
#如果该框的权重较小,则不进行绘画
if weights[i] < 0.7:
continue
cv2.rectangle(frame, (x,y), (x + w,y+ h),color,2)
#显示该帧
cv2.imshow("process", frame)
else:
print("视频已结束或遇到未知错误")
break
#如果检测到Q键,则退出循环
if cv2.waitKey(100) & 0xFF == ord("q"):
break
#释放内存并关闭窗口
cap.release()
cv2.destroyAllWindows()
使用简单的测试来查看效果
能够看出系统能够正常识别,但是存在着精度不足的问题。
5 知识拓展
行人检测本质上属于目标检测问题。目标检测问题则是物体识别和物体定位的综合,即不仅仅要识别出物体的分类,也要得到物体的具体位置。
我们可以在基于上文所搭建系统的前提下,使用其他系统来对框内行人进行分析。如可以使用卷积神经网络来分析每个人是否佩戴口罩等。
除此以外,我们可以使用神经网络来对行人图像进行分割,这种方式能够得到精确到像素级别的行人蒙版(而不是一个方框),这样能够极大地提高了检测的准确率以及避免了各种误判的可能。