当前位置: 首页 > news >正文

GLM-OCR模型C语言基础调用示例:嵌入式视觉应用入门

GLM-OCR模型C语言基础调用示例:嵌入式视觉应用入门

如果你是一名C语言开发者,或者正在捣鼓树莓派、ESP32这类嵌入式设备,想给它们加上“眼睛”,让它们能看懂图片里的文字,那你来对地方了。

今天咱们不聊复杂的Python生态,也不搞庞大的深度学习框架部署。我们就用最纯粹的C语言,聊聊怎么在资源有限的嵌入式环境里,调用一个强大的OCR(光学字符识别)服务。想象一下,你的智能门锁能识别访客车牌,你的仓库巡检小车能自动读取货架标签,或者一个简单的工业设备能检查产品上的批次号——这些场景的起点,可能就是今天要讲的这个基础调用方法。

我们假设你已经有一个部署好的GLM-OCR模型服务(比如在另一台性能更强的服务器或云端),它提供了标准的HTTP API。我们的任务,就是教会你的C程序,如何把一张图片“送过去”识别,再把结果“拿回来”。整个过程,我们会用到libcurl这个网络库来处理HTTP通信,以及cJSON来解析返回的数据。放心,我会一步步拆开讲,保证你能跟着做出来。

1. 动手之前:环境与思路准备

在开始写代码之前,我们得先把“战场”布置好,并且搞清楚我们要打一场什么样的“仗”。

1.1 你需要准备什么

首先,确保你的开发环境里有这几样东西:

  • 一个C编译器:比如gcc,这是标配。
  • libcurl开发库:这是我们进行HTTP网络通信的核心工具。在Ubuntu或Debian上,你可以用sudo apt-get install libcurl4-openssl-dev来安装。其他系统请参考对应包管理器的命令。
  • cJSON库:一个轻量级的单文件C语言JSON解析器,非常适合嵌入式环境。我们需要从它的GitHub仓库下载cJSON.ccJSON.h这两个文件。
  • 一个可访问的GLM-OCR服务端点:这是关键。你需要知道它的URL地址,例如http://your-ocr-server:port/v1/ocr。同时,也要了解它需要的具体请求格式(比如图片是作为Base64字符串放在JSON里,还是作为multipart/form-data上传)。
  • 一张测试图片:准备一个包含清晰文字的图片文件,比如test.jpg

1.2 整个流程是怎么跑的

我们的程序大概会像下面这样工作,你可以先有个整体印象:

  1. 读取图片:程序从硬盘上把我们的测试图片读进来,变成一堆二进制数据。
  2. 编码图片:把这堆二进制数据,转换成Base64格式的字符串。因为JSON里不能直接放二进制,Base64就是一种把二进制“翻译”成纯文本的方法。
  3. 组装请求:按照OCR服务的要求,构造一个JSON字符串。这个JSON里通常会包含一个字段,比如"image",它的值就是我们刚才生成的Base64字符串。
  4. 发送请求:使用libcurl,把这个JSON字符串通过HTTP POST请求,发送到我们指定的OCR服务地址。
  5. 接收回复:服务识别完后,会返回一个JSON格式的结果。我们用libcurl接收它。
  6. 解析结果:使用cJSON库,从这个返回的JSON字符串里,把识别出来的文字、文字所在的位置框(Bounding Box)等信息提取出来。
  7. 展示结果:最后,把这些信息在终端上打印出来,看看识别得对不对。

下面这张图描绘了这个过程,你可以对照着看:

graph TD A[开始: 准备测试图片] --> B[步骤一: 读取图片二进制数据] B --> C[步骤二: 将数据编码为Base64字符串] C --> D[步骤三: 构造包含Base64的JSON请求体] D --> E[步骤四: 使用libcurl发送HTTP POST请求] E --> F[OCR服务端进行文字识别] F --> G[步骤五: 接收服务端返回的JSON结果] G --> H[步骤六: 使用cJSON解析结果] H --> I[步骤七: 打印/处理识别出的文本与坐标] I --> J[结束]

2. 核心代码一步步实现

理论说完了,咱们直接上代码。我会把关键部分拆开讲解,你可以把它们组合成一个完整的.c文件。

2.1 引入必要的“工具包”

首先,告诉编译器我们要用哪些库。

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <curl/curl.h> // libcurl的头文件 #include "cJSON.h" // 我们下载的cJSON头文件 // 一个简单的函数,用于将二进制数据转换为Base64 // 这里为了简化,我们使用一个占位实现。在实际项目中, // 你可能需要实现或引入一个完整的Base64编码函数。 char* base64_encode(const unsigned char* data, size_t input_length) { // 这是一个简化示例。实际使用时,建议使用可靠的Base64实现库。 // 例如,OpenSSL库中的 `BIO_f_base64()` 或 libb64等。 // 此处返回一个模拟的字符串以供流程演示。 char* encoded_data = (char*)malloc(100); sprintf(encoded_data, "[Base64_Encoded_Data_of_Image]"); return encoded_data; }

2.2 读取图片并编码

我们写一个函数来完成读取图片和Base64编码的工作。

char* load_image_as_base64(const char* filename) { FILE* file = fopen(filename, "rb"); if (!file) { fprintf(stderr, "错误:无法打开文件 %s\n", filename); return NULL; } // 获取文件大小 fseek(file, 0, SEEK_END); long file_size = ftell(file); fseek(file, 0, SEEK_SET); // 分配内存读取文件 unsigned char* buffer = (unsigned char*)malloc(file_size); if (!buffer) { fclose(file); fprintf(stderr, "错误:内存分配失败\n"); return NULL; } fread(buffer, 1, file_size, file); fclose(file); // 将二进制数据转换为Base64 char* base64_str = base64_encode(buffer, file_size); free(buffer); // 释放原始图片数据内存 return base64_str; }

2.3 组装JSON请求体

接下来,根据OCR服务的要求,构造发送的JSON数据。这里假设服务需要{"image": "base64_string"}这样的格式。

char* build_request_json(const char* base64_image) { cJSON* root = cJSON_CreateObject(); // 创建JSON对象 cJSON_AddStringToObject(root, "image", base64_image); // 添加image字段 // 将cJSON对象转换为格式化的字符串 char* json_str = cJSON_Print(root); cJSON_Delete(root); // 释放cJSON对象内存 return json_str; }

2.4 发送HTTP请求并接收响应

这是和服务器通信的核心部分。我们使用libcurl的“简单”接口。

// 这个结构体用于存储libcurl收到的响应数据 struct MemoryStruct { char* memory; size_t size; }; // 这是一个回调函数,libcurl每收到一块数据就会调用它 static size_t WriteMemoryCallback(void* contents, size_t size, size_t nmemb, void* userp) { size_t realsize = size * nmemb; struct MemoryStruct* mem = (struct MemoryStruct*)userp; char* ptr = realloc(mem->memory, mem->size + realsize + 1); if (!ptr) { fprintf(stderr, "错误:响应数据内存分配失败\n"); return 0; } mem->memory = ptr; memcpy(&(mem->memory[mem->size]), contents, realsize); mem->size += realsize; mem->memory[mem->size] = 0; // 添加字符串结束符 return realsize; } int call_ocr_api(const char* url, const char* json_payload, struct MemoryStruct* chunk) { CURL* curl; CURLcode res; struct curl_slist* headers = NULL; curl_global_init(CURL_GLOBAL_DEFAULT); curl = curl_easy_init(); if (curl) { // 设置目标URL curl_easy_setopt(curl, CURLOPT_URL, url); // 设置POST请求和JSON数据 curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_payload); // 设置HTTP头,告诉服务器我们发送的是JSON headers = curl_slist_append(headers, "Content-Type: application/json"); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); // 设置接收响应数据的回调函数和存储结构 chunk->memory = malloc(1); chunk->size = 0; curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)chunk); // 设置超时时间(单位:秒),嵌入式环境下网络可能不稳定 curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L); // 执行请求 res = curl_easy_perform(curl); // 检查请求是否成功 if (res != CURLE_OK) { fprintf(stderr, "curl_easy_perform() 失败: %s\n", curl_easy_strerror(res)); } // 清理libcurl资源 curl_easy_cleanup(curl); curl_slist_free_all(headers); } curl_global_cleanup(); return (res == CURLE_OK) ? 0 : 1; // 返回0表示成功 }

2.5 解析OCR返回的结果

服务器会返回一个JSON,我们需要从中提取信息。假设返回格式类似:{"text": "识别出的文字", "boxes": [[x1,y1,x2,y2], ...]}

void parse_ocr_response(const char* json_response) { cJSON* root = cJSON_Parse(json_response); if (!root) { const char* error_ptr = cJSON_GetErrorPtr(); if (error_ptr != NULL) { fprintf(stderr, "JSON解析错误: %s\n", error_ptr); } return; } // 提取识别文本 cJSON* text_item = cJSON_GetObjectItem(root, "text"); if (cJSON_IsString(text_item) && (text_item->valuestring != NULL)) { printf("识别出的文本: %s\n", text_item->valuestring); } // 提取文字位置框(如果存在) cJSON* boxes = cJSON_GetObjectItem(root, "boxes"); if (cJSON_IsArray(boxes)) { printf("文字位置框 (x1, y1, x2, y2):\n"); cJSON* box; int box_index = 0; cJSON_ArrayForEach(box, boxes) { if (cJSON_IsArray(box) && cJSON_GetArraySize(box) >= 4) { cJSON* x1 = cJSON_GetArrayItem(box, 0); cJSON* y1 = cJSON_GetArrayItem(box, 1); cJSON* x2 = cJSON_GetArrayItem(box, 2); cJSON* y2 = cJSON_GetArrayItem(box, 3); printf(" 框[%d]: [%d, %d, %d, %d]\n", box_index++, x1->valueint, y1->valueint, x2->valueint, y2->valueint); } } } cJSON_Delete(root); // 释放JSON对象内存 }

2.6 把所有零件组装起来:主函数

最后,我们在main函数里把上面的流程串起来。

int main(int argc, char* argv[]) { const char* image_filename = "test.jpg"; // 你的测试图片 const char* ocr_server_url = "http://your-ocr-server:port/v1/ocr"; // 替换为你的OCR服务地址 printf("1. 正在加载并编码图片...\n"); char* base64_image = load_image_as_base64(image_filename); if (!base64_image) { return 1; // 出错退出 } printf("2. 正在构建JSON请求...\n"); char* json_payload = build_request_json(base64_image); free(base64_image); // 编码数据已存入JSON,可以释放 printf("3. 正在调用OCR API...\n"); struct MemoryStruct response_chunk; if (call_ocr_api(ocr_server_url, json_payload, &response_chunk) != 0) { free(json_payload); return 1; // 网络请求失败 } free(json_payload); // 请求已发送,释放JSON内存 printf("4. 解析识别结果...\n"); if (response_chunk.memory) { parse_ocr_response(response_chunk.memory); free(response_chunk.memory); // 释放响应数据内存 } else { printf("未收到响应数据。\n"); } printf("5. 完成!\n"); return 0; }

3. 编译、运行与问题排查

代码都有了,怎么让它跑起来呢?

3.1 如何编译这个程序

假设你的代码文件叫glm_ocr_demo.c,并且cJSON.ccJSON.h放在同一个目录下,你可以用类似下面的命令来编译:

gcc -o glm_ocr_demo glm_ocr_demo.c cJSON.c -lcurl -lm

解释一下这个命令:

  • gcc:编译器。
  • -o glm_ocr_demo:指定输出的可执行文件名字。
  • glm_ocr_demo.c cJSON.c:编译我们自己的主代码和cJSON库的代码。
  • -lcurl:链接libcurl库。
  • -lm:链接数学库(某些环境下cJSON可能需要)。

3.2 运行它!

编译成功后,在当前目录下会生成一个叫glm_ocr_demo的文件(Windows下可能是.exe)。在运行前,请确保:

  1. 将代码中的http://your-ocr-server:port/v1/ocr替换成你真实的OCR服务地址。
  2. test.jpg替换成你准备好的图片文件名,或者把图片放到程序同级目录下并命名为test.jpg

然后在终端里运行:

./glm_ocr_demo

如果一切顺利,你应该能看到程序一步步执行的提示,并最终打印出识别出的文字。

3.3 可能会遇到的坑

第一次运行很可能不会一帆风顺,这里有几个常见问题和解决思路:

  • 编译错误:找不到curl/curl.h

    • 问题:说明libcurl的开发包没装好。
    • 解决:回头检查1.1节,用系统包管理器正确安装libcurl4-openssl-dev或类似名称的包。
  • 编译错误:cJSON.h找不到

    • 问题cJSON.h文件不在当前目录,或者编译命令里没包含cJSON.c
    • 解决:确保cJSON.ccJSON.h和你的glm_ocr_demo.c在同一个文件夹。检查编译命令是否正确包含了cJSON.c
  • 运行时报错或没反应

    • 问题:网络连接失败、OCR服务地址不对、服务未启动、或者请求格式不符合服务端要求。
    • 解决
      1. 检查地址:用pingcurl命令先手动测试一下你的OCR服务地址是否能通。
      2. 检查格式这是最容易出错的地方!我们的示例代码假设服务端接收{"image": "base64_string"}这种JSON格式。但你的GLM-OCR服务可能要求图片以multipart/form-data形式上传,或者JSON的字段名是"image_data"务必查阅你的OCR服务提供的API文档,调整build_request_json函数中的字段名和结构。
      3. 查看日志:查看OCR服务端的日志,看它收到了什么请求,为什么拒绝或出错。
      4. 简化测试:可以先用curl命令行工具,手动构造一个请求发给服务端,确保API本身是工作的。例如:curl -X POST http://your-server/ocr -H "Content-Type: application/json" -d '{"image":"your_base64_string"}'
  • Base64编码问题

    • 问题:示例中的base64_encode函数是个空壳。
    • 解决:你需要一个真正的Base64编码实现。可以考虑:
      1. 使用libb64这样的专用库。
      2. 使用OpenSSL库中的BIO_f_base64()相关函数。
      3. 在网上找一个经过验证的、轻量级的C语言Base64编码代码,集成到你的项目中。

4. 总结

走完这一趟,你应该已经掌握了在C语言环境下,通过HTTP API调用远程AI服务的基本套路。这个过程的核心无非就是数据准备(图片转Base64)、网络通信(libcurl发POST请求)、数据解析(cJSON读返回结果)这三板斧。

对于嵌入式应用来说,这种将计算密集型的OCR任务放在远程服务器或网关,设备端只负责采集图片和通信的思路,是非常经典且实用的架构。它能让资源受限的嵌入式设备,也能享受到大模型强大的视觉能力。

当然,这只是一个起点。在实际项目中,你可能还需要考虑更多,比如网络异常的重试机制、请求的异步处理、结果的本地缓存、更完善的内存管理等等。但有了这个基础示例,你已经有了一个可以运行的原型,完全可以把它集成到你的树莓派项目、ESP32应用或者其他物联网设备程序中,去尝试解决那些真实的、有趣的识别问题了。


获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

http://www.jsqmd.com/news/513045/

相关文章:

  • C#多线程窗体关闭时如何彻底退出?这4种方法你试过吗?
  • Akagi AI助手:智能分析从入门到精通
  • OpenClaw+ollama-QwQ-32B内容创作闭环:从草稿到公众号发布
  • 三月二十一下午总结
  • 丹青识画系统MySQL分析结果存储方案:亿级图像数据管理实践
  • UniApp小程序包体积超2M?HBuilderX发行模式与miniprogram-ci上传的避坑实战
  • MTK平台ALSA驱动实战:手把手解析Codec与Codec_dai的注册流程(附时序图)
  • Wox智能交互引擎:重新定义生产力工具的技术突破 | 跨平台启动器新范式
  • Windows 11下Zotero 7与百度网盘的无缝同步配置(含软链接避坑技巧)
  • GHelper:轻量级硬件控制架构如何重塑华硕笔记本性能管理体验
  • 基于STM32的恐龙小跳与躲避障碍游戏
  • 深入浅出:DeepSeek-OCR、C3、VIST三种大模型Token压缩技术路线,带你理解压缩即智能
  • 在C# 上位机开发中,性能和响应速度直接决定系统的实时性、稳定性与用户体验,特别是在工业 HMI/SCADA、设备监控、生产线控制等场景下,毫秒级的延迟都可能导致误
  • 积分商城小程序如何制作,SaaS积分商城搭建教程 - 码云数智
  • 双系统用户必看:Windows更新后Ubuntu启动失败的急救指南(附详细修复步骤)
  • 线段树技巧进阶
  • B2C单用户外贸商城源码解析:从零搭建到多语言支付集成
  • Qwen3-32B-Chat百度搜索意图匹配:针对‘Qwen3部署教程‘需求的精准内容覆盖
  • 2026年羊绒衫厂家推荐:高端品牌代工与OEM定制靠谱供应商及合作避坑指南 - 品牌推荐
  • CosyVoice-300M Lite中英混合合成实战:跨语言语音生成教程
  • EEPROMReader:嵌入式系统类型安全的编译期EEPROM管理库
  • Qwen3.5-9B编码能力实战:Python/SQL/Shell代码生成与调试效果分享
  • 3D动作时序连贯性分析:HY-Motion生成结果专业评估
  • 瑜伽馆小程序制作全流程,怎么自己做小程序 - 码云数智
  • 星露谷农场规划器终极指南:3步打造完美农场布局
  • Cadence vs Synopsys:数字后端工程师的EDA工具选择指南(附实战案例)
  • MGeo模型部署教程:阿里云ECS+GPU实例上稳定运行MGeo-base的完整步骤
  • 机械臂力控(4)---对阻抗和导纳更深层次的理解
  • 永续经营:亚马逊领导者的“守城”与“拓疆”法则
  • 5G时代如何DIY一个宽带圆极化天线?从参数优化到实测效果全记录