#利用yolov3的模型结构和权重参数实现对物体的实时检测,正确率挺高的,其主要原理是利用神经网络去将我们的图像不断的进行处理,最后利用图像处理中的金字塔思想,做了3次采样变化,得到不同的特征图,通过用3种不同的方式进行预测,判断处最合适的预测,并将结果返回。有兴趣的可以去看看相关的论文。
1.代码运行后展示不同物体的结果(使用手机的图片)



总的来说,检测的效果不错,但在我运行过程中,出现了实时视频的卡顿,不流畅的问题,可能是因为我使用的是CPU,处理的速度跟不上,有兴趣的可以用GPU试一下。官方网站显示的有相关的fps标准,可能是设备问题,所以帧率没有达到45fps.关于权重文件和模型配置,可以去官网下载,网址:
https://pjreddie.com/darknet/yolo/
你使用对应的yolov3d的model就要下载对应的权重文件和模型配置,不一样的模型内部的神经网络层也不太一样,以yolov3-320,对应的输出特征图像是3个,而yolov3-tiny对应的输出特征图像是2个。当然精度和速度不能权衡,两个之间一个好,另一个就必然下降。
如图:

我自己尝试了320和tiny的模型,有兴趣的可以尝试不一样的model。
当下载好对应的权重参数和模型配置后,还要下载coco.name数据集,里面有80个常见的物体种类的名字,模型会根据预测返回对每个种类的概率。
coco.name数据集我已经上传到github上:
https://github.com/Drift-Of-Little-Forest/opencv-practice.git
下面就是代码的复现了:
通过以下代码,实现对数据集的读取,并以列表形式展示出来,方便我们进行索引
- ## 导入coco数据集,里面有各种80多种类别
- classesFile = "opencv_data_ku/coco.names"
- classNames = []
- with open(classesFile, 'rt') as f:
- classNames = f.read().rstrip('\n').split('\n')
- print(classNames)
然后就是导入神经网络模型:
- "===============引入我们的yolo3模块=========="
- ## Model Files
- #可以去yolo官网自己搜下面相关的文件参数
- modelConfiguration = "yolov3-320.cfg"
- modelWeights = "opencv_data_ku/yolov3.weights"
- #调用函数进行模型的配置和权重的配置
- net = cv.dnn.readNetFromDarknet(modelConfiguration, modelWeights)
- #要求网络使用其支持的特定计算后
- net.setPreferableBackend(cv.dnn.DNN_BACKEND_OPENCV)
- #使用CPU进行计算
- net.setPreferableTarget(cv.dnn.DNN_TARGET_CPU)
紧接着就是调动相机,进行获取实时图像:
- cap = cv.VideoCapture(0)
- while True:
- success, img = cap.read()
- img = cv2.flip(img, 1)
- cv.imshow('Image', img)
- cv.waitKey(1)
上面的代码中:
img = cv2.flip(img, 1)
是让图像实现镜像,左右跟我们实时的一致。
然后就是将实时获取的图像传输就我们的模型当中:
- #神经网络的输入图像需要采用称为blob的特定格式。用需要检测的原始图像image构造一个blob图像,
- #对原图像进行像素归一化1 / 255.0,缩放尺寸
- # (320, 320),交换了R与B通道
- blob = cv.dnn.blobFromImage(img, 1 / 255, (320, 320), 1, crop=False)
- #将blob变成网络的输入
- net.setInput(blob)
- #获取神经网络所有层的名称
- layersNames = net.getLayerNames()
- print('所有层:',layersNames)
- #我们不需要改变其他层,只需要找到未连接层,也就是网络的最后一层,在最后一层的基础上进行前向传播就可以了
- print('三个输出层的索引号',net.getUnconnectedOutLayers())
- for i in net.getUnconnectedOutLayers():
- outputNames = [layersNames[i - 1]]
- print('输出层名字',outputNames)
- #前向传播
- outputs = net.forward(outputNames)
- print(outputs)
- #了解每个输出层的形状
- print(outputs[0].shape)
通过上面的操作,我们已经可以获得3个输出层的内容,例如(300,85),(1200,85),(4800,85),就是对不同特征图像的每一个特征像素的判断,85列中的内容大概如下:

可以清楚看到,前5项,分别是(x,y,w,h,conf),也就是预测框的中心点和矩形的长宽,后面一个是置信度。后面的是对种类的预测概率,我们可以通过对列表的索引,切片等操作,把这些信息给提取出来,有了这些我们就可以用opencv 中的
cv.rectangle,cv.putText
在图像中画出对应的框和标注对应的内容。需要注意的是opencv中画矩形框,是需要定义框的起始位置和窗高的,所以我们要进行转换:(前5项,分别是(x,y,w,h,conf),也就是预测框的中心点和矩形的长宽,后面一个是置信度,这是在一个像素点上的,如果是放在整张图像上呢?,肯定要乘以图像的宽和高,可以看我画的示意图:)

