五、pc端使用C++调用ncnn

由于有很多人再问一个输出层对不上的bug问题,在此我在开头重点提点,请各位大佬仔细看好我的标红字体!!!!!!!!

由于yolov5转ncnn不包括后处理部分,因此在c++的代码里需要重构整个后处理部分,不多说,直接上代码了:

cmake_minimum_required(VERSION 3.17)
project(yolov5s)

find_package(OpenCV REQUIRED core highgui imgproc)

#这里链接的时编译后的ncnn的include和lib文件夹,根据自己的路径去更改
include_directories(./CLion-2020.2/clion-2020.2/ncnn-master/build/install/include/ncnn)  
link_directories(./CLion-2020.2/clion-2020.2/ncnn-master/build/install/lib)

find_package(ncnn)
FIND_PACKAGE( OpenMP REQUIRED)
if (OPENMP_FOUND)
    message("OPENMAP FOUND")
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
    set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}")
endif()

project(yolov5s)

set(CMAKE_CXX_STANDARD 14)
set(CMAKE_BUILD_TYPE Debug)

add_executable(yolov5s yolov5.cpp)

target_link_libraries(yolov5s ncnn ${OpenCV_LIBS})

这部分代码时cmakelist里的,大家可以参考着去配置cmake!!

接下来是头文件.h的代码:

#ifndef YOLO_V5_H
#define YOLO_V5_H

#include "net.h"
#include <opencv2/opencv.hpp>
#include <math.h>
#include <chrono>
#include <memory>

struct YoloSize{    //定义结构体YoloSize
    int width;
    int height;
};

struct YoloLayerData{       //定义结构体YoloLayerData
    std::string name;       //构造一个默认为空的string name,使用标准函数库中的函数或对象需要使用std进行限定,即这里
    int stride;             //调用的是std中的string,防止自己定义的类与标准库中的函数冲突
    std::vector<YoloSize> anchors;  //创建一个容器,放入类似于数组的序列
};

struct BoxInfo {    //定义结构体BoxInfo
    float x1;
    float y1;
    float x2;
    float y2;
    float score;
    int label;
};

class Yolov5Detector {      //定义类
public:                     //公共行为属性
    Yolov5Detector(const char* param, const char* bin);     //Yolov5Detector函数
    ~Yolov5Detector();      //析构函数,用于释放内存,不带任何参数,没有返回值,应与类名相同
    std::vector<BoxInfo> Detect(cv::Mat image, float threshold, float nms_threshold=0.4);   //将detect的结果放入boxinfo容器中

private:                    //私有行为属性
    static std::vector<BoxInfo> DecodeInfer(ncnn::Mat &data, int stride,const YoloSize& frame_size, int net_size,int num_classes,const std::vector<YoloSize> &anchors, float threshold);     //解码结果
    static void Nms(std::vector<BoxInfo>& result,float nms_threshold);      //static数据可以时private成员,全局变量不行

private:
    std::vector<std::string> labels_{"person", "aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat", "chair",
                                     "cow", "diningtable", "dog", "horse", "motorbike", "pottedplant", "sheep", "sofa", "train", "tvmonitor"};

    int input_size_ = 640;
    int num_class_ = 20;
    int num_thread_ = 1;
    bool hasGPU_ = false;

    std::vector<YoloLayerData> layers{
        {"392",32,{{116,90},{156,198},{373,326}}},
        {"373",16,{{30,61},{62,45},{59,119}}},
        {"output",8,{{10,13},{16,30},{33,23}}},
    };

    ncnn::Net* Net_;
    static Yolov5Detector *detector_;
};


#endif //YOLO_V5_H

这一个文件是一个.h文件,主要是定义一些结构体和类,供cpp里调用,这里面需要注意几个点,如下图:

yolov5 opencv可视化 yolov5 opencv 调用_yolov5 opencv可视化

这里的input_size要设置为640,即onnx模型输入的两倍,具体为啥不清楚,反正设置为320后了效果会有偏差! 这里这三个输出层的名称,每个人转出来都不太一样,需要自己的情况去改,这三个名字怎么找,在ncnn的官方git社区里有一个netron的可视化工具可以直接看转换后onnx模型的结构,里面可以找到这三个输出层的名称,传送门:netron可视化网络结构

接下来是cpp的代码:

