图生图 (Image-to-Image) :Python 和 C++ 两种实现方式

图生图:Python 和 C++ 两种实现方式


一、完整代码

1.1 Python 版本

python 复制代码
"""
图生图 (Image-to-Image) Demo
使用火山引擎 Ark API / 豆包 Seedream 4.5 模型
"""

import os
import argparse
import base64
import urllib.request
from openai import OpenAI

MODEL = "doubao-seedream-4-5-251128"

client = OpenAI(
    base_url="https://ark.cn-beijing.volces.com/api/v3",
    api_key="6e69285a-1461-459e-954e-a492db0d8d3b",
)


def img2img(image_path: str, prompt: str, size: str = "2K", watermark: bool = True):
    """本地图片 Base64 编码后传入图生图接口"""
    with open(image_path, "rb") as f:
        image_b64 = base64.b64encode(f.read()).decode("utf-8")

    ext = os.path.splitext(image_path)[1].lower()
    mime_map = {".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".webp": "image/webp"}
    mime = mime_map.get(ext, "image/png")

    response = client.images.generate(
        model=MODEL,
        prompt=prompt,
        size=size,
        response_format="url",
        extra_body={
            "image": f"data:{mime};base64,{image_b64}",
            "watermark": watermark,
        },
    )
    return response.data[0].url


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="图生图 Demo - 豆包 Seedream 4.5")
    parser.add_argument("-i", "--input", required=True, help="输入图片路径")
    parser.add_argument("-o", "--output", default="output.png", help="输出图片路径 (默认: output.png)")
    parser.add_argument("-p", "--prompt", required=True, help="提示词")
    parser.add_argument("-s", "--size", default="2K", help="输出尺寸 (默认: 2K)")
    parser.add_argument("--no-watermark", action="store_true", help="去除水印")
    args = parser.parse_args()

    url = img2img(
        image_path=args.input,
        prompt=args.prompt,
        size=args.size,
        watermark=not args.no_watermark,
    )
    print(f"下载中: {url}")
    urllib.request.urlretrieve(url, args.output)
    print(f"已保存到: {args.output}")

用法:

bash 复制代码
python img2img_demo.py -i ./test.png -p "转化为油画风格"
python img2img_demo.py -i ./test.png -p "赛博朋克" -o cyber.png --no-watermark

1.2 C++ 版本

cpp 复制代码
/**
 * 图生图 (Image-to-Image) Demo - C++ 版本
 * 使用火山引擎 Ark API / 豆包 Seedream 4.5 模型
 *
 * 编译: cmake -B build && cmake --build build
 * 使用: ./img2img -i input.png -p "提示词" -o output.png
 */

#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <string>
#include <cstring>
#include <curl/curl.h>
#include <nlohmann/json.hpp>

using json = nlohmann::json;

const std::string MODEL = "doubao-seedream-4-5-251128";
const std::string BASE_URL = "https://ark.cn-beijing.volces.com/api/v3";
const std::string API_KEY = "6e69285a-1461-459e-954e-a492db0d8d3b";

// ============================================================
// Base64 编码
// ============================================================
const std::string BASE64_CHARS =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

std::string base64_encode(const unsigned char* data, size_t len) {
    std::string result;
    result.reserve(4 * ((len + 2) / 3));

    for (size_t i = 0; i < len; i += 3) {
        unsigned int n = (unsigned int)data[i] << 16;
        if (i + 1 < len) n |= (unsigned int)data[i + 1] << 8;
        if (i + 2 < len) n |= (unsigned int)data[i + 2];

        result.push_back(BASE64_CHARS[(n >> 18) & 0x3F]);
        result.push_back(BASE64_CHARS[(n >> 12) & 0x3F]);
        result.push_back((i + 1 < len) ? BASE64_CHARS[(n >> 6) & 0x3F] : '=');
        result.push_back((i + 2 < len) ? BASE64_CHARS[n & 0x3F] : '=');
    }
    return result;
}

