AI 商品图:场景列表、抠图与任务生成

描述

获取商品图推荐场景,对商品进行抠图处理,并提交场景图生成任务。任务提交后,使用业务任务 ID 查询生成进度和结果。

接口概览

接口方法地址
推荐场景列表GEThttps://openapi.meitu.com/design/enterprise/ai_product/materials
商品抠图POSThttps://openapi.meitu.com/design/enterprise/ai_product/sod
任务投递POSThttps://openapi.meitu.com/design/enterprise/ai_product/create
任务查询GEThttps://openapi.meitu.com/design/enterprise/ai_product/query

权限

使用已开通该能力的 AK/SK,通过签名 SDK 签名。详见开放平台接口签名。

GET 参数放在 URL 查询串中,并将完整 URL 用于签名;POST 使用 Content-Type: application/json。商品抠图调用示例额外包含 X-Aigcp-Way: handler。

调用顺序

  1. 获取推荐场景列表,选择场景 id。
  2. 如需调整商品区域,调用抠图接口获取结果及 x、y、width、height;无需自行抠图时,投递请求可省略 mask_url,由服务自动处理。
  3. 提交生成任务,保存响应中的 data.id。
  4. 调用本篇专用查询接口,传入该 id。当 process_status=2 时获取结果,3 表示失败。

本篇任务查询使用 data.id,不是 task_ids[] 或 scene_result_images[].task_id,也不使用通用任务接口的状态值。

一、获取推荐场景列表

调用方式

GET https://openapi.meitu.com/design/enterprise/ai_product/materials

请求参数

参数名类型参数说明
cursorstring分页游标,首次请求可省略,默认空;后续请求使用上次响应的 data.cursor。
countinteger本次希望返回的条数,示例为 20;原文未说明默认值和上限。

示例 URL:

https://openapi.meitu.com/design/enterprise/ai_product/materials?count=20

返回值说明

code=0 表示成功;reqid 为排查问题使用的请求 ID。场景列表在 data.data,分页游标在 data.cursor。游标是接口返回的不透明值,不自行拼接。

data.data 元素

字段类型说明
idinteger (64-bit)场景素材 ID。投递时转为字符串传给 material_id。
default_namestring场景名称。
thumbnailobject缩略图,包含 url、width、height。
typestring类型,例如 ai_product_photo_scene。
ai_product_type / is_color_scene / can_image_videointeger产品类型、色彩场景标识、图片与视频支持标识;can_image_video=0 表示不支持。
alg_params / extra / light_modestring预留算法参数、扩展信息和光照模式,可为空。
max_ratio / min_rationumber最大、最小比例,原文默认 0。
prompt_idstring关联提示词 ID。
preview_v2object预览配置。
portrait_style_category_ids / v2_category_ids / v2_style_category_idsarray人像风格、分类和风格分类 ID 列表。

返回值示例

HTTP 200。以下响应由原文示例整理,ID、名称、地址等为演示数据,不保证素材当前可用;调用时使用场景列表实际返回的 ID。

{
  "reqid": "example-request-id",
  "code": 0,
  "data": {
    "cursor": "cs-1-1",
    "data": [
      {
        "ai_product_type": 0,
        "alg_params": "",
        "can_image_video": 0,
        "default_name": "示例场景",
        "extra": "",
        "id": 1759202837959028,
        "is_color_scene": 0,
        "light_mode": "",
        "max_ratio": 0,
        "min_ratio": 0,
        "portrait_style_category_ids": [],
        "preview_v2": {
          "4592": {}
        },
        "prompt_id": "10893",
        "thumbnail": {
          "height": 1024,
          "url": "https://example.com/scene.jpeg",
          "width": 1024
        },
        "type": "ai_product_photo_scene",
        "v2_category_ids": [
          4592
        ],
        "v2_style_category_ids": [
          4606
        ]
      }
    ]
  }
}

二、商品抠图

描述与版本

对商品图进行抠图,返回抠图地址及位置、尺寸信息。版本:1.0。

