C语言基础:编写简易程序调用DeOldify REST API
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和make:
gcc --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星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。
