【yolov4目标检测】(4) opencv+yolov4-tiny 实现选择性目标检测,附python完整代码

各位同学好,今天和大家分享一下如何使用 opencv 调用 yolov4-tiny 目标检测方法,并对指定类别进行检测。用b站的视频做测试。

点击按钮 'all',按钮变红色,对所有的类别检测

点击按钮 'person',按钮变红色,只对'person'类别检测


1.文件配置

首先,我们需要导入 yolov4-tiny 网络模型结构 cfg 文件,网络权重 weights 文件,以及 COCO 数据集的分类名称的 txt 文件。这里我已经给大家提供好了各种配置文件及代码,有需要的自取。

链接:https://pan.baidu.com/s/19SMNuvCQGvl_V9O0RHsLog 

提取码:zyp7


2. 目标检测

(1)首先使用 cv2.dnn.readNet() 函数构造CSPDarknet53网络结构,传入模型结构cfg文件,以及网络权重weights文件。新版的 opencv(4.1.2以上)针对神经网络模块提供了支持图像分类、检测、分割的几种方法,自动实现输入图像的预处理及后处理。这里使用目标检测模块cv2.dnn_DetectionModel() 传入网络模型。

(2)目标检测方法如下:

classids, scores, bboxes = cv2.dnn_DetectionModel.detect(frame, confThreshold, numsThreshold)

参数:

frame:输入的图像

confThreshold:用来过滤选择框的置信度阈值,目标检测最小置信度

numsThreshold:非极大值抑制中的自定义阈值

返回值:

classIds:类别索引

confidences:置信度,检测框属于某个分类的概率

boxes:检测框信息,左上角坐标(x,y),框的宽高(w, h)

(3)使用 model.setInputParams(size, scale) 设置网络模型的输入参数设置size 表示将输入的图像缩放至指定大小。size越大检测效果越好,但是检测速度越慢。scale 表示像素值的缩放大小。在opencv中每个像素值的范围在0-255之间,而在神经网络中每个像素值在0-1之间,scale=1/255

综上,目标检测代码如下:

# yolov4-tiny目标检测
import cv2

#(1)导入yolov4-tiny网络模型结构
# 传入模型结构.cfg文件,模型权重参数.weight文件
net = cv2.dnn.readNet('dnn_model\yolov4-tiny.cfg', 'dnn_model\yolov4-tiny.weights')

# 定义一个目标检测模型,将模型传进去
model = cv2.dnn_DetectionModel(net)

# 设置模型的输入
model.setInputParams(size=(320,320), scale=1/255)

#(2)获取分类文本的信息
classes = []  # 存放每个分类的名称
with open('dnn_model\classes.txt') as file_obj:
    # 获取文本中的每一行
    for class_name in file_obj.readlines():
        # 删除文本中的换行符、空格等
        class_name = class_name.strip()
        # 将每个分类名保存到列表中
        classes.append(class_name)

#(3)视频捕获
filepath = 'C:\\GameDownload\\Deep Learning\\traffic6.mp4'
cap = cv2.VideoCapture(filepath) 

#(4)对每一帧视频图像处理
while True:
    
    # 返回是否读取成功ret和读取的帧图像frame
    ret, frame = cap.read()

    # 图像比较大把它缩小一点
    frame = cv2.resize(frame, (1280,720))
    
    # 视频比较短,循环播放
    if cap.get(cv2.CAP_PROP_POS_FRAMES) == cap.get(cv2.CAP_PROP_FRAME_COUNT):
        
        # 如果当前帧==总帧数,那就重置当前帧为0
        cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
        
    # 目标检测
    classids, scores, bboxes = model.detect(frame, 0.5, 0.3)

    print('classids:', classids)  # 如:[0]
    print('score:', scores)   # 如:0.9469002
    print('bboxes:', bboxes)  # 如:[159 176 816 533]
    
    #(5)显示检测结果
    # 遍历所有的检测框信息,把它们绘制出来
    for class_id, score, bbox in zip(classids, scores, bboxes):
        
        # 获取检测框的左上角坐标和宽高
        x, y, w, h = bbox
        
        # 获取检测框对应的分类名
        class_name = classes[class_id]
        
        # 绘制矩形框
        cv2.rectangle(frame, (x,y), (x+w,y+h), (255,255,0), 2)
        # 显示分类文本
        cv2.putText(frame, class_name, (x,y+h+20), cv2.FONT_HERSHEY_COMPLEX, 1, (0,255,0), 2)
        # 显示类别概率
        cv2.putText(frame, str(int(score*100))+'%', (x,y-5), cv2.FONT_HERSHEY_COMPLEX, 1, (0,255,255), 2)
    
    #(6)显示图像
    cv2.imshow('Image', frame)  #窗口名,图像变量
    if cv2.waitKey(30) & 0xFF==27:  #每帧滞留30毫秒后消失
        break