// ============================================================
// 读取文件为字节数组
// ============================================================
std::vector<unsigned char> read_file(const std::string& path) {
    std::ifstream file(path, std::ios::binary);
    if (!file) {
        std::cerr << "无法打开文件: " << path << std::endl;
        exit(1);
    }
    return {std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()};
}

// ============================================================
// 根据扩展名推断 MIME 类型
// ============================================================
std::string get_mime(const std::string& path) {
    auto dot = path.find_last_of('.');
    if (dot == std::string::npos) return "image/png";
    std::string ext = path.substr(dot);
    if (ext == ".jpg" || ext == ".jpeg") return "image/jpeg";
    if (ext == ".webp") return "image/webp";
    return "image/png";
}

// ============================================================
// libcurl 回调:将响应写入 string
// ============================================================
static size_t write_callback(void* contents, size_t size, size_t nmemb, void* userp) {
    ((std::string*)userp)->append((char*)contents, size * nmemb);
    return size * nmemb;
}

// ============================================================
// 调用图生图 API,返回结果图片 URL
// ============================================================
std::string img2img(const std::string& image_path,
                    const std::string& prompt,
                    const std::string& size = "2K",
                    bool watermark = true) {
    // 1. 读取图片并 Base64 编码
    auto raw = read_file(image_path);
    std::string b64 = base64_encode(raw.data(), raw.size());
    std::string mime = get_mime(image_path);
    std::string data_uri = "data:" + mime + ";base64," + b64;

    // 2. 构造 JSON 请求体
    json body;
    body["model"] = MODEL;
    body["prompt"] = prompt;
    body["size"] = size;
    body["response_format"] = "url";
    body["image"] = data_uri;
    body["watermark"] = watermark;

    std::string body_str = body.dump();

    // 3. 发送 HTTP POST
    CURL* curl = curl_easy_init();
    if (!curl) {
        std::cerr << "curl 初始化失败" << std::endl;
        exit(1);
    }

    std::string response_data;
    struct curl_slist* headers = nullptr;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    headers = curl_slist_append(headers, ("Authorization: Bearer " + API_KEY).c_str());

    curl_easy_setopt(curl, CURLOPT_URL, (BASE_URL + "/images/generations").c_str());
    curl_easy_setopt(curl, CURLOPT_POST, 1L);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body_str.c_str());
    curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, body_str.size());
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_data);
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, 120L);

    CURLcode res = curl_easy_perform(curl);
    if (res != CURLE_OK) {
        std::cerr << "请求失败: " << curl_easy_strerror(res) << std::endl;
        exit(1);
    }

    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (http_code != 200) {
        std::cerr << "HTTP " << http_code << ": " << response_data << std::endl;
        exit(1);
    }

    // 4. 解析返回的图片 URL
    auto resp = json::parse(response_data);
    return resp["data"][0]["url"];
}

// ============================================================
// 下载图片到本地
// ============================================================
void download(const std::string& url, const std::string& output_path) {
    CURL* curl = curl_easy_init();
    if (!curl) {
        std::cerr << "curl 初始化失败" << std::endl;
        exit(1);
    }

    std::ofstream out(output_path, std::ios::binary);
    if (!out) {
        std::cerr << "无法创建文件: " << output_path << std::endl;
        exit(1);
    }

    curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, 120L);

    auto file_callback = [](void* ptr, size_t size, size_t nmemb, void* stream) -> size_t {
        auto* ofs = (std::ofstream*)stream;
        ofs->write((char*)ptr, size * nmemb);
        return size * nmemb;
    };
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, +file_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &out);

    CURLcode res = curl_easy_perform(curl);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK) {
        std::cerr << "下载失败: " << curl_easy_strerror(res) << std::endl;
        exit(1);
    }
}

// ============================================================
// CLI
// ============================================================
struct Args {
    std::string input;
    std::string output = "output.png";
    std::string prompt;
    std::string size = "2K";
    bool no_watermark = false;
};