图片要求

支持 JPG、PNG。

调用方式

POST https://openapi.meitu.com/design/enterprise/ai_product/sod

请求头:Content-Type: application/json、X-Aigcp-Way: handler,以及 SDK 生成的签名头。

请求参数

是否必选参数名类型参数说明
是urlstring商品图片 URL。

输入值示例

{
  "url": "https://example.com/product.png"
}

返回值说明

字段类型说明
reqidstring请求 ID。
codeinteger0 表示成功。
data.urlstring抠图结果地址。
data.x / data.yinteger商品区域起始坐标。
data.width / data.heightinteger商品区域宽高,单位为像素。

需要构造 box1 时,按 x,y,width,height 顺序组合,以下示例对应 "1,0,286,550"。

返回值示例

HTTP 200。

{
  "reqid": "example-request-id",
  "code": 0,
  "data": {
    "url": "https://example.com/cutout.png",
    "height": 550,
    "width": 286,
    "x": 1,
    "y": 0
  }
}

三、任务投递

描述与版本

提交 AI 商品场景图生成任务。版本:1.0。支持 JPG、PNG 输入图片。

调用方式

POST https://openapi.meitu.com/design/enterprise/ai_product/create

Content-Type: application/json

请求参数

所有字段直接放在请求体根级。

是否必选参数名类型参数说明
是urlstring商品原图 URL。
是material_idstring场景素材 ID,从场景列表的 id 获取;多个 ID 用英文逗号分隔,整体仍为字符串。
否mask_urlstring商品抠图 mask 地址,不传时自动抠图。
否box1string原始商品区域,格式 x,y,width,height,取自抠图返回坐标和宽高;不调整位置或大小时可省略。
否box2string商品在画布上的外框,格式 x,y,w,h;不传时自动居中,数值可包含小数。
否product_categorystring英文商品类别,默认 product。
否callback_urlstring接收任务状态的 POST 回调地址。
否scene_typenumber1 推荐场景,需 material_id;2 自定义场景,需 prompt。默认 1。
条件必选promptstring自定义场景描述,scene_type=2 时必填。
否canvas_wnumber画布宽度,默认 512,范围 50–1024,步长 0.5。生成图宽度为画布宽度的两倍。
否canvas_hnumber画布高度,默认 512,范围 50–1024,步长 0.5。生成图高度为画布高度的两倍。
否anglenumber逆时针旋转角度,范围 0–360;box2 填旋转前的数据。
否flipnumber0 不镜像,1 左右镜像,2 上下镜像,3 左右和上下镜像。
否refimage_urlstring参考图 URL,仅 scene_type=2 时有效。
否prompt_vs_refimagenumber文本与参考图的生图倾向,范围 -1.0–1.0,仅 scene_type=2 时有效。
否guidance_scalenumber创意相关性参数,范围 5.0–15.0,仅 scene_type=2 时有效。
否stepsinteger采样步数,范围 15–35,仅 scene_type=2 时有效。
否prompt_negstring不希望生成的内容描述,仅 scene_type=2 时有效。

material_id 按原文保留为必选。自定义场景还需填写 prompt;原文没有明确允许自定义场景省略 material_id。

输入值示例

推荐场景生成,使用自动抠图。请将 material_id 替换为场景列表实际返回的 ID。

{
  "url": "https://example.com/product.png",
  "material_id": "1759202837959028",
  "scene_type": 1,
  "product_category": "product",
  "canvas_w": 512,
  "canvas_h": 512
}

如需位置和尺寸调整,可在请求中补充 mask_url、box1、box2。其中 mask_url 必须是适用于该请求的 mask 地址,不能仅凭字段名把任意图片结果当作 mask。

返回值说明

code=0 表示任务投递成功,不表示图片已经生成。保存 data.id,通过下一节的查询接口获取最终结果。