#include "yolov5.h"
#include <fstream>

Yolov5Detector::Yolov5Detector(const char *param, const char* bin) {
    Net_ = new ncnn::Net();
    ncnn::Option opt;
    opt.use_winograd_convolution = true;
    opt.use_packing_layout = false;
    opt.lightmode = true;
    Net_->opt = opt;
    int ret = Net_->load_param(param);
    std::cout << ret << std::endl;
    ret = Net_->load_model(bin);
    std::cout << ret << std::endl;
}


Yolov5Detector::~Yolov5Detector() {
    delete Net_;
}


std::vector<BoxInfo> Yolov5Detector::Detect(cv::Mat img, float threshold, float nms_threshold) {        //detect的值传给BoxInfo,定义命名空间Yolov5Detector类下的函数Detect

//    cv::Mat input_mat;
//    //TODO padding
//    float im_scale = (float)img.cols/input_size_;
//    std::cout << "scale" << im_scale << std::endl;
//    int new_w = int(img.cols/im_scale);
//    int new_h = int(img.rows/im_scale);
//    cv::resize(img,input_mat,cv::Size(new_w,new_h));    //resize
//    int p_w = input_size_ - new_w;
//    int p_h = input_size_ - new_h;
//    //扩充图像边缘
//    cv::copyMakeBorder(input_mat, input_mat,        //原图像与扩充后的图像
//                       0, p_h,                 //表示在图像四周扩充边缘的大小,top,bottom,left,right
//                       0, p_w,
//                       cv::BORDER_CONSTANT,     //以常数值扩充边界
//                       cv::Scalar(114, 114, 114));      //常量扩充的数值
//    
/*****************************************yolov5后处理中的自适应缩放图片*************************************************************/
    cv::Mat input_mat;
    //TODO padding
    float col_scale = input_size_/(float)img.cols;
    float row_scale = input_size_/(float)img.rows;
    float im_scale;
    if (col_scale < row_scale)
    {
        im_scale = col_scale;
    } else
    {
        im_scale = row_scale;
    }
    std::cout << "scale" << im_scale << std::endl;
    int new_w = int(img.cols * im_scale);
    int new_h = int(img.rows * im_scale);
    cv::resize(img,input_mat,cv::Size(new_w,new_h));    //resize
    int dw = input_size_ - new_w;
    int dh = input_size_ - new_h;
    int p_w = dw%64/2;
    int p_h = dh%64/2;
    int top = (int)std::round(p_h-0.1) ;
    int bottom = (int)std::round(p_h+0.1);
    int left = (int)std::round(p_w-0.1);
    int right = (int)std::round(p_w+0.1);
    //扩充图像边缘
    cv::copyMakeBorder(input_mat, input_mat,        //原图像与扩充后的图像
                       top, bottom,                 //表示在图像四周扩充边缘的大小,top,bottom,left,right
                       left, right,
                       cv::BORDER_CONSTANT,     //以常数值扩充边界
                       cv::Scalar(114, 114, 114));      //常量扩充的数值
    //cv::imshow("img1",input_mat);
    //cv::waitKey(0);
/************************************************************************************************××××××******/

    cv::resize(input_mat, input_mat, cv::Size(input_size_/2, input_size_/2));     //把图片resize到320
//    cv::imshow("img2",input_mat);
//    cv::waitKey(0);
    ncnn::Mat in_net = ncnn::Mat::from_pixels(input_mat.data, ncnn::Mat::PIXEL_BGR2RGB, input_mat.cols, input_mat.rows);    //定义输入,将BGR转换为RGB
    float norm[3] = {1/255.f,1/255.f,1/255.f};      //方差
    float mean[3] = {0,0,0};        //均值
    in_net.substract_mean_normalize(mean,norm);     //正则化
    auto ex = Net_->create_extractor();     //提取网络结构和参数
    ex.set_light_mode(true);
    ex.set_num_threads(4);
//    ex.set_vulkan_compute(hasGPU);
    ex.input(0, in_net);    //输入
    std::vector<BoxInfo> result;      //定义结果
    for(const auto& layer: layers){
        ncnn::Mat blob;
        auto t0 = std::chrono::high_resolution_clock::now();    //计算推理时间
        ex.extract(layer.name.c_str(),blob);        //提取网络输出层结果
        auto t1 = std::chrono::high_resolution_clock::now();
        auto boxes = DecodeInfer(blob,layer.stride,{(int)img.cols,(int)img.rows},input_size_,num_class_,layer.anchors,threshold);   //声明解码函数
        auto t2 = std::chrono::high_resolution_clock::now();
        std::cout << "time1: " << std::chrono::duration_cast<std::chrono::microseconds>(t1 - t0).count() / 1000.0 << "ms." << std::endl;
        std::cout << "time2: " << std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1).count() / 1000.0 << "ms." << std::endl;
        result.insert(result.begin(),boxes.begin(),boxes.end());        //在result的首部插入整个boxes数组
    }
    Nms(result,nms_threshold);      //进行NMS
    return result;
}


inline float Sigmoid(float x){      //内联函数,用于增强性能,但是只适用于简单短小的函数
    return 1.0f / (1.0f + std::exp(-x));
}

//定义解码函数,位于命名空间Yolov5Detector类下
std::vector<BoxInfo> Yolov5Detector::DecodeInfer(ncnn::Mat &data, int stride, const YoloSize &frame_size, int net_size, int num_classes,const std::vector<YoloSize> &anchors, float threshold) {
    std::vector<BoxInfo> result;
    int grid_size = int(sqrt(data.h));      //data.h是什么意思?data(dims=3, w=25, h=400, c=3)
    float *mat_data[data.c];                //data.c是什么意思?
    for(int i=0;i<data.c;i++){
        mat_data[i] = data.channel(i);      //
    }
    float cx,cy,w,h;
    for(int shift_y=0;shift_y<grid_size;shift_y++){
        for(int shift_x=0;shift_x<grid_size;shift_x++){
            //int loc = shift_x+shift_y*grid_size;
            for(int i=0;i<3;i++) {
                float *record = mat_data[i];
                float *cls_ptr = record + 5;
                for(int cls = 0; cls<num_classes;cls++){
                    float score = Sigmoid(cls_ptr[cls]) * Sigmoid(record[4]);
                    if(score>threshold){
                        cx = (Sigmoid(record[0]) * 2.f - 0.5f + (float)shift_x) * (float) stride;   //中心点横坐标
                        cy = (Sigmoid(record[1]) * 2.f - 0.5f + (float)shift_y) * (float) stride;   //中心点纵坐标
                        w = pow(Sigmoid(record[2]) * 2.f,2)*anchors[i].width;                 //box的宽
                        h = pow(Sigmoid(record[3]) * 2.f,2)*anchors[i].height;                //box的高
                        //std::cout << cx << " "<<cy<<" "<<w<<" "<<h<<std::endl;
                        //printf("[grid size=%d, stride = %d]x y w h %f %f %f %f\n",grid_size,stride,record[0],record[1],record[2],record[3]);
                        BoxInfo box;
                        box.x1 = std::max(0,std::min(frame_size.width,int((cx - w / 2.f) * (float)frame_size.width / (float)net_size)));    //左上角坐标
                        box.y1 = std::max(0,std::min(frame_size.height,int((cy - h / 2.f) * (float)frame_size.height / (float)net_size)));
                        box.x2 = std::max(0,std::min(frame_size.width,int((cx + w / 2.f) * (float)frame_size.width / (float)net_size)));    //右下角坐标
                        box.y2 = std::max(0,std::min(frame_size.height,int((cy + h / 2.f) * (float)frame_size.height / (float)net_size)));
                        box.score = score;
                        box.label = cls;
                        result.push_back(box);
                    }
                }
            }
            for(auto& ptr:mat_data){
                ptr+=(num_classes + 5);
            }
        }
    }
    return result;
}