Args parse_args(int argc, char* argv[]) {
    Args args;
    for (int i = 1; i < argc; i++) {
        std::string arg = argv[i];
        if ((arg == "-i" || arg == "--input") && i + 1 < argc) {
            args.input = argv[++i];
        } else if ((arg == "-o" || arg == "--output") && i + 1 < argc) {
            args.output = argv[++i];
        } else if ((arg == "-p" || arg == "--prompt") && i + 1 < argc) {
            args.prompt = argv[++i];
        } else if ((arg == "-s" || arg == "--size") && i + 1 < argc) {
            args.size = argv[++i];
        } else if (arg == "--no-watermark") {
            args.no_watermark = true;
        }
    }
    return args;
}

int main(int argc, char* argv[]) {
    if (argc < 2) {
        std::cout << "图生图 Demo - 豆包 Seedream 4.5\n"
                  << "用法: ./img2img -i <输入图> -p <提示词> [-o <输出>] [-s <尺寸>] [--no-watermark]\n"
                  << "示例: ./img2img -i test.png -p \"转化为油画风格\"\n"
                  << "      ./img2img -i test.png -p \"赛博朋克\" -o cyber.png --no-watermark\n";
        return 1;
    }

    auto args = parse_args(argc, argv);

    if (args.input.empty()) {
        std::cerr << "错误: 缺少 -i/--input" << std::endl;
        return 1;
    }
    if (args.prompt.empty()) {
        std::cerr << "错误: 缺少 -p/--prompt" << std::endl;
        return 1;
    }

    std::string url = img2img(args.input, args.prompt, args.size, !args.no_watermark);
    std::cout << "下载中: " << url << std::endl;
    download(url, args.output);
    std::cout << "已保存到: " << args.output << std::endl;

    return 0;
}

编译和用法:

bash 复制代码
# 安装依赖
sudo apt install libcurl4-openssl-dev nlohmann-json3-dev cmake g++

# 编译
cmake -B build && cmake --build build

# 运行
./build/img2img -i ./test.png -p "转化为油画风格"
./build/img2img -i ./test.png -p "赛博朋克" -o cyber.png --no-watermark

二、什么是图生图 / 这个代码在做什么

2.1 图生图(Image-to-Image)

图生图就是给 AI 一张参考图 + 一段文字描述,让 AI 基于参考图生成新图片

  • 文生图:只给文字 → AI 凭空画
  • 图生图:给图片 + 文字 → AI 照着图片改、换风格、换背景

2.2 整体流程

无论 Python 还是 C++,走的都是同一条链路:

复制代码
┌──────────┐     ┌──────────┐     ┌───────────────┐     ┌──────────┐     ┌──────────┐
│ 本地图片  │ ──→ │ Base64   │ ──→ │ JSON body     │ ──→ │ HTTP POST│ ──→ │ 火山引擎  │
│ test.png │     │ 编码     │     │ {image, prompt}│     │          │     │ Ark API  │
└──────────┘     └──────────┘     └───────────────┘     └──────────┘     └────┬─────┘
                                                                            │
                                                                   返回结果图片 URL
                                                                            │
                                                              ┌─────────────┘
                                                              │
                                                        ┌─────▼──────┐
                                                        │ 下载到本地  │
                                                        │ output.png │
                                                        └────────────┘

为什么要 Base64 编码: JSON 只能传文本,图片是二进制。Base64 把图片的每一个字节翻译成可打印字符,这样图片就能"塞进"JSON 字符串里,和 API Key、prompt 一起打包发给服务器。


三、Python 方式的实现

Python 方式的核心优势:有现成的 OpenAI SDK

3.1 SDK 做了什么

python 复制代码
client = OpenAI(
    base_url="https://ark.cn-beijing.volces.com/api/v3",
    api_key="...",
)

这行创建了一个 HTTP 客户端对象。关键是 base_url 指向火山引擎,而不是 OpenAI 官网------火山引擎的 API 协议兼容 OpenAI 格式,所以能用同一个 SDK。

之后调用 client.images.generate(model=..., prompt=..., extra_body={...}) 时,SDK 在背后做了这些事:

  1. 把参数和 extra_body 合并,转成 JSON 字符串
  2. 拼完整 URL:https://ark.cn-beijing.volces.com/api/v3/images/generations
  3. 加上 Authorization: Bearer xxx 请求头
  4. 加上 Content-Type: application/json
  5. 发 HTTP POST 请求
  6. 解析返回的 JSON 响应
  7. 检查状态码,非 200 则抛异常
  8. 返回封装好的 Python 对象

你只写了一行代码,SDK 代劳了七步。

3.2 依赖的标准库

Python 版除了 openai SDK,其余的 base64argparseurllib 全是标准库自带,不需要额外安装:

用途
base64 图片二进制 → Base64 文本
argparse 解析 -i / -p / -o 命令行参数
urllib.request 下载生成的图片到本地

四、C++ 方式的实现

C++ 没有 OpenAI SDK,也没有内置 Base64,也没有内置 JSON 库,也没有内置 HTTP 库。所有东西要么手写,要么用第三方库。

4.1 依赖

Python C++
HTTP 通信 openai SDK(内部用 httpx/urllib) libcurl
JSON 内置 dict nlohmann/json(第三方头文件库)
Base64 标准库 import base64 手写 ~15 行(或用 OpenSSL)
文件读写 open() + f.read() std::ifstream + istreambuf_iterator
CLI 参数 argparse(标准库) 手写 argv 循环

4.2 关键差异:HTTP 请求

这是两个版本最大的不同点。Python 的 client.images.generate() 一行,在 C++ 里对应的是整套 libcurl 操作流程(下面有完整章节讲)。

4.3 关键差异:Base64

Python 有 base64.b64encode() 标准库,C++ 没有。要么自己写 ~15 行(如本项目),要么引入 OpenSSL、Boost 等第三方库。


五、C++ 中的 curl 深度解析

5.1 curl 是什么

curl 是一个发送网络请求的工具/库。它有两个形态:

形态 叫什么 怎么用 本项目用到
命令行工具 curl 终端里敲 curl -X POST ... 没有
C 语言库 libcurl 代码里 #include <curl/curl.h> 全部用它

它们是同一个项目(同名、同作者、同代码基础),只是调用层面不同。libcurl 就是命令行 curl 的 C 语言内核 ,你在终端敲的每个 curl 命令,底层都是通过 libcurl 的 API 执行的。

5.2 命令行 curl vs 代码中的 libcurl

如果把 C++ 代码做的事翻译成你熟悉的命令行 curl,就是:

bash 复制代码
curl -X POST \
  "https://ark.cn-beijing.volces.com/api/v3/images/generations" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer 6e69285a-..." \
  -d '{"model":"doubao-seedream-4-5-251128","prompt":"转化为油画风格","image":"data:image/png;base64,iVBORw0KGgo...","size":"2K","response_format":"url","watermark":true}'

代码里的每一行 curl_easy_setopt,在命令行 curl 里都有对应的参数:

复制代码
命令行的 -X POST       →  curl_easy_setopt(curl, CURLOPT_POST, 1L)
命令行的 -H "..."      →  curl_slist_append(headers, "...")
命令行的 -d '...'      →  curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body_str.c_str())
命令行的 https://...    →  curl_easy_setopt(curl, CURLOPT_URL, "https://...")

区别只是:命令行 curl 帮你完成了 init / perform / cleanup,而代码里你得手动做。

5.3 libcurl 的四步固定流程

每次用 libcurl 发请求,必须走这四个步骤:

复制代码
curl_easy_init()          ← 创建会话对象
      │
      ▼
curl_easy_setopt() × N    ← 配置各种参数
      │
      ▼
curl_easy_perform()       ← 真正执行,阻塞等待
      │
      ▼
