C语言基础编写简易程序调用DeOldify REST API最近在折腾一些老照片修复的项目发现DeOldify这个AI模型效果确实惊艳。不过官方提供的接口调用示例大多是Python或者Web应用这让一些习惯在嵌入式环境或者纯粹用C语言开发的朋友有点无从下手。其实用C语言调用REST API并没有想象中那么复杂。今天我就带你从零开始用最基础的C语言结合一个非常实用的网络库——libcurl写一个能调用DeOldify API给老照片上色的命令行工具。整个过程就像搭积木一步步来你会发现C语言也能轻松玩转AI接口。1. 环境准备与项目搭建在开始写代码之前我们得先把“工地”平整好把需要的工具都备齐。1.1 安装必要的开发工具首先你需要一个C语言编译器。在Linux或macOS上通常系统自带了gcc。在Windows上你可以安装MinGW或者使用Visual Studio。这里我以Linux环境为例命令也最通用。打开你的终端先确认一下有没有安装gcc和makegcc --version make --version如果显示了版本号说明已经安装好了。如果没有可以用系统的包管理器安装比如在Ubuntu上sudo apt update sudo apt install build-essential1.2 安装libcurl开发库我们这个程序的核心是发送HTTP请求自己从头实现TCP/IP协议和HTTP协议太麻烦了所以我们用libcurl库。它是一个非常强大且易用的客户端URL传输库支持一大堆协议。安装开发库包含头文件和链接库# Ubuntu/Debian sudo apt install libcurl4-openssl-dev # CentOS/RHEL/Fedora sudo yum install libcurl-devel # macOS (使用Homebrew) brew install curl安装成功后你可以尝试编译一个简单的测试程序来验证。1.3 创建项目目录和文件找个你喜欢的地方创建一个项目文件夹比如叫deoldify_c_client。mkdir deoldify_c_client cd deoldify_c_client在这个文件夹里我们创建两个文件deoldify_client.c这是我们主要的C语言源代码文件。Makefile用来编译和链接我们程序的脚本文件。现在你的“工地”就准备好了我们可以开始“砌砖”了。2. 理解程序的核心流程在动手写代码之前我们先在脑子里把整个程序的流程过一遍这样写起来会更有条理。调用DeOldify API给图片上色主要分四步走读取图片从你的电脑硬盘上把一张老照片比如old_photo.jpg读进程序的内存里。编码图片API接口通常不能直接接收原始的图片二进制数据需要把它转换成一种叫Base64的文本格式。这就像把一张图片“翻译”成一长串字母和数字。发送请求通过HTTP协议把我们编码好的图片数据作为一个POST请求的“身体”发送到DeOldify的服务器地址。接收并保存结果服务器处理完后会返回一张新的、上了色的图片数据通常也是Base64格式。我们的程序需要接收它解码回二进制数据然后保存成一个新的图片文件比如colorized_photo.jpg。整个过程中第1步和第4步涉及文件读写是C语言基础操作。第2步的Base64编码我们可以找一个现成的、轻量的代码片段。最核心也最有挑战性的是第3步用libcurl库构造并发送一个HTTP POST请求。别担心libcurl的接口设计得很友好我们一步步来。3. 分步编写核心代码好了理论说完了我们打开deoldify_client.c文件开始写真正的代码。我会把代码分成几个部分并加上详细的注释。3.1 包含必要的头文件和辅助函数首先在文件的开头我们要引入所有需要用到的库。#include stdio.h #include stdlib.h #include string.h #include curl/curl.h // libcurl的主头文件 // 声明一个Base64编码函数我们稍后实现它 char* base64_encode(const unsigned char* data, size_t input_length, size_t* output_length);这里除了标准的输入输出、内存和字符串操作库最关键的就是引入了curl/curl.h。3.2 实现图片读取和Base64编码我们先来实现第一步把图片文件读进来并转换成Base64。// 函数将文件内容读取为Base64字符串 char* file_to_base64(const char* filename) { FILE* file fopen(filename, rb); // 以二进制模式打开文件 if (!file) { perror(无法打开文件); 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; } // 读取文件内容 size_t bytes_read fread(buffer, 1, file_size, file); fclose(file); if (bytes_read ! file_size) { free(buffer); fprintf(stderr, 读取文件失败\n); return NULL; } // 调用Base64编码函数 size_t base64_len; char* base64_data base64_encode(buffer, file_size, base64_len); free(buffer); // 释放原始图片数据内存 if (!base64_data) { fprintf(stderr, Base64编码失败\n); return NULL; } // 为Base64字符串添加结束符方便后续使用 base64_data[base64_len] \0; return base64_data; }这个函数file_to_base64做了几件事打开文件、获取大小、读取数据、编码、清理内存。逻辑很清晰。其中用到的base64_encode函数为了文章简洁我在这里先给出一个简单的实现。在实际项目中你可以使用更健壮的库如OpenSSL中的Base64函数。// 一个简单的Base64编码实现用于演示生产环境建议使用成熟库 char* base64_encode(const unsigned char* data, size_t input_length, size_t* output_length) { const char base64_chars[] ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/; *output_length 4 * ((input_length 2) / 3); // 计算输出长度 char* encoded_data (char*)malloc(*output_length 1); // 多分配1字节给结束符 if (!encoded_data) return NULL; for (size_t i 0, j 0; i input_length;) { uint32_t octet_a i input_length ? data[i] : 0; uint32_t octet_b i input_length ? data[i] : 0; uint32_t octet_c i input_length ? data[i] : 0; uint32_t triple (octet_a 0x10) (octet_b 0x08) octet_c; encoded_data[j] base64_chars[(triple 3 * 6) 0x3F]; encoded_data[j] base64_chars[(triple 2 * 6) 0x3F]; encoded_data[j] base64_chars[(triple 1 * 6) 0x3F]; encoded_data[j] base64_chars[(triple 0 * 6) 0x3F]; } // 处理填充字符 for (size_t i 0; i (3 - input_length % 3) % 3; i) { encoded_data[*output_length - 1 - i] ; } return encoded_data; }3.3 使用libcurl发送HTTP POST请求这是最核心的部分。我们需要构造一个符合DeOldify API要求的JSON数据并通过libcurl发送出去。首先我们需要一个回调函数。当libcurl从服务器接收到数据时它会一块一块地传回来我们需要一个函数来把这些数据块拼接起来。// 回调函数用于收集从服务器返回的数据 size_t write_callback(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, 内存不足 (realloc returned NULL)\n); return 0; } mem-memory ptr; memcpy((mem-memory[mem-size]), contents, realsize); mem-size realsize; mem-memory[mem-size] 0; // 添加字符串结束符 return realsize; } // 定义一个结构体来存储服务器返回的数据 struct MemoryStruct { char* memory; size_t size; };接下来是发送请求的主函数call_deoldify_api。// 函数调用DeOldify API int call_deoldify_api(const char* base64_image, const char* api_url, struct MemoryStruct* chunk) { CURL* curl; CURLcode res; int ret_code 0; // 0表示成功其他值表示失败 // 初始化libcurl curl_global_init(CURL_GLOBAL_DEFAULT); curl curl_easy_init(); if (curl) { // 1. 设置目标URL curl_easy_setopt(curl, CURLOPT_URL, api_url); // 2. 构造JSON请求体 // 假设API期望的JSON格式是 {image: base64_string_here} size_t json_len strlen(base64_image) 50; // 估算长度留有余量 char* json_data (char*)malloc(json_len); if (!json_data) { fprintf(stderr, 分配JSON内存失败\n); curl_easy_cleanup(curl); return -1; } snprintf(json_data, json_len, {\image\: \%s\}, base64_image); // 3. 设置POST数据和相关选项 curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_data); curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, strlen(json_data)); // 4. 设置HTTP头告诉服务器我们发送的是JSON struct curl_slist* headers NULL; headers curl_slist_append(headers, Content-Type: application/json); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); // 5. 设置接收数据的回调函数和存储位置 chunk-memory malloc(1); // 初始化为空 chunk-size 0; curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)chunk); // 6. 设置超时时间单位秒 curl_easy_setopt(curl, CURLOPT_TIMEOUT, 60L); // 处理图片可能需要较长时间 // 7. 执行请求 res curl_easy_perform(curl); // 8. 检查执行结果 if (res ! CURLE_OK) { fprintf(stderr, curl_easy_perform() 失败: %s\n, curl_easy_strerror(res)); ret_code -1; } else { // 可以在这里获取HTTP状态码进行更精细的错误处理 long http_code 0; curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, http_code); if (http_code ! 200) { fprintf(stderr, HTTP请求失败状态码: %ld\n, http_code); fprintf(stderr, 服务器返回: %s\n, chunk-memory); // 打印错误信息 ret_code -1; } } // 9. 清理工作 free(json_data); curl_slist_free_all(headers); curl_easy_cleanup(curl); } else { fprintf(stderr, 无法初始化libcurl\n); ret_code -1; } curl_global_cleanup(); return ret_code; }这个函数看起来有点长但每一步都有明确的注释。它完成了设置URL、构造JSON、设置请求头、发送请求、接收响应和错误处理的全过程。请注意这里的JSON格式{image: base64_string_here}是一个示例你需要根据你实际使用的DeOldify API文档进行调整。API的URL也需要替换成有效的地址。3.4 解析响应并保存结果图片服务器成功处理后会返回数据。假设返回的也是一个包含Base64图片数据的JSON比如{result_image: base64_string_here}。我们需要解析它并保存图片。这里我们实现一个简单的解析函数实际项目中建议使用cJSON等库和保存函数。// 函数从JSON响应中提取Base64图片字符串简易解析仅用于演示 char* extract_base64_from_json(const char* json_response) { // 这是一个非常简单的解析寻找 result_image: 后面的引号内容 // 生产环境务必使用正式的JSON解析库 const char* key \result_image\: \; char* start strstr(json_response, key); if (!start) { fprintf(stderr, 在响应中未找到result_image字段\n); return NULL; } start strlen(key); // 移动到值的开头 char* end strchr(start, \); // 找到值的结尾引号 if (!end) { fprintf(stderr, 响应格式错误未找到结尾引号\n); return NULL; } size_t len end - start; char* base64_str (char*)malloc(len 1); if (!base64_str) return NULL; strncpy(base64_str, start, len); base64_str[len] \0; return base64_str; } // 函数将Base64字符串解码并保存为文件 int save_base64_to_file(const char* base64_str, const char* output_filename) { // 注意这里需要一个Base64解码函数是上面编码函数的逆过程。 // 为了文章长度我们假设有一个 base64_decode 函数可用。 // 你可以用OpenSSL的 EVP_DecodeBlock 或寻找其他实现。 size_t decoded_len; unsigned char* decoded_data base64_decode(base64_str, strlen(base64_str), decoded_len); if (!decoded_data) { fprintf(stderr, Base64解码失败\n); return -1; } FILE* file fopen(output_filename, wb); if (!file) { perror(无法创建输出文件); free(decoded_data); return -1; } size_t written fwrite(decoded_data, 1, decoded_len, file); fclose(file); free(decoded_data); if (written ! decoded_len) { fprintf(stderr, 写入文件不完整\n); return -1; } printf(结果图片已保存至: %s\n, output_filename); return 0; }4. 整合主函数并编译运行现在我们把上面所有的“积木”拼装起来形成一个完整的main函数。int main(int argc, char* argv[]) { // 检查命令行参数 if (argc ! 4) { fprintf(stderr, 用法: %s 输入图片路径 API_URL 输出图片路径\n, argv[0]); fprintf(stderr, 示例: %s old_photo.jpg http://your-api.com/colorize colorized.jpg\n, argv[0]); return 1; } const char* input_image argv[1]; const char* api_url argv[2]; const char* output_image argv[3]; printf(正在处理图片: %s\n, input_image); // 步骤1: 读取图片并编码为Base64 char* base64_data file_to_base64(input_image); if (!base64_data) { fprintf(stderr, 图片读取或编码失败\n); return 1; } printf(图片Base64编码完成长度: %zu\n, strlen(base64_data)); // 步骤2: 准备接收服务器响应的内存结构 struct MemoryStruct chunk; chunk.memory NULL; chunk.size 0; // 步骤3: 调用API printf(正在调用DeOldify API...\n); if (call_deoldify_api(base64_data, api_url, chunk) ! 0) { fprintf(stderr, API调用失败\n); free(base64_data); if (chunk.memory) free(chunk.memory); return 1; } free(base64_data); // 编码数据不再需要 printf(API调用成功收到响应数据。\n); // 步骤4: 解析响应并保存图片 char* result_base64 extract_base64_from_json(chunk.memory); if (result_base64) { if (save_base64_to_file(result_base64, output_image) 0) { printf(处理成功\n); } free(result_base64); } // 步骤5: 清理 if (chunk.memory) free(chunk.memory); return 0; }最后我们需要一个Makefile来简化编译过程。CC gcc CFLAGS -Wall -g LDFLAGS -lcurl TARGET deoldify_client SRC deoldify_client.c all: $(TARGET) $(TARGET): $(SRC) $(CC) $(CFLAGS) -o $ $^ $(LDFLAGS) clean: rm -f $(TARGET) .PHONY: all clean现在回到终端在项目目录下执行make命令make如果一切顺利你会看到一个名为deoldify_client的可执行文件。运行它记得替换成你真实的API地址./deoldify_client ./your_old_photo.jpg http://your-deoldify-server/colorize ./colorized_result.jpg程序就会开始工作读取图片、编码、发送请求、等待、接收响应、解码、保存新图片。如果网络通畅且API服务正常你就能在当前目录下得到一张名为colorized_result.jpg的上色后的图片。5. 总结与后续思考走完这一趟你会发现用C语言调用一个现代的AI服务API本质上还是文件I/O、内存操作、字符串处理和网络通信这些经典问题的组合。libcurl库极大地简化了HTTP通信的复杂性让我们可以专注于业务逻辑。这个示例程序还有很多可以完善的地方。比如JSON解析部分非常脆弱你应该引入像cJSON这样的专业库。错误处理也可以更细致区分网络错误、API错误、数据格式错误等。对于大图片Base64编码会显著增加内存占用和数据传输量可能需要考虑分块传输或使用其他二进制传输方式。但无论如何这个简单的“骨架”已经搭起来了。它证明了即使在资源受限的嵌入式环境或对执行文件体积有严格要求的系统级开发中C语言程序同样有能力与云端AI服务进行交互为老设备或专用系统注入智能。你可以基于这个框架去尝试调用其他提供REST API的AI服务比如OCR识别、语音合成等等思路都是相通的。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。