void Yolov5Detector::Nms(std::vector<BoxInfo> &input_boxes, float nms_thresh) {
    std::sort(input_boxes.begin(), input_boxes.end(), [](BoxInfo a, BoxInfo b){return a.score > b.score;});
    std::vector<float>vArea(input_boxes.size());    //定义box面积容器
    for (int i = 0; i < int(input_boxes.size()); ++i)   //遍历所有的box
    {
        vArea[i] = (input_boxes.at(i).x2 - input_boxes.at(i).x1 + 1)        //计算面积(x2-x1)×(y2-y1)
                   * (input_boxes.at(i).y2 - input_boxes.at(i).y1 + 1);
    }
    for (int i = 0; i < int(input_boxes.size()); ++i)   //遍历所有的面积
    {
        for (int j = i + 1; j < int(input_boxes.size());)
        {
            float xx1 = std::max(input_boxes[i].x1, input_boxes[j].x1);     //求解两个box相交处的左上角坐标
            float yy1 = std::max(input_boxes[i].y1, input_boxes[j].y1);
            float xx2 = std::min(input_boxes[i].x2, input_boxes[j].x2);     //求解两个box相交处的右下角坐标
            float yy2 = std::min(input_boxes[i].y2, input_boxes[j].y2);
            float w = std::max(float(0), xx2 - xx1 + 1);        //计算相交矩形的宽
            float h = std::max(float(0), yy2 - yy1 + 1);        //计算相交矩形的高
            float inter = w * h;                                //计算相交矩形的面积
            float ovr = inter / (vArea[i] + vArea[j] - inter);      //overlap
            if (ovr >= nms_thresh)
            {
                input_boxes.erase(input_boxes.begin() + j);     //若overlap大于阈值,删除该box
                vArea.erase(vArea.begin() + j);                 //若overlap大于阈值,删除该面积
            }
            else
            {
                j++;
            }
        }
    }
}

int main()
{
    //cv::VideoCapture cap;
    const char *yolov5_param = "/home/zhangyi/CLionProjects/yolov5s/last_500_320.param";
    const char *yolov5_bin = "/home/zhangyi/CLionProjects/yolov5s/last_500_320.bin";
    float nms_threshold = 0.4;
    float threshold = 0.3;
    Yolov5Detector ret(yolov5_param, yolov5_bin);

/*****************************************将预测结果写入txt中*************************************************************/
//    std::vector<cv::String> filename;
//    cv::String folder = "/home/zhangyi/CLionProjects/yolov5s/img_val";
//    cv::glob(folder, filename);
//    std::ofstream out("out.txt");
//    for(size_t i=0; i<filename.size(); ++i)
//    {
//        std::cout << filename[i] << std::endl;
//        cv::Mat frame = cv::imread(filename[i]);
//        std::vector<BoxInfo> result = ret.Detect(frame, threshold, nms_threshold);
//
//        std::ofstream write("out.txt", std::ios::app);
//        out << filename[i] << " ";
//        for(int j=0; j<result.size(); ++j)
//        {
//            const auto obj = result[j];
//            //std::ofstream write("out.txt", std::ios::app);
//            std::cout << obj.x1 << "," << obj.y1 << "," << obj.x2 << "," << obj.y2 << "," << obj.label << "\n";
//            out << obj.score << "," << obj.x1 << "," << obj.y1 << "," << obj.x2 << "," << obj.y2 << "," << obj.label << " ";
//        }
//        //std::ofstream write("out.txt", std::ios::app);
//        out << "\n";
//    }
/*****************************************************************************************************/

    cv::Mat frame;
    const char *img_path = "/home/zhangyi/CLionProjects/yolov5s/img_val/2008_000243.jpg";
    std::cout << img_path << std::endl;
    cv::Mat img = cv::imread(img_path, 1);
    frame = cv::imread(img_path, 1);

    std::vector<BoxInfo> result = ret.Detect(frame, threshold, nms_threshold);


    /*画图部分*/
    for(int i=0; i<result.size(); i++)
    {
        const auto obj = result[i];
        fprintf(stderr, "%d = %.5f at %.2f %.2f %.2f x %.2f\n", obj.label, obj.score,
                obj.x1, obj.y1, obj.x2, obj.y2);
        cv::rectangle(frame, cvPoint(obj.x1, obj.y1), cvPoint(obj.x2, obj.y2), cv::Scalar(255, 0, 0));

        char text[256];
        static const char* labels[] = {"person", "aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat", "chair",
                "cow", "diningtable", "dog", "horse", "motorbike", "pottedplant", "sheep", "sofa", "train", "tvmonitor"};

        sprintf(text, "%s %.1f%%", labels[obj.label], obj.score * 100);

        int baseLine = 0;
        cv::Size label_size = cv::getTextSize(text, cv::FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);

        int x = obj.x1;
        int y = obj.y1 - label_size.height - baseLine;
        if (y < 0)
            y = 0;
        if (x + label_size.width > frame.cols)
            x = frame.cols - label_size.width;

        cv::rectangle(frame, cv::Rect(cv::Point(x, y), cv::Size(label_size.width, label_size.height + baseLine)),
                      cv::Scalar(255, 255, 255), -1);

        cv::putText(frame, text, cv::Point(x, y + label_size.height),
                    cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 0, 0));
    }
    cv::imshow("image", frame);
    cv::waitKey(0);

    return 0;
}

