昇腾NPU推理YOLOV10目标检测(C++)

1.准备工作

基础环境:

需要安装NPU固件驱动,CANN的包在昇腾官网下载,安装最新版就可以了。

C++环境搭建链接:

cplusplus/environment/catenation_environmental_guidance_CN.md · Ascend/samples - Gitee.com

按照上面的链接,需要安装:presentagent, opencv, ffmpeg+acllite

其中ffmpeg和opencv建议手动编译,主要注意下面框中的FFMPEG是YES就行,否则有BUG。

我的opencv命令是这样的,贴出来仅供参考:
 

cmake -D CMAKE_BUILD_TYPE=Release -D CMAKE_INSTALL_PREFIX=/usr/local -D OPENCV_EXTRA_MODULES_PATH=../../opencv_contrib-4.4.0/modules -D WITH_FFMPEG=ON -D FFMPEG_INCLUDE_DIR=/usr/local/include -D FFMPEG_LIB_DIR=/usr/local/lib .. 

yoloV10推理:

模型转换

昇腾推理的模型是OM,需要pt->onnx,转om,再推理。

在yoloV10官网下载预训练好的pt模型,官网链接:
https://github.com/THU-MIG/yolov10

补充:这里可以直接下载预训练好的onn模型:

https://github.com/THU-MIG/yolov10/releases

onnx转om:

用atc工具转换步骤:

1.生效环境,2.atc命令转换,得到om。这里贴出命令。

 source /usr/local/Ascend/ascend-toolkit/set_env.sh

atc --model=yolov10m.onnx --framework=5 --output=yolov10m-detect --input_shape="images:1,3,640,640"  --soc_version=Ascend910B

模型推理

前面的准备工作完了,下面分析一下yolov10模型的输入和输出,

输入处理

模型输入1,3,640,640-->和V8一样,rbg,chw,归一化,(需要注意的是,yolov10、yolov8预处理都没有标准化,yolov5有,之气被这个坑拖了好久,印象很深刻。)

可以看看这个链接,讲的真的很好:

YOLOv10推理详解及部署实现_yolov10部署-CSDN博客

话不多说,上代码:

    if (frame.empty()) {
        ERROR_LOG("The input of v10detect model is empty!");
        return FAILED;
    }
    
    //原图的宽和高
    this->src_Width_ = frame.cols; 
    this->src_Height_ = frame.rows; 

    //================前处理==================
    cv::resize(frame, frame, cv::Size(v10detect_modelHeight_,v10detect_modelWidth_)); // RESIZE
    cv::cvtColor(frame, frame, cv::COLOR_BGR2RGB); // BGR2RGB 
    cv::Mat nchwMat(frame.rows, frame.cols * frame.channels(), CV_32FC1);
    uint8_t* ptMat = frame.ptr<uint8_t>(0);
    int height = frame.rows;
    int width = frame.cols;
    int channels = frame.channels();
    int area = height * width;

    for (int c = 0; c < channels; ++c)
    {
        for (int h = 0; h < height; ++h)
        {
            for (int w = 0; w < width; ++w)
            {
                int srcIdx = h * width * channels + w * channels + c;
                int dstIdx = c * area + h * width + w;
                float pixelValue = ptMat[srcIdx]/ 255.0f;
                nchwMat.at<float>(dstIdx) = pixelValue;
            }
        }
    }

推理

昇腾NPU的OM推理步骤大致是:

1.ACL资源初始化,只需要初始化一次

2.运行管理资源申请:

2.1指定Device(1、多),

2.2创建Context

2.3创建Stream。

3.模型加载:

3.1从系统文件加载模型

3.2获取模型描述信息,获取模型输入输出个数,查询模型输入输出字节大小aclmdlCreateDesc()aclmdlGetDesc(Desc_,Id_)  等API。