curl_easy_cleanup()       ← 销毁会话,释放资源

步骤 1 --- curl_easy_init()

在堆上创建一个 curl 会话对象(句柄)。"easy" 不是"简单"的意思 ,是相对于 curl_multi_(多路并发接口)而言的"单次同步请求模式"。

步骤 2 --- curl_easy_setopt()

这是配置的核心。curl 用一个统一的函数来设置所有参数,通过第二个参数(option)来区分"你要设什么东西":

cpp 复制代码
curl_easy_setopt(curl, CURLOPT_URL,             "...");   // 设目标 URL
curl_easy_setopt(curl, CURLOPT_POST,            1L);      // 设 HTTP 方法为 POST
curl_easy_setopt(curl, CURLOPT_HTTPHEADER,      headers); // 设请求头
curl_easy_setopt(curl, CURLOPT_POSTFIELDS,      "...");   // 设 body 内容
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE,   123);     // 设 body 长度
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,   callback);// 设接收回调
curl_easy_setopt(curl, CURLOPT_WRITEDATA,       &buf);    // 设回调参数
curl_easy_setopt(curl, CURLOPT_TIMEOUT,         120L);    // 设超时

每个 CURLOPT_* 控制一个维度。整个配置阶段就是不断调 setopt,把你关心的事情都设好。

步骤 3 --- curl_easy_perform()

真正的执行。调用后阻塞当前线程 ,直到请求完成、超时、或出错。返回值是 CURLcode 枚举,表示网络层面的结果(不是 HTTP 状态码)。

步骤 4 --- curl_easy_cleanup()

销毁会话,释放 libcurl 内部持有的所有资源(连接池、缓存、cookie 等)。

5.4 回调机制:libcurl 最核心的设计

这是使用 libcurl 最容易困惑的地方。大多数 HTTP 库都是"调一个函数,返回完整响应",libcurl 不是。

libcurl 的做法是:服务器返回数据是分块到达的,每收到一块,libcurl 就调一次你注册的回调函数,把这一小块数据塞给你。

cpp 复制代码
// 注册回调
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);  // "用这个函数收数据"
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_data);      // "这个指针传给回调"

// 回调的实现
static size_t write_callback(void* contents, size_t size, size_t nmemb, void* userp) {
    // contents → 刚收到的一小块数据
    // size * nmemb → 这一小块有多大
    // userp → 就是上面设置的 &response_data
    ((std::string*)userp)->append((char*)contents, size * nmemb);
    return size * nmemb;
}

为什么不直接返回完整结果: 网络数据不是一次到齐的。TCP 是流式协议,服务器可能先发 1460 字节,再发 1460 字节,再发 800 字节。回调让你每一块到达时立刻可以处理,不用等全部收完。对于大文件下载(比如图片),可以一块一块直接写磁盘,不占满内存。

WRITEDATAWRITEFUNCTION 的关系:

复制代码
curl 内部每收到一块数据:
  write_callback(
      contents,       ← 数据块地址
      size,           ← 每元素大小 (通常是 1)
      nmemb,          ← 元素个数
      &response_data  ← 你通过 CURLOPT_WRITEDATA 传入的指针
  )

WRITEFUNCTION 决定"谁来处理",WRITEDATA 决定"处理时把什么传进去"。

5.5 两次 curl 调用的回调对比

本项目中共用了两次 libcurl------一次发图生图请求,一次下载结果图片。它们的回调设计不同:

img2img(发 POST) download(GET 下载)
目的 收 JSON 响应文本 收二进制图片数据
写到哪里 std::string(内存) std::ofstream(直接写磁盘)
为什么 JSON 很小,需要解析后取 URL 图片可能很大,不占内存
实现方式 static 普通函数 C++ lambda 表达式
cpp 复制代码
// POST 时:数据拼进 string,后续 json::parse() 解析
static size_t write_callback(void* contents, size_t size, size_t nmemb, void* userp) {
    ((std::string*)userp)->append((char*)contents, size * nmemb);
    return size * nmemb;
}

// 下载时:数据直接写磁盘
auto file_callback = [](void* ptr, size_t size, size_t nmemb, void* stream) -> size_t {
    auto* ofs = (std::ofstream*)stream;
    ofs->write((char*)ptr, size * nmemb);
    return size * nmemb;
};

5.6 错误处理的两个层次

cpp 复制代码
// 第一层:网络错误
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK) {
    // "网线拔了"、"DNS 挂了"、"SSL 过期"、"超时"
    // 服务器根本没收到请求
}

// 第二层:HTTP 错误
long http_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
if (http_code != 200) {
    // 服务器收到了请求,但拒绝了:
    //   400 → 参数错误
    //   401 → API Key 无效
    //   500 → 服务器内部错误
}

必须先检查第一层再检查第二层------网络通才有 HTTP 状态码可言。

5.7 资源释放顺序

cpp 复制代码
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);  // 1. 先读数据
curl_slist_free_all(headers);                                   // 2. 释放请求头链表
curl_easy_cleanup(curl);                                        // 3. 销毁会话

顺序不能乱:

  • getinfo 必须在 cleanup 之前(会话销毁了就读不到了)
  • free_all 可以在 getinfo 后任意时机(headers 不属于会话内部状态)

六、两个版本的关键对应关系

环节 Python C++
读文件 open(path, "rb").read() std::ifstream + istreambuf_iterator
Base64 base64.b64encode()(标准库) base64_encode()(手写 ~15 行)
拼 Data URI f"data:{mime};base64,{b64}" "data:" + mime + ";base64," + b64
构造 JSON dict 字面量 nlohmann::json,语法几乎一样
序列化 JSON SDK 内部 json.dumps() body.dump()
HTTP 请求 client.images.generate() 一行 curl_easy_* 约 20 行
设置鉴权 api_key="..." 参数 手动拼 Authorization 请求头
解析响应 .data[0].url json::parse()["data"][0]["url"]
下载图片 urllib.request.urlretrieve() curl_easy_* GET + ofstream
CLI 参数 argparse(标准库) 手写 argv for 循环

七、总结

Python 版本适合快速验证、demo。 60 行代码,SDK 代劳了大量底层操作,你可以专注于业务逻辑(拼 prompt、调参数)。

C++ 版本适合嵌入更大的 C++ 项目。 代码量大约是 Python 的 4 倍,但你能完全控制每一步------用哪个 HTTP 库、JSON 怎么解析、内存如何分配。curl 虽然用起来啰嗦,但它是一个稳定了 25 年的 C 库,几乎每台 Linux 机器上都有,没有额外的运行时依赖。

两个版本发出的 HTTP 请求是完全一样的,服务器无法区分请求是 Python 发的还是 C++ 发的------它们等价。

相关推荐
JOJO数据科学7 小时前
JupyterLab Electron 鸿蒙 PC 适配全记录:从 Python 原生崩溃到 node-static 本地工作台
python·electron·harmonyos
xufengzhu7 小时前
第三方 Python 库 redis-py + hiredis 的使用
开发语言·redis·python
JAVA面经实录9177 小时前
操作系统(面试全覆盖)
java·计算机网络·面试
编程的一拳超人7 小时前
Maven 国内高速镜像推荐(按速度排序)
java·maven
llxxyy卢7 小时前
polar夏季赛部分题目
开发语言·python
闵孚龙7 小时前
PyTorch 系列 之 nn.Module:所有模型的骨架
人工智能·pytorch·python
AI玫瑰助手7 小时前
Python模块:from...import...导入指定内容
开发语言·python·信息可视化
小森林之主8 小时前
Python re 模块速查:从实战对比中掌握正则表达式
python·正则表达式·性能测试·re模块·编程实战
FL16238631298 小时前
[cmake]基于C++使用纯opencv部署ppocrv5v6的onnx模型
开发语言·c++·opencv