最近刚出的opencv4.4.0也支持了yolov4,便尝试用opencv调用yolov4进行检测,做个记录。当然,yolov3、yolov4-tiny等也能调用,只需修改加载的cfg和weight文件就行。如果想使用GPU加速的话,需要安装opencv的GPU版,可以参考:ubuntu下安装opencv,并配置DNN模块使用CUDA加速
地址:百度网盘
提取码:2zfk
代码我放到github上了,看这里
最近在做一个项目,其中把yolov4检测部分封装成了一个Detection类,下面的代码是从中分出来的修改的,先给出成员变量:
//图像属性
int m_width; //图像宽度
int m_height; //图像高度
//网络处理相关
Net m_model; //网络模型
Mat m_frame; //每一帧
Mat m_blob; //从每一帧创建一个4D的blob用于网络输入
vector<Mat> m_outs; //网络输出
vector<float> m_confs; //置信度
vector<Rect> m_boxes; //检测框左上角坐标、宽、高
vector<int> m_classIds; //类别id
vector<int> m_perfIndx; //非极大阈值处理后边界框的下标
//检测超参数
int m_inpWidth; //网络输入图像宽度
int m_inpHeight; //网络输入图像高度
float m_confThro; //置信度阈值
float m_NMSThro; //NMS非极大抑制阈值
vector<string> m_classes; //类别名称
首先需要对yolov4进行加载,包括配置文件、模型权重、检测类别名称,即加载yolov4.cfg,yolov4.weights,coco.names三个文件。
//读取网络模型和类别
void Detection::ReadModel()
{
string classesFile = "./yolo/coco.names";
String modelConfig = "./yolo/yolov4.cfg";
String modelWeights = "./yolo/yolov4.weights";
//加载类别名
ifstream ifs(classesFile.c_str());
string line;
while (getline(ifs, line)) m_classes.push_back(line);
//加载网络模型
m_model = readNetFromDarknet(modelConfig, modelWeights);
//OPENCL
m_model.setPreferableBackend(DNN_BACKEND_OPENCV);
m_model.setPreferableTarget(DNN_TARGET_OPENCL);
//GPU
// m_model.setPreferableBackend(DNN_BACKEND_CUDA);
// m_model.setPreferableTarget(DNN_TARGET_CUDA);
}
//检测过程
bool Detection::Detecting(Mat frame)
{
m_frame = frame.clone();
//创建4D的blob用于网络输入
blobFromImage(m_frame, m_blob, 1 / 255.0,Size(m_inpWidth, m_inpHeight), Scalar(0, 0, 0), true, false);
//设置网络输入
m_model.setInput(m_blob);
//前向预测得到网络输出,forward需要知道输出层,这里用了一个函数找到输出层
m_model.forward(m_outs, GetOutputsNames());
//使用非极大抑制NMS删除置信度较低的边界框
PostProcess();
//画检测框
Drawer();
return true;
}
//获取网络输出层名称
vector<String> Detection::GetOutputsNames()
{
static vector<String> names;
if (names.empty())
{
//得到输出层索引号
vector<int> outLayers = m_model.getUnconnectedOutLayers();
//得到网络中所有层名称
vector<String> layersNames = m_model.getLayerNames();
//获取输出层名称
names.resize(outLayers.size());
for (int i = 0; i < outLayers.size(); ++i)
names[i] = layersNames[outLayers[i] - 1];
}
return names;
}
主要是得出目标类别、置信度、检测框信息,被分别存到m_classIds、m_confs、m_boxes中,然后通过NMS找出每个目标最佳检测框的下标。
//使用非极大抑制NMS去除置信度较低的边界框
void Detection::PostProcess()
{
for (int num = 0; num < m_outs.size(); num++)
{
Point Position;
double confidence;
//得到每个输出的数据
float* data = (float*)m_outs[num].data;
for (int j = 0; j < m_outs[num].rows; j++, data += m_outs[num].cols)
{
//得到该输出的所有类别的
Mat scores = m_outs[num].row(j).colRange(5, m_outs[num].cols);
//获取最大置信度对应的值和位置
minMaxLoc(scores, 0, &confidence, 0, &Position);
//对置信度大于阈值的边界框进行相关计算和保存
if (confidence > m_confThro)
{
//data[0],data[1],data[2],data[3]都是相对于原图像的比例
int centerX = (int)(data[0] * m_width);
int centerY = (int)(data[1] * m_height);
int width = (int)(data[2] * m_width);
int height = (int)(data[3] * m_height);
int left = centerX - width / 2;
int top = centerY - height / 2;
//保存信息
m_classIds.push_back(Position.x);
m_confs.push_back((float)confidence);
m_boxes.push_back(Rect(left, top, width, height));
}
}
}
//非极大值抑制,以消除具有较低置信度的冗余重叠框
NMSBoxes(m_boxes, m_confs, m_confThro, m_NMSThro, m_perfIndx);
}
//画出检测结果
void Detection::Drawer()
{
//获取所有最佳检测框信息
for (int i = 0; i < m_perfIndx.size(); i++)
{
int idx = m_perfIndx[i];
Rect box = m_boxes[idx];
DrawBoxes(m_classIds[idx], m_confs[idx], box.x, box.y,
box.x + box.width, box.y + box.height);
}
}
//画出检测框和相关信息
void Detection::DrawBoxes(int classId, float conf, int left, int top, int right, int bottom)
{
//画检测框
rectangle(m_frame, Point(left, top), Point(right, bottom), Scalar(255, 178, 50), 3);
//该检测框对应的类别和置信度
string label = format("%.2f", conf);
if (!m_classes.empty())
{
CV_Assert(classId < (int)m_classes.size());
label = m_classes[classId] + ":" + label;
}
//将标签显示在检测框顶部
int baseLine;
Size labelSize = getTextSize(label, FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);
top = max(top, labelSize.height);
rectangle(m_frame, Point(left, top - round(1.5*labelSize.height)), Point(left + round(1.5*labelSize.width), top + baseLine), Scalar(255, 255, 255), FILLED);
putText(m_frame, label, Point(left, top), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(0, 0, 0), 1);
}
写了个test.cpp
#include "Detection.h"
#include
using namespace std;
using namespace cv;
using namespace dnn;
void TestDetection()
{
string image_path = "./data/test.jpg";
string save_path = "./data/result.jpg";
Mat img = imread(image_path);
cout<<"width: "<<img.cols<<endl;
cout<<"height: "<<img.rows<<endl;
Detection detection = Detection();
detection.Initialize(img.cols, img.rows);
detection.Detecting(img);
imwrite(save_path, detection.GetFrame());
return;
}
int main()
{
TestDetection();
return 0;
}