在本教程中,您将学习如何使用OpenCV、深度学习和计算机视觉实现新冠肺炎社交距离检测器。
我在计算机视觉领域见过很多人使用“社交距离探测器”,但我不确定他们是如何工作的。
敏俊是对的-我在社交媒体上看到了很多社交距离检测器的实现,我最喜欢的是来自reddit用户danrapko和rohit kumar sriastava的实现。
今天,我将为你提供一个你自己的社交距离探测器的起点。然后,您可以在您认为合适的时候对其进行扩展,以开发您自己的项目。
在本教程的第一部分,我们将简要讨论什么是社交距离,以及如何使用OpenCV和深度学习来实现社交距离检测器。
我们的Detect_People实用函数,它使用YOLO对象检测器检测视频流中的人。
我们的Python驱动程序脚本,它将所有片段粘合在一起,形成一个功能齐全的OpenCV社交距离检测器。
我们将通过回顾结果来结束这篇文章,包括对限制和未来改进的简要讨论。
最后,如果你不想/不能应用相机校准,你仍然可以使用社交距离探测器,但你必须严格依赖像素距离,这并不一定那么准确。
为简单起见,我们的OpenCV社交距离检测器实现将依赖于像素距离-我将把它留给读者作为练习,按照您认为合适的方式扩展实现。
请务必从这篇博客文章的“下载”部分获取代码。从那里解压文件,并使用tree命令查看我们的项目是如何组织的:
$tree--dirsfirst.├──pyimagesearch│├──__init__.py│和├──Detection.py│使用└──Social_Distance_config.py├──yolo-coco│├──coco│├──yolov3.cfg│├──│├──yolov3.cfg││├──yolov3.weights├──output.avi├──equans.mp4└──Social_Distance_Detector.py2目录,9个文件。
我们的YOLO对象检测器文件(包括CNN架构定义、预先训练的权重和类名)保存在yolo-coco/目录中。此YOLO模型与OpenCV的DNN模块兼容。
py:与一些更简单的模型相比,使用OpenCV进行YOLO对象检测需要更多的代码行。为方便起见,我决定将对象检测逻辑放在此文件的函数中。这样做可以使驱动程序脚本的帧处理循环不会变得特别混乱。
我们的社交距离检测器应用程序逻辑驻留在Social_Distance_Detector.py脚本中。该文件负责在视频流的帧上循环,并确保在大流行期间人们彼此保持健康的距离。它与视频文件和网络摄像头流都兼容。
我们的输入视频文件是walewans.mp4,它来自Tride的物体检测测试视频。output.avi文件包含处理后的输出文件。
为了帮助保持代码的整洁和组织,我们将使用一个配置文件来存储重要的变量。
现在让我们来看看它们--打开pyimagesearch模块内的Social_Distance_config.py文件,并浏览一下:
#YOLO目录的基本路径MODEL_PATH=";yolo-coco";#初始化最小概率以过滤弱检测和#应用非最大值抑制时的阈值MIN_CONF=0.3NMS_THRESH=0.3。
在这里,我们有到YOLO对象检测模型(第2行)的路径。定义了最小目标检测置信度和非最大值抑制阈值。
#指示是否应使用NVIDIA CUDA GPU的布尔值USE_GPU=FALSE#定义两个人之间的最小安全距离(以像素为单位)#MIN_DISTANCE=50。
第10行上的USE_GPU布尔值指示是否使用支持NVIDIA CUDA的GPU来加快推理速度(要求OpenCV的“dnn”模块安装时支持NVIDIA GPU)。
第14行定义了为了遵守社交距离协议,人们必须保持的最小距离(以像素为单位)。
我们将使用YOLO对象检测器来检测视频流中的人。
与其他对象检测方法(如单镜头检测器或更快的R-CNN)相比,将YOLO与OpenCV一起使用需要更多的输出处理,因此为了保持代码的整洁,让我们实现封装任何YOLO对象检测逻辑的Detect_People函数。
打开pyimagesearch模块内的Detection.py文件,让我们开始吧:
#从.Social_Distance_config导入NMS_THRESH导入所需的包从.Social_Distance_config导入MIN_CONF导入Numpy作为npimport CV2。
我们从导入开始,包括第2行和第3行的配置文件(nms_Thresh和min_conf)中需要的那些内容(根据需要,请参阅上一节)。我们还将在此脚本中利用NumPy和OpenCV(第4行和第5行)。
我们的脚本由用于检测人员的单个函数定义组成-现在让我们定义该函数:
def Detect_People(frame,net,ln,PersonIdx=0):#获取框架的尺寸并初始化#Results列表(H,W)=frame。Shape[:2]Results=[]。
PersonIdx:YOLO模型可以检测许多类型的对象;这个索引是专门针对Person类的,因为我们不会考虑其他对象。
然后,我们初始化结果列表,函数最终返回该列表。结果包括(1)人的预测概率,(2)检测的包围盒坐标,(3)目标的质心。
#从输入帧构造BLOB,然后执行YOLO对象检测器的前向#遍,给出我们的边界框#和相关概率blob=cv2.dnn.blobFromImage(frame,1/255.0,(416416),swapRB=True,Crop=False)net.setInput(Blob)layerOutput=net.ward(Ln)#初始化检测到的边界框、质心和#置信度的列表,分别为框。
预处理我们的帧需要我们构造一个BLOB(第16和17行)。从那里,我们可以使用YOLO和OpenCV执行对象检测(第18行和第19行)。
第23-25行初始化将很快包含我们的边界框、对象质心和对象检测置信度的列表。
#在每一层输出上循环以便在层中输出输出:#在每个检测上循环以便在输出中进行检测:#提取类别ID和置信度(即概率)#当前对象检测分数=检测[5:]classID=np.argmax(分数)置信度=分数[classID]#通过以下方式过滤检测:(1)确保检测到的对象#是人,以及(2)如果classID==PersonIdx和置信度>,则满足最低#置信度;min_conf:#相对于#图像大小向后缩放边界框坐标,请记住,YOLO#实际上返回#边界框的中心(x,y)坐标,后跟框';width和#Height box=检测[0:4]*np.array([W,H,W,H])(centerX,centerY,width,high)=box.astype(";int";)#使用center(x,y)坐标导出边界框的左上角x=int(centerX-(width/2))y=int(centerY-(高度/2))#更新我们的边界框坐标列表、#质心和置信度框。append([x,y,int(Width),int(Height)])质心。append((centerX,centerY))置信度.append(Float(置信度。
在每一层输出和检测上循环(第28-30行),我们首先提取当前检测对象的类ID和置信度(即,概率)(第33-35行)。
从那里,我们验证(1)当前检测到的是一个人,以及(2)满足或超过了最低置信度(第40行)。
假设是这样,我们计算边界框坐标,然后导出边界框的中心(即,质心)(第46和47行)。请注意,我们是如何通过先前收集的框架尺寸缩放(即乘以)检测的。
然后,第51和52行使用边界框坐标导出对象的左上角坐标。
#应用非最大值抑制来抑制弱的、重叠的#包围盒idxs=cv2.dnn.NMSBooks(Box,Confience,min_conf,nms_Thresh)#如果len(Idxs)>;0:#循环遍历idxs.flatten()中为i保存的索引:#提取边界框坐标(x,y)=(boxs[i][0],boxs[i][1])(w,h)=(boxes[i][2],boxs[i][3])#更新我们的结果列表,使其包含Person#预测概率、边界框坐标、#和质心r=(置信度[i],(x,y,x+w,y+h。Centroids[i])result。append(R)#返回返回结果列表。
非最大值抑制的目的是抑制弱的、重叠的边界框。第62行应用此方法(它内置到OpenCV中)并产生检测的IDX。
假设NMS的结果至少产生一个检测(第65行),我们循环遍历它们,提取边界框坐标,并更新包含以下内容的结果列表:
#导入所需的包从pyimagesearch根据配置从pyimagesearch导入Social_Distance_config。检测导入Detect_Peoplefrom scip.空间导入距离作为显示端口号py作为npimport argparseimport imutilsimport cv2import os。
第2-9行中最值得注意的导入包括我们的配置、我们的Detect_People函数和欧几里德距离度量(缩写为dist,用于确定质心之间的距离)。
#构造参数parse并解析参数sap=argparse.ArgumentParser()ap.add_argument(";-i";,";--input";,type=str,default=";";,help=";)ap.add_argument(";-o";,";--output";,type=str,default=";";,help=";)ap.add_argument(";-o";,";--output";,type=str,default=";";,help=";)。(可选)输出视频文件的路径";)ap.add_参数(";-d";,&-display";,type=int,default=1,help=";是否应显示输出帧";)args=vars(ap.parse_args())。
--input:可选视频文件的路径。如果未提供视频文件路径,默认情况下将使用计算机的第一个网络摄像头。
--OUTPUT:输出(即已处理)视频文件的可选路径。如果未提供此参数,则不会将处理后的视频导出到磁盘。
--Display:默认情况下,我们会在处理每一帧时在屏幕上显示我们的社交距离应用程序。或者,可以将此值设置为0以在后台处理流。
#LOAD COCO类标签我们的YOLO模型是在labelsPath=os.path.sep.Join([config.MODEL_PATH,";COCO.NAMES";])Labels=open(labelsPath).read().strip().split(";\n";)#上导出YOLO权重和模型配置权重的路径Path=os.path.sep.Join([config.MODEL_PATH,";yolov3.weight";])configPath=os.path.sep.join([config.MODEL_PATH,";yolov3.cfg";])。
在这里,我们加载我们的Load Coco标签(第22和23行),并定义我们的YOLO路径(第26和27行)。
#加载我们在COCO数据集(80个类)上培训的YOLO对象检测器打印(";[INFO]正在从磁盘加载YOLO.";)net=cv2.dnn.readNetFromDarknet(configPath,Weight tsPath)#检查我们是否要使用GPUif config.USE_GPU:#将CUDA设置为首选的后端和目标打印(";[info]将首选的后端和目标设置为CUDA.";)。
使用OpenCV的DNN模块,我们将YOLO网络加载到内存中(第31行)。如果您在配置中设置了USE_GPU选项,则后端处理器将设置为支持NVIDIA CUDA的GPU。如果您没有支持CUDA的GPU,请确保将配置选项设置为False,以便您的CPU是使用的处理器。
#从YOLOln=net.getLayerNames()ln=[ln[i[0]-1]for i in net.getUnconnectedOutLayers()]#初始化视频流和指向输出视频文件的指针(";[info]访问视频流.";)vs=cv2。VideoCapture(args[";input";]if args[";input";]。
这里,第41和42行收集了来自YOLO的输出层名称;我们需要它们来处理我们的结果。
然后,我们开始我们的视频流(通过--input命令行参数的视频文件或网络摄像机流)第46行。
目前,我们将输出视频写入器初始化为None。进一步的设置发生在帧处理循环中。
最后,我们准备开始处理框架,并确定人们是否保持安全的社交距离:
#循环遍历视频流中的帧,而True:#从文件中读取下一帧(已抓取,帧)=vs.read()#如果未抓取帧,则我们已到达流的末尾#如果未抓取:Break#调整帧大小,然后检测其中的人(且仅检测人)frame=imutils.resize(frame,width=700)Results=Detect_People(frame,net,ln,PersonIdx=LABELS.index(";Person。
我们用于测试的输入视频的尺寸相当大,因此我们在保持纵横比不变的情况下调整每一帧的大小(第60行)。
使用上一节中实现的Detect_People函数,我们将获取YOLO对象检测的结果(第61和62行)。如果需要刷新函数调用所需的输入参数或输出结果的格式,请务必参考上一节中的清单。
然后,我们在第66行初始化我们的违规集合;该集合维护违反公共卫生专业人员制定的社会距离规则的人的列表。
我们现在准备检查画面中人物之间的距离:
如果len(Results)>;=2:#从结果中提取所有质心并计算所有质心对之间的#欧几里德距离=np.array([r[2]for r in Results])D=dis.cdist(质心,质心,指标=";欧几里得";)#在范围(0,D.Shape[0])的距离矩阵的上三角形上循环:对于范围(i+1,D.Shape[1])中的j:#如果D[i,j]<;config.MIN_DISTANCE:#使用#违反的质心对的索引更新我们的违例集合。add(I)Violate.add(J),查看任意两个#质心对之间的距离是否小于配置的像素数#。
假设在帧(第70行)中至少检测到两个人,我们将继续执行以下操作:
从第77行和第78行开始,在距离矩阵的上三角形上循环(因为矩阵是对称的。
检查一下这个距离是否违反了公共卫生专业人员规定的最小社交距离(线路82)。如果两个人距离太近,我们会将他们添加到违规集合中。
我说,一点也不好玩!因此,让我们使用矩形、圆形和文本来注释我们的框架:
#循环枚举(Results)中(i,(prob,bbox,centroid))的结果:#提取边界框和质心坐标,然后#初始化批注的颜色(startX,starty,endX,endy)=bbox(cx,cy)=centroid color=(0,255,0)#如果违规集中存在索引对,则#如果i在Violation中更新颜色:color=(0,0,255)cv2.Rectangle(Frame,(StartX,starty),(endX,Endy),color,2)cv2.Circle(Frame,(cx,Cy),5,color,1)#绘制#输出Frame text=";社交距离违规:{}";.format(len(违规))cv2.putText(frame,text,(10,frame.shape[0]-25),cv2.FONT_Hershey_Simplex,0.85,(0,0,255),3)。
检查当前索引是否存在于我们的违规集中,如果存在,则将颜色更新为红色(第98和99行)。
绘制人物的边界框和他们的对象质心(线103和104)。每个都是颜色协调的,所以我们会看看哪些人离得太近。
显示有关社交距离违规总数的信息(我们违规集合的长度(第108-110行)
#如果args[";display";]>;0:#show the output frame cv2.imshow(";frame";,frame)key=cv2.waitKey(1)&;0xFF#如果按下了`q`键,如果key==order(";q";):Break#如果提供了输出视频文件路径,则中断循环。产出";]!=";";且Writer为None:#Initialize our video Writer Fourcc=cv2.VideoWriter_Fourcc(*";MJPG";)Writer=cv2.VideoWriter(args[";output";],ourcc,25,(frame.Shape[1],frame.Shape[0]),True)#如果视频编写器不为None,请将帧写入输出#video file if Writer。
在等待按下Q(退出)键时(第117-121行),如果需要,在屏幕上显示帧(第114-116行)。
确保使用本教程的“下载”部分下载源代码和示例演示视频。
$time python Social_Distance_Detector.py--输入行人.mp4\-输出输出。avi--display 0[info]正在从磁盘加载YOLO.[info]访问视频流.real 3m43.120suser 23m20.616ssys 0m25.824s。
在这里,你可以看到,我能够在我的CPU上以3m43s的速度处理整个视频,结果显示,我们的社交距离检测器正确地标记了违反社交距离规则的人。
当前实现的问题是速度。我们的基于CPU的社交距离检测器获得了~2.3FPS,这对于实时处理来说太慢了。
您可以通过(1)使用支持NVIDIA CUDA的GPU和(2)编译/安装支持NVIDIA GPU的OpenCV“dnn”模块来获得更高的帧处理速率。
如果您已经安装了支持NVIDIA GPU的OpenCV,则只需在Social_Distance_config.py文件中设置use_gpu=True即可:
$time python Social_Distance_Detector.py--输入行人.mp4\-输出输出。avi--display 0[info]正在从磁盘加载YOLO.[info]将首选后端和目标设置为CUDA.[info]访问视频流.real 0m56.008suser 1m15.772ssys 0m7.036s。
在这里,我们只用了56秒就处理了整个视频,相当于~9.38 FPS,这是307%的加速!
正如本教程前面提到的。
..