YOLO学习笔记——第四篇 YOLOv3实现物体检测

基于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. 准备工作

也可通过百度网盘下载:
链接:https://pan.baidu.com/s/1sObWQwzLT8iVVVFEy_th2Q?pwd=nvfn
提取码:nvfn

在一步步操作实现之余,如果有时间,请务必从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重新训练,找到问题所在。

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值