# RapidOcr



本示例通过RapidOcr模型说明如何使用ONNXRuntime C++ API进行图像文本识别模型的推理，包括如何预处理、推理并获取推理结果。



## 模型简介

本示例使用了ch_PP-OCRv3_det + ch_ppocr_mobile_v2.0_cls + ch_PP-OCRv3_rec三个模型，onnx文件在Resource/Models/文件夹下，模型结构可以通过netron (https://netron.app/) 查看，并通过netron查询各个模型的输入输出。

## 预处理

在将数据输入到模型之前，需要对图像做如下预处理操作：
这段代码的目的是在进行字符识别之前，对图像进行预处理，包括读取图像、调整大小、填充、缩放等操作。然后，它将处理后的图像和其他参数传递给同名的detect函数来执行字符识别，并将结果存储在result对象中，最后将result对象作为函数的返回值。
 

本示例代码主要实现了预处理操作：

```c++
OcrResult OcrLite::detect(const char *path, const char *imgName,
                          const int padding, const int maxSideLen,
                          float boxScoreThresh, float boxThresh, float unClipRatio, bool doAngle, bool mostAngle) {
    std::string imgFile = getSrcImgFilePath(path, imgName);

    cv::Mat originSrc = imread(imgFile, cv::IMREAD_COLOR);//default : BGR
    int originMaxSide = (std::max)(originSrc.cols, originSrc.rows);
    int resize;
    if (maxSideLen <= 0 || maxSideLen > originMaxSide) {
        resize = originMaxSide;
    } else {
        resize = maxSideLen;
    }
    resize += 2 * padding;
    cv::Rect paddingRect(padding, padding, originSrc.cols, originSrc.rows);
    cv::Mat paddingSrc = makePadding(originSrc, padding);
    ScaleParam scale = getScaleParam(paddingSrc, resize);
    OcrResult result;
    result = detect(path, imgName, paddingSrc, paddingRect, scale,
                    boxScoreThresh, boxThresh, unClipRatio, doAngle, mostAngle);
    return result;

OcrResult OcrLite::detect(const cv::Mat &mat, int padding, int maxSideLen, float boxScoreThresh, float boxThresh,
                          float unClipRatio, bool doAngle, bool mostAngle) {
    cv::Mat originSrc = mat;
    int originMaxSide = (std::max)(originSrc.cols, originSrc.rows);
    int resize;
    if (maxSideLen <= 0 || maxSideLen > originMaxSide) {
        resize = originMaxSide;
    } else {
        resize = maxSideLen;
    }
    resize += 2 * padding;
    cv::Rect paddingRect(padding, padding, originSrc.cols, originSrc.rows);
    cv::Mat paddingSrc = makePadding(originSrc, padding);
    ScaleParam scale = getScaleParam(paddingSrc, resize);
    OcrResult result;
    result = detect(NULL, NULL, paddingSrc, paddingRect, scale,
                    boxScoreThresh, boxThresh, unClipRatio, doAngle, mostAngle);
    return result;
}


```

这两段代码展示了OcrLite类中的两个重载版本的detect函数。
第一个版本接受文件路径和图像名称作为参数，从文件中读取图像进行处理。它的实现流程如下：
1、使用OpenCV的imread函数从文件中读取图像，以BGR格式存储在originSrc变量中。计算originSrc图像的最大边长，将其存储在originMaxSide变量中，使用std::max函数比较图像的宽度和高度。
2、创建一个cv::Rect对象paddingRect，表示填充区域的位置和大小，其中padding用于指定填充的大小，originSrc.cols和originSrc.rows表示填充后的图像的宽度和高度。调用名为makePadding的函数，传递originSrc和padding参数，返回填充后的图像，并将其存储在paddingSrc变量中。
3、调用名为getScaleParam的函数，传递paddingSrc和resize参数，返回一个ScaleParam对象scale，用于缩放图像。
4、调用同名的detect函数，传递文件路径和图像名称作为参数，填充后的图像paddingSrc、填充区域paddingRect、缩放参数scale，以及其他参数，将返回的结果存储在result对象中。返回result对象作为函数的结果。
第二个版本接受一个cv::Mat对象作为输入，直接对该图像进行处理。它的实现流程与前一个版本类似，只是省略了文件读取的步骤，而是直接使用传入的mat作为原始图像。这两个版本的detect函数的目的是根据输入的图像进行字符识别，并返回一个OcrResult对象作为结果。具体的处理步骤包括图像大小调整、填充、缩放和字符识别等过程。

## 推理

### 推理分为三部分：

#### 第一部分：

DbNet::getTextBoxes(){}使用ch_ppocr_v3_det_infer.onnx模型，这是一个预训练的文本检测模型，用于文本检测任务。它可以检测图像中的文本区域，并返回文本框的位置和边界框信息。

```c++
DbNet::getTextBoxes(cv::Mat &src, ScaleParam &s, float boxScoreThresh, float boxThresh, float unClipRatio) {
    
    //创建输入
    cv::Mat srcResize;
    resize(src, srcResize, cv::Size(s.dstWidth, s.dstHeight));
    std::vector<float> inputTensorValues = substractMeanNormalize(srcResize, meanValues, normValues);
    std::array<int64_t, 4> inputShape{1, srcResize.channels(), srcResize.rows, srcResize.cols};
    auto memoryInfo = Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU);
    Ort::Value inputTensor = Ort::Value::CreateTensor<float>(memoryInfo, inputTensorValues.data(),
                                                             inputTensorValues.size(), inputShape.data(),
                                                             inputShape.size());
    assert(inputTensor.IsTensor());
    std::vector<const char *> inputNames = {inputNamesPtr.data()->get()};
    std::vector<const char *> outputNames = {outputNamesPtr.data()->get()};
    
    //进行推理
    auto outputTensor = session->Run(Ort::RunOptions{nullptr}, inputNames.data(), &inputTensor,
                                     inputNames.size(), outputNames.data(), outputNames.size());
    assert(outputTensor.size() == 1 && outputTensor.front().IsTensor());
    std::vector<int64_t> outputShape = outputTensor[0].GetTensorTypeAndShapeInfo().GetShape();
    int64_t outputCount = std::accumulate(outputShape.begin(), outputShape.end(), 1,
                                          std::multiplies<int64_t>());
    float *floatArray = outputTensor.front().GetTensorMutableData<float>();
    std::vector<float> outputData(floatArray, floatArray + outputCount);

    ...
}
```

#### 第二部分：

Angle AngleNet::getAngle(){}使用ch_ppocr_v2_cls_infer.onnx模型：这是一个预训练的分类器模型，用于文本分类任务。它可以用于判断文本属于哪个类别或类别的概率。

```c++
Angle AngleNet::getAngle(cv::Mat &src) {
    std::vector<float> inputTensorValues = substractMeanNormalize(src, meanValues, normValues);
    std::array<int64_t, 4> inputShape{1, src.channels(), src.rows, src.cols};
    auto memoryInfo = Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU);
    Ort::Value inputTensor = Ort::Value::CreateTensor<float>(memoryInfo, inputTensorValues.data(),
                                                             inputTensorValues.size(), inputShape.data(),
                                                             inputShape.size());
    //创建输入
    assert(inputTensor.IsTensor());
    std::vector<const char *> inputNames = {inputNamesPtr.data()->get()};
    std::vector<const char *> outputNames = {outputNamesPtr.data()->get()};
    //进行推理    
    auto outputTensor = session->Run(Ort::RunOptions{nullptr}, inputNames.data(), &inputTensor,
                                     inputNames.size(), outputNames.data(), outputNames.size());
    assert(outputTensor.size() == 1 && outputTensor.front().IsTensor());
    std::vector<int64_t> outputShape = outputTensor[0].GetTensorTypeAndShapeInfo().GetShape();
    int64_t outputCount = std::accumulate(outputShape.begin(), outputShape.end(), 1,
                                          std::multiplies<int64_t>());
    float *floatArray = outputTensor.front().GetTensorMutableData<float>();
    std::vector<float> outputData(floatArray, floatArray + outputCount);
    return scoreToAngle(outputData);
}
```

#### 第三部分：

TextLine CrnnNet::getTextLine(){}使用ch_ppocr_v3_rec_infer.onnx：这是一个预训练的文本识别模型，用于文本识别任务。它可以接收一个文本框的图像区域作为输入，并返回该区域中文本的识别

```c++
TextLine CrnnNet::getTextLine(const cv::Mat &src) {
    float scale = (float) dstHeight / (float) src.rows;
    int dstWidth = int((float) src.cols * scale);
    cv::Mat srcResize;
    resize(src, srcResize, cv::Size(dstWidth, dstHeight));
    std::vector<float> inputTensorValues = substractMeanNormalize(srcResize, meanValues, normValues);
    std::array<int64_t, 4> inputShape{1, srcResize.channels(), srcResize.rows, srcResize.cols};
    auto memoryInfo = Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU);
    Ort::Value inputTensor = Ort::Value::CreateTensor<float>(memoryInfo, inputTensorValues.data(),
                                                             inputTensorValues.size(), inputShape.data(),
                                                             inputShape.size());
    //创建输入
    assert(inputTensor.IsTensor());
    std::vector<const char *> inputNames = {inputNamesPtr.data()->get()};
    std::vector<const char *> outputNames = {outputNamesPtr.data()->get()};
    //进行推理    
    auto outputTensor = session->Run(Ort::RunOptions{nullptr}, inputNames.data(), &inputTensor,
                                     inputNames.size(), outputNames.data(), outputNames.size());
    assert(outputTensor.size() == 1 && outputTensor.front().IsTensor());
    std::vector<int64_t> outputShape = outputTensor[0].GetTensorTypeAndShapeInfo().GetShape();
    int64_t outputCount = std::accumulate(outputShape.begin(), outputShape.end(), 1,
                                          std::multiplies<int64_t>());
    float *floatArray = outputTensor.front().GetTensorMutableData<float>();
    std::vector<float> outputData(floatArray, floatArray + outputCount);
    return scoreToTextLine(outputData, outputShape[1], outputShape[2]);
}
