ncnn模型部署——yolov5-6.0Pyotorch模型转onxx模型再转ncnn模型部署

欢迎学习ncnn系列相关文章,从训练、模型转换、精度分析,评估到部署Android端,推荐好资源:

一、YoloV5训练自己数据集并测试
二、ncnn编译和安装
三、onnx模型转ncnn模型并推理可执行程序(resnet18例子)
四、yolov5-6.0Pyotorch模型转onxx模型再转ncnn模型部署
五、训练自己YOLOv5模型转ncnn模型并部署到Android手机端


在这里插入图片描述

一、源码包准备

1.1 教程配套源码

本教程配套源码包获取方法为文章末扫码到公众号「视觉研坊」中回复关键字:yolov5 ncnn模型部署。获取下载链接。

源码包下载解压后的样子如下:

在这里插入图片描述

1.2 官网下载

1.2.1 yolov5包下载

在终端使用命令下载yolov5包:

git clone https://github.com/ultralytics/yolov5.git

在这里插入图片描述

1.2.2 切换到v6.0分支

在终端使用命令切换到v6.0分支:

git checkout v6.0

在这里插入图片描述

1.2.3 v6.0权重下载

在终端使用命令下载v6.0权重:

wget https://github.com/ultralytics/yolov5/releases/download/v6.0/yolov5s.pt

在这里插入图片描述

二、环境准备

本教程yolov5是Pytorch模型,所以先准备好一个Pytorch环境,我自己运行环境的版本号见下,仅供参考,其它版本也行。

安装本教程版本的命令:

pip install torch==1.10.1+cu111 torchvision==0.11.2+cu111 torchaudio==0.10.1 -f https://download.pytorch.org/whl/cu111/torch_stable.html

在这里插入图片描述

三、Pytorch模型转onnx模型

2.1 转onnx模型

在终端输入命令,export.py脚本在源码包中:

python export.py --weights yolov5s.pt --img 640 --batch 1 --train

上面命令解析:

命令中–train表示去除了yolov5的后处理,因为后处理是比较细碎的算子,避免NMS等操作一起导出,如果加上去会让整个网络变得很臃肿,拖累网络的推理速度。使用C++代码编写后处理脚步,后处理部分见5.1。

在这里插入图片描述

2.2 可能报错

2.2.1 报错

在转换时可能出现问题:urllib.error.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1131)>

2.2.2 问题分析

在尝试从 https://ultralytics.com/assets/Arial.ttf 下载 Arial 字体文件时发生的。这个文件被用于在图像上绘制文本。错误发生在 SSL 证书验证阶段,这可能是由于网络问题或者服务器的 SSL 证书问题。

2.2.3 解决办法

下载Arial.ttf文件,下载链接为:https://ultralytics.com/assets/Arial.ttf,下载好后放到指定路径。

在这里插入图片描述

2.3 算子融合与优化

2.3.1 安装onnxsim优化包

安装onnxsim包,在终端使用下面命令:

pip install onnxsim -i https://pypi.mirrors.ustc.edu.cn/simple/

2.3.2 优化

使用onnxsim进行算子融合和优化(网络结构算子精简),生成yolov5s-sim.onnx为onnx模型文件,命令:

python -m onnxsim yolov5s.onnx yolov5s-sim.onnx

在这里插入图片描述

四、onnx模型转ncnn模型

使用onnx2ncnn工具将onnx模型转为ncnn模型,会在model_param文件夹中生成ncnn模型两个文件,命令:

bin/onnx2ncnn model_param/yolov5s-sim.onnx model_param/yolov5s-6.0.param model_param/yolov5s-6.0.bin

在这里插入图片描述

4.1 修改动态尺寸输入

yolov5采用特征金字塔,一共用三个地方输出,输出的blob分别是output、375和400。另外我们要将reshape的参数进行修改,变成0=-1,这是为了支持动态尺寸输入。

上面生成的.param文件中参数:

在这里插入图片描述

修改输入图像为动态尺寸,yolov5和ncnn都支持动态尺寸输入,见下:

在这里插入图片描述

五、C++脚本

5.1 yolov5后处理

