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;
}
网上随便下载一个图片,测试,结果如下,终于大功告成了: