- 个人主页:风间琉璃
- 版权: 本文由【风间琉璃】原创、在CSDN首发、需要转载请联系博主
- 如果文章对你有帮助、欢迎关注、点赞、收藏(一键三连)和订阅专栏哦
目录
前言
一、YOLOV4简介
二、预处理
1.获取分类名
2.获取输出层名称
3.图像尺度变换
三、模型加载和推理
四、后处理
五、源码
YOLOv4(You Only Look Once version 4)是计算机视觉领域中的一个物体检测算法,它是 YOLO 系列算法的最新版本之一。YOLOv4 极大地改进了物体检测的性能和准确性,并且在速度和精度之间取得了良好的平衡。
yolov4网络结构如下:
yolov4相对于yolov3 spp性能提升的不多,但是相对应yolov3,性能得到了极大的提高。
yolov4基本组成:
1. CBM:Yolov4网络结构中的最小组件,由Conv+Bn+Mish激活函数三者组成。
2. CBL:由Conv+Bn+Leaky_relu激活函数三者组成。
3. Res unit:借鉴Resnet网络中的残差结构,让网络可以构建的更深。
4. CSPX:借鉴CSPNet网络结构,由卷积层和X个Res unint模块Concat组成。
5. SPP:采用1×1,5×5,9×9,13×13的最大池化的方式,进行多尺度融合。
其他操作:
1. Concat:张量拼接,维度会扩充,对应于cfg文件中的route操作。
2. Add:张量相加,不会扩充维度,对应于cfg文件中的shortcut操作。
总得来说,作者将近几年关于深度学习领域最新研究的tricks移植到Yolov4中做验证测试,将yolov3的精度提高了不少。虽然没有全新的创新,但很多改进之处都值得借鉴,借用Yolov4作者的总结
详细请参考大佬写的笔记:深入浅出Yolo系列之Yolov3&Yolov4&Yolov5&Yolox核心基础知识完整讲解_yolo4理解江大白_江大白*的博客-CSDN博客
yolov4网络模型和配置文件下载:
yolov4.cfg:https://github.com/AlexeyAB/darknet/blob/master/cfg/yolov4.cfg
yolov4.weights:https://github.com/AlexeyAB/darknet/releases/download/darknet_yolo_v3_optimal/yolov4.weights
coco.names:https://github.com/AlexeyAB/darknet/blob/master/data/coco.names
数据集采用的coco数据集,需要将coco.names包含训练模型的所有类名称加载到内存中。
string classsepath = "F:/data/CQU/VS/yolov4/coco.names";
//获取数据集标签
vector getclasssnames(string classnamespath)
{
ifstream ifs(classnamespath);
vector names;
if (ifs.is_open())
{
string line;
while (getline(ifs, line))
{
//printf("name:%s\n", line.c_str());
names.push_back(line);
}
}
return names;
}
YOLOv4由于可以在三个不同的尺度上进行检测,也就是说它有三个输出层,因此我们需要获得它的三个输出层的名称。
//获取未连接的输出层名称
vector getoutputnames(const Net& net)
{
vector names = net.getUnconnectedOutLayersNames();
for (int i = 0; i < names.size(); i++)
{
printf("output layer name:%s\n", names.at(i).c_str());
}
return names;
}
神经网络的输入图像需要采用称为blob的特定格式。从输入图像或视频流中读取帧后,将通过blobFromImage函数将其转换为神经网络的输入blob。
在此过程中,它使用比例因子1/255将图像像素值缩放到0到1的目标范围。它还将图像的大小调整为给定大小(416,416)而不进行裁剪。视频处理使用416,小尺寸,帧率会提高一些,图像目标检测是使用608尺寸的。
Mat blob = blobFromImage(frame, 1 / 255.0, Size(416, 416));
加载网络直接使用readNetFromDarknet或者readNet都行。
//加载网络
Net net = readNetFromDarknet(config, weights);
if (net.empty())
{
printf("Could not load yolov4....\n");
return;
}
这里可以根据个人的情况设置是否使用CUDA加速,我编译过CUDA的OpenCV,所以这里利用第二种方式
//设置计算平台
#if 0
//cpu:
net.setPreferableBackend(DNN_BACKEND_OPENCV);
net.setPreferableTarget(DNN_TARGET_CPU);
#elif 1
//cuda
net.setPreferableBackend(DNN_BACKEND_CUDA);
net.setPreferableTarget(DNN_TARGET_CUDA_FP16);
#endif
预处理和网络模型都完成后就可以进行图像的预测。
net.setInput(blob);
vector outs;
net.forward(outs, outputnamse);
预测的结果保存在outs的Mat类型矩阵中。接下来就需要对这个预测结果进行后处理。
yolov4的后处理基本上和yolov3的后处理操作是一样的,具体可以参考yolov3的处理:OpenCV之YOLOv3目标检测_风间琉璃•的博客-CSDN博客
//后处理
float* data; //存储每一层输出结果
Mat scores; //存储分类的概率,大小80
vector boxes; //边框信息”x,y,w,h
vector classIds; //存储所有分类索引值
vector confidences; //置信度存储用于NMS
int centerX, centerY, width, height, left, top;
float confidenceThreshold = 0.5;
float nmsThreashold = 0.4;
double confidence; //获取自信度
Point classIdPoint;//获取分类索引值
//对每一个输出层找到所有目标以及位置
for (int i = 0; i < outs.size(); i++)
{
data = (float*)outs[i].data; //分别处理每一层
for (int j = 0; j < outs[i].rows; j++, data += outs[i].cols)
{
//分类概率
scores = outs[i].row(j).colRange(5, outs[i].cols);
minMaxLoc(scores, 0, &confidence, 0, &classIdPoint);
if (confidence > confidenceThreshold)
{
centerX = (int)(data[0] * frame.cols);
centerY = (int)(data[1] * frame.rows);
width = (int)(data[2] * frame.cols);
height = (int)(data[3] * frame.rows);
left = centerX - width / 2;
top = centerY - height / 2;
classIds.push_back(classIdPoint.x);
confidences.push_back((float)confidence);
boxes.push_back(Rect(left, top, width, height));
}
}
}
//非极大值抑制消除重叠度较高但置信度低的边界框
vector indices; //NMSBoxes输出,包含经过非极大值抑制后的所选边界框的索引
NMSBoxes(boxes, confidences, confidenceThreshold, nmsThreashold, indices);
for (int i = 0; i < indices.size(); i++)
{
int idx = indices[i];
Rect box = boxes[idx];
//在图像上绘制检测结果,包括类别标签、置信度和边界框
drawPred(classesnames, classIds[idx], confidences[idx], box.x, box.y, box.x + box.width, box.y + box.height, frame);
}
//FPS计算
t = ((double)getTickCount() - t) / getTickFrequency();//求输入帧后经过的周期数/每秒系统计的周期数=一帧用时多少秒
//求倒数得到每秒经过多少帧,即帧率
double fps = 1.0 / t;
string text = format("FPS:%.2f", fps);
cv::putText(frame, text, Point(10, 50), FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 255, 0), 2, 8, 0);
imshow("yolov4", frame);
int c = waitKey(1);
if (c == 27)
{
break;
}
绘制边界框:
//画预测的目标bounding box
void drawPred(vector classesnames,int classId, float conf, int left, int top, int right, int bottom, Mat& frame)
{
//获取类别名称及其置信度
string label = format("%.2f", conf);
if (!classesnames.empty())
{
CV_Assert(classId < (int)classesnames.size());
label = classesnames[classId] + ":" + label;
}
//box 和 text 的颜色
Scalar rectColor, textColor;
//在边界框的顶部显示标签
int baseLine;
Size labelSize = getTextSize(label, FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);
//确保顶部位置足够高以容纳标签
top = max(top, labelSize.height);
// 创建随机数生成器
random_device rd;
mt19937 generator(rd());
// 创建均匀分布对象,范围是1到50
uniform_int_distribution distribution(1, 80);
// 生成随机数
int random_number = distribution(generator);
//设置颜色
rectColor = Scalar(random_number * 11 % 256, random_number * 22 % 256, random_number * 33 % 256);
textColor = Scalar(255 - random_number * 11 % 256, 255 - random_number * 22 % 256, 255 - random_number * 33 % 256);
//绘制检测目标的矩形框
rectangle(frame, Point(left, top), Point(right, bottom), rectColor, 2);
//文字边框
rectangle(frame, Point(left, top - round(1.5 * labelSize.height)), Point(left + round(1.5 * labelSize.width), top + baseLine), textColor, FILLED);
putText(frame, label, Point(left, top), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 255, 0), 1);
}
运行结果:
图片:
视频:
YOLOv4:下一代目标检测算法的革命性突破
资源下载:https://download.csdn.net/download/qq_53144843/88357523
// yolov4.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include
#include
#include
#include
#include
using namespace std;
using namespace cv;
using namespace cv::dnn;
void image_detection(string config, string weights, string classespath, string image_path);
void video_detection(string config, string weights, string classespath, string video_path);
//获取数据集标签
vector getclasssnames(string classnamespath)
{
ifstream ifs(classnamespath);
vector names;
if (ifs.is_open())
{
string line;
while (getline(ifs, line))
{
//printf("name:%s\n", line.c_str());
names.push_back(line);
}
}
return names;
}
//获取未连接的输出层名称
vector getoutputnames(const Net& net)
{
vector names = net.getUnconnectedOutLayersNames();
for (int i = 0; i < names.size(); i++)
{
printf("output layer name:%s\n", names.at(i).c_str());
}
return names;
}
//画预测的目标bounding box
void drawPred(vector classesnames,int classId, float conf, int left, int top, int right, int bottom, Mat& frame)
{
//获取类别名称及其置信度
string label = format("%.2f", conf);
if (!classesnames.empty())
{
CV_Assert(classId < (int)classesnames.size());
label = classesnames[classId] + ":" + label;
}
//box 和 text 的颜色
Scalar rectColor, textColor;
//在边界框的顶部显示标签
int baseLine;
Size labelSize = getTextSize(label, FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);
//确保顶部位置足够高以容纳标签
top = max(top, labelSize.height);
// 创建随机数生成器
random_device rd;
mt19937 generator(rd());
// 创建均匀分布对象,范围是1到50
uniform_int_distribution distribution(1, 80);
// 生成随机数
int random_number = distribution(generator);
//设置颜色
rectColor = Scalar(random_number * 11 % 256, random_number * 22 % 256, random_number * 33 % 256);
textColor = Scalar(255 - random_number * 11 % 256, 255 - random_number * 22 % 256, 255 - random_number * 33 % 256);
//绘制检测目标的矩形框
rectangle(frame, Point(left, top), Point(right, bottom), rectColor, 2);
//文字边框
rectangle(frame, Point(left, top - round(1.5 * labelSize.height)), Point(left + round(1.5 * labelSize.width), top + baseLine), textColor, FILLED);
putText(frame, label, Point(left, top), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 255, 0), 1);
}
int main()
{
string config = "F:/data/CQU/VS/yolov4/yolov4.cfg";
string weights = "F:/data/CQU/VS/yolov4/yolov4.weights";
string classsepath = "F:/data/CQU/VS/yolov4/coco.names";
string image_path = "F:/data/CQU/VS/yolov4/dog.jpg";
string video_path = "F:/data/CQU/VS/yolov4/car.mp4";
//image_detection(config, weights, classsepath, image_path);
video_detection(config, weights, classsepath, video_path);
}
void image_detection(string config, string weights, string classespath, string image_path)
{
//加载网络
Net net = readNetFromDarknet(config, weights);
if (net.empty())
{
printf("Could not load yolov4....\n");
return;
}
//设置计算平台
#if 1
//cpu:
net.setPreferableBackend(DNN_BACKEND_OPENCV);
net.setPreferableTarget(DNN_TARGET_CPU);
#elif 0
//cuda
net.setPreferableBackend(DNN_BACKEND_CUDA);
net.setPreferableTarget(DNN_TARGET_CUDA_FP16);
#endif
//获取标签
vector classesnames = getclasssnames(classespath);
//获取输出层名称
vector outputnamse = getoutputnames(net);
Mat image = imread(image_path);
if (image.empty())
{
printf("Colud not load image...\n");
return;
}
Mat blob = blobFromImage(image, 1 / 255.0, Size(608, 608));
net.setInput(blob);
vector outs;
net.forward(outs, outputnamse);
//后处理
float* data; //存储每一层输出结果
Mat scores; //存储分类的概率,大小80
vector boxes; //边框信息”x,y,w,h
vector classIds; //存储所有分类索引值
vector confidences; //置信度存储用于NMS
int centerX, centerY, width, height, left, top;
float confidenceThreshold = 0.2;
float nmsThreashold = 0.4;
double confidence; //获取自信度
Point classIdPoint;//获取分类索引值
//对每一个输出层找到所有目标以及位置
for (int i = 0; i < outs.size(); i++)
{
data = (float*)outs[i].data; //分别处理每一层
for (int j = 0; j < outs[i].rows; j++, data += outs[i].cols)
{
//分类概率
scores = outs[i].row(j).colRange(5, outs[i].cols);
minMaxLoc(scores, 0, &confidence, 0, &classIdPoint);
if (confidence > confidenceThreshold)
{
centerX = (int)(data[0] * image.cols);
centerY = (int)(data[1] * image.rows);
width = (int)(data[2] * image.cols);
height = (int)(data[3] * image.rows);
left = centerX - width / 2;
top = centerY - height / 2;
classIds.push_back(classIdPoint.x);
confidences.push_back((float)confidence);
boxes.push_back(Rect(left, top, width, height));
}
}
}
//非极大值抑制消除重叠度较高但置信度低的边界框
vector indices; //NMSBoxes输出,包含经过非极大值抑制后的所选边界框的索引
NMSBoxes(boxes, confidences, confidenceThreshold, nmsThreashold, indices);
for (int i = 0; i < indices.size(); i++)
{
int idx = indices[i];
Rect box = boxes[idx];
//在图像上绘制检测结果,包括类别标签、置信度和边界框
drawPred(classesnames,classIds[idx], confidences[idx], box.x, box.y, box.x + box.width, box.y + box.height, image);
}
imshow("yolov4", image);
waitKey(0);
}
void video_detection(string config, string weights, string classespath, string video_path)
{
//加载网络
Net net = readNetFromDarknet(config, weights);
if (net.empty())
{
printf("Could not load yolov4....\n");
return;
}
//设置计算平台
#if 0
//cpu:
net.setPreferableBackend(DNN_BACKEND_OPENCV);
net.setPreferableTarget(DNN_TARGET_CPU);
#elif 1
//cuda
net.setPreferableBackend(DNN_BACKEND_CUDA);
net.setPreferableTarget(DNN_TARGET_CUDA_FP16);
#endif
//获取标签
vector classesnames = getclasssnames(classespath);
//获取输出层名称
vector outputnamse = getoutputnames(net);
VideoCapture capture;
capture.open(video_path);
if (!capture.isOpened())
{
printf("Colud not open video...\n");
return;
}
Mat frame;
while (capture.read(frame))
{
Mat blob = blobFromImage(frame, 1 / 255.0, Size(416, 416));
net.setInput(blob);
//获得当前系统的计时间周期数,求FPS
double t = (double)getTickCount();
vector outs;
net.forward(outs, outputnamse);
//后处理
float* data; //存储每一层输出结果
Mat scores; //存储分类的概率,大小80
vector boxes; //边框信息”x,y,w,h
vector classIds; //存储所有分类索引值
vector confidences; //置信度存储用于NMS
int centerX, centerY, width, height, left, top;
float confidenceThreshold = 0.5;
float nmsThreashold = 0.4;
double confidence; //获取自信度
Point classIdPoint;//获取分类索引值
//对每一个输出层找到所有目标以及位置
for (int i = 0; i < outs.size(); i++)
{
data = (float*)outs[i].data; //分别处理每一层
for (int j = 0; j < outs[i].rows; j++, data += outs[i].cols)
{
//分类概率
scores = outs[i].row(j).colRange(5, outs[i].cols);
minMaxLoc(scores, 0, &confidence, 0, &classIdPoint);
if (confidence > confidenceThreshold)
{
centerX = (int)(data[0] * frame.cols);
centerY = (int)(data[1] * frame.rows);
width = (int)(data[2] * frame.cols);
height = (int)(data[3] * frame.rows);
left = centerX - width / 2;
top = centerY - height / 2;
classIds.push_back(classIdPoint.x);
confidences.push_back((float)confidence);
boxes.push_back(Rect(left, top, width, height));
}
}
}
//非极大值抑制消除重叠度较高但置信度低的边界框
vector indices; //NMSBoxes输出,包含经过非极大值抑制后的所选边界框的索引
NMSBoxes(boxes, confidences, confidenceThreshold, nmsThreashold, indices);
for (int i = 0; i < indices.size(); i++)
{
int idx = indices[i];
Rect box = boxes[idx];
//在图像上绘制检测结果,包括类别标签、置信度和边界框
drawPred(classesnames, classIds[idx], confidences[idx], box.x, box.y, box.x + box.width, box.y + box.height, frame);
}
//FPS计算
t = ((double)getTickCount() - t) / getTickFrequency();//求输入帧后经过的周期数/每秒系统计的周期数=一帧用时多少秒
//求倒数得到每秒经过多少帧,即帧率
double fps = 1.0 / t;
string text = format("FPS:%.2f", fps);
cv::putText(frame, text, Point(10, 50), FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 255, 0), 2, 8, 0);
imshow("yolov4", frame);
int c = waitKey(1);
if (c == 27)
{
break;
}
}
capture.release();//释放资源
waitKey(0);
}
结束语
感谢你观看我的文章呐~本次航班到这里就结束啦
希望本篇文章有对你带来帮助 ,有学习到一点知识~
躲起来的星星也在努力发光,你也要努力加油(让我们一起努力叭)。
最后,博主要一下你们的三连呀(点赞、评论、收藏),不要钱的还是可以搞一搞的嘛~
不知道评论啥的,即使扣个666也是对博主的鼓舞吖 感谢