这里面也有个地方需要注意,如下:

yolov5 opencv可视化 yolov5 opencv 调用_yolov5 opencv可视化_02

因为.h文件里设置输入size为640,而转出的ncnn模型接受输入为320,所以这里需要除以2!

其中

yolov5 opencv可视化 yolov5 opencv 调用_c++_03

这块的代码是yolov5中后处理中加入的自适应图片缩放机制,具yolov5的paper说这个操作可以提速30%,在这里也会对ncnn模型的精度有影响,加了这个能提高map2.5个点!

yolov5 opencv可视化 yolov5 opencv 调用_yolov5 opencv可视化_04

这一部分代码是对测试集进行测试,将结果写入一个txt文本中,然后去测试map,因为map计算以python版本居多,我嫌麻烦,没有复写c++版的,有兴趣可以直接写一个c++版的测试map代码,直接在c++里测试即可!

这部分想要测试map的时候把这部分注释打开,然后这部分以下的代码注释掉就ok!

六、ncnn的map测试

对于转ncnn后测试map指标不是一个很好的选择,我只是想与之前的pt模型做一下对比,才搞了这么一个测试,很多测试指标都是看预测的tensor与原tensor之间查了几位小数来做测试指标!

上一节已经将测试集的测试结果保存为txt文本了,这时将结果拿到pycharm中进行map测试!

由于我使用的是voc数据集,voc计算map的开源代码有很多,搜一搜就有,里面需要将某一类的结果先提取出来保存为单独一个txt文本,所以最后一共有20个txt文本,分别时20类目标的预测结果,下面是这一步骤的代码:

import os

def file_name(file_dir):
    with open("val.txt", 'w') as f:
        for root, dirs, files in os.walk(file_dir):
            for file in files:
                img_name = file.split(".")[0]
                f.write(img_name)
                f.write("\n")

def cls_pred_file(pred_file):
    with open(pred_file) as f:
        lines = f.readlines()
        classes_name = ["person", "aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat", "chair",
                     "cow", "diningtable", "dog", "horse", "motorbike", "pottedplant", "sheep", "sofa", "train", "tvmonitor"]
        for cls in classes_name:
            with open("./datasets/score/pred_out/%s.txt"%cls, 'w') as F:
                print("Writing %s.txt"%cls)
                for line in lines:
                    img_name = line.strip().split(" ")[0]
                    objects = line.strip().split(" ")[1:]
                    for i in range(len(objects)):
                        score = objects[i].split(",")[0]
                        x1 = objects[i].split(",")[1]
                        y1 = objects[i].split(",")[2]
                        x2 = objects[i].split(",")[3]
                        y2 = objects[i].split(",")[4]
                        label = int(objects[i].split(",")[5])
                        if classes_name[label] == cls:
                            F.write(img_name + " " + score + " " + x1 + " " + y1 + " " + x2 + " " + y2)
                            F.write("\n")
            print("%s.txt is done!"%cls)

if __name__ == "__main__":
    #file_name("./datasets/score/labels/val")
    cls_pred_file("./out.txt")

这里就生成了20类目标的预测结果,接下来就是测试map的过程了,代码如下:

# --------------------------------------------------------
# Fast/er R-CNN
# Licensed under The MIT License [see LICENSE for details]
# Written by Bharath Hariharan
# --------------------------------------------------------

import xml.etree.ElementTree as ET
import os
import _pickle as cPickle
import numpy as np

def parse_rec(filename):  # 通过ET解析xml后返回一个obj
    """ Parse a PASCAL VOC xml file """
    tree = ET.parse(filename)
    objects = []
    # 解析xml文件,将GT框信息放入一个列表
    for obj in tree.findall('object'):
        obj_struct = {}
        obj_struct['name'] = obj.find('name').text
        obj_struct['pose'] = obj.find('pose').text
        obj_struct['truncated'] = int(obj.find('truncated').text)
        obj_struct['difficult'] = int(obj.find('difficult').text)
        bbox = obj.find('bndbox')
        obj_struct['bbox'] = [int(bbox.find('xmin').text),
                              int(bbox.find('ymin').text),
                              int(bbox.find('xmax').text),
                              int(bbox.find('ymax').text)]
        objects.append(obj_struct)
    return objects


def voc_ap(rec, prec, use_07_metric=False):
    """ ap = voc_ap(rec, prec, [use_07_metric])
    Compute VOC AP given precision and recall.
    If use_07_metric is true, uses the
    VOC 07 11 point method (default:False).
    """
    if use_07_metric:
        # 11 point metric
        ap = 0.
        for t in np.arange(0., 1.1, 0.1):
            if np.sum(rec >= t) == 0:
                p = 0
            else:
                p = np.max(prec[rec >= t])
            ap = ap + p / 11.
    else:
        # correct AP calculation
        # first append sentinel values at the end
        mrec = np.concatenate(([0.], rec, [1.]))
        mpre = np.concatenate(([0.], prec, [0.]))

        # compute the precision envelope
        for i in range(mpre.size - 1, 0, -1):
            mpre[i - 1] = np.maximum(mpre[i - 1], mpre[i])

        # to calculate area under PR curve, look for points
        # where X axis (recall) changes value
        i = np.where(mrec[1:] != mrec[:-1])[0]

        # and sum (\Delta recall) * prec
        ap = np.sum((mrec[i + 1] - mrec[i]) * mpre[i + 1])
    return ap


