C语言开发者如何调用GLM-OCR轻量级本地库集成教程如果你是一名C或C开发者正在为你的桌面应用、嵌入式设备或者某个需要高性能、低延迟的本地程序寻找一个OCR光学字符识别解决方案那么这篇文章就是为你准备的。我们常常会遇到这样的场景项目对性能要求苛刻不能依赖网络服务或者运行环境资源有限无法承载庞大的深度学习框架又或者你只是单纯地希望将OCR能力像调用一个标准库函数那样干净利落地集成到你的C/C工程里。这时候一个编译好的、纯粹的动态链接库.so或.dll就显得格外诱人。今天我们就来手把手地实现这个目标将GLM-OCR模型封装成一个C语言可以直接调用的轻量级本地库。整个过程不依赖复杂的Python环境也没有繁重的深度学习框架包袱最终你得到的就是一个可以轻松链接、高效运行的OCR引擎。1. 教程目标与准备工作在开始敲代码之前我们先明确一下这趟旅程的终点站。通过这篇教程你将能够将一个训练好的GLM-OCR模型编译成可供C程序调用的动态库。编写一个简洁的C接口实现图像加载、调用OCR、获取文本结果的核心流程。掌握如何编写Makefile来管理这个跨平台的编译过程。了解在C环境中进行内存管理和多线程调用时需要注意的关键点。听起来是不是很直接让我们看看需要准备些什么。你需要的基础知识熟悉C/C语言编程。了解基本的Linux/Windows命令行操作。对Makefile或CMake有初步了解会更有帮助但并非必须。环境与工具准备编译环境我们需要一个C编译器如g和构建工具如make。在Ubuntu上可以通过sudo apt-get install build-essential来安装。在Windows上可以使用MinGW或MSVC。深度学习推理库这是核心。为了高效运行GLM-OCR模型我们需要一个轻量级且支持C接口的推理引擎。这里我推荐使用ONNX Runtime的C API版本。它性能优秀跨平台支持好并且对ONNX模型格式GLM-OCR可以导出为此格式有很好的支持。图像处理库OCR需要处理图像。我们可以使用经典的stb_image头文件库来读取图片它单文件、无依赖非常适合集成。当然你也可以使用OpenCV但为了极致轻量我们选择stb。GLM-OCR模型文件你需要一个训练好的GLM-OCR模型并将其导出为ONNX格式.onnx文件。这是与推理引擎对接的桥梁。准备好这些我们的工作台就算搭好了。接下来进入最核心的环节打造我们的C语言OCR库。2. 构建C语言OCR接口库我们的目标是创建一个名为libglmocr的库。它对外暴露几个简单的C函数内部则封装了所有模型加载、图像预处理、推理和后处理的复杂逻辑。2.1 设计核心的C接口一个好的接口应该简单、清晰且安全。我们设计三个主要函数// glm_ocr.h - 头文件定义接口 #ifndef GLM_OCR_H #define GLM_OCR_H #ifdef __cplusplus extern C { #endif // 定义OCR结果结构体 typedef struct { char* text; // 识别出的文本 float confidence; // 识别置信度 int bbox[4]; // 文本框坐标 [x1, y1, x2, y2] } OcrResult; // 句柄类型隐藏内部实现细节 typedef void* GlmOcrHandle; /** * brief 初始化OCR引擎 * param model_path ONNX模型文件路径 * return 成功返回引擎句柄失败返回NULL */ GlmOcrHandle glm_ocr_init(const char* model_path); /** * brief 对图像进行OCR识别 * param handle OCR引擎句柄 * param image_data 图像像素数据RGB格式 * param width 图像宽度 * param height 图像高度 * param results 输出结果数组指针 * param max_results 最大返回结果数防止溢出 * return 实际识别到的文本区域数量失败返回-1 */ int glm_ocr_detect(GlmOcrHandle handle, const unsigned char* image_data, int width, int height, OcrResult* results, int max_results); /** * brief 释放OCR引擎及相关资源 * param handle OCR引擎句柄指针函数内会将其置为NULL */ void glm_ocr_free(GlmOcrHandle* handle); // 辅助函数释放单个OcrResult的内存 void glm_ocr_free_result(OcrResult* result); // 辅助函数释放整个OcrResult数组的内存 void glm_ocr_free_results(OcrResult* results, int count); #ifdef __cplusplus } #endif #endif // GLM_OCR_H这个头文件就是我们对外的全部承诺。任何C程序只要包含这个头文件链接我们的库就能调用这三个函数来完成OCR。GlmOcrHandle是一个不透明的指针它隐藏了内部所有的模型、推理会话等复杂对象保证了接口的简洁性。2.2 实现接口封装推理引擎接下来我们创建glm_ocr.c来实现这些接口。这里会包含与ONNX Runtime交互的所有代码。// glm_ocr.c - 接口实现 #include glm_ocr.h #include onnxruntime_c_api.h // ONNX Runtime C API #include stdlib.h #include string.h // 内部结构体存储引擎状态 struct GlmOcrEngine { OrtEnv* env; OrtSession* session; OrtSessionOptions* options; // ... 其他内部状态如输入输出名称、内存信息等 char* input_name; char* output_name; }; GlmOcrHandle glm_ocr_init(const char* model_path) { struct GlmOcrEngine* engine (struct GlmOcrEngine*)malloc(sizeof(struct GlmOcrEngine)); if (!engine) return NULL; // 1. 初始化ONNX Runtime环境 OrtApi* ort OrtGetApiBase()-GetApi(ORT_API_VERSION); OrtStatus* status ort-CreateEnv(ORT_LOGGING_LEVEL_WARNING, GLM-OCR, (engine-env)); if (status ! NULL) { /* 错误处理 */ free(engine); return NULL; } // 2. 创建会话选项 status ort-CreateSessionOptions((engine-options)); // 可以在这里设置线程数、优化级别等例如 // ort-SetIntraOpNumThreads(engine-options, 1); // 单线程避免干扰主程序 // ort-SetSessionGraphOptimizationLevel(engine-options, ORT_ENABLE_ALL); // 3. 加载模型并创建推理会话 status ort-CreateSession(engine-env, model_path, engine-options, (engine-session)); // 4. 获取模型的输入输出名称这里需要根据你的GLM-OCR模型实际情况调整 // 通常需要通过ort-SessionGetInput/OutputName等API动态获取 // 为简化示例我们假设已知名称 engine-input_name input; // 假设输入节点名为input engine-output_name output; // 假设输出节点名为output if (status ! NULL) { // 发生错误释放已分配的资源 ort-ReleaseSessionOptions(engine-options); ort-ReleaseEnv(engine-env); free(engine); return NULL; } return (GlmOcrHandle)engine; }glm_ocr_detect函数是核心它需要完成图像预处理、运行推理、解析结果三大步。预处理如缩放、归一化、转换为NCHW张量和后处理将网络输出解码为文本和框是OCR中的关键代码较长但其逻辑是标准的。int glm_ocr_detect(GlmOcrHandle handle, const unsigned char* image_data, int width, int height, OcrResult* results, int max_results) { if (!handle || !image_data || !results || max_results 0) return -1; struct GlmOcrEngine* engine (struct GlmOcrEngine*)handle; OrtApi* ort OrtGetApiBase()-GetApi(ORT_API_VERSION); // --- 1. 图像预处理 --- // 将RGB uint8数据预处理为模型需要的输入格式例如归一化到[0,1]或[-1,1]转换为NCHW int64_t input_shape[] {1, 3, height, width}; // 示例shape: 批大小1, 3通道高宽 size_t input_tensor_size 3 * height * width; float* input_data (float*)malloc(input_tensor_size * sizeof(float)); // ... 这里需要编写将image_data转换为float并做归一化的代码 ... // --- 2. 创建输入Tensor并运行推理 --- OrtMemoryInfo* memory_info; ort-CreateCpuMemoryInfo(OrtArenaAllocator, OrtMemTypeDefault, memory_info); OrtValue* input_tensor NULL; ort-CreateTensorWithDataAsOrtValue(memory_info, input_data, input_tensor_size * sizeof(float), input_shape, 4, ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT, input_tensor); const char* input_names[] {engine-input_name}; const OrtValue* input_tensors[] {input_tensor}; const char* output_names[] {engine-output_name}; OrtValue* output_tensor NULL; ort-Run(engine-session, NULL, input_names, input_tensors, 1, output_names, 1, output_tensor); // --- 3. 后处理解析output_tensor填充results --- // 这是最模型相关的部分。你需要根据GLM-OCR模型的输出结构来解析。 // 例如输出可能是 [1, N, 6]其中N是检测到的框数6包含坐标、置信度和类别。 float* output_data; ort-GetTensorMutableData(output_tensor, (void**)output_data); // ... 解析output_data将识别出的文本、框和置信度填入OcrResult数组 ... int actual_num_results 0; // 解析后得到的实际结果数 // 注意实际数量不应超过max_results actual_num_results (actual_num_results max_results) ? max_results : actual_num_results; for (int i 0; i actual_num_results; i) { // 示例假设output_data中每6个元素代表一个结果 [x1, y1, x2, y2, conf, text_idx] // 你需要将text_idx映射为文本这可能需要一个字符字典 results[i].bbox[0] (int)output_data[i*6]; results[i].bbox[1] (int)output_data[i*61]; // ... 赋值bbox, confidence ... // results[i].text strdup(识别出的文本); // 需要根据模型输出解码文本 } // --- 4. 清理临时资源 --- ort-ReleaseValue(output_tensor); ort-ReleaseValue(input_tensor); ort-ReleaseMemoryInfo(memory_info); free(input_data); // 注意results中的text字段内存需要调用者通过glm_ocr_free_results释放 return actual_num_results; }最后别忘了资源释放函数这是良好C编程习惯的体现能有效避免内存泄漏。void glm_ocr_free(GlmOcrHandle* handle) { if (handle *handle) { struct GlmOcrEngine* engine (struct GlmOcrEngine*)(*handle); OrtApi* ort OrtGetApiBase()-GetApi(ORT_API_VERSION); ort-ReleaseSession(engine-session); ort-ReleaseSessionOptions(engine-options); ort-ReleaseEnv(engine-env); free(engine-input_name); free(engine-output_name); free(engine); *handle NULL; // 将外部句柄置空防止悬空指针 } }库的代码主体就完成了。接下来我们需要一个“施工图”来告诉编译器如何把它们组装起来。3. 编译与打包编写Makefile为了让编译过程自动化我们编写一个Makefile。它定义了如何将.c文件编译成.o目标文件再链接成动态库。# Makefile CC gcc CXX g CFLAGS -fPIC -O2 -Wall -I./ -I$(ONNXRUNTIME_INCLUDE_DIR) CXXFLAGS $(CFLAGS) LDFLAGS -shared -L$(ONNXRUNTIME_LIB_DIR) -lonnxruntime # 假设ONNX Runtime的头文件和库路径已通过环境变量设置 # 例如export ONNXRUNTIME_INCLUDE_DIR/path/to/onnxruntime/include # export ONNXRUNTIME_LIB_DIR/path/to/onnxruntime/lib TARGET libglmocr.so # Linux动态库 # TARGET glmocr.dll # Windows动态库使用MinGW时 OBJS glm_ocr.o stb_image.o # 假设我们集成了stb_image.c all: $(TARGET) $(TARGET): $(OBJS) $(CXX) -o $ $^ $(LDFLAGS) glm_ocr.o: glm_ocr.c glm_ocr.h $(CC) $(CFLAGS) -c $ -o $ stb_image.o: stb_image.c stb_image.h $(CC) $(CFLAGS) -c $ -o $ clean: rm -f $(OBJS) $(TARGET) .PHONY: all clean在命令行中进入项目目录执行make命令你就会得到libglmocr.so文件。在Windows的MinGW环境下可能需要调整TARGET和编译选项为glmocr.dll。库已经准备好了是时候看看怎么使用它了。4. 在C程序中调用我们的OCR库让我们写一个简单的main.c来测试我们的劳动成果。这个程序会加载一张图片调用我们的OCR库进行识别并打印出结果。// main.c - 测试程序 #include glm_ocr.h #define STB_IMAGE_IMPLEMENTATION #include stb_image.h // 用于加载图片 #include stdio.h int main(int argc, char* argv[]) { if (argc 3) { printf(Usage: %s model_path.onnx image_path.jpg\n, argv[0]); return 1; } const char* model_path argv[1]; const char* image_path argv[2]; // 1. 初始化OCR引擎 printf(Initializing OCR engine with model: %s\n, model_path); GlmOcrHandle ocr_handle glm_ocr_init(model_path); if (!ocr_handle) { fprintf(stderr, Failed to initialize OCR engine.\n); return -1; } // 2. 使用stb_image加载图片 int width, height, channels; unsigned char* image_data stbi_load(image_path, width, height, channels, 3); // 强制加载为3通道RGB if (!image_data) { fprintf(stderr, Failed to load image: %s\n, image_path); glm_ocr_free(ocr_handle); return -1; } printf(Image loaded: %dx%d, channels: %d\n, width, height, channels); // 3. 准备结果缓冲区并调用识别 const int max_results 100; OcrResult results[max_results]; int num_results glm_ocr_detect(ocr_handle, image_data, width, height, results, max_results); if (num_results 0) { fprintf(stderr, OCR detection failed.\n); } else { printf(Found %d text region(s):\n, num_results); for (int i 0; i num_results; i) { printf( Region %d: [%d, %d, %d, %d] conf%.2f\n, i, results[i].bbox[0], results[i].bbox[1], results[i].bbox[2], results[i].bbox[3], results[i].confidence); if (results[i].text) { printf( Text: %s\n, results[i].text); } } } // 4. 释放资源非常重要 // 先释放结果中的文本内存 for (int i 0; i num_results; i) { glm_ocr_free_result(results[i]); } // 释放图像数据 stbi_image_free(image_data); // 最后释放OCR引擎 glm_ocr_free(ocr_handle); printf(Done.\n); return 0; }编译这个测试程序gcc -o test_ocr main.c -I./ -L./ -lglmocr -lm # 运行前确保动态库在链接路径中例如 export LD_LIBRARY_PATH./:$LD_LIBRARY_PATH # Linux # 或 set PATH./;%PATH% # Windows (对于.dll) ./test_ocr ./models/glm-ocr.onnx ./test_image.jpg如果一切顺利你将在终端看到图片中识别出的文字区域和内容。5. 关键注意事项与进阶提示恭喜你走到了这一步一个基础的C接口OCR库已经可以工作了。但在投入实际项目前还有几个重要的坑需要留意。内存管理是重中之重。我们的接口遵循“谁分配谁释放”的原则。glm_ocr_init分配了引擎内部资源由glm_ocr_free释放。glm_ocr_detect返回的OcrResult中的text字符串是由库内部分配的例如使用了strdup因此我们提供了glm_ocr_free_result和glm_ocr_free_results函数供调用者释放。务必成对使用避免内存泄漏。多线程安全。默认情况下一个GlmOcrHandle不建议在多个线程中同时调用glm_ocr_detect因为底层的ONNX Runtime会话可能不是线程安全的。有两种策略每个线程创建独立的句柄这是最安全简单的方式每个线程初始化自己的GlmOcrHandle。虽然会占用更多内存但互不干扰。外部加锁如果必须共享一个句柄那么调用者需要在调用glm_ocr_detect前后加锁如互斥锁。我们的库接口本身不提供锁这给了调用者最大的灵活性。性能调优小技巧。批处理如果有多张图片需要识别可以考虑修改接口支持批处理一次性传入多张图片这通常能提升GPU利用率。输入尺寸固定如果业务场景中图片尺寸固定可以在初始化时设定好避免每次推理都动态调整。缓存会话对于长时间运行的服务初始化一次引擎然后反复使用避免频繁的加载/释放模型。错误处理需要加强。示例代码中简化了错误处理。在生产环境中你应该检查每一个ONNX Runtime API调用的返回值OrtStatus*并将错误信息通过某种方式如设置一个last_error线程局部变量反馈给调用者方便调试。6. 总结走完这个流程你应该已经掌握了将GLM-OCR这类AI模型集成到纯C环境中的核心方法。整个过程就像在拼装一个精密的仪器设计简洁的接口蓝图用C封装复杂的推理逻辑组装核心部件编写Makefile自动化构建制定装配流程最后在C程序中轻松调用按下启动按钮。这种方式的优势很明显部署极其简单只需要分发一个动态库和模型文件运行时资源消耗可控没有Python解释器的开销与现有C/C项目集成无缝调用方式符合开发者习惯。当然这里展示的是一个最基础的框架特别是模型后处理部分需要你根据GLM-OCR模型的具体输出格式来完善。这可能需要你深入研究一下模型的输出层。此外对于更复杂的场景比如支持GPU推理、处理中文文本等可能还需要引入额外的依赖如CUDA库、中文字典。希望这个教程能为你打开一扇门。当你需要将AI能力嵌入到那些对性能和依赖有严苛要求的角落时这套轻量级本地集成的思路会是一个非常可靠的选择。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。
C语言开发者如何调用GLM-OCR:轻量级本地库集成教程
C语言开发者如何调用GLM-OCR轻量级本地库集成教程如果你是一名C或C开发者正在为你的桌面应用、嵌入式设备或者某个需要高性能、低延迟的本地程序寻找一个OCR光学字符识别解决方案那么这篇文章就是为你准备的。我们常常会遇到这样的场景项目对性能要求苛刻不能依赖网络服务或者运行环境资源有限无法承载庞大的深度学习框架又或者你只是单纯地希望将OCR能力像调用一个标准库函数那样干净利落地集成到你的C/C工程里。这时候一个编译好的、纯粹的动态链接库.so或.dll就显得格外诱人。今天我们就来手把手地实现这个目标将GLM-OCR模型封装成一个C语言可以直接调用的轻量级本地库。整个过程不依赖复杂的Python环境也没有繁重的深度学习框架包袱最终你得到的就是一个可以轻松链接、高效运行的OCR引擎。1. 教程目标与准备工作在开始敲代码之前我们先明确一下这趟旅程的终点站。通过这篇教程你将能够将一个训练好的GLM-OCR模型编译成可供C程序调用的动态库。编写一个简洁的C接口实现图像加载、调用OCR、获取文本结果的核心流程。掌握如何编写Makefile来管理这个跨平台的编译过程。了解在C环境中进行内存管理和多线程调用时需要注意的关键点。听起来是不是很直接让我们看看需要准备些什么。你需要的基础知识熟悉C/C语言编程。了解基本的Linux/Windows命令行操作。对Makefile或CMake有初步了解会更有帮助但并非必须。环境与工具准备编译环境我们需要一个C编译器如g和构建工具如make。在Ubuntu上可以通过sudo apt-get install build-essential来安装。在Windows上可以使用MinGW或MSVC。深度学习推理库这是核心。为了高效运行GLM-OCR模型我们需要一个轻量级且支持C接口的推理引擎。这里我推荐使用ONNX Runtime的C API版本。它性能优秀跨平台支持好并且对ONNX模型格式GLM-OCR可以导出为此格式有很好的支持。图像处理库OCR需要处理图像。我们可以使用经典的stb_image头文件库来读取图片它单文件、无依赖非常适合集成。当然你也可以使用OpenCV但为了极致轻量我们选择stb。GLM-OCR模型文件你需要一个训练好的GLM-OCR模型并将其导出为ONNX格式.onnx文件。这是与推理引擎对接的桥梁。准备好这些我们的工作台就算搭好了。接下来进入最核心的环节打造我们的C语言OCR库。2. 构建C语言OCR接口库我们的目标是创建一个名为libglmocr的库。它对外暴露几个简单的C函数内部则封装了所有模型加载、图像预处理、推理和后处理的复杂逻辑。2.1 设计核心的C接口一个好的接口应该简单、清晰且安全。我们设计三个主要函数// glm_ocr.h - 头文件定义接口 #ifndef GLM_OCR_H #define GLM_OCR_H #ifdef __cplusplus extern C { #endif // 定义OCR结果结构体 typedef struct { char* text; // 识别出的文本 float confidence; // 识别置信度 int bbox[4]; // 文本框坐标 [x1, y1, x2, y2] } OcrResult; // 句柄类型隐藏内部实现细节 typedef void* GlmOcrHandle; /** * brief 初始化OCR引擎 * param model_path ONNX模型文件路径 * return 成功返回引擎句柄失败返回NULL */ GlmOcrHandle glm_ocr_init(const char* model_path); /** * brief 对图像进行OCR识别 * param handle OCR引擎句柄 * param image_data 图像像素数据RGB格式 * param width 图像宽度 * param height 图像高度 * param results 输出结果数组指针 * param max_results 最大返回结果数防止溢出 * return 实际识别到的文本区域数量失败返回-1 */ int glm_ocr_detect(GlmOcrHandle handle, const unsigned char* image_data, int width, int height, OcrResult* results, int max_results); /** * brief 释放OCR引擎及相关资源 * param handle OCR引擎句柄指针函数内会将其置为NULL */ void glm_ocr_free(GlmOcrHandle* handle); // 辅助函数释放单个OcrResult的内存 void glm_ocr_free_result(OcrResult* result); // 辅助函数释放整个OcrResult数组的内存 void glm_ocr_free_results(OcrResult* results, int count); #ifdef __cplusplus } #endif #endif // GLM_OCR_H这个头文件就是我们对外的全部承诺。任何C程序只要包含这个头文件链接我们的库就能调用这三个函数来完成OCR。GlmOcrHandle是一个不透明的指针它隐藏了内部所有的模型、推理会话等复杂对象保证了接口的简洁性。2.2 实现接口封装推理引擎接下来我们创建glm_ocr.c来实现这些接口。这里会包含与ONNX Runtime交互的所有代码。// glm_ocr.c - 接口实现 #include glm_ocr.h #include onnxruntime_c_api.h // ONNX Runtime C API #include stdlib.h #include string.h // 内部结构体存储引擎状态 struct GlmOcrEngine { OrtEnv* env; OrtSession* session; OrtSessionOptions* options; // ... 其他内部状态如输入输出名称、内存信息等 char* input_name; char* output_name; }; GlmOcrHandle glm_ocr_init(const char* model_path) { struct GlmOcrEngine* engine (struct GlmOcrEngine*)malloc(sizeof(struct GlmOcrEngine)); if (!engine) return NULL; // 1. 初始化ONNX Runtime环境 OrtApi* ort OrtGetApiBase()-GetApi(ORT_API_VERSION); OrtStatus* status ort-CreateEnv(ORT_LOGGING_LEVEL_WARNING, GLM-OCR, (engine-env)); if (status ! NULL) { /* 错误处理 */ free(engine); return NULL; } // 2. 创建会话选项 status ort-CreateSessionOptions((engine-options)); // 可以在这里设置线程数、优化级别等例如 // ort-SetIntraOpNumThreads(engine-options, 1); // 单线程避免干扰主程序 // ort-SetSessionGraphOptimizationLevel(engine-options, ORT_ENABLE_ALL); // 3. 加载模型并创建推理会话 status ort-CreateSession(engine-env, model_path, engine-options, (engine-session)); // 4. 获取模型的输入输出名称这里需要根据你的GLM-OCR模型实际情况调整 // 通常需要通过ort-SessionGetInput/OutputName等API动态获取 // 为简化示例我们假设已知名称 engine-input_name input; // 假设输入节点名为input engine-output_name output; // 假设输出节点名为output if (status ! NULL) { // 发生错误释放已分配的资源 ort-ReleaseSessionOptions(engine-options); ort-ReleaseEnv(engine-env); free(engine); return NULL; } return (GlmOcrHandle)engine; }glm_ocr_detect函数是核心它需要完成图像预处理、运行推理、解析结果三大步。预处理如缩放、归一化、转换为NCHW张量和后处理将网络输出解码为文本和框是OCR中的关键代码较长但其逻辑是标准的。int glm_ocr_detect(GlmOcrHandle handle, const unsigned char* image_data, int width, int height, OcrResult* results, int max_results) { if (!handle || !image_data || !results || max_results 0) return -1; struct GlmOcrEngine* engine (struct GlmOcrEngine*)handle; OrtApi* ort OrtGetApiBase()-GetApi(ORT_API_VERSION); // --- 1. 图像预处理 --- // 将RGB uint8数据预处理为模型需要的输入格式例如归一化到[0,1]或[-1,1]转换为NCHW int64_t input_shape[] {1, 3, height, width}; // 示例shape: 批大小1, 3通道高宽 size_t input_tensor_size 3 * height * width; float* input_data (float*)malloc(input_tensor_size * sizeof(float)); // ... 这里需要编写将image_data转换为float并做归一化的代码 ... // --- 2. 创建输入Tensor并运行推理 --- OrtMemoryInfo* memory_info; ort-CreateCpuMemoryInfo(OrtArenaAllocator, OrtMemTypeDefault, memory_info); OrtValue* input_tensor NULL; ort-CreateTensorWithDataAsOrtValue(memory_info, input_data, input_tensor_size * sizeof(float), input_shape, 4, ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT, input_tensor); const char* input_names[] {engine-input_name}; const OrtValue* input_tensors[] {input_tensor}; const char* output_names[] {engine-output_name}; OrtValue* output_tensor NULL; ort-Run(engine-session, NULL, input_names, input_tensors, 1, output_names, 1, output_tensor); // --- 3. 后处理解析output_tensor填充results --- // 这是最模型相关的部分。你需要根据GLM-OCR模型的输出结构来解析。 // 例如输出可能是 [1, N, 6]其中N是检测到的框数6包含坐标、置信度和类别。 float* output_data; ort-GetTensorMutableData(output_tensor, (void**)output_data); // ... 解析output_data将识别出的文本、框和置信度填入OcrResult数组 ... int actual_num_results 0; // 解析后得到的实际结果数 // 注意实际数量不应超过max_results actual_num_results (actual_num_results max_results) ? max_results : actual_num_results; for (int i 0; i actual_num_results; i) { // 示例假设output_data中每6个元素代表一个结果 [x1, y1, x2, y2, conf, text_idx] // 你需要将text_idx映射为文本这可能需要一个字符字典 results[i].bbox[0] (int)output_data[i*6]; results[i].bbox[1] (int)output_data[i*61]; // ... 赋值bbox, confidence ... // results[i].text strdup(识别出的文本); // 需要根据模型输出解码文本 } // --- 4. 清理临时资源 --- ort-ReleaseValue(output_tensor); ort-ReleaseValue(input_tensor); ort-ReleaseMemoryInfo(memory_info); free(input_data); // 注意results中的text字段内存需要调用者通过glm_ocr_free_results释放 return actual_num_results; }最后别忘了资源释放函数这是良好C编程习惯的体现能有效避免内存泄漏。void glm_ocr_free(GlmOcrHandle* handle) { if (handle *handle) { struct GlmOcrEngine* engine (struct GlmOcrEngine*)(*handle); OrtApi* ort OrtGetApiBase()-GetApi(ORT_API_VERSION); ort-ReleaseSession(engine-session); ort-ReleaseSessionOptions(engine-options); ort-ReleaseEnv(engine-env); free(engine-input_name); free(engine-output_name); free(engine); *handle NULL; // 将外部句柄置空防止悬空指针 } }库的代码主体就完成了。接下来我们需要一个“施工图”来告诉编译器如何把它们组装起来。3. 编译与打包编写Makefile为了让编译过程自动化我们编写一个Makefile。它定义了如何将.c文件编译成.o目标文件再链接成动态库。# Makefile CC gcc CXX g CFLAGS -fPIC -O2 -Wall -I./ -I$(ONNXRUNTIME_INCLUDE_DIR) CXXFLAGS $(CFLAGS) LDFLAGS -shared -L$(ONNXRUNTIME_LIB_DIR) -lonnxruntime # 假设ONNX Runtime的头文件和库路径已通过环境变量设置 # 例如export ONNXRUNTIME_INCLUDE_DIR/path/to/onnxruntime/include # export ONNXRUNTIME_LIB_DIR/path/to/onnxruntime/lib TARGET libglmocr.so # Linux动态库 # TARGET glmocr.dll # Windows动态库使用MinGW时 OBJS glm_ocr.o stb_image.o # 假设我们集成了stb_image.c all: $(TARGET) $(TARGET): $(OBJS) $(CXX) -o $ $^ $(LDFLAGS) glm_ocr.o: glm_ocr.c glm_ocr.h $(CC) $(CFLAGS) -c $ -o $ stb_image.o: stb_image.c stb_image.h $(CC) $(CFLAGS) -c $ -o $ clean: rm -f $(OBJS) $(TARGET) .PHONY: all clean在命令行中进入项目目录执行make命令你就会得到libglmocr.so文件。在Windows的MinGW环境下可能需要调整TARGET和编译选项为glmocr.dll。库已经准备好了是时候看看怎么使用它了。4. 在C程序中调用我们的OCR库让我们写一个简单的main.c来测试我们的劳动成果。这个程序会加载一张图片调用我们的OCR库进行识别并打印出结果。// main.c - 测试程序 #include glm_ocr.h #define STB_IMAGE_IMPLEMENTATION #include stb_image.h // 用于加载图片 #include stdio.h int main(int argc, char* argv[]) { if (argc 3) { printf(Usage: %s model_path.onnx image_path.jpg\n, argv[0]); return 1; } const char* model_path argv[1]; const char* image_path argv[2]; // 1. 初始化OCR引擎 printf(Initializing OCR engine with model: %s\n, model_path); GlmOcrHandle ocr_handle glm_ocr_init(model_path); if (!ocr_handle) { fprintf(stderr, Failed to initialize OCR engine.\n); return -1; } // 2. 使用stb_image加载图片 int width, height, channels; unsigned char* image_data stbi_load(image_path, width, height, channels, 3); // 强制加载为3通道RGB if (!image_data) { fprintf(stderr, Failed to load image: %s\n, image_path); glm_ocr_free(ocr_handle); return -1; } printf(Image loaded: %dx%d, channels: %d\n, width, height, channels); // 3. 准备结果缓冲区并调用识别 const int max_results 100; OcrResult results[max_results]; int num_results glm_ocr_detect(ocr_handle, image_data, width, height, results, max_results); if (num_results 0) { fprintf(stderr, OCR detection failed.\n); } else { printf(Found %d text region(s):\n, num_results); for (int i 0; i num_results; i) { printf( Region %d: [%d, %d, %d, %d] conf%.2f\n, i, results[i].bbox[0], results[i].bbox[1], results[i].bbox[2], results[i].bbox[3], results[i].confidence); if (results[i].text) { printf( Text: %s\n, results[i].text); } } } // 4. 释放资源非常重要 // 先释放结果中的文本内存 for (int i 0; i num_results; i) { glm_ocr_free_result(results[i]); } // 释放图像数据 stbi_image_free(image_data); // 最后释放OCR引擎 glm_ocr_free(ocr_handle); printf(Done.\n); return 0; }编译这个测试程序gcc -o test_ocr main.c -I./ -L./ -lglmocr -lm # 运行前确保动态库在链接路径中例如 export LD_LIBRARY_PATH./:$LD_LIBRARY_PATH # Linux # 或 set PATH./;%PATH% # Windows (对于.dll) ./test_ocr ./models/glm-ocr.onnx ./test_image.jpg如果一切顺利你将在终端看到图片中识别出的文字区域和内容。5. 关键注意事项与进阶提示恭喜你走到了这一步一个基础的C接口OCR库已经可以工作了。但在投入实际项目前还有几个重要的坑需要留意。内存管理是重中之重。我们的接口遵循“谁分配谁释放”的原则。glm_ocr_init分配了引擎内部资源由glm_ocr_free释放。glm_ocr_detect返回的OcrResult中的text字符串是由库内部分配的例如使用了strdup因此我们提供了glm_ocr_free_result和glm_ocr_free_results函数供调用者释放。务必成对使用避免内存泄漏。多线程安全。默认情况下一个GlmOcrHandle不建议在多个线程中同时调用glm_ocr_detect因为底层的ONNX Runtime会话可能不是线程安全的。有两种策略每个线程创建独立的句柄这是最安全简单的方式每个线程初始化自己的GlmOcrHandle。虽然会占用更多内存但互不干扰。外部加锁如果必须共享一个句柄那么调用者需要在调用glm_ocr_detect前后加锁如互斥锁。我们的库接口本身不提供锁这给了调用者最大的灵活性。性能调优小技巧。批处理如果有多张图片需要识别可以考虑修改接口支持批处理一次性传入多张图片这通常能提升GPU利用率。输入尺寸固定如果业务场景中图片尺寸固定可以在初始化时设定好避免每次推理都动态调整。缓存会话对于长时间运行的服务初始化一次引擎然后反复使用避免频繁的加载/释放模型。错误处理需要加强。示例代码中简化了错误处理。在生产环境中你应该检查每一个ONNX Runtime API调用的返回值OrtStatus*并将错误信息通过某种方式如设置一个last_error线程局部变量反馈给调用者方便调试。6. 总结走完这个流程你应该已经掌握了将GLM-OCR这类AI模型集成到纯C环境中的核心方法。整个过程就像在拼装一个精密的仪器设计简洁的接口蓝图用C封装复杂的推理逻辑组装核心部件编写Makefile自动化构建制定装配流程最后在C程序中轻松调用按下启动按钮。这种方式的优势很明显部署极其简单只需要分发一个动态库和模型文件运行时资源消耗可控没有Python解释器的开销与现有C/C项目集成无缝调用方式符合开发者习惯。当然这里展示的是一个最基础的框架特别是模型后处理部分需要你根据GLM-OCR模型的具体输出格式来完善。这可能需要你深入研究一下模型的输出层。此外对于更复杂的场景比如支持GPU推理、处理中文文本等可能还需要引入额外的依赖如CUDA库、中文字典。希望这个教程能为你打开一扇门。当你需要将AI能力嵌入到那些对性能和依赖有严苛要求的角落时这套轻量级本地集成的思路会是一个非常可靠的选择。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。