# 释放视频资源
cap.release()
cv2.destroyAllWindows()

检测效果如下:

【yolov4目标检测】(4) opencv+yolov4-tiny 实现选择性目标检测,附python完整代码_第1张图片


3. 绘制按钮

在同路径下定义一个绘制矩形按钮的类 drawButton,避免重复写很多代码。调用时只需要传入帧图像frame,和需要检测类别的名称classnames。代码就不详细介绍了,了解过面向对象编程的同学应该都看得懂吧。对面向对象不熟悉的可以看我之前的博客:https://blog.csdn.net/dgvv4/category_11569694.html?spm=1001.2014.3001.5482

# 定义创建按钮的函数
import cv2

# 定义类
class drawButton:
    
    # 定义类属性
    w = 200  # 每个按钮的宽
    h = 60  # 每个按钮的高
    angerLine = 15  # 角点线段长度
    thick = 3  # 角点线段厚度
    
    # 矩形框颜色
    colorBG = (255,255,0)  # 底色
    colorBL = (255,0,255) # 边界
    colorBA = (0,255,255) # 角点边线颜色
    
    # 初始化
    def __init__(self, classnames:list):
        
        # 分配属性
        self.classnames = classnames
        self.len = len(classnames)
    
    # 美化角点
    def drawAnger(self, img, x, y):

        # 给矩形的四个角添加线段
        cv2.line(img, (x,y), (x,y+self.angerLine), self.colorBA, self.thick)
        cv2.line(img, (x,y), (x+self.angerLine,y), self.colorBA, self.thick)
        cv2.line(img, (x+self.w,y), (x+self.w,y+self.angerLine), self.colorBA, self.thick)
        cv2.line(img, (x+self.w,y), (x+self.w-self.angerLine,y), self.colorBA, self.thick)
        cv2.line(img, (x,y+self.h), (x,y+self.h-self.angerLine), self.colorBA, self.thick)
        cv2.line(img, (x,y+self.h), (x+self.angerLine,y+self.h), self.colorBA, self.thick)
        cv2.line(img, (x+self.w,y+self.h), (x+self.w,y+self.h-self.angerLine), self.colorBA, self.thick)
        cv2.line(img, (x+self.w,y+self.h), (x+self.w-self.angerLine,y+self.h), self.colorBA, self.thick)

    # 定义绘图方法
    def drawRec_alone(self, img, x, y, name):
        
        # 透明矩形参数设置
        alphaReserve = 0.5  # 透明度
        BChannel, GChannel, RChannel = self.colorBG  # 设置矩形颜色 
        yMin, yMax = y, y+self.h  # 矩形框的y坐标范围
        xMin, xMax = x, x+self.w  # 矩形框的y坐标范围
        
        # 绘制透明矩形
        img[yMin:yMax, xMin:xMax, 0] = img[yMin:yMax, xMin:xMax, 0] * alphaReserve + BChannel * (1 - alphaReserve)
        img[yMin:yMax, xMin:xMax, 1] = img[yMin:yMax, xMin:xMax, 1] * alphaReserve + GChannel * (1 - alphaReserve)
        img[yMin:yMax, xMin:xMax, 2] = img[yMin:yMax, xMin:xMax, 2] * alphaReserve + RChannel * (1 - alphaReserve)
        
        # 矩形框边界
        cv2.rectangle(img, (x,y), (x+self.w, y+self.h), self.colorBL, 2)
        
        # 美化角点
        self.drawAnger(img, x, y)
        
        # 显示文本
        cv2.putText(img, name, (x+20, y+40), cv2.FONT_HERSHEY_COMPLEX, 1.3, (255,255,255), 3)

    # 绘制多个按钮框,保存每个按钮的左上坐标
    def drawRec_many(self, img):

        self.recList = []  # 存放每个矩形框的左上角坐标        

        # 每个分类绘制一个矩形框
        for i in range(self.len):
            
            # 分类名
            name = self.classnames[i]
            
            # 每个矩形框的左上角坐标
            recx = 10
            recy = 100*i+50
            
            # 保存在列表中
            self.recList.append([[recx, recy],  # 左上角
                                [recx+self.w, recy],  # 右上角
                                [recx+self.w, recy+self.h],  # 右下角
                                [recx, recy+self.h]  # 左下角
                                ])  
            
            # 每一个分类画一个矩形框
            # 10代表x位置,y=110*i+50,矩形框之间纵向间隔50
            self.drawRec_alone(img, recx, recy, name)

然后我们用单张图片测试一下效果:

