画面扩展
描述
根据输入图片的内容生成外围区域,扩展图片画面。支持四边等比例扩展、分别指定四边扩展比例、分别指定四边扩展像素数,并可通过英文提示词引导生成内容。
图片样例
| 输入图片 | 输出图片 |
|---|---|
![]() | ![]() |
图片要求
- 输入格式:JPG、JPEG、PNG、BMP、HEIF。
- 输入文件大小:不超过 30 MB。
- 输入图片数量:1 张。
- 输出尺寸:宽度和高度均不超过 4096 像素。扩展后的尺寸超过上限时,结果图会缩放后返回;按像素扩展时,实际返回的扩展像素数也会相应减少,扩展比例保持不变。
调用 URL
- 请求地址:
https://openapi.meitu.com/api/v1/sdk/sync/push - 任务名称(
task):/v1/OutPainting/468257 - 任务类型(
task_type):formula
调用方法
POST
Content-Type: application/json
权限
使用 Access Key(AK)和 Secret Key(SK)进行请求签名,详见开放平台接口签名。
请求参数
| 是否必选 | 参数名 | 类型 | 参数说明 |
|---|---|---|---|
| 必选 | task | string | 固定为 /v1/OutPainting/468257。 |
| 必选 | task_type | string | 固定为 formula。 |
| 必选 | init_images | object[] | 输入图片列表,传入 1 张原图。 |
| 必选 | params | string | 算法参数序列化后的 JSON 字符串,结构见下文。 |
| 可选 | sync_timeout | int | 同步等待时间,单位为秒,默认 30;-1 表示不等待。返回 data.status = 9 时,使用任务查询接口获取结果。 |
init_images 元素
| 是否必选 | 参数名 | 类型 | 参数说明 |
|---|---|---|---|
| 必选 | url | string | 图片 URL 或 Base64 编码数据;传输方式由 profile.media_profiles.media_data_type 指定。 |
| 可选 | profile | object | 图片传输信息。 |
profile
| 是否必选 | 参数名 | 类型 | 参数说明 |
|---|---|---|---|
| 可选 | media_extra | object | 多媒体附加参数。 |
| 可选 | media_profiles | object | 多媒体传输信息。 |
| 可选 | version | string | 示例使用 v1。 |
media_profiles
| 是否必选 | 参数名 | 类型 | 参数说明 |
|---|---|---|---|
| 可选 | media_data_type | string | url:通过 URL 传入图片;jpg:通过 Base64 编码传入图片。 |
params
先构造以下参数对象,再将其序列化为字符串,作为请求体的 params 值。
| 是否必选 | 参数名 | 类型 | 参数说明 |
|---|---|---|---|
| 必选 | parameter | object | 画面扩展算法参数。 |
parameter
| 是否必选 | 参数名 | 类型 | 参数说明 |
|---|---|---|---|
| 可选 | rsp_media_type | string | 结果返回方式:url 为图片 URL,jpg 为 Base64 编码数据。默认 url。 |
| 可选 | expand_ratio | float | 四边等比例扩展,范围 [0, 1],默认 0.5。左右两边各按原图宽度的该比例扩展,上下两边各按原图高度的该比例扩展。 |
| 可选 | free_expand_ratio | object | 分别指定四边扩展比例,包含 left、right、top、bottom,每项范围为 [0, 1]。 |
| 可选 | free_expand_pixel | object | 分别指定四边扩展像素数,包含 left、right、top、bottom,每项均为非负整数。 |
| 可选 | generate_num | int | 生成图片数量,默认 1,最多 20。 |
| 可选 | seed | int | 随机种子,范围 [-1, 65535],默认 0。设为 -1 时使用随机种子;在相同输入和参数下,可使用固定种子复现生成效果。 |
| 可选 | extra_prompt | string | 额外提示词,用于引导生成内容。默认仅支持英文,多个提示词使用英文逗号 , 分隔。 |
| 可选 | high_quality_encode | bool | 是否使用高质量、低压缩率的方式编码结果图,默认 false。开启后不改变生成内容,可提升编码质量,但会增加处理耗时。 |
rsp_media_type 位于 params 字符串中的 parameter 对象内。
设置扩展范围时,expand_ratio、free_expand_ratio 和 free_expand_pixel 三者只传一个。不指定扩展方式时,使用默认的 expand_ratio = 0.5。
对于宽为 W、高为 H 的原图,expand_ratio = r 时,缩放前的目标尺寸为 W × (1 + 2r)、H × (1 + 2r)。例如:0.1 对应原宽高的 120%,0.5 对应原宽高的 200%。
free_expand_ratio
选择按四边比例扩展时,填写以下四个字段:
| 是否必选 | 参数名 | 类型 | 参数说明 |
|---|---|---|---|
| 必选 | left | float | 向左增加的宽度占原图宽度的比例。例如 0.1 表示向左增加原图宽度的 10%。 |
| 必选 | right | float | 向右增加的宽度占原图宽度的比例。 |
| 必选 | top | float | 向上增加的高度占原图高度的比例。 |
| 必选 | bottom | float | 向下增加的高度占原图高度的比例。 |
各字段范围均为 [0, 1],0 表示不向该方向扩展。缩放前的目标宽度为 W × (1 + left + right),目标高度为 H × (1 + top + bottom)。
free_expand_pixel
选择按四边像素扩展时,填写以下四个字段:
| 是否必选 | 参数名 | 类型 | 参数说明 |
|---|---|---|---|
| 必选 | left | int | 向左增加的像素数。 |
| 必选 | right | int | 向右增加的像素数。 |
| 必选 | top | int | 向上增加的像素数。 |
| 必选 | bottom | int | 向下增加的像素数。 |
各字段均为非负整数,0 表示不向该方向扩展。缩放前的目标宽度为 W + left + right,目标高度为 H + top + bottom。
输入值示例
以下三个示例分别展示一种扩展方式。请将 https://example.com/input.jpg 替换为实际可访问的图片地址。params 始终为 JSON 字符串。
四边等比例扩展
expand_ratio = 0.25 表示左右各增加原宽度的 25%,上下各增加原高度的 25%。例如,1000 × 800 像素的输入图片,缩放前目标尺寸为 1500 × 1200 像素。
{
"task": "/v1/OutPainting/468257",
"task_type": "formula",
"init_images": [
{
"url": "https://example.com/input.jpg",
"profile": {
"media_profiles": {
"media_data_type": "url"
},
"version": "v1"
}
}
],
"params": "{\"parameter\":{\"rsp_media_type\":\"url\",\"expand_ratio\":0.25,\"generate_num\":1,\"seed\":-1,\"high_quality_encode\":false}}",
"sync_timeout": 30
}按四边比例扩展
向左增加原宽度的 10%,向上增加原高度的 20%,向下增加原高度的 5%,右侧不扩展。对于 1000 × 800 像素的输入图片,缩放前目标尺寸为 1100 × 1000 像素。
{
"task": "/v1/OutPainting/468257",
"task_type": "formula",
"init_images": [
{
"url": "https://example.com/input.jpg",
"profile": {
"media_profiles": {
"media_data_type": "url"
},
"version": "v1"
}
}
],
"params": "{\"parameter\":{\"rsp_media_type\":\"url\",\"free_expand_ratio\":{\"left\":0.1,\"right\":0,\"top\":0.2,\"bottom\":0.05},\"generate_num\":1,\"seed\":-1,\"high_quality_encode\":false}}",
"sync_timeout": 30
}按四边像素扩展
向左增加 100 像素,向上增加 200 像素,向下增加 50 像素,右侧不扩展。对于 1000 × 800 像素的输入图片,缩放前目标尺寸为 1100 × 1050 像素。
{
"task": "/v1/OutPainting/468257",
"task_type": "formula",
"init_images": [
{
"url": "https://example.com/input.jpg",
"profile": {
"media_profiles": {
"media_data_type": "url"
},
"version": "v1"
}
}
],
"params": "{\"parameter\":{\"rsp_media_type\":\"url\",\"free_expand_pixel\":{\"left\":100,\"right\":0,\"top\":200,\"bottom\":50},\"generate_num\":1,\"seed\":-1,\"high_quality_encode\":false}}",
"sync_timeout": 30
}返回值说明
以下说明及示例采用 URL 返回方式,展示同步任务接口的任务状态和结果。任务成功后,从 data.result.urls 获取结果图片。
| 字段 | 类型 | 说明 |
|---|---|---|
| code | int | 请求处理状态码,0 表示请求处理正常;非 0 表示请求失败。任务是否完成需同时检查 data.status。 |
| message | string | 响应消息或错误信息。 |
| data | object/null | 任务信息;请求失败时可能为 null。 |
data
| 字段 | 类型 | 说明 |
|---|---|---|
| status | int | 任务状态:-1 未找到任务;0 已创建;1 执行中;2 失败;9 需要查询结果;10 成功。 |
| result | object | 任务 ID 和处理结果。 |
| progress | number | 任务进度,例如 0.1、0.85、1。 |
result
| 字段 | 类型 | 说明 |
|---|---|---|
| id | string | 任务 ID;查询结果时作为 task_id 参数传入。 |
| urls | string[] | 任务成功后的结果图片 URL 列表。 |
当 data.status = 9 时,使用 data.result.id 调用任务查询接口。查询请求为 GET https://openapi.meitu.com/api/v1/sdk/status?task_id=<任务ID>,同样需要签名。
查询返回状态 0 或 1 时表示任务尚未完成;10 表示成功;2 表示失败。请及时查询并保存结果,任务在 24 小时后过期,不支持历史任务查询。
返回值示例
请求成功返回示例
{
"code": 0,
"message": "",
"data": {
"status": 10,
"result": {
"id": "50309bd5-a827-4125-bc96-62039c93770b",
"urls": [
"https://example.com/result.png"
]
},
"progress": 1
}
}需要查询返回示例
使用返回的 data.result.id 作为任务查询接口的 task_id。
{
"code": 0,
"message": "",
"data": {
"status": 9,
"result": {
"id": "50309bd5-a827-4125-bc96-62039c93770b"
},
"progress": 0
}
}请求失败返回示例
{
"code": 20008,
"message": "UNSUITABLE_IMAGE",
"data": null
}当前 API 特有的错误代码与信息
| 错误代码 | 错误信息 | 说明 |
|---|---|---|
| 20001 | PROCESS_ERROR | 处理错误 |
| 20003 | DETECT_NOT_FACE | 未检测到人脸 |
| 20004 | MORE_THAN_ONE_FACE | 检测到多个人脸 |
| 20007 | MISSING_LANDMARK_ARGUMENTS | 未传入人脸关键点 |
| 20008 | UNSUITABLE_IMAGE | 图片不符合要求 |
| 20009 | UNSUPPORT_TYPE | 不支持的 type |
| 20010 | DETECT_NOT_FACE | 未检测到第二张图片中的人脸 |
| 20011 | UNSUITABLE_VERTICAL_IMAGE | 图片高度不符合要求 |
| 20012 | UNSUITABLE_HORIZONTAL_IMAGE | 图片宽度不符合要求 |
| 20013 | RESOLUTION_TOO_LARGE_ERROR | 图片分辨率过大 |
| 20014 | NOT_FOUND | 未找到图片 |
| 20015 | PICTURE_OVERRUN_ERROR | 图片超出限制 |
| 20020 | DETECT_FACE_OUTOFIMAGE | 面部五官缺失 |
| 20021 | DETECT_FACE_PITCHANGLE_BIG | 人脸俯仰角过大 |
| 20022 | DETECT_FACE_YAWANGLE_BIG | 人脸偏航角过大 |
| 20023 | DETECT_FACE_LOWAREA | 人脸区域占比过小或像素不足 |
| 21001 | LOAD_MODEL_ERROR | 模型加载失败 |
| 21002 | HAIR_MASK_LOSS | 缺少头发蒙版 |
| 21003 | FACE_NUM_ERROR | 人脸数量错误 |
| 21004 | AR_PARSE_FAULT | AR plist 解析失败 |
| 21005 | AR_EEEOR_COUNT | AR 人脸错误 |
| 21006 | AR_FACE_OUT | AR 超出人脸范围 |
| 21007 | JSON_ERROR | JSON 内容错误 |
| 21008 | BACKGROUND_IMAGE_LOSS | 缺少背景图片 |
| 21009 | BODY_MASK_LOSS | 缺少身体蒙版 |
| 21010 | FACE_ANGLE_ERROR | 人脸角度错误 |
| 21011 | SKIN_MASK_LOSS | 缺少皮肤蒙版 |
| 21012 | BODY_INFO_LOSS | 缺少骨骼点或外轮廓点 |
| 21013 | RECT_OUT_IMAGE | 区域超出图片范围 |
| 30001 | GEN_ERROR | 生成错误 |
通用的错误代码与信息
详见 API 错误码。
SDK 调用示例
以下示例均按 expand_ratio = 0.25 提交画面扩展任务。调用前替换 AK、SK 和输入图片 URL。
如需按方向比例或像素数扩展,用 free_expand_ratio 或 free_expand_pixel 替换 expand_ratio,不要同时传入多种扩展参数。示例打印接口响应;返回 data.status = 9 时,按上述说明查询任务结果。
Python
先按 Python 签名 SDK 接入文档引入 SDK。
import json
import requests
from sign_sdk import sign
def api_call_example():
key = "your_access_key"
secret = "your_secret_key"
url = "https://openapi.meitu.com/api/v1/sdk/sync/push"
headers = {
"Content-Type": "application/json",
sign.HeaderHost: "openapi.meitu.com",
}
inner_params = {
"parameter": {
"rsp_media_type": "url",
"expand_ratio": 0.25,
"generate_num": 1,
"seed": -1,
"high_quality_encode": False
}
}
payload = {
"task": "/v1/OutPainting/468257",
"task_type": "formula",
"init_images": [
{
"url": "https://example.com/input.jpg",
"profile": {
"media_profiles": {
"media_data_type": "url"
},
"version": "v1"
}
}
],
"params": json.dumps(inner_params, ensure_ascii=False),
"sync_timeout": 30
}
body = json.dumps(payload, ensure_ascii=False)
signer = sign.Signer(key, secret)
signed_request = signer.sign(url, "POST", headers, body)
with requests.Session() as session:
response = session.send(signed_request, timeout=60)
print("Status:", response.status_code)
print("Response:", response.text)
if __name__ == "__main__":
api_call_example()Go
先按 Go 签名 SDK 接入文档引入 SDK。
package main
import (
"fmt"
"io"
"net/http"
"time"
"github.com/mtlab/api/signer"
)
func main() {
key := "your_access_key"
secret := "your_secret_key"
signObj := signer.NewSigner(key, secret)
url := "https://openapi.meitu.com/api/v1/sdk/sync/push"
headers := make(http.Header)
headers.Set(signer.HeaderHost, "openapi.meitu.com")
headers.Set("Content-Type", "application/json")
body := `{
"task": "/v1/OutPainting/468257",
"task_type": "formula",
"init_images": [
{
"url": "https://example.com/input.jpg",
"profile": {
"media_profiles": {
"media_data_type": "url"
},
"version": "v1"
}
}
],
"params": "{\"parameter\":{\"rsp_media_type\":\"url\",\"expand_ratio\":0.25,\"generate_num\":1,\"seed\":-1,\"high_quality_encode\":false}}",
"sync_timeout": 30
}`
req, err := signObj.Sign(url, http.MethodPost, headers, body)
if err != nil {
fmt.Println("Failed to sign request:", err)
return
}
client := &http.Client{Timeout: 60 * time.Second}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Failed to send request:", err)
return
}
defer resp.Body.Close()
responseBody, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Println("Failed to read response:", err)
return
}
fmt.Println("Status:", resp.StatusCode)
fmt.Println("Response:", string(responseBody))
}PHP
先按 PHP 签名 SDK 接入文档引入 SDK。
<?php
require_once __DIR__ . '/signer.php';
$key = 'your_access_key';
$secret = 'your_secret_key';
$url = 'https://openapi.meitu.com/api/v1/sdk/sync/push';
$headers = [
'Content-Type' => 'application/json',
'Host' => 'openapi.meitu.com',
];
$innerParams = json_encode([
'parameter' => [
'rsp_media_type' => 'url',
'expand_ratio' => 0.25,
'generate_num' => 1,
'seed' => -1,
'high_quality_encode' => false,
],
], JSON_UNESCAPED_SLASHES);
$body = json_encode([
'task' => '/v1/OutPainting/468257',
'task_type' => 'formula',
'init_images' => [
[
'url' => 'https://example.com/input.jpg',
'profile' => [
'media_profiles' => [
'media_data_type' => 'url',
],
'version' => 'v1',
],
],
],
'params' => $innerParams,
'sync_timeout' => 30,
], JSON_UNESCAPED_SLASHES);
$signer = new Signer($key, $secret);
$curl = $signer->sign($url, 'POST', $headers, $body);
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_TIMEOUT, 60);
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 接入文档引入 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 {
Signer signer = new Signer("your_access_key", "your_secret_key");
String url = "https://openapi.meitu.com/api/v1/sdk/sync/push";
String method = "POST";
Map<String, String> headers = new HashMap<>();
headers.put("Content-Type", "application/json");
headers.put(Signer.HeaderHost, "openapi.meitu.com");
String body = "{\n"
+ " \"task\": \"/v1/OutPainting/468257\",\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"
+ " \"params\": \"{\\\"parameter\\\":{\\\"rsp_media_type\\\":\\\"url\\\",\\\"expand_ratio\\\":0.25,\\\"generate_num\\\":1,\\\"seed\\\":-1,\\\"high_quality_encode\\\":false}}\",\n"
+ " \"sync_timeout\": 30\n"
+ "}";
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());
}
connection.setDoOutput(true);
try (OutputStream output = connection.getOutputStream()) {
output.write(body.getBytes(StandardCharsets.UTF_8));
}
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: "
+ new String(output.toByteArray(), StandardCharsets.UTF_8));
}
}
} finally {
connection.disconnect();
}
}
}
