# 🎨 图像拓展功能 - API调用文档 ## 📋 功能概述 图像拓展(Image Out-painting)功能使用阿里云 Dashscope API,可以将图片智能扩展,自动填充边缘区域,实现图片尺寸的放大。 --- ## 🔧 API配置 ### 环境变量 ```bash # 阿里云 Dashscope API Key(用于图像拓展) GENERATE_BG_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxx ``` **说明:** 此配置与背景生成功能共用同一个 API Key。 --- ## 🌐 API端点 ### 1. 提交拓展任务 **端点:** `https://dashscope.aliyuncs.com/api/v1/services/aigc/image2image/out-painting` **方法:** `POST` **请求头:** ```http Content-Type: application/json Authorization: Bearer {GENERATE_BG_API_KEY} X-DashScope-Async: enable ``` **说明:** `X-DashScope-Async: enable` 是必需的,用于启用异步模式。 --- ### 2. 查询任务状态 **端点:** `https://dashscope.aliyuncs.com/api/v1/tasks/{taskId}` **方法:** `GET` **请求头:** ```http Authorization: Bearer {GENERATE_BG_API_KEY} ``` --- ## 📤 请求参数 ### 请求体结构 ```json { "model": "image-out-painting", "input": { "image_url": "https://example.com/image.jpg" // 方式1:公网URL // 或 "image_base64": "iVBORw0KGgoAAAANSUhEUgAA..." // 方式2:Base64编码(推荐) }, "parameters": { "x_scale": 1.5, // X轴放大倍数(1.0-2.0) "y_scale": 1.5, // Y轴放大倍数(1.0-2.0) "best_quality": false, // 是否使用最佳质量(true/false) "limit_image_size": true, // 是否限制图片大小 "add_watermark": false // 是否添加水印(建议false) } } ``` ### 参数说明 | 参数 | 类型 | 必填 | 说明 | 默认值 | |------|------|------|------|--------| | `model` | string | 是 | 固定值 `"image-out-painting"` | - | | `input.image_url` | string | 否* | 图片的公网URL(需可访问) | - | | `input.image_base64` | string | 否* | 图片的Base64编码 | - | | `parameters.x_scale` | number | 否 | X轴放大倍数(1.0-2.0) | 1.5 | | `parameters.y_scale` | number | 否 | Y轴放大倍数(1.0-2.0) | 1.5 | | `parameters.best_quality` | boolean | 否 | 是否使用最佳质量 | false | | `parameters.limit_image_size` | boolean | 否 | 是否限制图片大小 | true | | `parameters.add_watermark` | boolean | 否 | 是否添加水印 | false | **注意:** `input.image_url` 和 `input.image_base64` 二选一,推荐使用 `image_base64`。 --- ## 📥 响应格式 ### 提交任务响应 **成功响应:** ```json { "output": { "task_id": "task-abc123def456", "task_status": "PENDING" } } ``` **失败响应:** ```json { "code": "InvalidParameter", "message": "image_base64 is required" } ``` ### 查询任务状态响应 **处理中:** ```json { "output": { "task_id": "task-abc123def456", "task_status": "RUNNING" } } ``` **成功:** ```json { "output": { "task_id": "task-abc123def456", "task_status": "SUCCEEDED", "output_image_url": "https://dashscope-result-bj.oss-cn-beijing.aliyuncs.com/..." } } ``` **失败:** ```json { "output": { "task_id": "task-abc123def456", "task_status": "FAILED", "message": "任务处理失败", "code": "InternalError" } } ``` --- ## 🔄 任务状态 | 状态 | 说明 | 处理方式 | |------|------|---------| | `PENDING` | 等待处理 | 继续轮询 | | `RUNNING` | 处理中 | 继续轮询 | | `SUCCEEDED` | 成功 | 获取结果URL | | `FAILED` | 失败 | 抛出错误 | --- ## 💻 代码实现示例 ### TypeScript/Node.js 实现 ```typescript import { NextRequest, NextResponse } from "next/server"; import sharp from "sharp"; /** * 图像拓展核心实现 * @param imageBase64 - Base64编码的图片数据 * @param apiKey - Dashscope API Key * @returns 拓展后的图片URL */ async function extendImage(imageBase64: string, apiKey: string): Promise { // 1. 提交拓展任务 const requestBody = { model: "image-out-painting", input: { image_base64: imageBase64, }, parameters: { x_scale: 1.5, y_scale: 1.5, best_quality: false, limit_image_size: true, add_watermark: false, }, }; const response = await fetch( "https://dashscope.aliyuncs.com/api/v1/services/aigc/image2image/out-painting", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}`, "X-DashScope-Async": "enable", }, body: JSON.stringify(requestBody), } ); const initialResult = await response.json(); if (initialResult.code) { throw new Error( `图像拓展API错误: ${initialResult.message || JSON.stringify(initialResult)}` ); } // 2. 获取任务ID const taskId = initialResult.output.task_id; const taskStatusUrl = `https://dashscope.aliyuncs.com/api/v1/tasks/${taskId}`; // 3. 等待3秒后开始轮询 await new Promise((resolve) => setTimeout(resolve, 3000)); // 4. 轮询获取结果 let attempts = 0; const maxAttempts = 60; // 最多轮询60次 const pollInterval = 2000; // 每2秒轮询一次 while (attempts < maxAttempts) { const statusResponse = await fetch(taskStatusUrl, { headers: { Authorization: `Bearer ${apiKey}`, }, }); const result = await statusResponse.json(); if (result.output?.task_status === "SUCCEEDED") { return result.output.output_image_url; } else if (result.output?.task_status === "FAILED") { throw new Error( `图像拓展任务失败: ${result.output.message || result.output.code || "未知错误"}` ); } attempts++; if (attempts < maxAttempts) { await new Promise((resolve) => setTimeout(resolve, pollInterval)); } } throw new Error(`图像拓展任务超时(等待${maxAttempts * 2 + 3}秒)`); } /** * 完整的图像拓展流程(包含图片上传到S3) */ export async function POST(request: NextRequest) { try { // 1. 获取用户会话 const session = await auth(); if (!session?.user?.id) { return NextResponse.json({ error: "未授权" }, { status: 401 }); } // 2. 获取API配置 const apiKey = process.env.GENERATE_BG_API_KEY; if (!apiKey) { return NextResponse.json( { error: "图像拓展API配置未设置" }, { status: 400 } ); } // 3. 获取请求体(Base64图片) const contentType = request.headers.get("content-type") || ""; let imageBase64: string; if (contentType.includes("application/json")) { const body = await request.json(); imageBase64 = body.image_base64; } else if (contentType.includes("multipart/form-data")) { const formData = await request.formData(); const file = formData.get("file") as File; const arrayBuffer = await file.arrayBuffer(); const buffer = Buffer.from(arrayBuffer); imageBase64 = buffer.toString("base64"); } else { return NextResponse.json( { error: "不支持的Content-Type" }, { status: 400 } ); } if (!imageBase64) { return NextResponse.json( { error: "缺少图片数据" }, { status: 400 } ); } // 4. 调用图像拓展API const extendedImageUrl = await extendImage(imageBase64, apiKey); // 5. 下载拓展后的图片 const imageResponse = await fetch(extendedImageUrl); if (!imageResponse.ok) { throw new Error(`下载拓展图片失败: ${imageResponse.status}`); } const arrayBuffer = await imageResponse.arrayBuffer(); const buffer = Buffer.from(arrayBuffer); // 6. 上传到S3(可选) // const { uploadFile } = await import("@/lib/storage/service"); // const thumbnailBuffer = await createThumbnail(buffer, "image/jpeg"); // const [thumbnailResult, originalResult] = await Promise.all([ // uploadFile(`extend-thumb-${Date.now()}.jpg`, thumbnailBuffer), // uploadFile(`extend-orig-${Date.now()}.jpg`, buffer), // ]); // 7. 保存到数据库(可选) // await db.insert(generatedImage).values({ // userId: session.user.id, // thumbnailUrl: thumbnailResult.url, // originalUrl: originalResult.url, // prompt: "图像拓展处理", // modelId: "extend-1.0", // }); // 8. 返回结果 return NextResponse.json({ success: true, original_url: extendedImageUrl, // 或 originalResult.url thumbnail_url: extendedImageUrl, // 或 thumbnailResult.url // base64: buffer.toString("base64"), // 可选:返回Base64 }); } catch (error) { console.error("[Extend API] 错误:", error); return NextResponse.json( { error: "图像拓展失败", detail: error instanceof Error ? error.message : String(error), }, { status: 500 } ); } } ``` --- ## 🔄 使用公网URL方式(备选方案) 如果不想使用Base64,可以先将图片上传到S3/MinIO,然后使用公网URL: ```typescript // 1. 上传图片到S3/MinIO获取公网URL const imageUrl = await uploadToS3(imageBuffer); // 2. 使用URL调用API const requestBody = { model: "image-out-painting", input: { image_url: imageUrl, // 使用URL而不是Base64 }, parameters: { x_scale: 1.5, y_scale: 1.5, best_quality: false, limit_image_size: true, add_watermark: false, }, }; ``` **注意:** 公网URL必须可访问,且不能是localhost或内网地址。 --- ## ⚙️ 配置参数说明 ### 放大倍数 - **x_scale / y_scale**: 1.0 - 2.0 - `1.0`: 不放大 - `1.5`: 放大1.5倍(推荐) - `2.0`: 放大2倍(最大) ### 质量设置 - **best_quality**: - `true`: 最佳质量,处理时间更长 - `false`: 标准质量,处理时间较短(推荐) ### 水印设置 - **add_watermark**: - `true`: 添加水印(不推荐) - `false`: 不添加水印(推荐) --- ## ⏱️ 性能指标 | 指标 | 值 | 说明 | |------|-----|------| | 单张处理时间 | 5-15秒 | 取决于图片大小和质量设置 | | 最大图片大小 | 4MB | 原始图片大小限制 | | 支持格式 | JPG, PNG, WebP | 常见图片格式 | | 并发限制 | 5个/秒 | 根据账户类型 | | 轮询间隔 | 2秒 | 建议值 | | 最大轮询次数 | 60次 | 总计约120秒超时 | --- ## 🛡️ 错误处理 ### 常见错误 | 错误码 | 说明 | 解决方案 | |--------|------|---------| | `InvalidParameter` | 参数错误 | 检查图片格式和参数值 | | `Arrearage` | 配额不足 | 充值或升级账户 | | `Throttling.RateQuota` | 请求过快 | 降低请求频率 | | `InternalError` | 内部错误 | 稍后重试 | | `submit algo service error` | API服务问题 | 稍后重试 | ### 重试机制 建议实现3次重试,每次重试间隔递增: ```typescript async function extendImageWithRetry( imageBase64: string, apiKey: string, maxRetries = 3 ): Promise { let lastError: Error; for (let retry = 0; retry < maxRetries; retry++) { try { return await extendImage(imageBase64, apiKey); } catch (error) { lastError = error as Error; console.error(`第${retry + 1}次拓展尝试失败:`, error); if (retry < maxRetries - 1) { const waitTime = (retry + 1) * 5; // 5秒、10秒 await new Promise((resolve) => setTimeout(resolve, waitTime * 1000)); } } } throw new Error(`图像拓展失败(已重试${maxRetries}次): ${lastError.message}`); } ``` --- ## 📊 完整流程示例 ``` 用户上传图片 ↓ 转换为Base64(或上传到S3获取URL) ↓ 提交拓展任务到Dashscope API ↓ 获取task_id ↓ 等待3秒(API初始化) ↓ 轮询任务状态(每2秒一次,最多60次) ↓ 任务完成,获取output_image_url ↓ 下载拓展后的图片 ↓ 上传到S3(可选) ↓ 保存到数据库(可选) ↓ 返回结果给前端 ``` --- ## 🔍 测试方法 ### 1. 使用curl测试 ```bash # 准备Base64图片 IMAGE_BASE64=$(base64 -i test.jpg) # 提交任务 curl -X POST \ https://dashscope.aliyuncs.com/api/v1/services/aigc/image2image/out-painting \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $GENERATE_BG_API_KEY" \ -H "X-DashScope-Async: enable" \ -d "{ \"model\": \"image-out-painting\", \"input\": { \"image_base64\": \"$IMAGE_BASE64\" }, \"parameters\": { \"x_scale\": 1.5, \"y_scale\": 1.5, \"best_quality\": false, \"limit_image_size\": true, \"add_watermark\": false } }" # 查询任务状态(替换task_id) curl -X GET \ https://dashscope.aliyuncs.com/api/v1/tasks/{task_id} \ -H "Authorization: Bearer $GENERATE_BG_API_KEY" ``` ### 2. 使用Node.js测试脚本 参考 `第三方源码参考/test-image-extend-final.js` --- ## 📝 注意事项 1. **API Key安全**: 不要在客户端代码中暴露API Key,应在服务端调用 2. **图片大小**: 建议先压缩图片到4MB以下再调用API 3. **异步处理**: 必须使用异步模式(`X-DashScope-Async: enable`) 4. **轮询超时**: 建议设置合理的超时时间(120秒) 5. **错误处理**: 实现重试机制以提高成功率 6. **配额限制**: 注意API调用配额,避免超出限制 --- ## 🔗 相关资源 - [阿里云Dashscope官方文档](https://help.aliyun.com/zh/model-studio/) - [图像拓展API文档](https://help.aliyun.com/zh/model-studio/developer-reference/api-details-9) --- ## ✅ 总结 图像拓展功能使用阿里云Dashscope API,支持Base64和URL两种输入方式,采用异步模式处理,需要轮询获取结果。建议使用Base64方式,简化流程,提高可靠性。 **关键点:** - ✅ 使用 `GENERATE_BG_API_KEY` 环境变量 - ✅ 必须设置 `X-DashScope-Async: enable` - ✅ 推荐使用Base64输入方式 - ✅ 实现轮询和重试机制 - ✅ 处理时间约5-15秒/张