最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

純C++實(shí)現(xiàn)PP-OCRv5文字識(shí)別的全流程

 更新時(shí)間:2026年02月28日 09:42:59   作者:weixin_46244623  
你是不是也遇到過(guò)這種情況:想在C++項(xiàng)目里加個(gè)OCR功能,結(jié)果光裝OpenCV就折騰半天?今天教你零OpenCV依賴,用Paddle Inference + stb_image,純C++實(shí)現(xiàn)PP-OCRv5文字識(shí)別全流程(檢測(cè)+識(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ō):

  1. 必須C++17:代碼用了結(jié)構(gòu)化綁定 auto [cx, cy] = ...,C++14編譯不過(guò)
  2. RPATH不能用RUNPATH:Paddle Inference間接依賴了OpenVINO的so,RUNPATH不傳遞給間接依賴,必須加 --disable-new-dtags 走RPATH
  3. 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.00CTC解碼置信度異常模型輸出已是softmax概率,不要再做softmax
邊緣字符丟失文字首尾被截?cái)?/td>裁剪時(shí)上下左右加padding
模型路徑NotFoundError必須在項(xiàng)目根目錄運(yùn)行,不能在build/里運(yùn)行

七、mobile vs server 怎么選?

PP-OCRv5 mobilePP-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)文章

最新評(píng)論

兴化市| 尤溪县| 武宣县| 淳化县| 剑川县| 加查县| 潜山县| 屯门区| 驻马店市| 永和县| 金秀| 涡阳县| 油尖旺区| 泰顺县| 张家界市| 苍梧县| 刚察县| 阳谷县| 凤阳县| 新兴县| 孟州市| 忻州市| 通河县| 巨鹿县| 夏河县| 乐至县| 璧山县| 葫芦岛市| 海原县| 株洲县| 浠水县| 临海市| 天祝| 定结县| 长白| 衡南县| 丹寨县| 鄂伦春自治旗| 黄梅县| 津市市| 翁源县|