PaddleOCR-PP-OCRv4推理详解及部署实现(下)
目录
- 前言
- 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的使用与说明
相关文章:

PaddleOCR-PP-OCRv4推理详解及部署实现(下)
目录 前言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.txt4.2.2 配置Makefile 4.3 ONNX导出4.4 engine生成4.4.1 检测模型…...

【Golang 面试基础题】每日 5 题(二)
✍个人博客:Pandaconda-CSDN博客 📣专栏地址:http://t.csdnimg.cn/UWz06 📚专栏简介:在这个专栏中,我将会分享 Golang 面试中常见的面试题给大家~ ❤️如果有收获的话,欢迎点赞👍收藏…...

状态模式与订单状态机的实现
状态模式 状态模式(State Design Pattern)是一种行为设计模式,用于在对象的内部状态改变时改变其行为。这种模式可以将状态的变化封装在状态对象中,使得对象在状态变化时不会影响到其他代码,提升了代码的灵活性和可维…...

【MSP430】MSP430是什么?与STM32对比哪个性能更佳?
一、MSP430是什么? MSP430F5529LP是一款由德州仪器(TI)推出的16位微控制器单元(MCU)开发板,具有USB功能,内存配置为128KB闪存和8KB RAM,工作频率高达25MHz。 这款MCU以其高性能和多…...

Win11 操作(四)g502鼠标连接电脑不亮灯无反应
罗技鼠标连接电脑不亮灯无反应 前言 罗技技术💩中💩,贴吧技术神中神! 最近买了一个g502,结果买回来直接插上电脑连灯都不亮,问了一下客服。客服简单的让我换接口,又是下载ghub之类的…...

自定义QDialog使用详解
自定义QDialog使用详解 一、创建 QDialog 对象二、QDialog设置布局三、QDialog控制模态行为3.1 模态和非模态区别3.2 QDialog的模态使用四、使用 QDialogButtonBox五、处理对话框的结果六、使用 QDialog 的信号和槽QDialog是Qt框架中用于创建对话框窗口的基本类。对话框窗口通常…...

Pytorch使用教学2-Tensor的维度
在PyTorch使用的过程中,维度转换一定少不了。而PyTorch中有多种维度形变的方法,我们该在什么场景下使用什么方法呢? 本小节我们使用的张量如下: # 一维向量 t1 torch.tensor((1, 2)) # 二维向量 t2 torch.tensor([[1, 2, 3], …...

Interesting bug caused by getattr
题意:由 getattr 引起的有趣的 bug 问题背景: I try to train 8 CNN models with the same structures simultaneously. After training a model on a batch, I need to synchronize the weights of the feature extraction layers in other 7 models. …...

获取后端返回的图形验证码
如果后端返回的直接就是一个图形,有以下几种方式展示 一、直接在img标签里面的src里面调用接口 <img :src"dialogSrc" class"photo" alt"验证码图片" click"changeDialog">let orgUrl "/api/captcha" …...

奇怪的Excel单元格字体颜色格式
使用VBA代码修改单元格全部字符字体颜色是个很简单的任务,例如设置A1单元格字体颜色为红色。 Range("A1").Font.Color RGB(255, 0, 0)有时需要修改部分字符的颜色,如下图所示,将红色字符字体颜色修改为蓝色。代码将会稍许复杂&am…...

