服装换色
描述
根据指定颜色对图片中的服装进行换色。输入原图和服装分割 mask,使用 color_str 指定目标颜色,使用 cloth_category 指定服装类别,并通过 inpaint 控制是否进行边缘优化。
版本
1.0
图片要求
- 图片格式:JPG、PNG。
- 本文示例按原文顺序传入两张图片:第 1 张为原图,第 2 张为服装分割 mask。
- 使用图片 URL 时,将
media_data_type显式设置为url,URL 不进行 Base64 编码。
调用 URL
任务提交接口:
https://openapi.meitu.com/api/v1/sdk/sync/push
任务名称(`task`):`/v1/change_cloth_color/495824`
任务类型(`task_type`):`formula`调用方法
POST
Content-Type: application/json权限
使用已开通该能力的 AK/SK,通过签名 SDK 生成请求签名。详见开放平台接口签名。
请求参数
| 是否必选 | 参数名 | 类型 | 参数说明 |
|---|---|---|---|
| 是 | task | string | 固定为 /v1/change_cloth_color/495824。 |
| 是 | task_type | string | 固定为 formula。 |
| 是 | init_images | array | 输入图片列表。本文示例依次传入原图、服装分割 mask。 |
| 是 | params | string | 算法参数对象序列化后的 JSON 字符串,结构见下文。 |
| 否 | sync_timeout | integer | 同步等待时长,单位为秒,默认值为 30。 |
init_images 元素
| 是否必选 | 参数名 | 类型 | 参数说明 |
|---|---|---|---|
| 是 | url | string | 图片 URL 或 Base64 编码数据,与 profile.media_profiles.media_data_type 对应。 |
| 是 | profile | object | 图片属性。 |
profile
| 是否必选 | 参数名 | 类型 | 参数说明 |
|---|---|---|---|
| 是 | version | string | 固定为 v1。 |
| 是 | media_profiles | object | 图片数据类型。 |
| 否 | media_extra | object | 图片附加信息,无需传入时可省略。 |
media_profiles
| 是否必选 | 参数名 | 类型 | 参数说明 |
|---|---|---|---|
| 否 | media_data_type | string | url:图片 URL;jpg:Base64 编码的图片数据。原算法默认值为 jpg;使用 URL 时必须显式指定 url。 |
params
params 必须为字符串。将下列对象序列化后赋给 params,再序列化整个请求体:
{
"parameter": {
"rsp_media_type": "jpg",
"color_str": "#FFC0CB",
"cloth_category": "None",
"inpaint": 0
}
}| 是否必选 | 参数名 | 类型 | 参数说明 |
|---|---|---|---|
| 是 | parameter | object | 服装换色参数。 |
| 否 | extra | object | 算法协议中的额外处理信息,无需传入时可省略。 |
parameter
| 是否必选 | 参数名 | 类型 | 参数说明 |
|---|---|---|---|
| 否 | rsp_media_type | string | jpg:结果图片以 Base64 编码返回。本文请求示例使用 jpg。 |
| 是 | color_str | string | 目标颜色,使用十六进制颜色代码,例如 #FFC0CB。 |
| 是 | cloth_category | string | 服装类别,取值见下表。原文标注默认值为 None,同时标注为必选;请求中请显式传入。 |
| 是 | inpaint | integer | 是否进行边缘优化:0 关闭,1 开启。原文标注默认值为 0,同时标注为必选;请求中请显式传入。 |
cloth_category 取值:
| 值 | 说明 |
|---|---|
| None | 原文默认值;原文未说明该值的具体处理规则。 |
| dress | 连衣裙。 |
| up | 上衣。 |
| down | 下装,如裤子、半身裙。 |
| coat | 外套。 |
| up-down | 上衣和下装一起换色。 |
| jumpsuit | 连体衣。 |
原文参数表仅说明 rsp_media_type = jpg,但成功示例使用 url。本文保留两处来源差异,不将该返回示例视为本 task 已确认支持 url 请求值的依据。
输入值示例
将图片地址替换为实际原图和服装分割 mask 的可访问地址。
{
"task": "/v1/change_cloth_color/495824",
"task_type": "formula",
"init_images": [
{
"url": "https://example.com/input.jpg",
"profile": {
"media_profiles": {
"media_data_type": "url"
},
"version": "v1"
}
},
{
"url": "https://example.com/cloth-mask.png",
"profile": {
"media_profiles": {
"media_data_type": "url"
},
"version": "v1"
}
}
],
"params": "{\"parameter\":{\"rsp_media_type\":\"jpg\",\"color_str\":\"#FFC0CB\",\"cloth_category\":\"None\",\"inpaint\":0}}",
"sync_timeout": 30
}返回值说明
统一网关的任务状态与查询方式见任务查询接口。网关通用字段如下;本 task 的实际结果封装层级尚未通过响应样本核验。
| 字段 | 类型 | 说明 |
|---|---|---|
| code | integer | 网关业务状态码。0 不代表算法任务已经完成,还需检查任务状态。 |
| message | string | 网关响应信息。 |
| data | object | 任务信息。 |
| data.task_id | string | 网关任务 ID,用于查询任务结果。 |
| data.status | integer | 任务状态,见下表。 |
| data.result | object | 任务结果;本 task 的内部字段层级待确认。 |
| data.status | 说明 |
|---|---|
| -1 | 未找到任务。 |
| 0 | 任务创建成功。 |
| 1 | 执行中。 |
| 2 | 失败。 |
| 9 | 同步等待超时,继续查询,不代表任务失败。 |
| 10 | 成功。 |
算法结果字段
以下字段来自原文算法直连接口,不表示它们位于统一网关响应根级。
| 字段 | 类型 | 说明 |
|---|---|---|
| parameter | object | 返回图片的类型和版本信息。 |
| parameter.rsp_media_type | string | 返回数据类型。原文字段说明为 jpg,成功示例为 url。 |
| parameter.version | string | 原文成功示例中的算法版本,如 1.0.0。 |
| media_info_list | array | 原文成功示例依次包含换色后的图片、服装分割 mask。 |
| media_info_list[].media_data | string | 根据 media_data_type 为 Base64 数据或图片 URL。 |
| media_info_list[].media_profiles | object | 结果图片属性。 |
| media_info_list[].media_profiles.media_data_type | string | jpg 表示 Base64 数据;url 表示图片 URL。 |
算法失败字段
| 字段 | 类型 | 说明 |
|---|---|---|
| ErrorCode | integer | 算法错误码。 |
| ErrorMsg | string | 算法错误信息。 |
| Data | string / null | 附加信息,原文失败示例为 null。 |
返回值示例
以下为原文旧直连接口的响应示例,仅修正 JSON 语法并替换示意 URL,非本 task 的实测响应,不作为完整统一网关响应示例。
算法成功返回示例
原文 HTTP 状态码:200。
{
"parameter": {
"rsp_media_type": "url",
"version": "1.0.0"
},
"media_info_list": [
{
"media_data": "https://example.com/recolored.jpg",
"media_profiles": {
"media_data_type": "url"
}
},
{
"media_data": "https://example.com/cloth-mask.png",
"media_profiles": {
"media_data_type": "url"
}
}
]
}需要查询时
统一网关返回 data.status = 9 时,使用 data.task_id 按任务查询接口继续获取结果;不要将等待超时当成失败重复提交任务。
算法失败返回示例
原文 HTTP 状态码:400。
{
"ErrorCode": 20003,
"ErrorMsg": "detect no face",
"Data": null
}通用的错误代码与信息
详见 API 错误码。
SDK 调用示例
以下示例内嵌完整请求体,无需创建额外 JSON 文件。调用前替换 AK、SK、原图 URL 和服装分割 mask URL,并按对应语言文档引入签名 SDK。
示例仅提交一次并打印响应。任务未完成时,使用任务查询接口获取结果。
Python
import json
import requests
from sign_sdk import sign
def main():
key = "YOUR_ACCESS_KEY"
secret = "YOUR_SECRET_KEY"
url = "https://openapi.meitu.com/api/v1/sdk/sync/push"
params = {
"parameter": {
"rsp_media_type": "jpg",
"color_str": "#FFC0CB",
"cloth_category": "None",
"inpaint": 0
}
}
payload = {
"task": "/v1/change_cloth_color/495824",
"task_type": "formula",
"init_images": [
{
"url": "https://example.com/input.jpg",
"profile": {
"media_profiles": {
"media_data_type": "url"
},
"version": "v1"
}
},
{
"url": "https://example.com/cloth-mask.png",
"profile": {
"media_profiles": {
"media_data_type": "url"
},
"version": "v1"
}
}
],
"params": json.dumps(params, ensure_ascii=True),
"sync_timeout": 30
}
body = json.dumps(payload, ensure_ascii=True)
headers = {
"Content-Type": "application/json",
sign.HeaderHost: "openapi.meitu.com",
}
signer = sign.Signer(key, secret)
signed_request = signer.sign(url, "POST", headers, body)
with requests.Session() as session:
response = session.send(
signed_request, timeout=(10, 60), verify=True, allow_redirects=False
)
print("Status:", response.status_code)
print("Response:", response.text)
if __name__ == "__main__":
main()Go
package main
import (
"fmt"
"io"
"net/http"
"time"
"github.com/mtlab/api/signer"
)
func main() {
url := "https://openapi.meitu.com/api/v1/sdk/sync/push"
body := `{
"task": "/v1/change_cloth_color/495824",
"task_type": "formula",
"init_images": [
{
"url": "https://example.com/input.jpg",
"profile": {
"media_profiles": {
"media_data_type": "url"
},
"version": "v1"
}
},
{
"url": "https://example.com/cloth-mask.png",
"profile": {
"media_profiles": {
"media_data_type": "url"
},
"version": "v1"
}
}
],
"params": "{\"parameter\":{\"rsp_media_type\":\"jpg\",\"color_str\":\"#FFC0CB\",\"cloth_category\":\"None\",\"inpaint\":0}}",
"sync_timeout": 30
}`
headers := make(http.Header)
headers.Set("Content-Type", "application/json")
headers.Set(signer.HeaderHost, "openapi.meitu.com")
signObj := signer.NewSigner("YOUR_ACCESS_KEY", "YOUR_SECRET_KEY")
req, err := signObj.Sign(url, http.MethodPost, headers, body)
if err != nil {
fmt.Println("Sign request failed:", 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("Send request failed:", err)
return
}
defer resp.Body.Close()
responseBody, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Println("Read response failed:", err)
return
}
fmt.Println("Status:", resp.StatusCode)
fmt.Println("Response:", string(responseBody))
}PHP
<?php
require_once __DIR__ . '/signer.php';
$params = json_encode([
'parameter' => [
'rsp_media_type' => 'jpg',
'color_str' => '#FFC0CB',
'cloth_category' => 'None',
'inpaint' => 0,
],
]);
$body = json_encode([
'task' => '/v1/change_cloth_color/495824',
'task_type' => 'formula',
'init_images' => [
[
'url' => 'https://example.com/input.jpg',
'profile' => [
'media_profiles' => ['media_data_type' => 'url'],
'version' => 'v1',
],
],
[
'url' => 'https://example.com/cloth-mask.png',
'profile' => [
'media_profiles' => ['media_data_type' => 'url'],
'version' => 'v1',
],
],
],
'params' => $params,
'sync_timeout' => 30,
], JSON_UNESCAPED_SLASHES);
$url = 'https://openapi.meitu.com/api/v1/sdk/sync/push';
$headers = [
'Content-Type' => 'application/json',
'Host' => 'openapi.meitu.com',
];
$signer = new Signer('YOUR_ACCESS_KEY', 'YOUR_SECRET_KEY');
$curl = $signer->sign($url, 'POST', $headers, $body);
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
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 body =
"{\n" +
" \"task\": \"/v1/change_cloth_color/495824\",\n" +
" \"task_type\": \"formula\",\n" +
" \"init_images\": [\n" +
" {\n" +
" \"url\": \"https://example.com/input.jpg\",\n" +
" \"profile\": {\n" +
" \"media_profiles\": {\n" +
" \"media_data_type\": \"url\"\n" +
" },\n" +
" \"version\": \"v1\"\n" +
" }\n" +
" },\n" +
" {\n" +
" \"url\": \"https://example.com/cloth-mask.png\",\n" +
" \"profile\": {\n" +
" \"media_profiles\": {\n" +
" \"media_data_type\": \"url\"\n" +
" },\n" +
" \"version\": \"v1\"\n" +
" }\n" +
" }\n" +
" ],\n" +
" \"params\": \"{\\\"parameter\\\":{\\\"rsp_media_type\\\":\\\"jpg\\\",\\\"color_str\\\":\\\"#FFC0CB\\\",\\\"cloth_category\\\":\\\"None\\\",\\\"inpaint\\\":0}}\",\n" +
" \"sync_timeout\": 30\n" +
"}";
String url = "https://openapi.meitu.com/api/v1/sdk/sync/push";
Map<String, String> headers = new HashMap<>();
headers.put("Content-Type", "application/json");
headers.put(Signer.HeaderHost, "openapi.meitu.com");
Signer signer = new Signer("YOUR_ACCESS_KEY", "YOUR_SECRET_KEY");
Map<String, String> signedHeaders = signer.sign(url, "POST", headers, body);
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
try {
connection.setRequestMethod("POST");
connection.setConnectTimeout(10000);
connection.setReadTimeout(60000);
connection.setInstanceFollowRedirects(false);
connection.setDoOutput(true);
for (Map.Entry<String, String> entry : signedHeaders.entrySet()) {
connection.setRequestProperty(entry.getKey(), entry.getValue());
}
byte[] bodyBytes = body.getBytes(StandardCharsets.UTF_8);
connection.setFixedLengthStreamingMode(bodyBytes.length);
try (OutputStream output = connection.getOutputStream()) {
output.write(bodyBytes);
}
int status = connection.getResponseCode();
System.out.println("Status: " + status);
InputStream stream = status >= 400
? connection.getErrorStream() : connection.getInputStream();
if (stream != null) {
try (InputStream input = stream;
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();
}
}
}