#利用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)
|