具体代码操作:
- w, h = int(det[2] * wT), int(det[3] * hT)
- x, y = int((det[0] * wT) - w / 2), int((det[1] * hT) - h / 2)
这样就可以画出我们对预测对象的框了,但是我们数据是300行,1200行,4800行,所以我们要进行遍历.........具体代码如下:
- def findObjects(outputs, img):
- hT, wT, cT = img.shape#输出照片的宽高通道数
- bbox = []
- classIds = []
- confs = []
- #对检测处的结果进行对比处理
- for output in outputs:
- for det in output:
- #可以看一下输出的内容
- scores = det[5:]#这里的意思是输出是某个种类的概率,前五个是框的位置以及置信度
- classId = np.argmax(scores)#找到是最大的种类编号
- confidence = scores[classId]#找到置信度
- if confidence > 0.5:#设立阈值
- w, h = int(det[2] * wT), int(det[3] * hT)
- x, y = int((det[0] * wT) - w / 2), int((det[1] * hT) - h / 2)
- #更新新的框
- bbox.append([x, y, w, h])
- #将索引加入到列表里面去
- classIds.append(classId)
- #置信度加入到创建的列表当中去
- confs.append(float(confidence))
- #对于这个函数,可以在目标检测中筛选置信度低于阈值的,还进行Nms筛选,
- # 至于参数,第一个参数是输入的预测框的尺寸,注意这里得尺寸是预测框左上角和右下角的尺寸,类似yolov3这种最后预测的坐标是中心点和长宽
- #第二个参数是预测中的的置信度得分
- #其实这个函数做了两件事情,对预测框和置信度设定阈值,进行筛选
- indices = cv.dnn.NMSBoxes(bbox, confs, 0.5, 0.6)
- #将框画出来
- for i in indices:
- i = i
- box = bbox[i]
- x, y, w, h = box[0], box[1], box[2], box[3]
- # print(x,y,w,h)
- cv.rectangle(img, (x, y), (x + w, y + h), (255, 0, 255), 2)
- cv.putText(img, f'{classNames[classIds[i]].upper()} {int(confs[i] * 100)}%',(x, y - 10), cv.FONT_HERSHEY_SIMPLEX, 0.6, (255, 0, 255), 2)
将实现这一系列操作后,就可以完成整个物体检测的工作了,其中yolov3的模型的工作原理是:


完整代码如下:(每一步都有注释方便大家更好的理解代码的含义)
- import numpy as np
- import cv2 as cv
- cap = cv.VideoCapture(0)
-
-
- ## 导入coco数据集,里面有各种80多种类别
- classesFile = "opencv_data_ku/coco.names"
- classNames = []
- with open(classesFile, 'rt') as f:
- classNames = f.read().rstrip('\n').split('\n')
- print(classNames)
-
- "===============引入我们的yolo3模块=========="
- ## Model Files
- #可以去yolo官网自己搜下面相关的文件参数
- modelConfiguration = "yolov3-320.cfg"
- modelWeights = "opencv_data_ku/yolov3.weights"
- #调用函数进行模型的配置和权重的配置
- net = cv.dnn.readNetFromDarknet(modelConfiguration, modelWeights)
- #要求网络使用其支持的特定计算后
- net.setPreferableBackend(cv.dnn.DNN_BACKEND_OPENCV)
- #使用CPU进行计算
- net.setPreferableTarget(cv.dnn.DNN_TARGET_CPU)
-
-
- def findObjects(outputs, img):
- hT, wT, cT = img.shape#输出照片的宽高通道数
- bbox = []
- classIds = []
- confs = []
- #对检测处的结果进行对比处理
- for output in outputs:
- for det in output:
- #可以看一下输出的内容
- scores = det[5:]#这里的意思是输出是某个种类的概率,前五个是框的位置以及置信度
- classId = np.argmax(scores)#找到是最大的种类编号
- confidence = scores[classId]#找到置信度
- if confidence > 0.5:#设立阈值
- w, h = int(det[2] * wT), int(det[3] * hT)
- x, y = int((det[0] * wT) - w / 2), int((det[1] * hT) - h / 2)
- #更新新的框
- bbox.append([x, y, w, h])
- #将索引加入到列表里面去
- classIds.append(classId)
- #置信度加入到创建的列表当中去
- confs.append(float(confidence))
- #对于这个函数,可以在目标检测中筛选置信度低于阈值的,还进行Nms筛选,
- # 至于参数,第一个参数是输入的预测框的尺寸,注意这里得尺寸是预测框左上角和右下角的尺寸,类似yolov3这种最后预测的坐标是中心点和长宽
- #第二个参数是预测中的的置信度得分
- #其实这个函数做了两件事情,对预测框和置信度设定阈值,进行筛选
- indices = cv.dnn.NMSBoxes(bbox, confs, 0.5, 0.6)
- #将框画出来
- for i in indices:
- i = i
- box = bbox[i]
- x, y, w, h = box[0], box[1], box[2], box[3]
- # print(x,y,w,h)
- cv.rectangle(img, (x, y), (x + w, y + h), (255, 0, 255), 2)
- cv.putText(img, f'{classNames[classIds[i]].upper()} {int(confs[i] * 100)}%',(x, y - 10), cv.FONT_HERSHEY_SIMPLEX, 0.6, (255, 0, 255), 2)
-
-
- while True:
- success, img = cap.read()
- #神经网络的输入图像需要采用称为blob的特定格式。用需要检测的原始图像image构造一个blob图像,
- #对原图像进行像素归一化1 / 255.0,缩放尺寸
- # (320, 320),交换了R与B通道
- blob = cv.dnn.blobFromImage(img, 1 / 255, (320, 320), 1, crop=False)
- #将blob变成网络的输入
- net.setInput(blob)
- #获取神经网络所有层的名称
- layersNames = net.getLayerNames()
- print('所有层:',layersNames)
- #我们不需要改变其他层,只需要找到未连接层,也就是网络的最后一层,在最后一层的基础上进行前向传播就可以了
- print('三个输出层的索引号',net.getUnconnectedOutLayers())
- for i in net.getUnconnectedOutLayers():
- outputNames = [layersNames[i - 1]]
- print('输出层名字',outputNames)
- #前向传播
- outputs = net.forward(outputNames)
- print(outputs)
- #了解每个输出层的形状
- print(outputs[0].shape)
- #调用框函数寻找对象的框
- findObjects(outputs, img)
-
- cv.imshow('Image', img)
- cv.waitKey(1)