字段类型说明
idinteger用于 /query?id=... 的业务任务 ID。
process_statusinteger1 处理中,2 已完成,3 失败。
result_image_urlsstring[]生成结果地址。投递时尚无结果,可能为空数组;完成后通过查询获取。
result_imagesobject[]结果对象列表:id 为字符串,url 为地址,process_status=2 表示该图完成。
scene_result_imagesobject[]按场景分组的结果:scene_id、urls、process_status、task_id。
result_numinteger生成结果数量。
task_idsarray内部任务列表,不是 /query 的查询 ID。
materialsobject / null场景素材信息。
url / mask_url / refimage_urlstring原图、mask 和参考图地址。
box1 / box2string原始区域及调整后区域,格式 x,y,width,height。
canvas_w / canvas_hnumber画布宽高。
angle / flip / scene_typenumber旋转、镜像和场景类型,含义同请求参数。
product_category / prompt / translate_promptstring商品类别、提示词及翻译后的提示词。
prompt_vs_refimage / guidance_scale / stepsnumber对应的生成参数。
created_at / finished_at / updated_atinteger创建、完成、更新时间戳。
client_id / uid / user_material_id / z_indexinteger客户端、用户、自定义素材及图层顺序信息。

返回值示例

HTTP 200。以下为精简的处理中响应;完整响应可能包含上表中的其他字段。

{
  "reqid": "example-request-id",
  "code": 0,
  "data": {
    "id": 46062184,
    "process_status": 1,
    "result_image_urls": [],
    "result_images": [],
    "scene_result_images": [],
    "result_num": 1,
    "task_ids": [
      995952
    ],
    "scene_type": 1,
    "canvas_w": 512,
    "canvas_h": 512,
    "url": "https://example.com/product.png"
  }
}

四、任务查询

调用方式

GET https://openapi.meitu.com/design/enterprise/ai_product/query

请求参数

是否必选参数名类型参数说明
是idinteger投递接口返回的 data.id。
https://openapi.meitu.com/design/enterprise/ai_product/query?id=46062184

替换为本次任务的真实 ID;不使用 task_ids 中的 ID。

返回值说明

响应结构与任务投递的任务详情一致:

  • process_status=1:处理中,稍后继续查询。
  • process_status=2:已完成,读取 result_image_urls。
  • process_status=3:失败。

result_images 包含逐张图片的 ID、URL 和状态;scene_result_images 包含按场景分组的结果。不要因任务仍在处理中而重复投递。

返回值示例

HTTP 200,精简的完成响应:

{
  "reqid": "example-request-id",
  "code": 0,
  "data": {
    "id": 46062184,
    "process_status": 2,
    "result_image_urls": [
      "https://example.com/result.jpeg"
    ],
    "result_images": [
      {
        "id": "995952-0",
        "url": "https://example.com/result.jpeg",
        "process_status": 2
      }
    ],
    "result_num": 1,
    "scene_result_images": [
      {
        "scene_id": 10023,
        "urls": [
          "https://example.com/result.jpeg"
        ],
        "process_status": 2,
        "task_id": 995952
      }
    ],
    "scene_type": 1,
    "canvas_w": 512,
    "canvas_h": 512,
    "url": "https://example.com/product.png"
  }
}

错误码与失败响应

失败时读取 code、message,并保留 reqid。原文失败示例为 HTTP 400。

错误码说明
11001参数错误,例如缺少 material_id 或查询 ID。
11002处理失败,例如抠图失败。

以下为投递失败示例:

{
  "reqid": "example-request-id",
  "code": 11001,
  "message": "缺少必要参数material_id"
}

其他错误详见 API 错误码。

以上响应示例基于原文整理,不是本次实测返回。

SDK 调用示例

每种语言提供一个完整示例,修改 operation 选择 materials、sod、create 或 query,每次只调用一个接口。默认获取场景列表,不自动串联投递任务或轮询。

引入对应签名 SDK 后,替换 AK、SK、图片 URL、实际场景 ID 或查询 ID。请求体已内嵌,不依赖外部 JSON 文件。分页时将返回的 cursor 进行 URL 编码后加入场景列表 URL,再对最终 URL 签名。

Python

Python 签名 SDK 接入文档

import json

import requests
from sign_sdk import sign

def build_request(operation):
    base = "https://openapi.meitu.com/design/enterprise/ai_product/"
    options = {
        "materials": ("GET", base + "materials?count=20", None),
        "sod": ("POST", base + "sod", {
            "url": "https://example.com/product.png"
        }),
        "create": ("POST", base + "create", {
            "url": "https://example.com/product.png",
            "material_id": "1759202837959028",
            "scene_type": 1,
            "product_category": "product",
            "canvas_w": 512,
            "canvas_h": 512
        }),
        "query": ("GET", base + "query?id=46062184", None),
    }
    method, url, payload = options[operation]
    body = "" if payload is None else json.dumps(payload, ensure_ascii=True)
    headers = {sign.HeaderHost: "openapi.meitu.com"}
    if method == "POST":
        headers["Content-Type"] = "application/json"
    if operation == "sod":
        headers["X-Aigcp-Way"] = "handler"
    signer = sign.Signer("YOUR_ACCESS_KEY", "YOUR_SECRET_KEY")
    return signer.sign(url, method, headers, body)

def main():
    operation = "materials"  # materials, sod, create, query
    request = build_request(operation)
    with requests.Session() as session:
        response = session.send(
            request, timeout=(10, 60), verify=True, allow_redirects=False
        )
        print("Status:", response.status_code)
        print("Response:", response.text)

if __name__ == "__main__":
    main()

Go

Go 签名 SDK 接入文档

package main

import (
    "fmt"
    "io"
    "net/http"
    "time"

    "github.com/mtlab/api/signer"
)

func main() {
    operation := "materials" // materials, sod, create, query
    base := "https://openapi.meitu.com/design/enterprise/ai_product/"
    var method, url, body string
    headers := make(http.Header)
    headers.Set(signer.HeaderHost, "openapi.meitu.com")
    switch operation {
    case "materials":
        method, url = http.MethodGet, base+"materials?count=20"
    case "sod":
        method, url = http.MethodPost, base+"sod"
        body = `{"url":"https://example.com/product.png"}`
        headers.Set("X-Aigcp-Way", "handler")
    case "create":
        method, url = http.MethodPost, base+"create"
        body = `{"url":"https://example.com/product.png","material_id":"1759202837959028","scene_type":1,"product_category":"product","canvas_w":512,"canvas_h":512}`
    case "query":
        method, url = http.MethodGet, base+"query?id=46062184"
    default:
        fmt.Println("Unknown operation:", operation)
        return
    }
    if method == http.MethodPost {
        headers.Set("Content-Type", "application/json")
    }
    signObj := signer.NewSigner("YOUR_ACCESS_KEY", "YOUR_SECRET_KEY")
    req, err := signObj.Sign(url, method, headers, body)
    if err != nil {
        fmt.Println("Sign error:", err)
        return
    }
    client := &http.Client{
        Timeout: 60 * time.Second,
        CheckRedirect: func(req *http.Request, via []*http.Request) error {
            return http.ErrUseLastResponse
        },
    }
    resp, err := client.Do(req)
    if err != nil {
        fmt.Println("Request error:", err)
        return
    }
    defer resp.Body.Close()
    data, err := io.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("Read error:", err)
        return
    }
    fmt.Println("Status:", resp.StatusCode)
    fmt.Println("Response:", string(data))
}

PHP

PHP 签名 SDK 接入文档

<?php
require_once __DIR__ . '/signer.php';