filepath = 'C:\\GameDownload\\Deep Learning\\TF2.jpg'
img = cv2.imread(filepath)
# 重塑图像大小
img = cv2.resize(img, (1280,720))
# 实例化,传入需要绘制的按钮的名称
draw = drawButton(['person', 'bus', 'car', 'mot', 'tree'])
# 调用类方法,绘制多个矩形按钮
draw.drawRec_many(img)
# 显示每个矩形按钮的四个角的坐标
print(draw.recList)
# 显示图像
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

【yolov4目标检测】(4) opencv+yolov4-tiny 实现选择性目标检测,附python完整代码_第2张图片


4. 构建可选类别的目标检测框架

首先我们看到下面代码中的第(4)步

这里用到鼠标响应 cv2.setMouseCallback() 可以在图像上用鼠标操作,触发一些事件

鼠标响应: setMousecallback(winname, onMouse, userdata=0)

参数:

winname: 窗口的名字

onMouse: 鼠标响应函数,回调函数。指定窗口里每次鼠标时间发生的时候,被调用的函数指针。

userdate:传给回调函数的参数 

回调函数: onMouse(int event, x, y,  flags, param)

event: 是 CV_EVENT_* 变量之一

x和y: 鼠标指针在图像坐标系的坐标(像素坐标)

flags: 是CV_EVENT_FLAG的组合

param: 是用户定义的传递到setMouseCallback函数调用的参数

常用事件event变量:

cv2_EVENT_MOUSEMOVE 0    # 滑动 
cv2_EVENT_LBUTTONDOWN 1  # 左键点击 
cv2_EVENT_RBUTTONDOWN 2  # 右键点击 
cv2_EVENT_MBUTTONDOWN 3  # 中间点击 
cv2_EVENT_LBUTTONUP 4    # 左键释放 
cv2_EVENT_RBUTTONUP 5    # 右键释放 
cv2_EVENT_MBUTTONUP 6    # 中间释放 
cv2_EVENT_LBUTTONDBLCLK 7   # 左键双击 
cv2_EVENT_RBUTTONDBLCLK 8   # 右键双击 
cv2_EVENT_MBUTTONDBLCLK 9   # 中间释放
cv2_EVENT_FLAG_LBUTTON 1    # 左键拖拽  
cv2_EVENT_FLAG_RBUTTON 2    # 右键拖拽  
cv2_EVENT_FLAG_MBUTTON 4    # 中间拖拽 
。。。。。。。。。。。。。。。。。。。

当在视频上点击鼠标左键时,判断鼠标所在位置(x, y)是否在矩形按钮内部,其中 buttonList 记录的是每个矩形按钮的四个角坐标 [左上角,右上角,右下角,左下角]。使用 cv2.pointPolygonTest(contour, pt, measureDist) 函数判断一个点 pt 是否在多边形轮廓 counter 内部。如果设置 measureDist=True返回值点 pt 到多边形轮廓 counter 的最小距离。如果设置 measureDist=False判断点 pt 是否在多边形轮廓内部返回值是:-1 代表在外部,0 代表在轮廓上,1 代表在内部。

如果鼠标点击位置在某个按钮内部记下这个按钮的索引 button_index = index,表示开始检测这个按钮对应的类别。

看到下面代码中的第(7)步。绘制检测框前,依次遍历检测返回的视频中的所有分类结果 class_name指定绘制的按键名称是 usenames如果所有分类中有分类在 usenames 中,并且鼠标点击的按钮的索引也等于usenames中某个类别的索引class_name == name and index == button_index,那么就绘制这个检测框。

# yolov4-tiny目标检测
import cv2
import numpy as np
from myFunction import drawButton

#(1)导入yolov4-tiny网络模型结构
# 传入模型结构.cfg文件,模型权重参数.weight文件
net = cv2.dnn.readNet('dnn_model\yolov4-tiny.cfg', 'dnn_model\yolov4-tiny.weights')

# 定义一个目标检测模型,将模型传进去
model = cv2.dnn_DetectionModel(net)

# 设置模型的输入
model.setInputParams(size=(416, 416), scale=1/255)

#(2)获取分类文本的信息
classes = []  # 存放每个分类的名称
with open('dnn_model\classes.txt') as file_obj:
    # 获取文本中的每一行
    for class_name in file_obj.readlines():
        # 删除文本中的换行符、空格等
        class_name = class_name.strip()
        # 将每个分类名保存到列表中
        classes.append(class_name)

#(3)视频捕获
filepath = 'C:\\GameDownload\\Deep Learning\\trafficvideo3.mp4'
cap = cv2.VideoCapture(filepath)  

#(4)创建鼠事件
button_index = None  # 存放哪个按键被点亮了

# 定义鼠标回调函数
def click_button(event, x, y, flags, params):
    
    # 调用外部变量
    global button_index
    
    # 设置事鼠标件event为点击鼠标左键
    if event == cv2.EVENT_LBUTTONDOWN:
        
        # 检查鼠标的坐标是否在矩形框按键内部,index代表第几个按钮
        # 遍历每个矩形框,每个框包含四个角的坐标
        for index, pt in enumerate(np.array(buttonList)):  # 要转换成numpy类型     
        
            # 如果设为True,计算鼠标左键距离矩形框的距离
            is_inside = cv2.pointPolygonTest(pt, (x,y), False)
            
            if is_inside > 0:  # 鼠标在矩形框内部
                
                print(f'click in the No.{index+1}', (x,y))
                
                # 激活哪个分类的检测框
                button_index = index

#(5)创建窗口
cv2.namedWindow('Image')  # 窗口名和显示图像的窗口名相同

# 设置鼠标回调,窗口名和上面相同,自定义回调函数
cv2.setMouseCallback('Image', click_button)

# 创建按钮
usenames = ['all', 'person', 'car', 'bus', 'truck']
button = drawButton(usenames)

#(6)定义检测框绘制函数
colorline = (0,255,0)  # 角点线段颜色
angerline = 13  # 角点线段长度

def drawbbx(img, x, y, w, h, predName, score):
    
    # 检测框
    cv2.rectangle(img, (x, y), (x+w, y+h), (255,255,0), 1)
    # 角点美化
    cv2.line(img, (x,y), (x+angerline,y), colorline, 2)
    cv2.line(img, (x,y), (x,y+angerline), colorline, 2)    
    cv2.line(img, (x+w,y), (x+w,y+angerline), colorline, 2)
    cv2.line(img, (x+w,y), (x+w-angerline,y), colorline, 2)
    cv2.line(img, (x,y+h), (x,y+h-angerline), colorline, 2)
    cv2.line(img, (x,y+h), (x+angerline,y+h), colorline, 2)
    cv2.line(img, (x+w,y+h), (x+w,y+h-angerline), colorline, 2)    
    cv2.line(img, (x+w,y+h), (x+w-angerline,y+h), colorline, 2)
    
    # 显示预测的类别
    cv2.putText(img, predName, (x,y+h+20), cv2.FONT_HERSHEY_COMPLEX, 1, (0,255,0), 2)
    
    # 显示预测概率
    cv2.putText(img, str(int(score*100))+'%', (x,y-5), cv2.FONT_HERSHEY_COMPLEX, 1, (0,255,255), 2)

#(6)对每一帧视频图像处理
while True:
    
    # 返回是否读取成功ret和读取的帧图像frame
    ret, frame = cap.read()
    
    # 图像比较大把它缩小一点
    frame = cv2.resize(frame, (1280,720))
    
    # 视频比较短,循环播放
    if cap.get(cv2.CAP_PROP_POS_FRAMES) == cap.get(cv2.CAP_PROP_FRAME_COUNT):
        
        # 如果当前帧==总帧数,那就重置当前帧为0
        cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
    
    #(7)目标检测
    classids, scores, bboxes = model.detect(frame, 0.5, 0.3)
    
    # 在画面上创建按钮
    button.drawRec_many(frame)
    
    # 获取所有矩形框的四个角的坐标
    buttonList = button.recList  
    
    
    #(8)显示检测结果
    # 遍历所有的检测框信息,把它们绘制出来
    for class_id, score, bbox in zip(classids, scores, bboxes):
        
        # 获取检测框的左上角坐标和宽高
        x, y, w, h = bbox
        
        # 获取检测框对应的分类名
        class_name = classes[class_id]
        
        
        # 遍历四个按键的名称
        for index, name in enumerate(usenames):
        
            # 设置检测条件,只有检测到的类别是person并且鼠标点击位置在矩形框内
            if class_name == name and index == button_index:
            
                # 绘制class_name类别的检测框
                drawbbx(frame, x, y, w, h, class_name, score)                

                # 改变按钮颜色
                cv2.rectangle(frame, buttonList[index][0], buttonList[index][2], (0,0,255), 5)
                
            # 点击按钮'all'
            elif name == 'all' and index == button_index:
                
                # 绘制所有类别的检测框
                drawbbx(frame, x, y, w, h, class_name, score)
                
                # 改变按钮颜色
                cv2.rectangle(frame, buttonList[index][0], buttonList[index][2], (0,0,255), 5)
                
    #(9)显示图像
    cv2.imshow('Image', frame)  #窗口名,图像变量
    if cv2.waitKey(30) & 0xFF==27:  #每帧滞留1毫秒后消失
        break

# 释放视频资源
cap.release()
cv2.destroyAllWindows()

检测目标设置为'car'的效果图如下:

你可能感兴趣的:(yolo目标检测,目标检测,opencv,python,yolo,机器视觉)