本文将介绍在OpenCV环境中运行yolov3深度学习网络调用的全部过程,文章主要分为以下部分内容:
1.YOLOv3是什么,能做什么事情?
2.为什么要使用OpenCV for YOLOv3
3.YOLOv3调用整体过程的解析
4.总结
这是yolo官网所给出的一副图片,我们可以不难看出,在相同mAP(可以理解为识别精度)上,yolov3具有相当高的识别速度,以至于官方“自嘲”将自己的识别速度停留在了时间轴的第二象限。接下来谈一谈什么是yolov3。
YOLO(you only look once)是一款先进的实时目标检测系统,目前已经发展到yolov5,正是这种超快的实时检测速度,yolo的创始人看到了自己的成果被用在了自己不想应用的领域,放弃了v3之后的开发。yolo采用one-stage模式,完成从原始图像的输入到物体位置和类别的输出。
YOLOv3的核心网络架构采用3个scale分别来检测大中小物体,并且三个scale之间也有相互关联,可以更加准确地识别信息,且每种scale分别输出维度为s×s×3×(4+1+80),s分别对应3个scale的值为13、26、52;3表示图像在识别过程中会对预测的物体输出3个先验框,最后挑选最优的来判定内容的信息;4+1+80,其中4代表先验框中心点的坐标(x,y,w,h),1代表开物体的置信程度,80代表yolov3所训练好的80种类型的分类。通过以上信息,有计算机进行对所检测内容与80种类型物体之间的相似程度,就可以判断出该物体的所属种类,实现物体识别。
速度快!!
OpenCV的DNN模块,其CPU运行是十分快的。比如,当用了OpenMP的Darknet在CPU上处理一张图片消耗2秒,OpenCV的实现只需要0.22秒。
整体代码如下:
import cv2 as cv
import numpy as np
cap=cv.VideoCapture(0)
while True:
ret,frame=cap.read()
cv.imshow("frame",frame)
c=cv.waitKey(1)
if c==27:
break
cap.release()
cv.destroyAllWindows()
coco.name文件中存储着80种已经训练好的识别类型名称,这些类别名称正好与yolo所训练的80种类别一一对应,将他们以列表的形式装在classNames列表中,已便输出时进行调用。
coco.names文件可以在csdn中查找,有其他作者提供的下载链接!!
import cv2 as cv
import numpy as np
cap=cv.VideoCapture(0)
classesFile="coco.names"
classNames=[]
with open (classesFile,"rt") as file:
classNames=file.read().splitlines()
while True:
ret,frame=cap.read()
cv.imshow("frame",frame)
c=cv.waitKey(1)
if c==27:
break
cap.release()
cv.destroyAllWindows()
import cv2 as cv
import numpy as np
cap=cv.VideoCapture(0)
classesFile="coco.names"
classNames=[]
with open (classesFile,"rt") as file:
classNames=file.read().splitlines()
modelConfiguration="yolov3-320.cfg" ###配置文件
modelWeights="yolov3-320.weights" ###配置权重文件
net=cv.dnn.readNetFromDarknet(modelConfiguration,modelWeights) ##将配置文件加入到dnn网络中
net.setPreferableBackend(cv.dnn.DNN_BACKEND_OPENCV) ##将DNN后端设置成opencv
net.setPreferableTarget(cv.dnn.DNN_TARGET_CPU) ##将DNN前端设置成cpu驱动
while True:
ret,frame=cap.read()
blob=cv.dnn.blobFromImage(frame,1/255,(416,416),[0,0,0],True,False) ##DNN网络的输入图像需要采用称为 blob 的特定格式
net.setInput(blob) ##将输出的blob作为传入网络的输入
layerNames=net.getLayerNames() ##获取输入层的名称
outputNames=[layerNames[i-1]for i in net.getUnconnectedOutLayers()] ##获得输入层的最后一层,以此遍历整个网络
outputs=net.forward(outputNames) ##获取输出
findobject(outputs,frame) ##自定义函数功能,找到当前目标类型
cv.imshow("frame",frame)
c=cv.waitKey(1)
if c==27:
break
cap.release()
cv.destroyAllWindows()
这里有如下说明:
1.yolov3-320.weights.权重:预先训练的权重。
2.yolov3-320.cfg:配置文件
以上两个文件下载地址可在YOLO官网darknet模块进行下载。
3.blob:神经网络的输入图像需要采用称为blob的特定格式。从摄像头读取每一帧开始,将通过blobFromImage()函数将其转换为神经网络所能够接受的blob格式。在此过程中,它使用比例因子1/255 将图像像素值缩放到 0 到 1 的目标范围。它还会将图像大小调整为给定的大小 (416,416),无需裁剪。bean值为 [0,0,0] ,并将 swapRB 参数保持为真。然后,输出 blob作为其输入传入网络,并运行正向传递以获取预测边界框的列表作为网络的输出。这些框经过后处理步骤,以过滤掉置信度分数较低的框。以上数据即便不明白来历,也可以在自己的opencv安装目录下进行查找,以下是查找方法:找到你的opencv安装目录,按路径opencv\sources\samples\dnn找到dnn文件夹中的models.yml 文件,里面存放有各种深度学习配置DNN网路的blob值,按顺序输入即可,下图为yolov3所需要配置参数的截图。
4.OpenCV的Net类中的forwa()函数需要结束层,直到它应该在网络中运行。我们想要运行整个网络,因此我们需要识别网络的最后一层。通过使用函数 getUnconnectedOutLayer()l获取到未连接输出层的名称,这些输出层本质上是网络的最后一层,代码中我们使用循环进行遍历,直至遍历到未连接层减1层,即为网络的最后一层。最后,我们运行网络的正向传递,从输出层获取输出。
import cv2 as cv
import numpy as np
def findobject(outputs,frame):
H,W,C=frame.shape ##获取原始帧图像的大小H,W
bbox=[] ##建立一个用来存储先验框的坐标列表
classIds=[] ##建立用来存储每一帧检测到的类别信息名称
confs=[] ##建立每一帧读取的置信度值
for output in outputs: ##由于每一帧图片的内容可能含有多个类别信息,例如图片中既含有人,也含有汽车、小狗等类别,我们需要对每个类别进行检测
for det in output: ##开始检测frame帧中的每个类别
scores=det[5:] ##获取该类别与80项全类别分别的相似概率
classId=np.argmax(scores) ##获得80项中最为相似的类别(相似概率值最大的类别)
confidence=scores[classId] ##获取该最大相似概率的值赋值给confidence
if confidence>0.5: ##若果相似度大于0.5,我们认为检测结果可靠,当然你也可以提高该数值进而提高你的识别精度
w=int(det[2]*W) ##获取先验框的四个坐标点
h=int(det[3]*H)
x=int(det[0]*W-w/2)
y=int(det[1]*H-h/2)
bbox.append((x,y,w,h)) ##将坐标添加到bbox中进行存储,便于对frame帧中所有类别的先验框坐标进行存储,最后输出
classIds.append(classId) ##将frame中每一类别对应的编号(1-80),便于在输出文本时,与对应coconame文件中的类别名称进行输出
confs.append(float(confidence)) ##将frame中每一类别对应的相似概率进行存储
indices=cv.dnn.NMSBoxes(bbox,confs,0.5,0.2) ##对frame中识别出来的每一类信息进行最大抑制由参数nms阈值控制
for i in indices:
box=bbox[i] ##依次读取最大已知参数nms阈值的先验框坐标
x,y,w,h=box[0],box[1],box[2],box[3]
cv.rectangle(frame,(x,y),(x+w,y+h),(0,0,255),2,8) ##对每个最终识别的目标进行矩形框选
cv.putText(frame,f"{classNames[classIds[i]].upper()} {round(confs[i]*100,2)}%",(x,y),cv.FONT_HERSHEY_COMPLEX,0.6,(128,0,255),2,8) ##对应coco.names相应的类别名称和相似概率进行文字输出
cap=cv.VideoCapture(0)
classesFile="coco.names"
classNames=[]
with open (classesFile,"rt") as file:
classNames=file.read().splitlines()
modelConfiguration="yolov3-320.cfg"
modelWeights="yolov3-320.weights"
net=cv.dnn.readNetFromDarknet(modelConfiguration,modelWeights)
net.setPreferableBackend(cv.dnn.DNN_BACKEND_OPENCV)
net.setPreferableTarget(cv.dnn.DNN_TARGET_CPU)
while True:
ret,frame=cap.read()
blob=cv.dnn.blobFromImage(frame,1/255,(416,416),[0,0,0],True,False)
net.setInput(blob)
layerNames=net.getLayerNames()
outputNames=[layerNames[i-1]for i in net.getUnconnectedOutLayers()]
outputs=net.forward(outputNames)
findobject(outputs,frame)
cv.imshow("frame",frame)
c=cv.waitKey(1)
if c==27:
break
cap.release()
cv.destroyAllWindows()
这里有如下说明:
1.我们需要对每一张图片frame中每个计算机认为的物体进行识别,为了提高检测速度,我们需要过滤掉置信程度小于0.5的信息,这只是初步筛选的内容。
2.即便如此,计算机也极有可能对一张图片中的同一个物体进行二次甚至多次的识别,认为这是两个或多个不同的物体,因此我们需要对所识别出来的物体进行非最大抑制由参数nms阈值控制,此数值控制的越低,我们所误检测的相同物体会越少,以提高我们的识别精度。
下图可以很好的帮助我们理解对图片进行非最大阈值控制的重要性:
随笔书写最近学习内容,希望以上内容可以帮到大家,有不懂的地方可以评论留言。
小编也是一个初学者,也有很多疑惑地的地方,如果有理解错误的地方也请大家批评指正,一起学习,一起进步!