$operation = 'materials'; // materials, sod, create, query
$base = 'https://openapi.meitu.com/design/enterprise/ai_product/';
$options = [
    'materials' => ['GET', $base . 'materials?count=20', null],
    'sod' => ['POST', $base . 'sod', [
        'url' => 'https://example.com/product.png'
    ]],
    'create' => ['POST', $base . 'create', [
        'url' => 'https://example.com/product.png',
        'material_id' => '1759202837959028',
        'scene_type' => 1,
        'product_category' => 'product',
        'canvas_w' => 512,
        'canvas_h' => 512
    ]],
    'query' => ['GET', $base . 'query?id=46062184', null],
];
[$method, $url, $payload] = $options[$operation];
$body = $payload === null ? '' : json_encode(
    $payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
);
$headers = ['Host' => 'openapi.meitu.com'];
if ($method === 'POST') {
    $headers['Content-Type'] = 'application/json';
}
if ($operation === 'sod') {
    $headers['X-Aigcp-Way'] = 'handler';
}
$signer = new Signer('YOUR_ACCESS_KEY', 'YOUR_SECRET_KEY');
$curl = $signer->sign($url, $method, $headers, $body);
if ($method === 'GET') {
    curl_setopt($curl, CURLOPT_HTTPGET, true);
}
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($curl, CURLOPT_TIMEOUT, 60);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);
$response = curl_exec($curl);
if ($response === false) {
    echo 'Error: ' . curl_error($curl) . PHP_EOL;
} else {
    echo 'Status: ' . curl_getinfo($curl, CURLINFO_HTTP_CODE) . PHP_EOL;
    echo 'Response: ' . $response . PHP_EOL;
}
curl_close($curl);

Java

Java 签名 SDK 接入文档

import com.meitu.openai.common.Signer;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) throws Exception {
        String operation = "materials"; // materials, sod, create, query
        String base = "https://openapi.meitu.com/design/enterprise/ai_product/";
        String method;
        String url;
        String body = "";
        Map<String, String> headers = new HashMap<>();
        headers.put(Signer.HeaderHost, "openapi.meitu.com");
        switch (operation) {
            case "materials":
                method = "GET";
                url = base + "materials?count=20";
                break;
            case "sod":
                method = "POST";
                url = base + "sod";
                body = "{\"url\":\"https://example.com/product.png\"}";
                headers.put("X-Aigcp-Way", "handler");
                break;
            case "create":
                method = "POST";
                url = base + "create";
                body = "{\"url\":\"https://example.com/product.png\",\"material_id\":\"1759202837959028\",\"scene_type\":1,\"product_category\":\"product\",\"canvas_w\":512,\"canvas_h\":512}";
                break;
            case "query":
                method = "GET";
                url = base + "query?id=46062184";
                break;
            default:
                throw new IllegalArgumentException("Unknown operation: " + operation);
        }
        if ("POST".equals(method)) {
            headers.put("Content-Type", "application/json");
        }
        Signer signer = new Signer("YOUR_ACCESS_KEY", "YOUR_SECRET_KEY");
        Map<String, String> signedHeaders = signer.sign(url, method, headers, body);
        HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
        try {
            connection.setRequestMethod(method);
            connection.setConnectTimeout(10000);
            connection.setReadTimeout(60000);
            connection.setInstanceFollowRedirects(false);
            for (Map.Entry<String, String> entry : signedHeaders.entrySet()) {
                connection.setRequestProperty(entry.getKey(), entry.getValue());
            }
            if ("POST".equals(method)) {
                connection.setDoOutput(true);
                byte[] bytes = body.getBytes(StandardCharsets.UTF_8);
                connection.setFixedLengthStreamingMode(bytes.length);
                try (OutputStream output = connection.getOutputStream()) {
                    output.write(bytes);
                }
            }
            int status = connection.getResponseCode();
            System.out.println("Status: " + status);
            InputStream responseStream = status >= 400
                    ? connection.getErrorStream() : connection.getInputStream();
            if (responseStream != null) {
                try (InputStream input = responseStream;
                     ByteArrayOutputStream output = new ByteArrayOutputStream()) {
                    byte[] buffer = new byte[4096];
                    int length;
                    while ((length = input.read(buffer)) != -1) {
                        output.write(buffer, 0, length);
                    }
                    System.out.println("Response: " + output.toString("UTF-8"));
                }
            }
        } finally {
            connection.disconnect();
        }
    }
}