# 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 inputTensorValues = substractMeanNormalize(srcResize, meanValues, normValues); std::array inputShape{1, srcResize.channels(), srcResize.rows, srcResize.cols}; auto memoryInfo = Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU); Ort::Value inputTensor = Ort::Value::CreateTensor(memoryInfo, inputTensorValues.data(), inputTensorValues.size(), inputShape.data(), inputShape.size()); assert(inputTensor.IsTensor()); std::vector inputNames = {inputNamesPtr.data()->get()}; std::vector 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 outputShape = outputTensor[0].GetTensorTypeAndShapeInfo().GetShape(); int64_t outputCount = std::accumulate(outputShape.begin(), outputShape.end(), 1, std::multiplies()); float *floatArray = outputTensor.front().GetTensorMutableData(); std::vector outputData(floatArray, floatArray + outputCount); ... } ``` #### 第二部分: Angle AngleNet::getAngle(){}使用ch_ppocr_v2_cls_infer.onnx模型:这是一个预训练的分类器模型,用于文本分类任务。它可以用于判断文本属于哪个类别或类别的概率。 ```c++ Angle AngleNet::getAngle(cv::Mat &src) { std::vector inputTensorValues = substractMeanNormalize(src, meanValues, normValues); std::array inputShape{1, src.channels(), src.rows, src.cols}; auto memoryInfo = Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU); Ort::Value inputTensor = Ort::Value::CreateTensor(memoryInfo, inputTensorValues.data(), inputTensorValues.size(), inputShape.data(), inputShape.size()); //创建输入 assert(inputTensor.IsTensor()); std::vector inputNames = {inputNamesPtr.data()->get()}; std::vector 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 outputShape = outputTensor[0].GetTensorTypeAndShapeInfo().GetShape(); int64_t outputCount = std::accumulate(outputShape.begin(), outputShape.end(), 1, std::multiplies()); float *floatArray = outputTensor.front().GetTensorMutableData(); std::vector 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 inputTensorValues = substractMeanNormalize(srcResize, meanValues, normValues); std::array inputShape{1, srcResize.channels(), srcResize.rows, srcResize.cols}; auto memoryInfo = Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU); Ort::Value inputTensor = Ort::Value::CreateTensor(memoryInfo, inputTensorValues.data(), inputTensorValues.size(), inputShape.data(), inputShape.size()); //创建输入 assert(inputTensor.IsTensor()); std::vector inputNames = {inputNamesPtr.data()->get()}; std::vector 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 outputShape = outputTensor[0].GetTensorTypeAndShapeInfo().GetShape(); int64_t outputCount = std::accumulate(outputShape.begin(), outputShape.end(), 1, std::multiplies()); float *floatArray = outputTensor.front().GetTensorMutableData(); std::vector outputData(floatArray, floatArray + outputCount); return scoreToTextLine(outputData, outputShape[1], outputShape[2]); }