純C++實(shí)現(xiàn)PP-OCRv5文字識(shí)別的全流程
一、效果先行
cd /home/michah/桌面/paddle_inference && ./build/ocr_demo build/640.png --text-only cd /home/michah/桌面/paddle_inference && ./build/ocr_demo build/640.png
一張CSDN個(gè)人主頁(yè)截圖,識(shí)別結(jié)果:


IP屬地:陜西省加入CSDN時(shí)間:2020-01-31 查看詳細(xì)資料 weixin_46244623 碼齡6年 131566總訪問(wèn)196原創(chuàng)248粉絲60關(guān)注 公眾號(hào)·懶人程序
檢測(cè)耗時(shí) 665ms,識(shí)別耗時(shí) 1453ms,CPU上跑的,不用GPU。
二、準(zhǔn)備工作
2.1 下載 Paddle Inference C++ 推理庫(kù)
去Paddle Inference官網(wǎng)下載C++預(yù)測(cè)庫(kù)(Linux CPU版本即可),解壓后目錄結(jié)構(gòu)長(zhǎng)這樣:
https://paddle-inference-lib.bj.bcebos.com/3.0.0/cxx_c/Linux/CPU/gcc8.2_avx_mkl/paddle_inference.tgz tar -xf paddle_inference-linux.tgz
paddle_inference/ ├── paddle/ │ ├── include/ # 頭文件 │ └── lib/ # .so 動(dòng)態(tài)庫(kù) ├── third_party/ # 第三方依賴(MKL、oneDNN等) └── version.txt
2.2 下載 PP-OCRv5 Server 模型
PP-OCRv5有mobile和server兩個(gè)版本,server精度更高。需要下載檢測(cè)模型和識(shí)別模型兩個(gè):
cd paddle_inference/models # 檢測(cè)模型 wget https://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/PP-OCRv5_server_det_infer.tar tar xf PP-OCRv5_server_det_infer.tar # 識(shí)別模型 wget https://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/PP-OCRv5_server_rec_infer.tar tar xf PP-OCRv5_server_rec_infer.tar
解壓后每個(gè)模型目錄里有三個(gè)文件:
inference.json— PIR格式模型結(jié)構(gòu)(PP-OCRv5新格式)inference.pdiparams— 模型參數(shù)inference.yml— 預(yù)處理配置
2.3 提取字典文件
識(shí)別模型的字典嵌在inference.yml里,用Python提取出來(lái):
import yaml
with open('models/PP-OCRv5_server_rec_infer/inference.yml') as f:
d = yaml.safe_load(f)
chars = d['PostProcess']['character_dict']
with open('models/ppocr_v5_dict.txt', 'w') as f:
for c in chars:
f.write(str(c) + '\n')
print(f'字典:{len(chars)} 字符') # 18383 字符
2.4 下載 stb 頭文件
我們用stb替代OpenCV來(lái)加載和縮放圖片,只需要兩個(gè)頭文件:
mkdir -p third_party/stb cd third_party/stb # stb_image.h(圖片加載) wget https://gitee.com/mirrors/stb/raw/master/stb_image.h # stb_image_resize2.h(圖片縮放) wget https://gitee.com/mirrors/stb/raw/master/stb_image_resize2.h
三、CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(PaddleInferenceDemo LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Paddle Inference SDK 路徑(當(dāng)前目錄即為 SDK 根目錄)
set(PADDLE_INFER_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
set(PADDLE_INCLUDE_DIR "${PADDLE_INFER_DIR}/paddle/include")
set(PADDLE_LIB_DIR "${PADDLE_INFER_DIR}/paddle/lib")
set(THIRD_PARTY_DIR "${PADDLE_INFER_DIR}/third_party")
# 頭文件目錄
include_directories(${PADDLE_INCLUDE_DIR})
include_directories(${THIRD_PARTY_DIR}/install/mklml/include)
include_directories(${THIRD_PARTY_DIR}/install/onednn/include)
include_directories(${THIRD_PARTY_DIR}/install/gflags/include)
include_directories(${THIRD_PARTY_DIR}/install/glog/include)
include_directories(${THIRD_PARTY_DIR}/install/xxhash/include)
include_directories(${THIRD_PARTY_DIR}/install/cryptopp/include)
include_directories(${THIRD_PARTY_DIR}/install/utf8proc/include)
include_directories(${THIRD_PARTY_DIR}/install/yaml-cpp/include)
include_directories(${THIRD_PARTY_DIR}/install/protobuf/include)
include_directories(${THIRD_PARTY_DIR}/stb)
include_directories(${PADDLE_INFER_DIR})
# 鏈接庫(kù)目錄
link_directories(${PADDLE_LIB_DIR})
link_directories(${THIRD_PARTY_DIR}/install/mklml/lib)
link_directories(${THIRD_PARTY_DIR}/install/onednn/lib)
link_directories(${THIRD_PARTY_DIR}/install/openvino/intel64)
link_directories(${THIRD_PARTY_DIR}/install/tbb/lib)
# oneDNN 庫(kù)只有 libdnnl.so.3,需要用絕對(duì)路徑
find_library(DNNL_LIB NAMES dnnl
PATHS ${THIRD_PARTY_DIR}/install/onednn/lib NO_DEFAULT_PATH)
if(NOT DNNL_LIB)
set(DNNL_LIB "${THIRD_PARTY_DIR}/install/onednn/lib/libdnnl.so.3")
endif()
# 構(gòu)建目標(biāo)
add_executable(ocr_demo ocr_demo.cpp)
# 使用 RPATH(非 RUNPATH),確保間接依賴(如OpenVINO)也能找到 .so
set(CMAKE_EXE_LINKER_FLAGS "-Wl,--disable-new-dtags")
set(ALL_RPATH
"${PADDLE_LIB_DIR};${THIRD_PARTY_DIR}/install/mklml/lib;${THIRD_PARTY_DIR}/install/onednn/lib;${THIRD_PARTY_DIR}/install/openvino/intel64;${THIRD_PARTY_DIR}/install/tbb/lib")
target_link_libraries(ocr_demo
paddle_inference phi_core phi common
${DNNL_LIB} mklml_intel iomp5 tbb
pthread dl rt m
)
set_target_properties(ocr_demo PROPERTIES
BUILD_RPATH "${ALL_RPATH}"
INSTALL_RPATH "${ALL_RPATH}"
)
幾個(gè)坑提前說(shuō):
- 必須C++17:代碼用了結(jié)構(gòu)化綁定
auto [cx, cy] = ...,C++14編譯不過(guò) - RPATH不能用RUNPATH:Paddle Inference間接依賴了OpenVINO的so,RUNPATH不傳遞給間接依賴,必須加
--disable-new-dtags走RPATH - oneDNN沒(méi)有l(wèi)ibdnnl.so:只有
libdnnl.so.3,直接-ldnnl鏈接失敗,需要用絕對(duì)路徑
四、核心代碼 ocr_demo.cpp
完整代碼約470行,實(shí)現(xiàn)了:圖片加載 → 檢測(cè)預(yù)處理 → DB文本檢測(cè) → DB后處理 → 裁剪文本區(qū)域 → 識(shí)別預(yù)處理 → CTC解碼 → 輸出結(jié)果
4.1 頭文件和結(jié)構(gòu)體
// PP-OCRv5 C++ 推理 Demo (檢測(cè) + 識(shí)別 全流程)
// 無(wú) OpenCV 依賴,使用 stb_image 加載圖片
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include "stb_image_resize2.h"
#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstring>
#include <fstream>
#include <iostream>
#include <numeric>
#include <queue>
#include <sstream>
#include <string>
#include <vector>
#include "paddle/include/paddle_inference_api.h"
struct Box {
int x0, y0, x1, y1; // 在原圖上的坐標(biāo)
float score;
};
4.2 加載字典
static std::vector<std::string> LoadDict(const std::string& path) {
std::vector<std::string> dict;
std::ifstream ifs(path, std::ios::in);
if (!ifs.is_open()) {
std::cerr << "無(wú)法打開(kāi)字典: " << path << std::endl;
return dict;
}
std::string line;
while (std::getline(ifs, line)) {
if (!line.empty() && line.back() == '\r') line.pop_back();
dict.push_back(line);
}
dict.push_back(" "); // CTC blank + space
return dict;
}
4.3 檢測(cè)預(yù)處理
PP-OCRv5檢測(cè)模型輸入要求:
- BGR順序
- ImageNet歸一化:mean=[0.485, 0.456, 0.406],std=[0.229, 0.224, 0.225]
- 長(zhǎng)邊縮放到1280,寬高對(duì)齊到32的倍數(shù)
struct DetInput {
std::vector<float> data; // CHW normalized
int resized_w, resized_h;
int orig_w, orig_h;
float ratio_w, ratio_h;
};
static bool PrepareDetInput(const unsigned char* img_rgb, int w, int h,
int resize_long, DetInput& out) {
out.orig_w = w;
out.orig_h = h;
float ratio = 1.0f;
if (std::max(w, h) > resize_long) {
ratio = static_cast<float>(resize_long) / std::max(w, h);
}
int new_w = static_cast<int>(w * ratio);
int new_h = static_cast<int>(h * ratio);
new_w = std::max(32, (new_w + 31) / 32 * 32);
new_h = std::max(32, (new_h + 31) / 32 * 32);
out.resized_w = new_w;
out.resized_h = new_h;
out.ratio_w = static_cast<float>(w) / new_w;
out.ratio_h = static_cast<float>(h) / new_h;
std::vector<unsigned char> resized(new_w * new_h * 3);
stbir_resize_uint8_linear(img_rgb, w, h, 0,
resized.data(), new_w, new_h, 0, STBIR_RGB);
const float mean[3] = {0.485f, 0.456f, 0.406f};
const float std_[3] = {0.229f, 0.224f, 0.225f};
out.data.resize(3 * new_h * new_w);
for (int c = 0; c < 3; ++c) {
int src_c = 2 - c; // RGB→BGR
for (int y = 0; y < new_h; ++y) {
for (int x = 0; x < new_w; ++x) {
float pixel = resized[(y * new_w + x) * 3 + src_c] / 255.0f;
pixel = (pixel - mean[c]) / std_[c];
out.data[c * new_h * new_w + y * new_w + x] = pixel;
}
}
}
return true;
}
4.4 DB后處理
檢測(cè)模型輸出的是概率圖(probability map),需要做后處理:二值化 → 連通組件標(biāo)記 → 求bbox → unclip擴(kuò)展
// BFS 連通組件標(biāo)記
static void FindConnectedComponents(const std::vector<uint8_t>& binary,
int w, int h,
std::vector<int>& labels,
int& num_labels) {
labels.assign(w * h, 0);
num_labels = 0;
const int dx[] = {1, -1, 0, 0};
const int dy[] = {0, 0, 1, -1};
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
if (binary[y * w + x] == 0 || labels[y * w + x] != 0) continue;
++num_labels;
std::queue<std::pair<int, int>> q;
q.push({x, y});
labels[y * w + x] = num_labels;
while (!q.empty()) {
auto [cx, cy] = q.front();
q.pop();
for (int d = 0; d < 4; ++d) {
int nx = cx + dx[d], ny = cy + dy[d];
if (nx >= 0 && nx < w && ny >= 0 && ny < h &&
binary[ny * w + nx] != 0 && labels[ny * w + nx] == 0) {
labels[ny * w + nx] = num_labels;
q.push({nx, ny});
}
}
}
}
}
}
static std::vector<Box> DBPostProcess(const float* prob_map, int map_w,
int map_h, float thresh,
float box_thresh, float unclip_ratio,
float ratio_w, float ratio_h,
int orig_w, int orig_h) {
// 1) 二值化
std::vector<uint8_t> binary(map_w * map_h);
for (int i = 0; i < map_w * map_h; ++i) {
binary[i] = (prob_map[i] >= thresh) ? 1 : 0;
}
// 2) 連通組件
std::vector<int> labels;
int num_labels = 0;
FindConnectedComponents(binary, map_w, map_h, labels, num_labels);
// 3) 對(duì)每個(gè)組件求 bbox 和平均 score
std::vector<Box> boxes;
if (num_labels == 0) return boxes;
std::vector<int> min_x(num_labels + 1, map_w);
std::vector<int> min_y(num_labels + 1, map_h);
std::vector<int> max_x(num_labels + 1, 0);
std::vector<int> max_y(num_labels + 1, 0);
std::vector<float> sum_score(num_labels + 1, 0);
std::vector<int> count(num_labels + 1, 0);
for (int y = 0; y < map_h; ++y) {
for (int x = 0; x < map_w; ++x) {
int l = labels[y * map_w + x];
if (l == 0) continue;
min_x[l] = std::min(min_x[l], x);
min_y[l] = std::min(min_y[l], y);
max_x[l] = std::max(max_x[l], x);
max_y[l] = std::max(max_y[l], y);
sum_score[l] += prob_map[y * map_w + x];
count[l]++;
}
}
for (int l = 1; l <= num_labels; ++l) {
if (count[l] < 3) continue;
float avg_score = sum_score[l] / count[l];
if (avg_score < box_thresh) continue;
int bw = max_x[l] - min_x[l];
int bh = max_y[l] - min_y[l];
float expand_x = bw * (unclip_ratio - 1.0f) * 0.5f;
float expand_y = bh * (unclip_ratio - 1.0f) * 0.5f;
Box box;
box.x0 = std::max(0, static_cast<int>((min_x[l] - expand_x) * ratio_w));
box.y0 = std::max(0, static_cast<int>((min_y[l] - expand_y) * ratio_h));
box.x1 = std::min(orig_w - 1, static_cast<int>((max_x[l] + expand_x) * ratio_w));
box.y1 = std::min(orig_h - 1, static_cast<int>((max_y[l] + expand_y) * ratio_h));
box.score = avg_score;
if (box.x1 - box.x0 < 3 || box.y1 - box.y0 < 3) continue;
boxes.push_back(box);
}
std::sort(boxes.begin(), boxes.end(),
[](const Box& a, const Box& b) { return a.y0 < b.y0; });
return boxes;
}
4.5 識(shí)別預(yù)處理
識(shí)別模型輸入要求:
- BGR順序
- 歸一化:mean=[0.5, 0.5, 0.5],std=[0.5, 0.5, 0.5]
- 高度固定48,寬度按裁剪區(qū)域等比例縮放(最大320)
- 裁剪時(shí)上下左右加padding,提升邊緣字符識(shí)別率
static std::vector<float> PrepareRecInput(const unsigned char* img_rgb,
int img_w, int img_h,
const Box& box,
int rec_h, int& rec_w) {
int crop_w = box.x1 - box.x0;
int crop_h = box.y1 - box.y0;
if (crop_w <= 0 || crop_h <= 0) return {};
// 上下左右各擴(kuò)展 padding
int pad = std::max(2, crop_h / 4);
int x0 = std::max(0, box.x0 - pad);
int y0 = std::max(0, box.y0 - pad);
int x1 = std::min(img_w, box.x1 + pad);
int y1 = std::min(img_h, box.y1 + pad);
crop_w = x1 - x0;
crop_h = y1 - y0;
if (crop_w <= 0 || crop_h <= 0) return {};
std::vector<unsigned char> crop(crop_w * crop_h * 3);
for (int y = 0; y < crop_h; ++y) {
for (int x = 0; x < crop_w; ++x) {
int src_idx = ((y0 + y) * img_w + (x0 + x)) * 3;
int dst_idx = (y * crop_w + x) * 3;
crop[dst_idx + 0] = img_rgb[src_idx + 0];
crop[dst_idx + 1] = img_rgb[src_idx + 1];
crop[dst_idx + 2] = img_rgb[src_idx + 2];
}
}
float wh_ratio = static_cast<float>(crop_w) / crop_h;
int target_w = std::min(rec_w, std::max(1, static_cast<int>(rec_h * wh_ratio)));
std::vector<unsigned char> resized(target_w * rec_h * 3);
stbir_resize_uint8_linear(crop.data(), crop_w, crop_h, 0,
resized.data(), target_w, rec_h, 0, STBIR_RGB);
const float mean[3] = {0.5f, 0.5f, 0.5f};
const float std_[3] = {0.5f, 0.5f, 0.5f};
std::vector<float> data(3 * rec_h * target_w);
for (int c = 0; c < 3; ++c) {
int src_c = 2 - c; // RGB→BGR
for (int y = 0; y < rec_h; ++y) {
for (int x = 0; x < target_w; ++x) {
float pixel = resized[(y * target_w + x) * 3 + src_c] / 255.0f;
pixel = (pixel - mean[c]) / std_[c];
data[c * rec_h * target_w + y * target_w + x] = pixel;
}
}
}
rec_w = target_w;
return data;
}
4.6 CTC 解碼
PP-OCRv5的識(shí)別模型輸出已經(jīng)是softmax之后的概率,不需要再做softmax(這是一個(gè)容易踩的坑,否則置信度全是0.00):
static std::string CTCDecode(const float* output, int time_steps, int class_num,
const std::vector<std::string>& dict,
float& confidence) {
std::string result;
int prev = 0;
float total_conf = 0.0f;
int char_count = 0;
for (int t = 0; t < time_steps; ++t) {
const float* probs = output + t * class_num;
int best_idx = 0;
float best_val = probs[0];
for (int c = 1; c < class_num; ++c) {
if (probs[c] > best_val) {
best_val = probs[c];
best_idx = c;
}
}
if (best_idx != 0 && best_idx != prev) {
int dict_idx = best_idx - 1;
if (dict_idx >= 0 && dict_idx < static_cast<int>(dict.size())) {
result += dict[dict_idx];
}
total_conf += best_val; // 直接用概率,不要再softmax!
char_count++;
}
prev = best_idx;
}
confidence = (char_count > 0) ? (total_conf / char_count) : 0.0f;
return result;
}
4.7 主函數(shù)(串聯(lián)全流程)
int main(int argc, char* argv[]) {
std::string image_path = "models/test.jpg";
std::string det_model = "models/PP-OCRv5_server_det_infer/inference.json";
std::string det_params = "models/PP-OCRv5_server_det_infer/inference.pdiparams";
std::string rec_model = "models/PP-OCRv5_server_rec_infer/inference.json";
std::string rec_params = "models/PP-OCRv5_server_rec_infer/inference.pdiparams";
std::string dict_path = "models/ppocr_v5_dict.txt";
int num_threads = 4;
bool text_only = false;
// 解析參數(shù)
for (int i = 1; i < argc; ++i) {
if (std::string(argv[i]) == "--text-only") text_only = true;
}
std::vector<std::string> pos_args;
for (int i = 1; i < argc; ++i) {
if (std::string(argv[i]).substr(0, 2) != "--") pos_args.push_back(argv[i]);
}
if (pos_args.size() >= 1) image_path = pos_args[0];
if (pos_args.size() >= 2) dict_path = pos_args[1];
if (pos_args.size() >= 3) num_threads = std::atoi(pos_args[2].c_str());
std::cout << " PP-OCRv5 C++ 推理 Demo" << std::endl;
std::cout << " Paddle: " << paddle_infer::GetVersion() << std::endl;
// ========== 加載圖片 ==========
int img_w, img_h, img_c;
unsigned char* img_rgb = stbi_load(image_path.c_str(), &img_w, &img_h, &img_c, 3);
if (!img_rgb) {
std::cerr << "無(wú)法加載圖片: " << image_path << std::endl;
return -1;
}
// ========== 加載字典 ==========
auto dict = LoadDict(dict_path);
if (dict.empty()) return -1;
// ========== 1. 文本檢測(cè) ==========
DetInput det_in;
PrepareDetInput(img_rgb, img_w, img_h, 1280, det_in);
paddle_infer::Config config;
config.SetModel(det_model, det_params);
config.DisableGpu();
config.SetCpuMathLibraryNumThreads(num_threads);
config.SwitchIrOptim(true);
config.EnableMKLDNN();
config.EnableNewIR(true); // PP-OCRv5 PIR格式必須開(kāi)啟!
auto predictor = paddle_infer::CreatePredictor(config);
auto input_names = predictor->GetInputNames();
auto input_t = predictor->GetInputHandle(input_names[0]);
input_t->Reshape({1, 3, det_in.resized_h, det_in.resized_w});
input_t->CopyFromCpu(det_in.data.data());
auto t0 = std::chrono::high_resolution_clock::now();
predictor->Run();
auto t1 = std::chrono::high_resolution_clock::now();
double det_ms = std::chrono::duration<double, std::milli>(t1 - t0).count();
auto output_names = predictor->GetOutputNames();
auto output_t = predictor->GetOutputHandle(output_names[0]);
auto shape = output_t->shape();
int map_h = shape[2], map_w = shape[3];
std::vector<float> prob_map(map_h * map_w);
output_t->CopyToCpu(prob_map.data());
auto boxes = DBPostProcess(prob_map.data(), map_w, map_h,
0.3f, 0.6f, 1.5f,
det_in.ratio_w, det_in.ratio_h,
img_w, img_h);
// ========== 2. 文本識(shí)別 ==========
paddle_infer::Config rec_config;
rec_config.SetModel(rec_model, rec_params);
rec_config.DisableGpu();
rec_config.SetCpuMathLibraryNumThreads(num_threads);
rec_config.SwitchIrOptim(true);
rec_config.EnableMKLDNN();
rec_config.EnableNewIR(true);
auto rec_predictor = paddle_infer::CreatePredictor(rec_config);
const int REC_H = 48;
const int REC_W = 320;
for (size_t i = 0; i < boxes.size(); ++i) {
auto& box = boxes[i];
int actual_w = REC_W;
auto rec_data = PrepareRecInput(img_rgb, img_w, img_h,
box, REC_H, actual_w);
if (rec_data.empty()) continue;
auto rec_input_names = rec_predictor->GetInputNames();
auto rec_input_t = rec_predictor->GetInputHandle(rec_input_names[0]);
rec_input_t->Reshape({1, 3, REC_H, actual_w});
rec_input_t->CopyFromCpu(rec_data.data());
rec_predictor->Run();
auto rec_output_names = rec_predictor->GetOutputNames();
auto rec_out_t = rec_predictor->GetOutputHandle(rec_output_names[0]);
auto rec_shape = rec_out_t->shape();
int time_steps = rec_shape[1];
int class_num = rec_shape[2];
std::vector<float> rec_output(time_steps * class_num);
rec_out_t->CopyToCpu(rec_output.data());
float confidence = 0.0f;
std::string text = CTCDecode(rec_output.data(), time_steps,
class_num, dict, confidence);
if (!text.empty()) {
if (text_only) {
std::cout << text << std::endl;
} else {
printf(" [%zu] (%d,%d)-(%d,%d) conf=%.2f \"%s\"\n",
i + 1, box.x0, box.y0, box.x1, box.y1,
confidence, text.c_str());
}
}
}
stbi_image_free(img_rgb);
return 0;
}
五、編譯和運(yùn)行
5.1 編譯
mkdir build && cd build cmake .. -DCMAKE_BUILD_TYPE=Release make -j$(nproc)
5.2 運(yùn)行
# 回到項(xiàng)目根目錄運(yùn)行(模型路徑是相對(duì)路徑) cd .. # 詳細(xì)模式(顯示坐標(biāo)+置信度+文字) ./build/ocr_demo 你的圖片.png # 純文字模式(只輸出識(shí)別文字) ./build/ocr_demo 你的圖片.png --text-only
5.3 運(yùn)行結(jié)果示例
PP-OCRv5 C++ 推理 Demo Paddle: version: 3.0.0 圖片: test.png (661x316) 字典: 18384 字符 [1/2] 文本檢測(cè)... 檢測(cè)到 18 個(gè)文本區(qū)域 (耗時(shí): 665 ms) [2/2] 文本識(shí)別... [1] (0,93)-(289,103) conf=0.82 "131566總訪問(wèn)196原創(chuàng)248粉絲60關(guān)注" [2] (0,115)-(295,126) conf=0.86 "IP屬地:陜西省加入CSDN時(shí)間:2020-01-31" [3] (0,136)-(108,148) conf=0.96 "查看詳細(xì)資料" [4] (0,225)-(184,235) conf=0.93 "weixin_46244623 碼齡6年" [5] (458,282)-(660,295) conf=0.99 "公眾號(hào)·懶人程序" 檢測(cè)耗時(shí): 665 ms 識(shí)別耗時(shí): 1453 ms (共 18 個(gè)區(qū)域) 總耗時(shí): 2118 ms
六、踩坑總結(jié)
| 坑 | 現(xiàn)象 | 解法 |
|---|---|---|
| C++標(biāo)準(zhǔn) | auto [cx, cy] 編譯報(bào)錯(cuò) | CMakeLists.txt 設(shè) CMAKE_CXX_STANDARD 17 |
| RPATH vs RUNPATH | 運(yùn)行時(shí) libopenvino.so.2500 not found | 加 -Wl,--disable-new-dtags |
| oneDNN鏈接 | -ldnnl 找不到 | 只有 libdnnl.so.3,用絕對(duì)路徑 |
| PIR格式模型 | Predictor 創(chuàng)建失敗 | PP-OCRv5 用 .json 格式,必須 EnableNewIR(true) |
| 置信度全是0.00 | CTC解碼置信度異常 | 模型輸出已是softmax概率,不要再做softmax |
| 邊緣字符丟失 | 文字首尾被截?cái)?/td> | 裁剪時(shí)上下左右加padding |
| 模型路徑 | NotFoundError | 必須在項(xiàng)目根目錄運(yùn)行,不能在build/里運(yùn)行 |
七、mobile vs server 怎么選?
| PP-OCRv5 mobile | PP-OCRv5 server | |
|---|---|---|
| 模型大小 | 小 | 大 |
| CPU耗時(shí) | ~600ms | ~2100ms |
| 識(shí)別精度 | 一般(小字容易錯(cuò)) | 高(CSDN完整識(shí)別) |
| 適合場(chǎng)景 | 移動(dòng)端/實(shí)時(shí)場(chǎng)景 | 服務(wù)端/精度優(yōu)先 |
想要速度快選mobile,想要識(shí)別準(zhǔn)選server,按需切換模型路徑即可,代碼完全通用。
以上就是純C++實(shí)現(xiàn)PP-OCRv5文字識(shí)別的全流程的詳細(xì)內(nèi)容,更多關(guān)于C++ PP-OCRv5文字識(shí)別的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
深度解析三個(gè)常見(jiàn)的C語(yǔ)言內(nèi)存函數(shù)
這篇文章主要深度解析了三個(gè)常見(jiàn)的C語(yǔ)言內(nèi)存函數(shù)memcpy,memmove,memcmp,所以本文將對(duì)memcpy,memmove,memcmp 三個(gè)函數(shù)進(jìn)行詳解和模擬實(shí)現(xiàn),需要的朋友可以參考下2023-07-07
C++實(shí)現(xiàn)停車(chē)場(chǎng)管理系統(tǒng)的示例代碼
停車(chē)場(chǎng)管理系統(tǒng)就是模擬停車(chē)場(chǎng)進(jìn)行車(chē)輛管理的系統(tǒng),該系統(tǒng)分為汽車(chē)信息模塊,用戶使用模塊和管理員用戶模塊,本文將用C++實(shí)現(xiàn)這一簡(jiǎn)單的系統(tǒng),希望對(duì)大家有所幫助2023-04-04
C語(yǔ)言 自增自減運(yùn)算的區(qū)別詳解及實(shí)例
這篇文章主要介紹了C語(yǔ)言中的++a和a++的區(qū)別詳解及實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-05-05
C語(yǔ)言中數(shù)據(jù)結(jié)構(gòu)之鏈表歸并排序?qū)嵗a
這篇文章主要介紹了C語(yǔ)言中數(shù)據(jù)結(jié)構(gòu)之鏈表歸并排序?qū)嵗a的相關(guān)資料,需要的朋友可以參考下2017-05-05
C語(yǔ)言中feof函數(shù)和ferror函數(shù)示例詳解
在C語(yǔ)言中feof函數(shù)用于檢查文件流的結(jié)束標(biāo)志,判斷文件在讀取時(shí)是否已經(jīng)到達(dá)了文件的末尾,這篇文章主要給大家介紹了關(guān)于C語(yǔ)言中feof函數(shù)和ferror函數(shù)的相關(guān)資料,需要的朋友可以參考下2024-09-09
C++圖論核心之最短路徑算法從原理到實(shí)戰(zhàn)
這篇文章主要介紹了C++圖論核心之最短路徑算法,最短路徑問(wèn)題是指,從在帶權(quán)的有向圖中從某一頂點(diǎn)出發(fā),找到通往另一頂點(diǎn)的最短路徑,最短指的是沿路徑各邊的權(quán)值總和最小,需要的朋友可以參考下2026-02-02
C語(yǔ)言實(shí)現(xiàn)掃雷游戲簡(jiǎn)易版
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)掃雷游戲簡(jiǎn)易版,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-11-11
C++實(shí)現(xiàn)LeetCode(143.鏈表重排序)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(143.鏈表重排序),本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07