在前面2.1步骤中,转onnx模型时没加上后处理,所以将后处理部分在主脚本中用C++代码直接写出来。

5.1.1 图片缩放及尺寸调整

我们将图片输入尺寸统一设置为640。

对于原始尺寸为1080×810×3的图片,首先将其长边缩放到640,短边根据长边的缩放比例同步缩放,得到640×480×3的尺寸。

为了满足模型要求,我们需要将480向上取整到64的倍数,最终得到640×512×3的图片尺寸。

5.1.2 模型输出解析

经过模型处理,会得到三个尺度的输出,分别对应8倍、16倍和32倍下采样。

每个尺度的输出尺寸分别为:
80×64×[(5+cls)×3]
40×32×[(5+cls)×3]
20×16×[(5+cls)×3]

其中:
(5+cls)×3表示每个预测框包含的信息,包括5个预测坐标和置信度(5),以及类别数(cls)和锚框anchor数量(3)

5.1.3 预测框坐标转换

(tx, ty, pw, ph)表示预测框的四个坐标,分别代表中心点偏移量、宽高预测值,见下:

在这里插入图片描述
模型输出的预测框坐标(tx, ty, pw, ph)是相对于特征图的,需要将其转换到原始图片空间才能进行后续处理。

转换公式如下:

bx=(2⋅σ(tx)−0.5)+cx

by=(2⋅σ(ty)−0.5)+cy

bw=pw⋅(2⋅σ(tw))^2

