yolov5和resnet比稍微麻烦了一点,主要就是多了nms部分,还有坐标点映射回原图的yolov5_scale_coords函数。流程大致分为五部分:1)图像等比例放缩,2)图像预处理,3)onnx推理,4)nms后处理,5)坐标点映射回原图
等比例放缩
还是和resnet一样的 letterbox 函数,就不重复了。
图像预处理
还是和resnet一样的,就不重复了,就cv::dnn::blobFromImage一句话。
onnx推理
和之前差不多,改下输入输出的维度、名称,然后resnet的一维输出变成yolo的二维输出,这个在 c++ 使用onnx推理 中写过了,也不重复了。
nms后处理
这个我懒得自己写,还好opencv里面有cv::dnn::NMSBoxes函数,直接用就好。
正常来说onnx推理后返回的预测值是252000 x (n+5) ,其中252000是框的总数一般是不变的, n是等于你设置的类别数,向量含义是(center_x, center_y, width, height, conf_框, conf_类别1,conf_类别2,…,,conf_类别n)。
这个函数主要就是把preds转成opencv_nms函数需要的输入 cv::Rect(left, top, width, height) 框坐标 和 float 置信度(类别置信度*框置信度)
vector<Detection> yolov5_nms(Mat preds, float conf_thres = 0.25, float iou_thres = 0.45)
{
vector<cv::Rect> boxes;
vector<float> confs;
vector<int> classIds;
for (int i = 0; i < preds.rows; i++)
{
float clsConf = preds.at<float>(i, 4);
if (clsConf > conf_thres)
{
// (cx,cy,w,h) to (left,top,w,h)
float centerX = preds.at<float>(i, 0);
float centerY = preds.at<float>(i, 1);
float width = preds.at<float>(i, 2);
float height = preds.at<float>(i, 3);
float left = centerX - width / 2;
float top = centerY - height / 2;
// 因为我这里只检测人,就直接这样来了,正常如果有80个类别,objConf表示最高的置信度,classId表示最高置信度的id
float objConf = preds.at<float>(i, 5);;
int classId = 0;
float confidence = clsConf * objConf;
boxes.push_back(cv::Rect(left, top, width, height));
confs.push_back(confidence);
classIds.push_back(classId);
}
}
vector<int> nms_result;
// 这里输入的boxes(float)被强制转为(int)了,可能会有点误差
cv::dnn::NMSBoxes(boxes, confs, conf_thres, iou_thres, nms_result);
cout << "amount of NMS indices: " << nms_result.size() << std::endl;
vector<Detection> output;
for (int i = 0; i < nms_result.size(); i++) {
int idx = nms_result[i];
Detection result;
result.class_id = classIds[idx];
result.confidence = confs[idx];
result.box = boxes[idx];
output.push_back(result);
}
return output;
}
坐标点映射
主要就是我要把检测的框放缩回原图,对原图进行裁减。结构体看着有点难受,可以直接输入输出改成boxes,我是因为之后可视化要标上类别信息所以写了个结构体。
vector<Detection> yolov5_scale_coords(Mat ori_img, vector<Detection> detections, Mat letter_img)
{
// boxes(ltwh)(left, top, width, height)
float ratio;
float scale_row = (float)(letter_img.rows) / (float)(ori_img.rows);
float scale_col= (float)(letter_img.cols) / (float)(ori_img.cols);
ratio = min(scale_row, scale_col);
float pad_row = ((float)(letter_img.rows) - (float)(ori_img.rows) * ratio) / 2.0;
float pad_col = ((float)(letter_img.cols) - (float)(ori_img.cols) * ratio) / 2.0;
// 这里的点应该要用float的,用int明显有误差
vector<Detection> new_detections;
int nums = detections.size();
for (int i = 0; i < nums; ++i)
{
auto detection = detections[i];
auto box = detection.box;
float left = (box