本文介绍了如何在 i.MX8MP 嵌入式平台上使用 C++ 调用豆包 AI 大模型(Doubao-vision-pro-32k)进行图像问答。我们将详细讲解代码实现的各个步骤,包括文件读取、Base64 编码、构造 JSON 请求体、使用 libcurl 进行 HTTP POST 请求以及解析响应数据等。
一、背景介绍
在嵌入式平台上进行视觉理解任务时,通常需要将图像数据上传至云端的 AI 服务进行处理。豆包 AI 大模型提供了一套 API 接口,可以通过 HTTP 请求将图像信息发送给模型,返回图像内容的问答结果。本文通过 C++ 代码实现这一流程,并在 i.MX8MP 平台上进行了部署和测试。
二、代码实现说明
1. 文件读取与 Base64 编码
为了让 API 能够接收图像数据,我们首先需要将本地图片以二进制方式读取,然后将其转换为 Base64 编码的字符串。代码中实现了两个函数:
- read_file:用于以二进制模式读取指定路径的文件,并返回存储在 std::vector 中的文件数据。
- base64_encode:将读取到的二进制数据转换为 Base64 编码字符串。
这部分代码确保了图片数据能够以字符串形式嵌入 JSON 请求体中。
2. 构造 JSON 请求体
使用 nlohmann::json 库,我们构造了一个与豆包 AI 模型 API 要求一致的 JSON 请求体。请求体中包含以下内容:
- 指定模型名称(例如 "doubao-vision-pro-32k-241028")。
- 消息数组,其中包含用户输入的文本问题以及图片数据(通过 Base64 编码后的数据,嵌入在 image_url 字段中)。
这种结构与 Python 版示例保持一致,确保接口能正确解析传递的参数。
3. 使用 libcurl 发送 HTTP POST 请求
接下来,我们利用 libcurl 发送 POST 请求:
- 设置 API URL(此处为 https://ark.cn-beijing.volces.com/api/v3/chat/completions,请根据实际情况调整)。
- 配置 HTTP 头信息,包括 Content-Type: application/json 以及通过环境变量 ARK_API_KEY 获取的 API 密钥(通过 Authorization: Bearer ... 进行认证)。
- 设置回调函数 WriteCallback 用于接收 HTTP 响应数据,并将其存入一个 std::string 中。
4. 解析响应并输出结果
请求成功后,返回的响应数据是 JSON 格式。代码使用 nlohmann::json 对响应进行解析,并提取出模型回复的内容(位于 JSON 的 "choices" 数组中)。若响应格式正确,则输出回复文本;否则打印异常信息。
三、代码运行步骤
- 环境配置
- 确保在 i.MX8MP 系统上安装了 libcurl、nlohmann/json 等依赖库。
- 设置环境变量 ARK_API_KEY,保存你的 API 密钥:export ARK_API_KEY="your_api_key_here"
- 编译代码
- 使用 g++ 编译代码:${CXX} -std=c++11 main.cpp -o ark_example -lcurl
- 运行程序
- 运行生成的可执行文件,并传入图片路径(若不传,则默认使用代码中指定的路径):./ark_example test.png

四、源代码
qingzong@q-PowerEdge-R740xd:~/tmp$ cat main.cpp
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <cstdlib>
#include <string>
#include <curl/curl.h>
#include <nlohmann/json.hpp>// 使用 nlohmann::json 处理 JSON
using json = nlohmann::json;// Base64 编码函数:将二进制数据转换为 Base64 字符串
std::string base64_encode(const unsigned char* data, size_t len) {static const std::string base64_chars ="ABCDEFGHIJKLMNOPQRSTUVWXYZ""abcdefghijklmnopqrstuvwxyz""0123456789+/";std::string ret;int i = 0;unsigned char char_array_3[3];unsigned char char_array_4[4];while (len--) {char_array_3[i++] = *(data++);if (i == 3) {char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);char_array_4[3] = char_array_3[2] & 0x3f;for (i = 0; i < 4; i++)ret.push_back(base64_chars[char_array_4[i]]);i = 0;}}if (i) {for (int j = i; j < 3; j++)char_array_3[j] = '\0';char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);char_array_4[3] = char_array_3[2] & 0x3f;for (int j = 0; j < i + 1; j++)ret.push_back(base64_chars[char_array_4[j]]);while ((i++ < 3))ret.push_back('=');}return ret;
}// 读取指定路径的文件,以二进制方式读取并返回数据
std::vector<unsigned char> read_file(const std::string &file_path) {std::ifstream file(file_path, std::ios::binary);if (!file) {std::cerr << "无法打开文件: " << file_path << std::endl;return {};}std::vector<unsigned char> buffer(std::istreambuf_iterator<char>(file), {});return buffer;
}// libcurl 回调函数:将 HTTP 响应写入 std::string
size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userp) {std::string* str = static_cast<std::string*>(userp);size_t totalSize = size * nmemb;str->append(static_cast<char*>(contents), totalSize);return totalSize;
}int main(int argc, char* argv[]) {// 从环境变量中获取 API 密钥const char* api_key = std::getenv("ARK_API_KEY");if (!api_key) {std::cerr << "请设置环境变量 ARK_API_KEY" << std::endl;return EXIT_FAILURE;}// 指定需要传给大模型的图片路径(根据实际情况修改)std::string image_path = "/home/qingzong/work/1.png";if (argc > 1) {image_path = argv[1];}std::cout << "Using image path: " << image_path << std::endl;// 读取图片并进行 Base64 编码std::vector<unsigned char> file_data = read_file(image_path);if (file_data.empty()) {std::cerr << "图片读取失败" << std::endl;return EXIT_FAILURE;}std::string base64_image = base64_encode(file_data.data(), file_data.size());// 构造 JSON 请求体,与 Python 代码中结构一致json payload;payload["model"] = "doubao-vision-pro-32k-241028";payload["messages"] = json::array({{{"role", "user"},{"content", json::array({{ {"type", "text"}, {"text", "图片里讲了什么?"} },{ {"type", "image_url"}, {"image_url", { {"url", "data:image/jpeg;base64," + base64_image} } } }})}}});std::string payload_str = payload.dump();// 设置 API URL,请根据实际 API 文档进行调整std::string api_url = "https://ark.cn-beijing.volces.com/api/v3/chat/completions";// 初始化 CURLCURL* curl = curl_easy_init();if (!curl) {std::cerr << "无法初始化 CURL" << std::endl;return EXIT_FAILURE;}std::string response_string;// 设置 CURL 选项curl_easy_setopt(curl, CURLOPT_URL, api_url.c_str());curl_easy_setopt(curl, CURLOPT_POST, 1L);curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload_str.c_str());curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_string);curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);// 设置 HTTP 请求头:Content-Type 及 API 认证信息(头名称依据实际情况调整)struct curl_slist* headers = nullptr;headers = curl_slist_append(headers, "Content-Type: application/json");std::string header_api = "Authorization: Bearer " + std::string(api_key);headers = curl_slist_append(headers, header_api.c_str());curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);// 执行 HTTP POST 请求CURLcode res = curl_easy_perform(curl);if (res != CURLE_OK) {std::cerr << "CURL 请求失败: " << curl_easy_strerror(res) << std::endl;} else {try {// 解析返回的 JSON 数据json response_json = json::parse(response_string);if (response_json.contains("choices") && !response_json["choices"].empty() &&response_json["choices"][0].contains("message") &&response_json["choices"][0]["message"].contains("content")){std::string reply = response_json["choices"][0]["message"]["content"];std::cout << "回复: " << reply << std::endl;} else {std::cout << "响应格式异常: " << response_string << std::endl;}} catch (json::parse_error& e) {std::cerr << "JSON 解析错误: " << e.what() << std::endl;}}// 清理 CURL 相关资源curl_slist_free_all(headers);curl_easy_cleanup(curl);return EXIT_SUCCESS;
}