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

在 i.MX8MP 上用 C++ 调用豆包 AI 大模型实现图像问答

本文介绍了如何在 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";

    // 初始化 CURL
    CURL* 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;
}

http://www.kler.cn/a/619570.html

相关文章:

  • 计算机网络复习(第二天)
  • 关于伽马变换小记
  • Kafka中的消息是如何存储的?
  • 汽车方向盘开关功能测试的技术解析
  • 人工智能与软件工程结合的发展趋势
  • VScode配置默认终端为Anaconda Prompt
  • Scala 基础语法
  • PPT——组合SCI论文图片
  • 基于SpringBoot + Vue 的校园周边美食探索及分享平台
  • Uni-app页面信息与元素影响解析
  • 蓝桥杯 双子数
  • SLAM——多传感器标定
  • EtherCAT转ProfiNet协议转换网关构建西门子PLC与海克斯康机器人的冗余通信链路
  • 具身机器人(Embodied Robotics)技术架构与发展路径
  • PostgreSQL学习之一次一密口令认证(TOTP)
  • 【LeetCode 题解】算法:8.字符串转换整数(atoi)
  • 清晰易懂的Rust安装与配置教程
  • SmolDocling文档处理模型介绍
  • git命令使用小记(打补丁)
  • IP综合实验