浅谈芯片验证中的仿真运行之 timescale (五)提防陷阱
一 仿真单位 timeunit 我们知道,当我们的代码中写清楚延时语句时,若不指定时间单位,则使用此单位; 例如: `timescale 1ns/1ps 则 #15 语句表示delay15ns; 例:如下代码,module a 的timescale是1ns/1ps, module b 是1ps/1ps; module b中的clk,频率是由输入参…...

uniapp 重置表单数据
场景 例如有数据如下 data(){return {queryForm:{value1:undefined,}} } 点击重置时候想重置form的数据, 操作 Object.assign(this.$data.queryForm, this.$options.data().queryForm); 就可以重置数据...

自学YOLO前置知识
YOLO前置知识 学习YOLO(You Only Look Once)之前,掌握一些前置知识会帮助你更好地理解和应用该技术。以下是一些推荐的前置知识领域: 计算机视觉基础: 图像处理:了解图像的基本处理技术,如滤波…...

Ubuntu18.04 编译报错: Could NOT find JNI
一、问题描述 Ubuntu18.04 编译报错 OpenCV 时,出现以下错误: Could NOT find JNI (missing: JAVA_INCLUDE_PATH JAVA_INCLUDE_PATH2 JAVA_AWT_INCLUDE_PATH)二、解决方法 先执行以下指令, export JAVA_HOME/usr/lib/jvm/java-8-openjdk-am…...

SQL labs-SQL注入(五,使用sqlmap进行cookie注入)
本文仅作为学习参考使用,本文作者对任何使用本文进行渗透攻击破坏不负任何责任。 引言: Cookie 是一些数据, 存储于你电脑上的文本文件中。当 web 服务器向浏览器发送 web 页面时,在连接关闭后,服务端不会记录用户的信息。Cookie…...

C语言——内存管理
目录 前言 一、内存分类 1. 栈区(Stack) 2. 堆区(Heap) 3. 数据段(Data Segment) 4. 代码段(Code Segment) 二、内存分配方式 1、静态内存分配 2、栈内分配 3、动态内存分配 &#x…...

Unity UGUI 之 Image和Rawimage
本文仅作学习笔记与交流,不作任何商业用途 本文包括但不限于unity官方手册,唐老狮,麦扣教程知识,引用会标记,如有不足还请斧正 1.Image是什么 Unity - 手册:图像 精灵格式是什么? 1.2重要参数 …...

Lua 语法学习笔记
Lua 语法学习笔记 安装(windows) 官网:https://www.lua.org/ 下载SDK 解压&修改名称(去除版本号) 将lua后面的版本号去掉,如lua54.exe->lua.ext 配置环境变量 数据类型 数据类型描述nil这个最简单,只有值n…...

Prometheus配置alertmanager告警
1、拉取镜像并运行 1、配置docker镜像源 [rootlocalhost ~]# vim /etc/docker/daemon.json {"registry-mirrors": ["https://dfaad.mirror.aliyuncs.com"] } [rootlocalhost ~]# systemctl daemon-reload [rootlocalhost ~]# systemctl restart docker2、…...

.net core 外观者设计模式 实现,多种支付选择
1,接口 /// <summary>/// Web页面支付/// </summary>public interface IWebPagePay{public WebPagePayResult CreatePay(string productName, string orderSn, string totalPrice);}2,实现接口 实现阿里支付 public class AliPagePay : IWe…...

Matlab 命令行窗口默认输出(异常)
目录 前言Matlab 先验知识1 异常输出的代码2 正常输出的代码 前言 在单独调试 Matlab 写的函数时出现不想出现的异常打印值,逐个注释排查才找到是 if elseif else 代码块的问题,会默认打印输出 else 部分第一个返回值的值(下方代码中的 P值&…...

LeetCode/NowCoder-二叉树OJ练习
励志冰檗:形容在清苦的生活环境中激励自己的意志。💓💓💓 目录 说在前面 题目一:单值二叉树 题目二:相同的树 题目三:对称二叉树 题目四:二叉树的前序遍历 题目五:另…...

PSINS工具箱函数介绍——insplot
insplot是一个绘图命令,用于将avp数据绘制出来 本文所述的代码需要基于PSINS工具箱,工具箱的讲解: PSINS初学指导基于PSINS的相关程序设计(付费专题)使用方法 此函数使用起来也很简单,直接后面加avp即可,如: insplot(avp);其中,avp为: 每行表示一个时间1~3列为姿态…...

Docker简单快速入门
1. 安装Docker 基于 Ubuntu 24.04 LTS 安装Docker 。 # 更新包索引并安装依赖包 sudo apt-get update sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common# 添加Docker的官方GPG密钥并存储在正确的位置 curl -fsSL https://mirror…...

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 图像物体的边界(200分) - 三语言AC题解(Python/Java/Cpp)
🍭 大家好这里是清隆学长 ,一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 💻 ACM银牌🥈| 多次AK大厂笔试 | 编程一对一辅导 👏 感谢大家的订阅➕ 和 喜欢💗 🍿 最新华为OD机试D卷目录,全、新、准,题目覆盖率达 95% 以上,支持题目在线…...

【无人机】低空经济中5G RedCap芯片的技术分析报告
1. 引言 图一. 新基建:低空经济 低空经济作为一种新兴的经济形态,涵盖了无人机、电动垂直起降飞行器(eVTOL)、低空物流、空中交通管理等多个领域。随着5G网络的普及和演进,5G RedCap(Reduced Capability&a…...

MongoDB教程(二十一):MongoDB大文件存储GridFS
💝💝💝首先,欢迎各位来到我的博客,很高兴能够在这里和您见面!希望您在这里不仅可以有所收获,同时也能感受到一份轻松欢乐的氛围,祝你生活愉快! 文章目录 引言一、GridFS…...

vue 搜索框
效果 创建搜索组件: 在Vue项目中,首先需要创建一个搜索组件。这个组件通常包含一个输入框和一个搜索按钮。使用v-model指令将输入框与组件的数据属性(如searchKeyword)进行双向绑定,以便获取用户输入的关键词。处理搜索…...

国科大作业考试资料-人工智能原理与算法-2024新编-第五次作业整理
1、本题以井字棋(圈与十字游戏)为例练习博弈中的基本概念。定义X_n为恰好有n个X而没有O 的行、列或者对角线的数目。同样O_n为正好有n 个O的行、列或者对角线的数目。效用函数给 X_3=1的棋局+1, 给O_3=1的棋局-1。所有其他终止状态效用值为0。对于非终止状态,使用线性的 …...

C++五子棋(未做完,但能玩,而且还不错)
代码放下面了,关于步骤介绍的我以后再完善一下。 #include<bits/stdc.h> #include<cstdio> #include<cstdlib> #include<ctime> #include<windows.h> #include<stdlib.h> #include<time.h> #define random(x) (rand()%x…...