def voc_eval(detpath,
             annopath,
             imagesetfile,
             classname,
             cachedir,
             ovthresh=0.2,
             use_07_metric=False):
    """rec, prec, ap = voc_eval(detpath,
                                annopath,
                                imagesetfile,
                                classname,
                                [ovthresh],
                                [use_07_metric])
    Top level function that does the PASCAL VOC evaluation.
    detpath: Path to detections
        detpath.format(classname) should produce the detection results file.
    annopath: Path to annotations
        annopath.format(imagename) should be the xml annotations file.
    imagesetfile: Text file containing the list of images, one image per line.
    classname: Category name (duh)
    cachedir: Directory for caching the annotations
    [ovthresh]: Overlap threshold (default = 0.5)
    [use_07_metric]: Whether to use VOC07's 11 point AP computation
        (default False)
    """
    # assumes detections are in detpath.format(classname)
    # assumes annotations are in annopath.format(imagename)
    # assumes imagesetfile is a text file with each line an image name 默认txt中是无后缀imgName
    # cachedir caches the annotations in a pickle file

    # first load gt
    if not os.path.isdir(cachedir):
        os.mkdir(cachedir)  # 若无pkl文件的路径,生成cachedir路径
    cachefile = os.path.join(cachedir, 'annots.pkl')
    # read list of images
    with open(imagesetfile, 'r') as f:
        lines = f.readlines()
    imagenames = [x.strip() for x in lines]  # imagenames为所有imgName的list

    if not os.path.isfile(cachefile):  # cache路径下无pkl
        # load annots
        recs = {}  # recs是一个dict,以imagename为key,解析xml后的obj为value,详情见下两句
        for i, imagename in enumerate(imagenames):
            # imagename = imagename.split(' ')[0]
            recs[imagename] = parse_rec(annopath.format(imagename))  # 依次写入format上imagename的xml路径到resc列表
            if i % 100 == 0:
                print('Reading annotation for {:d}/{:d}'.format(i + 1, len(imagenames)))  # 显示进程
        # save
        print('Saving cached annotations to {:s}'.format(cachefile))
        with open(cachefile, 'wb') as f:
            cPickle.dump(recs, f)  # 将resc列表中的内容写入pkl
    else:
        # load
        with open(cachefile, 'rb') as f:
            recs = cPickle.load(f)  # 若存在pkl,直接load到recs

    # extract gt objects for this class
    class_recs = {}
    npos = 0
    for imagename in imagenames:
        R = [obj for obj in recs[imagename] if obj['name'] == classname]  # 除去recs中其他类别
        bbox = np.array([x['bbox'] for x in R])
        difficult = np.array([x['difficult'] for x in R]).astype(np.bool)
        det = [False] * len(R)
        npos = npos + sum(~difficult)
        class_recs[imagename] = {'bbox': bbox,
                                 'difficult': difficult,
                                 'det': det}

    # read dets
    detfile = detpath.format(classname)
    with open(detfile, 'rb') as f:  # 读批量验证的结果txt文件
        lines = f.readlines()

    splitlines = [x.decode().strip().split(' ') for x in lines]  # split对txt每一行的数据做分割
    image_ids = [x[0] for x in splitlines]
    confidence = np.array([float(x[1]) for x in splitlines])
    BB = np.array([[float(z) for z in x[2:]] for x in splitlines])

    # sort by confidence
    sorted_ind = np.argsort(-confidence)
    sorted_scores = np.sort(-confidence)
    BB = BB[sorted_ind, :]
    image_ids = [image_ids[x] for x in sorted_ind]

    # go down dets and mark TPs and FPs 以下为计算对比各参数
    nd = len(image_ids)
    tp = np.zeros(nd)
    fp = np.zeros(nd)
    for d in range(nd):
        R = class_recs[image_ids[d]]
        bb = BB[d, :].astype(float)
        ovmax = -np.inf
        BBGT = R['bbox'].astype(float)

        if BBGT.size > 0:
            # compute overlaps
            # intersection
            ixmin = np.maximum(BBGT[:, 0], bb[0])
            iymin = np.maximum(BBGT[:, 1], bb[1])
            ixmax = np.minimum(BBGT[:, 2], bb[2])
            iymax = np.minimum(BBGT[:, 3], bb[3])
            iw = np.maximum(ixmax - ixmin + 1., 0.)
            ih = np.maximum(iymax - iymin + 1., 0.)
            inters = iw * ih

            # union
            uni = ((bb[2] - bb[0] + 1.) * (bb[3] - bb[1] + 1.) +
                   (BBGT[:, 2] - BBGT[:, 0] + 1.) *
                   (BBGT[:, 3] - BBGT[:, 1] + 1.) - inters)

            overlaps = inters / uni
            ovmax = np.max(overlaps)
            jmax = np.argmax(overlaps)

        if ovmax > ovthresh:
            if not R['difficult'][jmax]:
                if not R['det'][jmax]:
                    tp[d] = 1.
                    R['det'][jmax] = 1
                else:
                    fp[d] = 1.
        else:
            fp[d] = 1.

    # compute precision recall
    fp = np.cumsum(fp)
    tp = np.cumsum(tp)
    rec = tp / float(npos)
    # avoid divide by zero in case the first detection matches a difficult
    # ground truth
    prec = tp / np.maximum(tp + fp, np.finfo(np.float64).eps)
    ap = voc_ap(rec, prec, use_07_metric)

    return rec, prec, ap

if __name__ == "__main__":
    results_path = "./datasets/score/detection"
    cls_result = os.listdir(results_path)

    AP = []
    for i in range(len(cls_result)):
        class_name = cls_result[i].split(".txt")[0]
        rec, prec, ap = voc_eval("./datasets/score/pred_out/{}.txt",
                                 "./datasets/score/Annotations/{}.xml",
                                 "./val.txt",
                                 class_name,
                                 '.')
        print("{} :\t {}".format(class_name, ap))
        AP.append(ap)
    #map = sum(AP)/len(AP)
    map = tuple(AP)
    print("***************************")
    print("mAP :\t {}".format(float(sum(map) / len(map))))

最终的测试结果,ncnn的map在46.8,原torch模型的精度在(一)里面已经说过了,为51.5,转为ncnn后map掉了4.7个点,这个误差还是蛮大的,但是看了很多大佬的结果,意思好像是掉不了这么多点,里面的原因就不得而知了!可能是本人后处理部分写的有问题,也可能是模型训练的时候迭代次数不够,这个torch模型从头只迭代了500个epoch,具体什么原因不知道,有知道的大佬,欢迎指点迷津啊!

最后放上ncnn的测试结果图:

yolov5 opencv可视化 yolov5 opencv 调用_yolov5 opencv可视化_05

这与torch模型测试得是同一张图,大家可以对比一下!

好了,到这里yolov5转ncnn就结束了,希望对大家能有帮助,毕竟里面的坑太多!!!!