基于多模态大模型的HTTP API实现图像识别的功能(Python+C)
- 01 Mar, 2025
在之前的笔记中,通过在Python中利用OpenAI SDK访问月之暗面MoonShot的Kimi API,实现了一个单词对话和多轮对话的功能:[[基于Python快速实现与Kimi的AI对话]]。
本篇笔记在以上笔记的基础上,仍然利用OpenAI的API,访问Kimi多模态模型实现图片中对象识别的功能,同时使用Python和C代码实现并提供案例解析。能够利用C语言来访问多模态大模型开放出来的HTTP API接口,也就能够在低成本的MCU处理器上,基于合理的提示词设计,实现需要大模型服务支持才能提供出来的功能,
该例子的功能需求很简单,就是给大语言模型上传一张照片,请大模型给出照片中检测到的对象的描述。本案例使用在网上搜索到的一张小鸟的照片。

相比于与大模型之间进行的简单文本对话,要使用多模态大模型来实现图像识别的功能,最大的不同,在于客户端发给多模态大模型的HTTP请求,需要把图片文件进行base64编码后作为附件,与文本对话的提示词一起上传上去。
多模态大模型进行图像识别的Python实现
以下是基于Kimi多模态大模型进行图像识别的Python实现。需要注意的是:
- 代码运行需要先在环境变量中设置MOONSHOT_API_KEY,把自己的API Key设置进去,具体可以参考[[基于Python快速实现与Kimi的AI对话]]。
- 要能够对上传的照片附件中所包含的对象进行识别,需要选择能够支持多模态的大模型,否则会提示模型不支持图像识别的报错信息。对于Kimi而言,此时应该设置的多模态大模型节点应该是moonshot-v1-8k-vision-preview。
可参考代码中的注释对于其运行流程进行详细的理解:
import os
from openai import OpenAI
import base64
def get_api_key():
api_key = os.getenv("MOONSHOT_API_KEY")
if not api_key:
raise ValueError("请设置 MOONSHOT_API_KEY 环境变量")
return api_key
def oneshot_chat_completio_simpe(prompt):
try:
# 读取JPEG图片文件并转换为base64
image_path = 'C:\\\\Users\\\\Administrator\\\\Desktop\\\\小鸟.jpg'
# 上传给大模型的图片附件需要以base64进行编码
with open(image_path, 'rb') as image_file:
image_base64 = base64.b64encode(image_file.read()).decode('utf-8')
client = OpenAI(
api_key=get_api_key(),
base_url="<https://api.moonshot.cn/v1>",
)
# 构建包含图片的消息
messages = [
{
"role": "system",
"content": "你是 Kimi,由 Moonshot AI 提供的人工智能助手。请帮我分析图片中的小鸟品种,并给出详细的特征描述。"
},
{
"role": "user",
"content": [
{"type": "text", "text": prompt},
{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{image_base64}"}} #此处增加上传图片的base64编码
]
}
]
completion = client.chat.completions.create(
model="moonshot-v1-8k-vision-preview", #此处需要选择多模态大模型
messages=messages,
temperature=0.3,
)
return completion.choices[0].message.content
except Exception as e:
print(f"错误:{str(e)}")
return None
def main():
response = oneshot_chat_completio_simpe("请帮我分析图片中的小鸟品种,并给出详细的特征描述。")
if response:
print(response)
if __name__ == "__main__":
main()
执行以上代码后,大模型返回的信息如下:
C:\\Users\\Administrator\\Desktop\\test> & c:/Users/Administrator/Desktop/test/venv/Scripts/python.exe c:/Users/Administrator/Desktop/test/ai_test/moonshot.py
这是一只金丝雀(学名:Serinus canaria),也被称为金翅雀。金丝雀是一种小
型鸣禽,以其鲜艳的黄色羽毛而闻名。以下是金丝雀的一些特征描述:
1. **羽毛颜色**:金丝雀的羽毛主要是黄色,胸部和腹部的羽毛颜色较浅,呈现
出柔和的黄色或白色。翅膀上有黑色和白色的条纹,这些条纹在飞行时显得非常醒
目。
2. **体型**:金丝雀的体型较小,体长一般在12-14厘米之间,体重大约在10-20
克。
3. **喙**:金丝雀的喙短而尖,适合啄食种子和昆虫。
4. **眼睛**:金丝雀的眼睛较大,颜色通常为黑色,周围可能有一圈白色的羽毛
。
5. **鸣叫**:金丝雀以其美妙的歌声而著称,雄鸟的鸣叫声尤为悦耳,常用于吸
引配偶或领地宣示。
6. **习性**:金丝雀通常生活在开阔的林地、灌木丛和花园中。它们是群居鸟类,喜欢成群结队地活动。
7. **饮食**:金丝雀主要以种子为食,也吃昆虫和其他小型无脊椎动物。
金丝雀因其美丽的外观和悦耳的歌声,常被作为宠物饲养。在这张图片中,金丝雀站在一根树枝上,旁边有几颗红色的浆果,这可能是它的食物
来源之一。
多模态大模型进行图像识别的C实现
以上利用Python和Kimi多模态大模型,基于HTTP API的方式可以很方便的实现图像识别的功能。但是大多数低成本的MCU和SOC上是无法运行Python的,所以如果要想在这类处理器上利用大模型来扩展自己的能力,就需要用C语言来实现相同的功能逻辑,这就是这里需要用C语言来重新实现以上功能的原因所在。
以下是使用Kimi多模态大模型进行图像识别功能的C语言代码实现:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include </usr/include/x86_64-linux-gnu/curl/curl.h>
/* 用于存储API响应的结构体 */
struct MemoryStruct {
char *memory;
size_t size;
};
/* 用于存储读取的图像文件的结构体 */
struct ImageData {
unsigned char *data;
size_t size;
};
/* 回调函数,用于处理CURL接收到的数据 */
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) {
printf("内存分配失败\\n");
return 0;
}
mem->memory = ptr;
memcpy(&(mem->memory[mem->size]), contents, realsize);
mem->size += realsize;
mem->memory[mem->size] = 0;
return realsize;
}
/* 从环境变量获取API密钥 */
char* get_api_key() {
char* api_key = getenv("MOONSHOT_API_KEY");
if (!api_key) {
fprintf(stderr, "错误:请设置 MOONSHOT_API_KEY 环境变量\\n");
return NULL;
}
return api_key;
}
/* 读取图像文件 */
struct ImageData read_image_file(const char* filepath) {
struct ImageData img = {NULL, 0};
FILE *file = fopen(filepath, "rb");
if (!file) {
fprintf(stderr, "无法打开图像文件: %s\\n", filepath);
return img;
}
// 获取文件大小
fseek(file, 0, SEEK_END);
img.size = ftell(file);
fseek(file, 0, SEEK_SET);
// 分配内存
img.data = (unsigned char*)malloc(img.size);
if (!img.data) {
fprintf(stderr, "内存分配失败\\n");
fclose(file);
img.size = 0;
return img;
}
// 读取文件内容
size_t read_size = fread(img.data, 1, img.size, file);
fclose(file);
if (read_size != img.size) {
fprintf(stderr, "读取图像文件失败\\n");
free(img.data);
img.data = NULL;
img.size = 0;
}
return img;
}
/* Base64编码函数 */
char* base64_encode(const unsigned char* data, size_t input_length) {
static const char base64_chars[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
size_t output_length = 4 * ((input_length + 2) / 3);
char *encoded_data = (char*)malloc(output_length + 1);
if (!encoded_data) return NULL;
size_t i, j;
for (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 << 16) + (octet_b << 8) + octet_c;
encoded_data[j++] = base64_chars[(triple >> 18) & 0x3F];
encoded_data[j++] = base64_chars[(triple >> 12) & 0x3F];
encoded_data[j++] = base64_chars[(triple >> 6) & 0x3F];
encoded_data[j++] = base64_chars[triple & 0x3F];
}
// 添加填充
for (i = 0; i < (3 - input_length % 3) % 3; i++) {
encoded_data[output_length - 1 - i] = '=';
}
encoded_data[output_length] = '\\0';
return encoded_data;
}
/* 解析JSON响应,提取内容 */
char* extract_content_from_json(const char* json_str) {
// 简单的JSON解析,实际应用中应使用cJSON等库
// 这里使用简单的字符串处理方法提取content字段
const char* content_start = strstr(json_str, "\\"content\\":");
if (!content_start) return NULL;
content_start = strchr(content_start + 10, '"');
if (!content_start) return NULL;
content_start++;
const char* content_end = strstr(content_start, "\\",");
if (!content_end) {
content_end = strstr(content_start, "\\"");
if (!content_end) return NULL;
}
size_t content_length = content_end - content_start;
char* content = (char*)malloc(content_length + 1);
if (!content) return NULL;
strncpy(content, content_start, content_length);
content[content_length] = '\\0';
return content;
}
/* 主函数:图像识别 */
char* image_recognition(const char* prompt) {
CURL *curl;
CURLcode res;
struct MemoryStruct chunk;
char* result = NULL;
char* api_key = get_api_key();
if (!api_key) return NULL;
// 初始化内存结构
chunk.memory = malloc(1);
chunk.size = 0;
// 读取图像文件
struct ImageData img = read_image_file("/home/pavel/code/module/src/bird.jpg");
if (!img.data) {
free(chunk.memory);
return NULL;
}
// Base64编码图像数据
char* base64_img = base64_encode(img.data, img.size);
free(img.data); // 释放图像数据内存
if (!base64_img) {
free(chunk.memory);
return NULL;
}
// 构建JSON请求体
char* json_template = "{\\"model\\":\\"moonshot-v1-8k-vision-preview\\",\\"messages\\":[{\\"role\\":\\"system\\",\\"content\\":\\"你是 Kimi,由 Moonshot AI 提供的人工智能助手。请帮我分析图片中的小鸟品种,并给出详细的特征描述。\\"},{\\"role\\":\\"user\\",\\"content\\":[{\\"type\\":\\"text\\",\\"text\\":\\"%s\\"},{\\"type\\":\\"image_url\\",\\"image_url\\":{\\"url\\":\\"data:image/jpeg;base64,%s\\"}}]}],\\"temperature\\":0.3}";
// 计算JSON请求体大小并分配内存
size_t json_size = strlen(json_template) + strlen(prompt) + strlen(base64_img) - 4 + 1; // -4是因为有两个%s占位符
char* json_body = (char*)malloc(json_size);
if (!json_body) {
free(base64_img);
free(chunk.memory);
return NULL;
}
// 填充JSON请求体
snprintf(json_body, json_size, json_template, prompt, base64_img);
free(base64_img); // 释放base64编码数据内存
// 初始化CURL
curl_global_init(CURL_GLOBAL_ALL);
curl = curl_easy_init();
if (curl) {
struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "Content-Type: application/json");
char auth_header[256];
snprintf(auth_header, sizeof(auth_header), "Authorization: Bearer %s", api_key);
headers = curl_slist_append(headers, auth_header);
// 设置URL和请求头
curl_easy_setopt(curl, CURLOPT_URL, "<https://api.moonshot.cn/v1/chat/completions>");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_body);
// 设置写回调函数
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk);
// 执行请求
res = curl_easy_perform(curl);
// 检查请求是否成功
if (res != CURLE_OK) {
fprintf(stderr, "curl_easy_perform() 失败: %s\\n", curl_easy_strerror(res));
} else {
// 解析响应
result = extract_content_from_json(chunk.memory);
}
// 清理
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
}
// 释放资源
free(json_body);
free(chunk.memory);
curl_global_cleanup();
return result;
}
int main() {
const char* prompt = "请帮我分析图片中的小鸟品种,并给出详细的特征描述。";
char* result = image_recognition(prompt);
if (result) {
printf("分析结果:\\n%s\\n", result);
free(result);
} else {
printf("图像识别失败\\n");
}
return 0;
}
可以看到,C语言的实现相对就要复杂很多,以上代码中自行实现了json的解析和base64编码,同时依赖libcurl来实现与Kimi Web API服务器之间的HTTP通信。因此,在编译时应该要链接libcurl库:
gcc image-recognition.c -o image-recognition -lcurl
同样的,编译后生成的image-recognition要能够正常运行,也需要在Linux系统中设置MOONSHOT_API_KEY环境变量:
export MOONSHOT_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxx