当前位置: 首页 > news >正文

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 题(二)

✍个人博客&#xff1a;Pandaconda-CSDN博客 &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/UWz06 &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 Golang 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞&#x1f44d;收藏…...

状态模式与订单状态机的实现

状态模式 状态模式&#xff08;State Design Pattern&#xff09;是一种行为设计模式&#xff0c;用于在对象的内部状态改变时改变其行为。这种模式可以将状态的变化封装在状态对象中&#xff0c;使得对象在状态变化时不会影响到其他代码&#xff0c;提升了代码的灵活性和可维…...

【MSP430】MSP430是什么?与STM32对比哪个性能更佳?

一、MSP430是什么&#xff1f; MSP430F5529LP是一款由德州仪器&#xff08;TI&#xff09;推出的16位微控制器单元&#xff08;MCU&#xff09;开发板&#xff0c;具有USB功能&#xff0c;内存配置为128KB闪存和8KB RAM&#xff0c;工作频率高达25MHz。 这款MCU以其高性能和多…...

Win11 操作(四)g502鼠标连接电脑不亮灯无反应

罗技鼠标连接电脑不亮灯无反应 前言 罗技技术&#x1f4a9;中&#x1f4a9;&#xff0c;贴吧技术神中神&#xff01; 最近买了一个g502&#xff0c;结果买回来直接插上电脑连灯都不亮&#xff0c;问了一下客服。客服简单的让我换接口&#xff0c;又是下载ghub之类的&#xf…...

自定义QDialog使用详解

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

Pytorch使用教学2-Tensor的维度

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

Interesting bug caused by getattr

题意&#xff1a;由 getattr 引起的有趣的 bug 问题背景&#xff1a; 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. …...

获取后端返回的图形验证码

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

奇怪的Excel单元格字体颜色格式

使用VBA代码修改单元格全部字符字体颜色是个很简单的任务&#xff0c;例如设置A1单元格字体颜色为红色。 Range("A1").Font.Color RGB(255, 0, 0)有时需要修改部分字符的颜色&#xff0c;如下图所示&#xff0c;将红色字符字体颜色修改为蓝色。代码将会稍许复杂&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的数据&#xff0c; 操作 Object.assign(this.$data.queryForm, this.$options.data().queryForm); 就可以重置数据...

自学YOLO前置知识

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

Ubuntu18.04 编译报错: Could NOT find JNI

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

SQL labs-SQL注入(五,使用sqlmap进行cookie注入)

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

C语言——内存管理

目录 前言 一、内存分类 1. 栈区&#xff08;Stack&#xff09; 2. 堆区&#xff08;Heap&#xff09; 3. 数据段&#xff08;Data Segment&#xff09; 4. 代码段&#xff08;Code Segment&#xff09; 二、内存分配方式 1、静态内存分配 2、栈内分配 3、动态内存分配 &#x…...

Unity UGUI 之 Image和Rawimage

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

Lua 语法学习笔记

Lua 语法学习笔记 安装(windows) 官网&#xff1a;https://www.lua.org/ 下载SDK 解压&修改名称&#xff08;去除版本号&#xff09; 将lua后面的版本号去掉&#xff0c;如lua54.exe->lua.ext 配置环境变量 数据类型 数据类型描述nil这个最简单&#xff0c;只有值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&#xff0c;接口 /// <summary>/// Web页面支付/// </summary>public interface IWebPagePay{public WebPagePayResult CreatePay(string productName, string orderSn, string totalPrice);}2&#xff0c;实现接口 实现阿里支付 public class AliPagePay : IWe…...

设计模式和设计原则回顾

设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...

.Net框架,除了EF还有很多很多......

文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...

MongoDB学习和应用(高效的非关系型数据库)

一丶 MongoDB简介 对于社交类软件的功能&#xff0c;我们需要对它的功能特点进行分析&#xff1a; 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具&#xff1a; mysql&#xff1a;关系型数据库&am…...

UDP(Echoserver)

网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法&#xff1a;netstat [选项] 功能&#xff1a;查看网络状态 常用选项&#xff1a; n 拒绝显示别名&#…...

srs linux

下载编译运行 git clone https:///ossrs/srs.git ./configure --h265on make 编译完成后即可启动SRS # 启动 ./objs/srs -c conf/srs.conf # 查看日志 tail -n 30 -f ./objs/srs.log 开放端口 默认RTMP接收推流端口是1935&#xff0c;SRS管理页面端口是8080&#xff0c;可…...

CocosCreator 之 JavaScript/TypeScript和Java的相互交互

引擎版本&#xff1a; 3.8.1 语言&#xff1a; JavaScript/TypeScript、C、Java 环境&#xff1a;Window 参考&#xff1a;Java原生反射机制 您好&#xff0c;我是鹤九日&#xff01; 回顾 在上篇文章中&#xff1a;CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...

【7色560页】职场可视化逻辑图高级数据分析PPT模版

7种色调职场工作汇报PPT&#xff0c;橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版&#xff1a;职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...

vulnyx Blogger writeup

信息收集 arp-scan nmap 获取userFlag 上web看看 一个默认的页面&#xff0c;gobuster扫一下目录 可以看到扫出的目录中得到了一个有价值的目录/wordpress&#xff0c;说明目标所使用的cms是wordpress&#xff0c;访问http://192.168.43.213/wordpress/然后查看源码能看到 这…...

【从零开始学习JVM | 第四篇】类加载器和双亲委派机制(高频面试题)

前言&#xff1a; 双亲委派机制对于面试这块来说非常重要&#xff0c;在实际开发中也是经常遇见需要打破双亲委派的需求&#xff0c;今天我们一起来探索一下什么是双亲委派机制&#xff0c;在此之前我们先介绍一下类的加载器。 目录 ​编辑 前言&#xff1a; 类加载器 1. …...

掌握 HTTP 请求:理解 cURL GET 语法

cURL 是一个强大的命令行工具&#xff0c;用于发送 HTTP 请求和与 Web 服务器交互。在 Web 开发和测试中&#xff0c;cURL 经常用于发送 GET 请求来获取服务器资源。本文将详细介绍 cURL GET 请求的语法和使用方法。 一、cURL 基本概念 cURL 是 "Client URL" 的缩写…...