bh=ph⋅(2⋅σ(th)^2

其中:

(bx, by)代表预测框中心点的预测值
(cx, cy)代表锚框中心点的坐标
(pw, ph)代表锚框的宽高
σ(·)代表 sigmoid 函数
tw, th 代表预测框宽高相对于锚框宽高的预测值

5.1.4 恢复原始尺寸

将预测框中心点坐标(bx, by)乘以下采样倍数,即可得到原始图片空间的中心点坐标。

将预测框宽高(bw, bh)分别乘以锚框的宽高,即可得到原始图片空间的宽高。

5.1.5 NMS后处理

将所有预测框进行非极大值抑制(NMS),得到最终的检测结果。

5.2 代码修改

使用源码包中yolov5s.cpp脚本中使用时需要修改的地方见下:

在这里插入图片描述

5.3 代码

下面是yolov5s.cpp脚本的代码,包含了模型加载,模型推理,后处理等部分,我有详细注释,学者认真看源码。


#include "layer.h"
#include "net.h"

#if defined(USE_NCNN_SIMPLEOCV)
#include "simpleocv.h"
#else
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#endif
#include <float.h>
#include <stdio.h>
#include <vector>
#include <iostream>

#define YOLOV5_V60 1 
void pretty_print(const ncnn::Mat& m)
{
    for (int q=0; q<m.c; q++)
    {
        const float* ptr = m.channel(q);
        for (int z=0; z<m.d; z++)
        {
            for (int y=0; y<m.h; y++)
            {
                for (int x=0; x<m.w; x++)
                {
                    printf("%f ", ptr[x]);
                }
                ptr += m.w;
                printf("\n");
            }
            printf("\n");
        }
        printf("------------------------\n");
    }
}
struct Object
{
    cv::Rect_<float> rect;
    int label;
    float prob;
};

static inline float intersection_area(const Object& a, const Object& b)
{
    cv::Rect_<float> inter = a.rect & b.rect;
    return inter.area();
}

static void qsort_descent_inplace(std::vector<Object>& faceobjects, int left, int right)
{
    int i = left;
    int j = right;
    float p = faceobjects[(left + right) / 2].prob;

    while (i <= j)
    {
        while (faceobjects[i].prob > p)
            i++;

        while (faceobjects[j].prob < p)
            j--;

        if (i <= j)
        {
            // swap
            std::swap(faceobjects[i], faceobjects[j]);

            i++;
            j--;
        }
    }

    #pragma omp parallel sections
    {
        #pragma omp section
        {
            if (left < j) qsort_descent_inplace(faceobjects, left, j);
        }
        #pragma omp section
        {
            if (i < right) qsort_descent_inplace(faceobjects, i, right);
        }
    }
}

static void qsort_descent_inplace(std::vector<Object>& faceobjects)
{
    if (faceobjects.empty())
        return;

    qsort_descent_inplace(faceobjects, 0, faceobjects.size() - 1);
}

static void nms_sorted_bboxes(const std::vector<Object>& faceobjects, std::vector<int>& picked, float nms_threshold, bool agnostic = false)
{
    picked.clear();

    const int n = faceobjects.size();

    std::vector<float> areas(n);
    for (int i = 0; i < n; i++)
    {
        areas[i] = faceobjects[i].rect.area();
    }

    for (int i = 0; i < n; i++)
    {
        const Object& a = faceobjects[i];

        int keep = 1;
        for (int j = 0; j < (int)picked.size(); j++)
        {
            const Object& b = faceobjects[picked[j]];

            if (!agnostic && a.label != b.label)
                continue;

            // intersection over union
            float inter_area = intersection_area(a, b);
            float union_area = areas[i] + areas[picked[j]] - inter_area;
            // float IoU = inter_area / union_area
            if (inter_area / union_area > nms_threshold)
                keep = 0;
        }

        if (keep)
            picked.push_back(i);
    }
}

static inline float sigmoid(float x)
{
    return static_cast<float>(1.f / (1.f + exp(-x)));
}

// 下面是后处理函数   把坐标返回到原来的结果
static void generate_proposals(const ncnn::Mat& anchors, int stride, const ncnn::Mat& in_pad, const ncnn::Mat& feat_blob, float prob_threshold, std::vector<Object>& objects)
{
    const int num_grid = feat_blob.h;

    int num_grid_x;
    int num_grid_y;
    if (in_pad.w > in_pad.h)
    {
        num_grid_x = in_pad.w / stride;
        num_grid_y = num_grid / num_grid_x;
    }
    else
    {
        num_grid_y = in_pad.h / stride;
        num_grid_x = num_grid / num_grid_y;
    }

    const int num_class = feat_blob.w - 5;

    const int num_anchors = anchors.w / 2;
	//遍历三个anchor                    遍历结果,要把每个框的坐标,及推理出来的结果转换到在图像中实际的中心点坐标和宽高,保证画出来的结果正确
    for (int q = 0; q < num_anchors; q++)
    {
        const float anchor_w = anchors[q * 2];
        const float anchor_h = anchors[q * 2 + 1];

        const ncnn::Mat feat = feat_blob.channel(q);
		//遍历每一个结果
        for (int i = 0; i < num_grid_y; i++)
        {
            for (int j = 0; j < num_grid_x; j++)
            {
                const float* featptr = feat.row(i * num_grid_x + j);
                float box_confidence = sigmoid(featptr[4]);
                if (box_confidence >= prob_threshold)    // 如果大于阈值,取最大的类别数,得分最高
                {
                    // find class index with max class score
                    int class_index = 0;
                    float class_score = -FLT_MAX;
                    for (int k = 0; k < num_class; k++)
                    {
                        float score = featptr[5 + k];
                        if (score > class_score)
                        {
                            class_index = k;
                            class_score = score;
                        }
                    }
                    float confidence = box_confidence * sigmoid(class_score);
                    if (confidence >= prob_threshold)
                    {
                        // yolov5/models/yolo.py Detect forward
                        // y = x[i].sigmoid()
                        // y[..., 0:2] = (y[..., 0:2] * 2. - 0.5 + self.grid[i].to(x[i].device)) * self.stride[i]  # xy
                        // y[..., 2:4] = (y[..., 2:4] * 2) ** 2 * self.anchor_grid[i]  # wh
						//下面的代码就是前面说的对坐标的处理,
                        //bx,by乘以下采样的倍数就对应到图片原始尺寸的坐标,bw和bh乘以anchor的宽和高就是图像原始尺度的宽高
                        float dx = sigmoid(featptr[0]);   // 坐标转换
                        float dy = sigmoid(featptr[1]);
                        float dw = sigmoid(featptr[2]);
                        float dh = sigmoid(featptr[3]);

                        float pb_cx = (dx * 2.f - 0.5f + j) * stride;    // stride是下采样倍数
                        float pb_cy = (dy * 2.f - 0.5f + i) * stride;

                        float pb_w = pow(dw * 2.f, 2) * anchor_w;   // anchor_w为宽
                        float pb_h = pow(dh * 2.f, 2) * anchor_h;   // anchor_h为高

                        float x0 = pb_cx - pb_w * 0.5f;
                        float y0 = pb_cy - pb_h * 0.5f;
                        float x1 = pb_cx + pb_w * 0.5f;
                        float y1 = pb_cy + pb_h * 0.5f;

                        Object obj;
                        obj.rect.x = x0;
                        obj.rect.y = y0;
                        obj.rect.width = x1 - x0;
                        obj.rect.height = y1 - y0;
                        obj.label = class_index;
                        obj.prob = confidence;

                        objects.push_back(obj);
                    }
                }
            }
        }
    }
}

static int detect_yolov5(const cv::Mat& bgr, std::vector<Object>& objects)
{
    ncnn::Net yolov5;        // 定义一个网络

    yolov5.opt.use_vulkan_compute = true;
    //加载模型的参数
    if (yolov5.load_param("model_param/yolov5s-6.0.param"))      // 加载网络的参数和数据 
        exit(-1);
    if (yolov5.load_model("model_param/yolov5s-6.0.bin"))
        exit(-1);

    const int target_size = 640;                        // 目标大小
    const float prob_threshold = 0.25f;                 // 预测结果的阈值
    const float nms_threshold = 0.45f;                  // nms非极大值阈值

    ///下面一大段都是图片的缩放/
    int img_w = bgr.cols;
    int img_h = bgr.rows;
    const int MAX_STRIDE = 64;
    // letterbox pad to multiple of MAX_STRIDE
    int w = img_w;
    int h = img_h;
    float scale = 1.f;
    //判断长边,然后做对应缩放
    if (w > h)   // 先判断哪边长,然后就把长边缩放到640
    {
        scale = (float)target_size / w;   // 短边根据长边的比例进行同步缩放
        w = target_size;
        h = h * scale;
    }
    else
    {
        scale = (float)target_size / h;
        h = target_size;
        w = w * scale;
    }

    ncnn::Mat in = ncnn::Mat::from_pixels_resize(bgr.data, ncnn::Mat::PIXEL_BGR2RGB, img_w, img_h, w, h);   // 为了对齐到64的倍数,对缩放结果进行填充

    //把wpad和hpad对齐到MAX_STRIDE
    //就是利用除法的截断原理
    //其次就是因为要上取整所以加了MAX_STRIDE - 1
    int wpad = (w + MAX_STRIDE - 1) / MAX_STRIDE * MAX_STRIDE - w;
    int hpad = (h + MAX_STRIDE - 1) / MAX_STRIDE * MAX_STRIDE - h;
    ncnn::Mat in_pad;
    /*比如w=640,h=480,MAX_STRIDE=64,那么wpad=(640+64-1)/64×64-640=0。hapd=(480+64-1)/64×64-640=543/64×64-640=8*64-480=32。注意这里是整数的除法,小数
    会被截断,为了上取整所以加了MAX_STRIDE - 1。*/

    

    //填充
    ncnn::copy_make_border(in, in_pad, hpad / 2, hpad - hpad / 2, wpad / 2, wpad - wpad / 2, ncnn::BORDER_CONSTANT, 114.f);
    //归一化
    const float norm_vals[3] = {1 / 255.f, 1 / 255.f, 1 / 255.f};
    in_pad.substract_mean_normalize(0, norm_vals);

    ncnn::Extractor ex = yolov5.create_extractor();
    std::cout<<in_pad.w<<"  "<<in_pad.h<<std::endl;
    ex.input("images", in_pad);

    std::vector<Object> proposals;

    /下面提取三个尺度的输出/

    //对8倍下采样的输出进行提取和处理
    {
        ncnn::Mat out;
        ex.extract("output", out);
        
        ncnn::Mat anchors(6);
        anchors[0] = 10.f;        // 如果是自己训练的网络,要记录下载在这里修改
        anchors[1] = 13.f;
        anchors[2] = 16.f;
        anchors[3] = 30.f;
        anchors[4] = 33.f;
        anchors[5] = 23.f;

        std::vector<Object> objects8;
        generate_proposals(anchors, 8, in_pad, out, prob_threshold, objects8);

        proposals.insert(proposals.end(), objects8.begin(), objects8.end());
    }

    //对16倍下采样的输出进行提取和处理
    {
        ncnn::Mat out;
        ex.extract("375", out);
        

        ncnn::Mat anchors(6);
        anchors[0] = 30.f;    
        anchors[1] = 61.f;
        anchors[2] = 62.f;
        anchors[3] = 45.f;
        anchors[4] = 59.f;
        anchors[5] = 119.f;

        std::vector<Object> objects16;
        generate_proposals(anchors, 16, in_pad, out, prob_threshold, objects16);

        proposals.insert(proposals.end(), objects16.begin(), objects16.end());
    }
    
    //对32倍下采样的输出进行提取和处理
    {
        ncnn::Mat out;

        ex.extract("400", out);
        
        ncnn::Mat anchors(6);
        anchors[0] = 116.f;
        anchors[1] = 90.f;
        anchors[2] = 156.f;
        anchors[3] = 198.f;
        anchors[4] = 373.f;
        anchors[5] = 326.f;

        std::vector<Object> objects32;
        generate_proposals(anchors, 32, in_pad, out, prob_threshold, objects32);

        proposals.insert(proposals.end(), objects32.begin(), objects32.end());
    }

    //nms前对所有bbox根据框的置信度排序
    qsort_descent_inplace(proposals);

    // apply nms with nms_threshold
    std::vector<int> picked;
    nms_sorted_bboxes(proposals, picked, nms_threshold);

    int count = picked.size();

    objects.resize(count);
    for (int i = 0; i < count; i++)
    {
        objects[i] = proposals[picked[i]];

        // adjust offset to original unpadded
        float x0 = (objects[i].rect.x - (wpad / 2)) / scale;
        float y0 = (objects[i].rect.y - (hpad / 2)) / scale;
        float x1 = (objects[i].rect.x + objects[i].rect.width - (wpad / 2)) / scale;
        float y1 = (objects[i].rect.y + objects[i].rect.height - (hpad / 2)) / scale;

        // clip
        x0 = std::max(std::min(x0, (float)(img_w - 1)), 0.f);
        y0 = std::max(std::min(y0, (float)(img_h - 1)), 0.f);
        x1 = std::max(std::min(x1, (float)(img_w - 1)), 0.f);
        y1 = std::max(std::min(y1, (float)(img_h - 1)), 0.f);

        objects[i].rect.x = x0;
        objects[i].rect.y = y0;
        objects[i].rect.width = x1 - x0;
        objects[i].rect.height = y1 - y0;
    }

    return 0;
}

static void draw_objects(const cv::Mat& bgr, const std::vector<Object>& objects)
{
    static const char* class_names[] = {
        "person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat", "traffic light",
        "fire hydrant", "stop sign", "parking meter", "bench", "bird", "cat", "dog", "horse", "sheep", "cow",
        "elephant", "bear", "zebra", "giraffe", "backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee",
        "skis", "snowboard", "sports ball", "kite", "baseball bat", "baseball glove", "skateboard", "surfboard",
        "tennis racket", "bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl", "banana", "apple",
        "sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut", "cake", "chair", "couch",
        "potted plant", "bed", "dining table", "toilet", "tv", "laptop", "mouse", "remote", "keyboard", "cell phone",
        "microwave", "oven", "toaster", "sink", "refrigerator", "book", "clock", "vase", "scissors", "teddy bear",
        "hair drier", "toothbrush"
    };   // coco的类别数

    cv::Mat image = bgr.clone();

    for (size_t i = 0; i < objects.size(); i++)
    {
        const Object& obj = objects[i];

        fprintf(stderr, "%d = %.5f at %.2f %.2f %.2f x %.2f\n", obj.label, obj.prob,
                obj.rect.x, obj.rect.y, obj.rect.width, obj.rect.height);

        cv::rectangle(image, obj.rect, cv::Scalar(255, 0, 0));

        char text[256];
        sprintf(text, "%s %.1f%%", class_names[obj.label], obj.prob * 100);

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

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

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

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

    cv::imshow("image", image);
    cv::waitKey(0);
}

int main(int argc, char** argv)
{
    if (argc != 2)
    {
        fprintf(stderr, "Usage: %s [imagepath]\n", argv[0]);    // 传入一个图片的路径参数
        return -1;
    }

    const char* imagepath = argv[1];

    cv::Mat m = cv::imread(imagepath, 1);
    if (m.empty())
    {
        fprintf(stderr, "cv::imread %s failed\n", imagepath);    // 读取图片
        return -1;
    }

    std::vector<Object> objects;
    detect_yolov5(m, objects);   // 推理测试图片

    draw_objects(m, objects);    // 调用画框函数

    return 0;
}

六、构建和编译

6.1 CMakeLists.txt修改

下面说到的源码路径,指的是安装源码ncnn时的路径,关于安装源码ncnn见其它博文:ncnn编译和安装

在这里插入图片描述

6.2 生成构建文件

生成构建文件的命令为:

cmake ..

cmake … 是一个在当前目录下执行 CMake 的命令,它会查找上一级目录(由 … 表示)中的 CMakeLists.txt 文件,并生成构建文件。这些构建文件可以被 make、ninja 或其他构建工具使用,来编译和链接你的项目。

在这里插入图片描述

6.3 编译

在上面构建文件基础上执行编译命令:

make

bin目录下的yolov5s就是编译生成的可执行程序。

在这里插入图片描述

七、推理可执行程序

7.1 执行

通过cd命令到根目录下,执行推理命令:

bin/yolov5s image/kerbside.jpg

在这里插入图片描述

7.2 输出

下面是上面运行可执行程序后输出的结果:

在这里插入图片描述

在这里插入图片描述

八、总结

以上就是yolov5-6.0Pyotorch模型转onxx模型再转ncnn模型部署的详细过程,通过上面方法得到可执行程序后就可以直接拷贝到其它边缘设备上部署了。

本教程中有些参数解析没有上一篇详细,建议学者先看我上一篇博文:resnet18 ncnn模型推理

总结不易,多多支持,谢谢!

感谢您阅读到最后!关注公众号「视觉研坊」,获取干货教程、实战案例、技术解答、行业资讯!

  • 18
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
yolov5-lite模型换的过程可以分为以下几个步骤。首先,将模型换为ONNX格式,可以使用命令"./onnx2ncnn"来进行换。例如,"./onnx2ncnn yolov5ss-sim.onnx yolov5-lite.param yolov5-lite.bin"。\[1\]接下来,可以使用命令"./ncnnoptimize"对换后的模型进行优化,例如"./ncnnoptimize yolov5-lite.param yolov5-lite.bin yolov5-lite-opt.param yolov5-lite-opt.bin 65536"。\[1\]在换完成后,可以使用Oenvino框架进行推理,而不需要依赖pytorch等库。可以直接复制"利用Oenvino推理"的代码来进行推理。\[2\]最后,可以使用命令"./ncnn2table"将模型换为table格式,例如"./ncnn2table yolov5-lite-opt.param yolov5-lite-opt.bin imagelist.txt yolov5-lite.table mean=\[104,117,123\] norm=\[0.017,0.017,0.017\] shape=\[640,640,3\] pixel=BGR thread=8 method=kl"。\[3\]如果需要进行int8量化,可以使用命令"./ncnn2int8"来进行换,例如"./ncnn2int8 yolov5-lite-opt.param yolov5-lite-opt.bin yolov5-ite-opt-int8.param yolov5-lite-opt-int8.bin yolov5-lite.table"。\[3\]这样就完成了yolov5-lite模型换过程。 #### 引用[.reference_title] - *1* *3* [YOLOv5-Lite:NCNN+Int8部署和量化,树莓派也可实时](https://blog.csdn.net/weixin_45829462/article/details/119787840)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [(YOLOv5-lite)-ONNX模型换及Openvino推理](https://blog.csdn.net/weixin_45930948/article/details/124359754)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

视觉研坊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值