3.3根据输入输出的大小和数量aclmdlGetInputSizeByIndex(Decs_,Id_)等API,分别创建模型Device侧的输入输出数据集aclmdlCreateDataset()、创建数据缓冲区aclCreateDataBuffer(viod* inputbuffer_, size_t size_),有几个就创建几个、按照Index顺序添加数据到缓冲区aclmdlAddDatasetBuffer(Dataset_,buffer_)

4.执行推理:

4.1输入从Host侧拷贝到Device侧,

4.2推理:同步API:aclmdlExecute(Id,inputDataSet_,ootputDataset)

4.3输出从Device侧拷贝到Host侧。

5.资源释放:

5.1释放输入输出数据缓冲区,

5.2卸载模型,

5.3销毁stream

5.4销毁context

6.ACL资源初始化,只需要去初始化一次

输出处理

模型输出1,300,6-->6表示[left,top,right,bottom,confidence,classification](这里改动很好,再也不需要x,y,w,h转left,top,right,bottom了,不需要转置了,不需要NMS了,只需要仿射变换和删除阈值过低的对象就行,这里比以前版本的yolo的后处理省事太多了,感动。)。

从NPU芯片推理得到输出后,从NPU的device侧搬运到我们的Host侧,得到输出,在进行后处理,代码如下:
 

//计算仿射矩阵和仿射逆矩阵
void Objectv10Detect::Affine(uint32_t src_Width, uint32_t src_Height, uint32_t Dst_Width, uint32_t Dst_Height,
            std::vector<std::vector<float>>& Affine_Matrix, std::vector<std::vector<float>>& Inverse_Affine_Matrix) {
    float scalw = static_cast<float>(Dst_Width) / src_Width;
    float scalh = static_cast<float>(Dst_Height) / src_Height;

    // 仿射变换矩阵
    Affine_Matrix = {{scalw, 0, 0},{0, scalh, 0}};

    // 仿射逆矩阵
    Inverse_Affine_Matrix = {{1 / scalw, 0, 0},{0, 1 / scalh, 0}};
}


Result Objectv10Detect::Postprocess(aclmdlDataset* modelOutput,std::vector<std::vector<float>>& results)
{
    // Get feature vector data
    uint32_t dataSize = 0;
    float* featureData = (float*)GetInferenceOutputItem(dataSize, modelOutput,
                                                       0);

    if (featureData == nullptr) return FAILED;

    //输出是300,6
    results.resize(300);
    for (size_t i = 0; i < 300; ++i) {
        results[i].assign(featureData + i * 6, featureData + (i + 1) * 6);
    }

    if (v10detect_runMode_ == ACL_HOST) {
        delete[] ((float*)featureData);
    }

    //保留置信度大于阈值的
    std::vector<std::vector<float>> updated_results;
    for (const std::vector<float>& row : results) {
        if ( row[4] >= this->conf_th_) {
            updated_results.push_back(row);
        }
    }
    //更新results
    results=updated_results;
    
    //解码关键点
    std::vector<std::vector<float>> M, IM; // 声明仿射矩阵 M、仿射逆矩阵 IM
    Objectv10Detect::Affine(this->src_Width_, this->src_Height_, this->v10detect_modelWidth_, this->v10detect_modelHeight_, M, IM); // 计算仿射矩阵
    for (int i=0;i<results.size();++i)
    {
        // 提取信息:需要注意的是,v10输出不再是cx,cy,w,h而是修改为了left,top,right,bottom
        float left = results[i][0];//left 
        float top = results[i][1];//top  
        float right = results[i][2];//right  
        float bottom = results[i][3];//bottom  

        //仿射变换解码
        left = IM[0][0] * left + IM[0][2];
        top = IM[1][1] * top + IM[1][2];
        right = IM[0][0] * right + IM[0][2];
        bottom = IM[1][1] * bottom + IM[1][2];

        results[i][0]=left;
        results[i][1]=top;
        results[i][2]=right;
        results[i][3]=bottom;
    }
    
    return SUCCESS;
}

网上随便下载一个图片,测试,结果如下,终于大功告成了:

  • 28
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值