基于YOLOv3(包含spp版本)的物体检测
文章目录
1. 前言
本文不涉及原理知识,需要了解yolo系列的具体原理请转:
YOLO学习笔记——第一篇YOLOv1_isolatewind的博客-CSDN博客
YOLO学习笔记——第二篇YOLOv2_isolatewind的博客-CSDN博客
YOLO学习笔记——第三篇YOLOv3(含FPN网络解析)_isolatewind的博客-CSDN博客
以上三篇笔记是本人花大量时间阅读相关论文和资料整理而成,相信读者在阅读后能够较好地掌握yolo系列的精髓。
本文不涉及具体的训练方式、过程,只有实现过程,采用预先基于COCO数据集训练好的yolov3 网络(cfg文件)和网络权重(weights文件) 直接实现目标检测。主要使用python+opencv实现。
参考:【精读AI论文】YOLO V3目标检测(附YOLOV3代码复现)_哔哩哔哩_bilibili
2. 准备工作
- 本文相关代码、文件等都已经放在个人gitee中,自取isolatewind/yolo_v3 - 码云 - 开源中国 (gitee.com)
也可通过百度网盘下载:
链接:https://pan.baidu.com/s/1sObWQwzLT8iVVVFEy_th2Q?pwd=nvfn
提取码:nvfn
-
yolov3网络的weights由于过大无法上传,请自行于yolov3_weights下载
-
yolov3_spp网络的weights 请自行于yolov3_spp_weights下载
-
请自行安装opencv,并配置使用jupyter nootbook(也可以直接复制其中的代码放入.py文件中运行)
-
训练所用图片可以自己选择,也可也使用我的isolatewind/yolo_v3 - 码云 - 开源中国 (gitee.com)
在一步步操作实现之余,如果有时间,请务必从yolov1的基础原理开始学习,相关链接见上文。
3. 代码讲解(针对单张图片)
3.1 导入必要库函数
import cv2 # 导入opencv
import numpy as np
import matplotlib.pyplot as plt
# 魔法方法,使图片内嵌于jupyter notebook
%matplotlib inline
3.2 定义函数实现图片可视化
# 定义函数实现图片可视化
def look_img(img):
img_RGB = cv2.cvtColor(img,cv2.COLOR_BGR2RGB) # imread读入的图片是bgr形式,需要转换为rgb
plt.imshow(img_RGB)
plt.show()
3.3 导入预先训练好的YOLOv3模型和权重
# weights = "yolov3-spp.weights"
# config_file = "yolov3-spp.cfg"
weights = "yolov3.weights" # 权重
config_file = "yolov3.cfg" # 网络名
net = cv2.dnn.readNet(weights,config_file)
3.4 查看网络相关信息
net.getLayerNames() #输出网络各层的名称
# 获取某一层的权重
net.getParam('conv_14').shape
# 获得三个输出层的索引号(yolov3使用三种尺度的特征图进行预测,详情见前言中附的文章)
net.getUnconnectedOutLayers()
layersNames = net.getLayerNames()
output_layers_names = [layersNames[i[0]-1]for i in net.getUnconnectedOutLayers()]
output_layers_names # 显示输出层的名称
[‘yolo_82’, ‘yolo_94’, ‘yolo_106’]
3.5 读入coco数据集的标签
with open('coco.names','r')as f:
classes = f.read().splitlines()
3.6 读入测试图片
img = cv2.imread('images/test3.jpg') # 文件名自己更改
look_img(img) # 展示
print(img.shape)
height,width,_ = img.shape # 获取图片高、宽
(注:图源网络)
3.7 将图片预处理(resize)
blob = cv2.dnn.blobFromImage(img,1/255,(416,416),(0,0,0),swapRB=True,crop=False) # 该预先训练好的模型输入是416*416,所以需要将原图resize成416*416
print(blob.shape)
(1, 3, 416, 416)
3.8 前向推断
net.setInput(blob)
prediction = net.forward(output_layers_names)
# 打印初步预测信息
for i in range(len(prediction)):
print(prediction[i].shape)
结果输出为:
(507, 85)
(2028, 85)
(8112, 85)三种输出尺寸:
13 × 13 × 3 = 507 13 \times 13 \times 3 = 507 13×13×3=507
26 × 26 × 3 = 2028 26 \times 26 \times 3 = 2028 26×26×3=2028
52 × 52 × 3 = 8112 52 \times 52 \times 3 = 8112 52×52×3=8112
85 = 4(x,y,w,h) + 1(obj) + 80(coco分类种类)
3.9 从三个尺度输出结果中解析所有预测框的信息
# 存放预测框的坐标
boxes = []
# 存放(有无物体)置信度
objectess = []
# 存放类别概率
class_probs = []
# 存放预测框类别的索引号
class_ids = []
# 存放预测框类别名称
class_names = []
for scale in prediction: # 遍历三种尺度
for bbox in scale: # 遍历该种尺度每个预测框
obj = bbox[4] # 该预测框的置信度confidence
class_scores = bbox[5:] # 80种类别的预测概率
class_id = np.argmax(class_scores) # 取其中的最大值为该bbox的预测类别
class_name = classes[class_id] # 该bbox预测的类别名字
class_prob = class_scores[class_id] # 该bbox预测的类别概率
# 获取预测框中心点坐标、预测框宽高
center_x = int(bbox[0]*width)
center_y = int(bbox[1]*height)
w = int(bbox[2]*width)
h = int(bbox[3]*height)
# 预测框左上角坐标
x = int(center_x - w/2)
y = int(center_y - h/2)
boxes.append([x,y,w,h])
objectess.append(float(obj))
class_ids.append(class_id)
class_names.append(class_name)
class_probs.append(class_prob)
confidences = np.array(class_probs)*np.array(objectess) # obj*class_scoreen
# 检验预测框信息是否正确
print(len(boxes))
print(len(objectess))
print(len(confidences))
输出都为10647=(13*13+26*26+52*52)*3
因为一共生成三种尺度的特征图,每种尺度又有三种预测框
plt.plot(objectess, label = 'objectness')
plt.plot(class_probs, label='class_prob')
plt.plot(confidences,label='confidence')
plt.legend()
plt.show()
可见objectness和class_prob分布基本重合
3.10 置信度阈值过来+NMS极大值抑制
# 设定阈值
CONF_THRES = 0.1
NMS_THRES = 0.4 # 越小框越少
indexes = cv2.dnn.NMSBoxes(boxes,confidences,CONF_THRES,NMS_THRES)
# 最终留下的预测框的索引号
indexes.flatten()
len(indexes.flatten()) # 剩下的框的数量
3.11 画框
# 随机给每一个预测框生成一种颜色
colors = np.random.uniform(0,255,size=(len(boxes),3))
画框的最终效果通过
cv2.rectangle(img,(x,y),(w+x,h+y),color,8)
:图片,左上坐标,右下坐标,颜色,框的粗细
cv2.putText(img,string,(x,y+20),cv2.FONT_HERSHEY_SIMPLEX,3,(255,255,255),3)
: 图片,文字,左上坐标,字体,字体大小,颜色,字体粗细
# 遍历留下的每一个预测框,可视化
for i in indexes.flatten():
# 获取坐标与置信度
x, y, w, h = boxes[i]
confidence = str(round(confidences[i],2))
# 获取颜色,画框
color = colors[i % len(colors)]
cv2.rectangle(img,(x,y),(w+x,h+y),color,8) # 图片,左上坐标,右下坐标,颜色,框的粗细
# 写上类别名称与置信度
string = '{} {}'.format(class_names[i],confidence)
# 图片,文字,左上坐标,字体,字体大小,颜色,字体粗细
cv2.putText(img,string,(x,y+20),cv2.FONT_HERSHEY_SIMPLEX,3,(255,255,255),3)
print(x,y,w,h,confidence,class_names[i])
look_img(img) #展示最终结果
3.12 保存结果
cv2.imwrite('images/result3.jpg',img)
# cv2.imwrite('images/result_spp.jpg',img)
输出:True
4.代码讲解(针对摄像头视频流)
本节代码细节大体同上
4.1 导入相关文件
import cv2
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
def look_img(img):
img_RGB = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
plt.imshow(img_RGB)
plt.show()
weights = "yolov3.weights"
config_file = "yolov3.cfg"
net = cv2.dnn.readNet(weights, config_file)
layersNames = net.getLayerNames()
output_layers_names = [layersNames[i[0]-1] for i in net.getUnconnectedOutLayers()]
with open('coco.names','r')as f:
classes = f.read().splitlines()
CONF_THRES = 0.2
NMS_THRES = 0.4
4.2 单帧图片处理
def process_frame(img):
height,width,_ = img.shape
blob = cv2.dnn.blobFromImage(img, 1/255, (416,416),(0,0,0),swapRB=True,crop=False)
net.setInput(blob)
# 前向推断
prediction = net.forward(output_layers_names)
# 存放预测框的坐标
boxes = []
# 存放(有无物体)置信度
objectess = []
# 存放类别概率
class_probs = []
# 存放预测框类别的索引号
class_ids = []
# 存放预测框类别名称
class_names = []
# 遍历三种尺度
for scale in prediction:
for bbox in scale:
obj = bbox[4]
class_scores = bbox[5:]
# 以下代码为改进代码,加入后运行速度能有一定提升,具体分析看下面的文章
# temp = np.max(class_scores)
# temp1 = obj * temp
# if(temp1<CONF_THRES):
# continue
class_id = np.argmax(class_scores)
class_name = classes[class_id]
class_prob = class_scores[class_id]
# 获取预测框中心点坐标、预测框宽高
center_x = int(bbox[0]*width)
center_y = int(bbox[1]*height)
w = int(bbox[2]*width)
h = int(bbox[3]*height)
# 预测框左上角坐标
x = int(center_x - w/2)
y = int(center_y - h/2)
boxes.append([x,y,w,h])
objectess.append(float(obj))
class_ids.append(class_id)
class_names.append(class_name)
class_probs.append(class_prob)
confidences = np.array(class_probs)*np.array(objectess) # obj*class_scoreen
indexes = cv2.dnn.NMSBoxes(boxes,confidences,CONF_THRES,NMS_THRES) #阈值和极大值抑制
indexes.flatten()
colors = np.random.uniform(0,255,size=(len(boxes),3)) # 随机给每一个预测框生成一种颜色
# 遍历留下的每一个预测框,可视化
for i in indexes.flatten():
# 获取坐标与置信度
x, y, w, h = boxes[i]
confidence = str(round(confidences[i],2))
# 获取颜色,画框
color = colors[i % len(colors)]
# color = [255, 0, 255]
cv2.rectangle(img,(x,y),(w+x,h+y),color,4)
# 写上类别名称与置信度
string = '{} {}'.format(class_names[i],confidence)
# 图片,文字,左上坐标,字体,字体大小,颜色,字体粗细
cv2.putText(img,string,(x,y+20),cv2.FONT_HERSHEY_SIMPLEX,1,(255,255,255),1)
# look_img(img)
return img
# 测试函数是否编写正确
img = cv2.imread('images/test4.jpg')
process_frame(img)
4.3 开启摄像头
import cv2
def video_demo():
capture = cv2.VideoCapture(0)#0为电脑内置摄像头
while(True):
success, frame = capture.read()#摄像头读取,ret为是否成功打开摄像头,true,false。 frame为视频的每一帧图像
if not success:
print('ERROR')
break
frame = cv2.flip(frame, 1)#摄像头是和人对立的,将图像左右调换回来正常显示。
frame = process_frame(frame)
cv2.imshow("video", frame)
c = cv2.waitKey(50)
if c == 27: # 按下esc
capture.release()
break
video_demo()
cv2.destroyAllWindows()
(注:图源我自己qwq)
4.4 测试发现摄像头实时帧数很低,开始查找问题
先检查系统自带摄像头好坏
# 运行系统摄像头,检验好坏
import cv2
def video_demo():
capture = cv2.VideoCapture(0)#0为电脑内置摄像头
while(True):
success, frame = capture.read()#摄像头读取,ret为是否成功打开摄像头,true,false。 frame为视频的每一帧图像
if not success:
print('ERROR')
break
frame = cv2.flip(frame, 1)#摄像头是和人对立的,将图像左右调换回来正常显示。
cv2.imshow("video", frame)
c = cv2.waitKey(50)
if c == 27: # 当按下esc
capture.release()
break
video_demo()
cv2.destroyAllWindows()
再查看代码运行速度
import time
def process_frame1(img):
start = time.time()
height,width,_ = img.shape
blob = cv2.dnn.blobFromImage(img, 1/255, (416,416),(0,0,0),swapRB=True,crop=False)
end0 = time.time()
net.setInput(blob)
# 前向推断
prediction = net.forward(output_layers_names)
end1 = time.time()
# 存放预测框的坐标
boxes = []
# 存放(有无物体)置信度
objectess = []
# 存放类别概率
class_probs = []
# 存放预测框类别的索引号
class_ids = []
# 存放预测框类别名称
class_names = []
# 遍历三种尺度
for scale in prediction:
for bbox in scale:
obj = bbox[4]
class_scores = bbox[5:]
class_id = np.argmax(class_scores)
# 以下代码为改进代码,加入后运行速度能有一定提升,具体分析看下面的文章
# temp = np.max(class_scores)
# temp1 = obj * temp
# if(temp1<CONF_THRES):
# continue
class_name = classes[class_id]
class_prob = class_scores[class_id]
# 获取预测框中心点坐标、预测框宽高
center_x = int(bbox[0]*width)
center_y = int(bbox[1]*height)
w = int(bbox[2]*width)
h = int(bbox[3]*height)
# 预测框左上角坐标
x = int(center_x - w/2)
y = int(center_y - h/2)
boxes.append([x,y,w,h])
objectess.append(float(obj))
class_ids.append(class_id)
class_names.append(class_name)
class_probs.append(class_prob)
end2 = time.time()
confidences = np.array(class_probs)*np.array(objectess) # obj*class_scoreen
indexes = cv2.dnn.NMSBoxes(boxes,confidences,CONF_THRES,NMS_THRES) #阈值和极大值抑制
indexes.flatten()
end3 = time.time()
colors = np.random.uniform(0,255,size=(len(boxes),3)) # 随机给每一个预测框生成一种颜色
# 遍历留下的每一个预测框,可视化
for i in indexes.flatten():
# 获取坐标与置信度
x, y, w, h = boxes[i]
confidence = str(round(confidences[i],2))
# 获取颜色,画框
color = colors[i % len(colors)]
# color = [255, 0, 255]
cv2.rectangle(img,(x,y),(w+x,h+y),color,4)
# 写上类别名称与置信度
string = '{} {}'.format(class_names[i],confidence)
# 图片,文字,左上坐标,字体,字体大小,颜色,字体粗细
cv2.putText(img,string,(x,y+20),cv2.FONT_HERSHEY_SIMPLEX,1,(255,255,255),1)
# look_img(img)
end4 = time.time()
print(end0-start) # 图片预处理
print(end1-start) # 前向推断
print(end2-start) # 预测框遍历
print(end3-start) # 阈值和NMS处理
print(end4-start) # 绘制边框
img = cv2.imread('images/test4.jpg')
process_frame1(img)
改进前:
0.0020351409912109375
0.432842493057251
0.6452746391296387
0.6472692489624023
0.6482670307159424
需要针对代码进行优化,经过调试发现,耗时主要是在前向推断和遍历所有预测框花费
由于前向推断无法优化(只能重新训练网络),所以进行遍历所有预测框阶段的优化。
在遍历三种尺度时提前进行阈值处理,去除掉大量背景框信息,一定程度上加快了运行速度,但前向推断速度无法改进,所以帧数还是很低。
# 遍历三种尺度
for scale in prediction:
for bbox in scale:
obj = bbox[4]
class_scores = bbox[5:]
temp = np.max(class_scores)
temp1 = obj * temp
if(temp1<CONF_THRES):
continue
class_id = np.argmax(class_scores)
class_name = classes[class_id]
class_prob = class_scores[class_id]
改进后:
0.0029900074005126953
0.41588449478149414
0.535564661026001
0.5365622043609619
0.5365622043609619
可见提升了0.1s的运行速度,但本文不涉及网络结构的调整,所以无法更加优化,后续将会出从0开始手动搭建yolov3的网络,对yolov3重新训练,找到问题所在。