目录
- 前言
- 1. 检测模型
- 1.1 预处理
- 1.2 后处理
- 1.3 推理
- 2. 方向分类器模型
- 2.1 预处理
- 2.2 后处理
- 2.3 推理
- 3. 识别模型
- 3.1 预处理
- 3.2 后处理
- 3.3 推理
- 4. PP-OCRv4部署
- 4.1 源码下载
- 4.2 环境配置
- 4.2.1 配置CMakeLists.txt
- 4.2.2 配置Makefile
- 4.3 ONNX导出
- 4.4 engine生成
- 4.4.1 检测模型
- 4.4.2 方向分类器模型
- 4.4.3 识别模型
- 4.5 源码修改
- 4.6 运行
- 5. 补充说明
- 结语
- 下载链接
- 参考
前言
接着上篇文章 PaddleOCR-PP-OCRv4推理详解及部署实现(中) 来讲,在上篇文章中我们已经梳理完 PP-OCRv4 模型中三个模块的前后处理,这篇文章我们将在 C++ 上实现并利用 tensorRT 推理得到结果。若有问题欢迎各位看官批评指正😄
repo:https://github.com/Melody-Zhou/tensorRT_Pro-YOLOv8
1. 检测模型
1.1 预处理
上篇文章我们提到过检测模型的预处理主要做了如下操作:
- 1. resize
- 2. /255.0 减均值除标准差
- 3. c,h,w->h,w,c
- 4. h,w,c->b,c,h,w
那其实它就是做一个 resize 操作,再加上对每个像素值除以 255.0,减去均值除以标准差,这一系列的操作我们可以用一个 CUDA 核函数来完成,由于核函数中是对每个像素进行操作,因此非常容易实现除 255,减均值除标准差等操作,具体代码如下:
__global__ void resize_bilinear_and_normalize_kernel(uint8_t* src, int src_line_size, int src_width, int src_height, float* dst, int dst_width, int dst_height, float sx, float sy, Norm norm, int edge
){int position = blockDim.x * blockIdx.x + threadIdx.x;if (position >= edge) return;int dx = position % dst_width;int dy = position / dst_width;float src_x = (dx + 0.5f) * sx - 0.5f;float src_y = (dy + 0.5f) * sy - 0.5f;float c0, c1, c2;int y_low = floorf(src_y);int x_low = floorf(src_x);int y_high = limit(y_low + 1, 0, src_height - 1);int x_high = limit(x_low + 1, 0, src_width - 1);y_low = limit(y_low, 0, src_height - 1);x_low = limit(x_low, 0, src_width - 1);int ly = rint((src_y - y_low) * INTER_RESIZE_COEF_SCALE);int lx = rint((src_x - x_low) * INTER_RESIZE_COEF_SCALE);int hy = INTER_RESIZE_COEF_SCALE - ly;int hx = INTER_RESIZE_COEF_SCALE - lx;int w1 = hy * hx, w2 = hy * lx, w3 = ly * hx, w4 = ly * lx;float* pdst = dst + dy * dst_width + dx * 3;uint8_t* v1 = src + y_low * src_line_size + x_low * 3;uint8_t* v2 = src + y_low * src_line_size + x_high * 3;uint8_t* v3 = src + y_high * src_line_size + x_low * 3;uint8_t* v4 = src + y_high * src_line_size + x_high * 3;c0 = resize_cast(w1 * v1[0] + w2 * v2[0] + w3 * v3[0] + w4 * v4[0]);c1 = resize_cast(w1 * v1[1] + w2 * v2[1] + w3 * v3[1] + w4 * v4[1]);c2 = resize_cast(w1 * v1[2] + w2 * v2[2] + w3 * v3[2] + w4 * v4[2]);if(norm.channel_type == ChannelType::Invert){float t = c2;c2 = c0; c0 = t;}if(norm.type == NormType::MeanStd){c0 = (c0 * norm.alpha - norm.mean[0]) / norm.std[0];c1 = (c1 * norm.alpha - norm.mean[1]) / norm.std[1];c2 = (c2 * norm.alpha - norm.mean[2]) / norm.std[2];}else if(norm.type == NormType::AlphaBeta){c0 = c0 * norm.alpha + norm.beta;c1 = c1 * norm.alpha + norm.beta;c2 = c2 * norm.alpha + norm.beta;}int area = dst_width * dst_height;float* pdst_c0 = dst + dy * dst_width + dx;float* pdst_c1 = pdst_c0 + area;float* pdst_c2 = pdst_c1 + area;*pdst_c0 = c0;*pdst_c1 = c1;*pdst_c2 = c2;
}
Note:代码 Copy 自 preprocess_kernel.cu#L49
预处理部分比较简单直接拿杜老师现成的代码就行,下面我们看后处理部分
1.2 后处理
上篇文章我们分析过检测模型的后处理主要做了如下操作:
- boxes_from_bitmap:从给定的二值化图
_bitmap
中提取检测到的文本框并缩放 - filter_tag_det_res:对检测到的文本框进行过滤
- sorted_boxes:对过滤的文本框排序
我们先看如何从预测结果中提取检测到的文本框,部分代码如下所示:
static void boxes_from_bitmap(const Mat& pred, const Mat& bitmap, vector<vector<vector<int>>>& box_array, float box_thresh, float unclip_ratio, int min_size, int max_candidates
){int width = bitmap.cols;int height = bitmap.rows;vector<vector<cv::Point>> contours;vector<cv::Vec4i> hierarchy;cv::findContours(bitmap, contours, hierarchy, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);int num_contours = contours.size() >= max_candidates ? max_candidates : contours.size();Mat contour_image;cv::cvtColor(bitmap, contour_image, cv::COLOR_GRAY2BGR);// for(auto& contour : contours){// vector<vector<cv::Point>> single_contour = {contour};// cv::drawContours(contour_image, single_contour, -1, cv::Scalar(0, 0, 255), 2);// }// cv::imwrite("contour_image.jpg", contour_image);vector<vector<vector<int>>> boxes;for(auto& contour : contours){if(contour.size() <= min_size)continue;vector<vector<float>> array;float sside;auto box = cv::minAreaRect(contour);tie(array, sside) = get_mini_boxes(box);if(sside < min_size)continue;float score = box_score(pred, array);if(score < box_thresh)continue;auto points = box_unclip(array, unclip_ratio);// differenceif(points.size.height < 1.001 & points.size.width < 1.001)continue;vector<vector<float>> cliparray;tie(cliparray, sside) = get_mini_boxes(points);if(sside < min_size + 2)continue;int dest_width = pred.cols;int dest_height = pred.rows;vector<vector<int>> intcliparray;intcliparray.reserve(4);float x_scale = float(dest_width) / float(width);float y_scale = float(dest_height) / float(height);for(int i = 0; i < 4; ++i){int x = int(clamp(std::roundf(cliparray[i][0] * x_scale), 0.0f, float(dest_width)));int y = int(clamp(std::roundf(cliparray[i][1] * y_scale), 0.0f, float(dest_height)));intcliparray.push_back({x, y});}box_array.emplace_back(intcliparray);}
}
和我们上篇文章分析的一样,先通过 opencv 的 findContours 函数提取轮廓,接着从每个轮廓生成最小包围框,检查并过滤掉一些不符合条件的框,此外 score 也是通过计算框区域内的平均分数来评估,最后将符合条件的文本框的坐标映射回原图上即可,更多细节大家可以查看 app_ppocr/postprocess_det.cpp
Note:代码 Copy 自 postprocess_op.cpp#L246
检测模型的后处理代码比较繁琐,从二值化图中提取到文本框时使用的辅助函数比较多,其中对给定文本框做扩展处理的函数 unclip 需要使用到 PaddleOCR 官方提供的 clipper 库,大家感兴趣的可以看看:deploy/cpp_infer/src/clipper.cpp
1.3 推理
推理部分我们交给 tensorRT 就行,值得注意的是我们将检测模型的输入的宽高固定在 960x960,主要是因为 tensorRT 处理动态宽高比较麻烦,因此博主干脆将宽高固定
2. 方向分类器模型
2.1 预处理
上篇文章我们提到过方向分类器模型的预处理主要做了如下操作:
- 1. resize
- 2. /255.0,将像素值归一化到 [0,1]
- 3. 减均值(0.5)除标准差(0.5),将像素值转换到 [-1,1]
- 4. 填充
那它和检测模型的预处理差不多,区别在于 resize 尺寸并不固定,它是首先将高度 resize 到 48,再根据 ratio 缩放宽度,如果缩放后宽度大于 192 则只缩放到 192,这种情况就和检测模型的预处理一模一样,如果小于则正常缩放,此时剩余部分填充 0
因此我们可以将前面检测模型的核函数略微修改下即可,具体代码如下:
__global__ void resize_normalize_image_kernel(uint8_t* src, int src_line_size, int src_width, int src_height, float* dst, int dst_width, int dst_height, float sx, float sy, int resized_w, Norm norm, int edge
){int position = blockDim.x * blockIdx.x + threadIdx.x;if (position >= edge) return;int dx = position % dst_width;int dy = position / dst_width;if(dx >= resized_w){int area = dst_width * dst_height;float* pdst_c0 = dst + dy * dst_width + dx;float* pdst_c1 = pdst_c0 + area;float* pdst_c2 = pdst_c1 + area;*pdst_c0 = 0.0f;*pdst_c1 = 0.0f;*pdst_c2 = 0.0f;return;}float src_x = (dx + 0.5f) * sx - 0.5f;float src_y = (dy + 0.5f) * sy - 0.5f;float c0, c1, c2;int y_low = floorf(src_y);int x_low = floorf(src_x);int y_high = limit(y_low + 1, 0, src_height - 1);int x_high = limit(x_low + 1, 0, src_width - 1);y_low = limit(y_low, 0, src_height - 1);x_low = limit(x_low, 0, src_width - 1);int ly = rint((src_y - y_low) * INTER_RESIZE_COEF_SCALE);int lx = rint((src_x - x_low) * INTER_RESIZE_COEF_SCALE);int hy = INTER_RESIZE_COEF_SCALE - ly;int hx = INTER_RESIZE_COEF_SCALE - lx;int w1 = hy * hx, w2 = hy * lx, w3 = ly * hx, w4 = ly * lx;float* pdst = dst + dy * dst_width + dx * 3;uint8_t* v1 = src + y_low * src_line_size + x_low * 3;uint8_t* v2 = src + y_low * src_line_size + x_high * 3;uint8_t* v3 = src + y_high * src_line_size + x_low * 3;uint8_t* v4 = src + y_high * src_line_size + x_high * 3;c0 = resize_cast(w1 * v1[0] + w2 * v2[0] + w3 * v3[0] + w4 * v4[0]);c1 = resize_cast(w1 * v1[1] + w2 * v2[1] + w3 * v3[1] + w4 * v4[1]);c2 = resize_cast(w1 * v1[2] + w2 * v2[2] + w3 * v3[2] + w4 * v4[2]); if(norm.channel_type == ChannelType::Invert){float t = c2;c2 = c0; c0 = t;}if(norm.type == NormType::MeanStd){c0 = (c0 * norm.alpha - norm.mean[0]) / norm.std[0];c1 = (c1 * norm.alpha - norm.mean[1]) / norm.std[1];c2 = (c2 * norm.alpha - norm.mean[2]) / norm.std[2];}else if(norm.type == NormType::AlphaBeta){c0 = c0 * norm.alpha + norm.beta;c1 = c1 * norm.alpha + norm.beta;c2 = c2 * norm.alpha + norm.beta;}int area = dst_width * dst_height;float* pdst_c0 = dst + dy * dst_width + dx;float* pdst_c1 = pdst_c0 + area;float* pdst_c2 = pdst_c1 + area;*pdst_c0 = c0;*pdst_c1 = c1;*pdst_c2 = c2;
}
其中我们新增 resized_w 参数传入,根据与 resized_w 比较将剩余部分的像素填充 0 即可
2.2 后处理
上篇文章我们分析过方向分类器模型的后处理主要是根据分类标签和得分判断是否需要旋转图像,处理比较简单
具体代码如下:
float* parray = output->cpu<float>(ibatch);
auto& job = fetch_jobs[ibatch];
auto& image_based_cls = job.output;// get idx
int argmax_idx = parray[0] > parray[1] ? 0 : 1;
// get score
float max_value = std::max(parray[0], parray[1]);
if(argmax_idx == 1 && max_value > cls_thresh_){image_based_cls = 1;
}else{image_based_cls = 0;
}
这里的 parray 为模型预测结果,维度为 bx2,首先获取到预测概率最大的 index 和 score,根据 index 的类型和 score 大小判断是否需要旋转,对于需要旋转的图像返回 1,不需要的返回 0
2.3 推理
同样推理部分我们交给 tensorRT 就行,方向分类器的输入宽高是固定的(48x192),因此不需要做额外的处理
3. 识别模型
3.1 预处理
上篇文章我们提到过识别模型的预处理和方向分类器一模一样,只是 resize 的目标尺寸不同而已,因此代码可以直接沿用方向分类器的预处理代码,这边博主不再赘述
3.2 后处理
识别模型的后处理也比较简单,主要是将模型输出转换为可读的文本标签,具体代码如下:
float* parray = output->cpu<float>(ibatch);
auto& job = fetch_jobs[ibatch];
auto& image_based_text = job.output;// batch, max_chars, vocab_size
// parry->1x80x6625
int argmax_idx;
int count = 0;
int last_index = 0;
float score = 0.0f;
float max_value = 0.0f;for(int i = 0; i < max_chars; ++i){// get idxargmax_idx = int(argmax(&parray[i * vocab_size], &parray[(i + 1) * vocab_size]));// get scoremax_value = float(*std::max_element(&parray[i * vocab_size], &parray[(i + 1) * vocab_size]));if(argmax_idx > 0 && (!(i > 0 && argmax_idx == last_index))){score += max_value;count += 1;image_based_text.text += label_list_[argmax_idx];}last_index = argmax_idx;
}
if(count != 0){score /= count;
}
image_based_text.score = score;
这里的 parray 为模型预测结果,维度为 bx80x6625,首先获取到预测概率最大的 index 和 score,根据 index 去字符集中取对应的字符,score 则是所有字符预测概率值的平均,最后将每个文本框的字符拼接成文本返回即可
3.3 推理
同样推理部分我们交给 tensorRT 就行,识别模型的高度是固定的为 48,宽度并不固定,需要根据输入文本框的长度进行 resize,不过博主这里为了方便处理统一固定到 640。注意对于不同的输入宽度,模型的预测结果大小也不尽相同,对于输入宽度为 640 的文本,模型预测的最大字符数为 80,而对于输入宽度为 320 的文本,模型预测的最大字符数则只有 40
至此,我们简单分析了 C++ 上 PP-OCRv4 各个模块的前后处理过程,下面我们将完整的走一遍流程
4. PP-OCRv4部署
博主新建了一个仓库 tensorRT_Pro-YOLOv8,该仓库基于 shouxieai/tensorRT_Pro,并进行了调整以支持 YOLOv8 的各项任务,目前已支持分类、检测、分割、姿态点估计任务。
下面我们就来具体看看如何利用 tensorRT_Pro-YOLOv8 这个 repo 完成 PP-OCRv4 的推理。
4.1 源码下载
tensorRT_Pro-YOLOv8 的代码可以直接从 GitHub 官网上下载,源码下载地址是 https://github.com/Melody-Zhou/tensorRT_Pro-YOLOv8,Linux 下代码克隆指令如下:
git clone https://github.com/Melody-Zhou/tensorRT_Pro-YOLOv8.git
也可手动点击下载,点击右上角的 Code
按键,将代码下载下来。至此整个项目就已经准备好了。也可以点击 here 下载博主准备好的源代码(注意代码下载于 2024/7/24 日,若有改动请参考最新)
4.2 环境配置
需要使用的软件环境有 TensorRT、CUDA、cuDNN、OpenCV、Protobuf,所有软件环境的安装可以参考 Ubuntu20.04软件安装大全,这里不再赘述,需要各位看官自行配置好相关环境😄,外网访问较慢,这里提供下博主安装过程中的软件安装包下载链接 Baidu Drive【pwd:yolo】🚀🚀🚀
tensorRT_Pro-YOLOv8 提供 CMakeLists.txt 和 Makefile 两种方式编译,二者选一即可
4.2.1 配置CMakeLists.txt
主要修改五处
1. 修改第 13 行,修改 OpenCV 路径
set(OpenCV_DIR "/usr/local/include/opencv4/")
2. 修改第 15 行,修改 CUDA 路径
set(CUDA_TOOLKIT_ROOT_DIR "/usr/local/cuda-11.6")
3. 修改第 16 行,修改 cuDNN 路径
set(CUDNN_DIR "/usr/local/cudnn8.4.0.27-cuda11.6")
4. 修改第 17 行,修改 tensorRT 路径
set(TENSORRT_DIR "/home/jarvis/lean/TensorRT-8.6.1.6")
5. 修改第 20 行,修改 protobuf 路径
set(PROTOBUF_DIR "/home/jarvis/protobuf")
4.2.2 配置Makefile
主要修改五处
1. 修改第 4 行,修改 protobuf 路径
lean_protobuf := /home/jarvis/protobuf
2. 修改第 5 行,修改 tensorRT 路径
lean_tensor_rt := /home/jarvis/lean/TensorRT-8.6.1.6
3. 修改第 6 行,修改 cuDNN 路径
lean_cudnn := /usr/local/cudnn8.4.0.27-cuda11.6
4. 修改第 7 行,修改 OpenCV 路径
lean_opencv := /usr/local
5. 修改第 8 行,修改 CUDA 路径
lean_cuda := /usr/local/cuda-11.6
4.3 ONNX导出
导出细节可以查看 PaddleOCR-PP-OCRv4推理详解及部署实现(上),这边不再赘述。记得将导出的 ONNX 模型放在 tensorRT_Pro-YOLOv8/workspace 文件夹下,大家也可以点击 here 下载博主导出好的 ONNX
4.4 engine生成
4.4.1 检测模型
在 workspace 下新建 ppocr_det_build.sh,其内容如下:
#! /usr/bin/bashTRTEXEC=/home/jarvis/lean/TensorRT-8.6.1.6/bin/trtexecexport LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/jarvis/lean/TensorRT-8.6.1.6/lib${TRTEXEC} \--onnx=ppocr_det.sim.onnx \--minShapes=images:1x3x960x960 \--optShapes=images:1x3x960x960 \--maxShapes=images:8x3x960x960 \--memPoolSize=workspace:2048 \--saveEngine=ppocr_det.sim.FP16.trtmodel \--fp16 \> ppocr_det.log 2>&1
其中需要修改 TRTEXEC 的路径为你自己的路径,终端执行如下指令:
cd tensorRT_Pro-YOLOv8/workspace
bash ppocr_det_build.sh
执行后等待一段时间会在当前文件夹生成 ppocr_det.sim.FP16.trtmodel 即检测模型引擎文件,注意终端看不到任何日志打印输出,这是因为博主将 tensorRT 输出的日志信息保存到了 ppocr_det.log 文件中,大家也可以删除保存直接在终端显示相关日志信息
4.4.2 方向分类器模型
在 workspace 下新建 ppocr_cls_build.sh,其内容如下:
#! /usr/bin/bashTRTEXEC=/home/jarvis/lean/TensorRT-8.6.1.6/bin/trtexecexport LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/jarvis/lean/TensorRT-8.6.1.6/lib${TRTEXEC} \--onnx=ppocr_cls.sim.onnx \--minShapes=images:1x3x48x192 \--optShapes=images:1x3x48x192 \--maxShapes=images:8x3x48x192 \--memPoolSize=workspace:2048 \--saveEngine=ppocr_cls.sim.FP16.trtmodel \--fp16 \> ppocr_cls.log 2>&1
其中需要修改 TRTEXEC 的路径为你自己的路径,终端执行如下指令:
cd tensorRT_Pro-YOLOv8/workspace
bash ppocr_cls_build.sh
执行后等待一段时间会在当前文件夹生成 ppocr_cls.sim.FP16.trtmodel 即方向分类器模型引擎文件
4.4.3 识别模型
在 workspace 下新建 ppocr_rec_build.sh,其内容如下:
#! /usr/bin/bashTRTEXEC=/home/jarvis/lean/TensorRT-8.6.1.6/bin/trtexecexport LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/jarvis/lean/TensorRT-8.6.1.6/lib${TRTEXEC} \--onnx=ppocr_rec.sim.onnx \--minShapes=images:1x3x48x640 \--optShapes=images:1x3x48x640 \--maxShapes=images:8x3x48x640 \--memPoolSize=workspace:2048 \--saveEngine=ppocr_rec.sim.FP16.trtmodel \--fp16 \> ppocr_rec.log 2>&1
其中需要修改 TRTEXEC 的路径为你自己的路径,终端执行如下指令:
cd tensorRT_Pro-YOLOv8/workspace
bash ppocr_rec_build.sh
执行后等待一段时间会在当前文件夹生成 ppocr_rec.sim.FP16.trtmodel 即识别模型引擎文件
4.5 源码修改
Note:如果想推理自己训练的模型还需要修改下源码,PP-OCRv4 模型的推理代码主要在 app_ppocr.cpp 文件中,源码修改较简单,主要有以下几点:
- app_ppocr.cpp 118 行,修改自己想识别的图片路径
- app_ppocr.cpp 120-122 行,修改自己生成的 engine 引擎文件名
4.6 运行
OK!现在源码修改好了,Makefile 编译文件也搞定了,engine 模型也准备好了,可以编译运行了,直接在终端执行如下指令即可:
make ppocr -j64
推理过程如下图所示:
推理成功后会在 workspace 文件夹下保存 result_ocr.jpg 即推理好的图片
模型推理效果如下图所示:
可以看到与 Paddle 和 ONNX 的推理还是有些差别的,部分文本未识别出来,例如左上角的 0、1 未识别出来,还有右下角的字符 了
被识别成了 7
,那这可能是博主梳理的预处理或者后处理未与 python 版本完全对齐导致的
我们再多看看几张图的推理效果:
可以看到这几张图的效果还行
OK,以上就是使用 tensorRT_Pro-YOLOv8 推理 PP-OCRv4 的大致流程,若有问题,欢迎各位看官批评指正。
5. 补充说明
-
1. 前后处理代码主要参考自官方实现:https://github.com/PaddlePaddle/PaddleOCR/tree/main/deploy/cpp_infer
-
2. 由于博主能力有限,在梳理模型前后处理过程中难免有所遗漏,特别是检测模型的后处理,某些细节可能并未完全实现,这可能是影响精度对齐的原因
-
3. PP-OCRv4 整个推理框架的实现有借鉴 CUDA-BEVFusion,但并不多,因为代码还没有仔细调试看过,韩君老师之前有简单讲过 CUDA-BEVFusion 推理框架设计模式,大家感兴趣的可以看看:八. 实战:CUDA-BEVFusion部署分析-学习CUDA-BEVFusion推理框架设计模式
-
4. 某些后处理是否可以考虑核函数来实现呢,特别是文本检测部分,如果不能将全部的后处理放在 GPU 上完成,那是不是需要考虑 host 和 device 之间 memcpy 的 overhead
-
5. 高版本 TensorRT 对于 getMaxBatchSize 方法弃用了,因此它始终返回的是 1,我们也可以从日志中观察到这个警告,此时我们可以通过查询 profile 的维度来获取 maxBatchSize
// ====== src/tensorRT/infer/trt_infer.cpp =====// ====== trt_infer.cpp 261行 ===== // int max_batchsize = context->engine_->getMaxBatchSize(); nvinfer1::Dims maxDims = context->engine_->getProfileDimensions(0, 0, nvinfer1::OptProfileSelector::kMAX); int max_batchsize = maxDims.d[0];// ====== trt_infer.cpp 422行 ===== // return this->context_->engine_->getMaxBatchSize(); nvinfer1::Dims maxDims = this->context_->engine_->getProfileDimensions(0, 0, nvinfer1::OptProfileSelector::kMAX); int max_batchsize = maxDims.d[0]; return max_batchsize;
-
6. 在可视化中博主遇到了 OpenCV 不支持中文绘制的问题,因此这边直接把 cuOSD 库拿过来用了,它支持 stb_truetype 和 pango-cairo 后端,允许通过 TFF 或者使用 font-family 读取字体,大家对 cuOSD 感兴趣的话可以看看:cuOSD(CUDA On-Screen Display Library)库的学习
-
7. cuOSD 库 pango-cairo 后端需要包含 pango、glib、cairo 头文件,可以通过
dkpg -L
指令查找相关头文件,若没有找到需要自行安装# 1. 查找相关库 dpkg -L libpango1.0-dev libglib2.0-dev libcairo2-dev# 2. 安装 sudo apt-get update sudo apt-get install libpango1.0-dev libglib2.0-dev libcairo2-dev# 3. 头文件路径 /usr/include/pango-1.0 /usr/include/glib-2.0 /usr/lib/x86_64-linux-gnu/glib-2.0/include /usr/include/cairo
-
8. 方向分类器模块默认不开启,如果需要使用将 OcrParameter 中的 use_angle_cls 参数设置为 true 即可
-
9. 如果使用 TRT::compile 接口生成 engine 模型会出现 HardSwish 算子解析问题,这是因为默认的 onnxparser 版本比较老不支持 HardSwish 算子,可以自己手动替换,具体可以参考:RT-DETR推理详解及部署实现。另外低版本也可以通过写插件来支持,刚好杜老师有写 HSwish 的插件,我们直接用就行,在使用之前需要将所有的 HardSwish 的 op_type 修改为 Plugin,并且需要新增 name 属性名字为 HSwish,这个我们之前讲过,大家感兴趣的可以看下:LayerNorm Plugin的使用与说明
结语
博主在这里通过分析 PP-OCRv4 模型各个模块的导出,前后处理分析以及部署对 OCR 相关的任务有进一步的了解,同时把一些学过的知识内容又回顾了一遍,总归还是有所收获的🤗。感谢各位看到最后,创作不易,读后有收获的看官请帮忙点个👍⭐️
最后大家如果觉得 tensorRT_Pro-YOLOv8 这个 repo 对你有帮助的话,不妨点个 ⭐️ 支持一波,这对博主来说非常重要,感谢各位🙏。
下载链接
- 软件安装包下载链接【提取码:yolo】🚀🚀🚀
- 源代码、权重下载链接【提取码:20tr】
参考
- PaddleOCR-PP-OCRv4推理详解及部署实现(上)
- PaddleOCR-PP-OCRv4推理详解及部署实现(中)
- https://github.com/shouxieai/tensorRT_Pro
- https://github.com/Melody-Zhou/tensorRT_Pro-YOLOv8
- https://github.com/PaddlePaddle/PaddleOCR/blob/main/deploy/cpp_infer
- cuOSD(CUDA On-Screen Display Library)库的学习
- 八. 实战:CUDA-BEVFusion部署分析-学习CUDA-BEVFusion推理框架设计模式
- RT-DETR推理详解及部署实现
- LayerNorm Plugin的使用与说明