各位同学好,今天和大家分享一下如何使用 opencv 调用 yolov4-tiny 目标检测方法,并对指定类别进行检测。用b站的视频做测试。
点击按钮 'all',按钮变红色,对所有的类别检测
点击按钮 'person',按钮变红色,只对'person'类别检测
首先,我们需要导入 yolov4-tiny 网络模型结构 cfg 文件,网络权重 weights 文件,以及 COCO 数据集的分类名称的 txt 文件。这里我已经给大家提供好了各种配置文件及代码,有需要的自取。
链接:https://pan.baidu.com/s/19SMNuvCQGvl_V9O0RHsLog
提取码:zyp7
(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()
检测效果如下:
在同路径下定义一个绘制矩形按钮的类 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()
首先我们看到下面代码中的第(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'的效果图如下: