利用AI扩展图片功能
本文介绍了在图库类项目中如何利用阿里云百炼AI进行图片优化操作,重点讲解了AI扩图功能的实现方法。文章首先说明了技术文档的使用注意事项,强调仅限个人学习研究,不得商用或传播版权内容。 核心内容包括: 通过阿里云百炼API实现图像扩展功能,采用异步调用模式(创建任务+轮询结果) 项目实现分为后端任务创建和前端轮询结果两部分 详细代码示例展示了如何调用API: 配置API Key等敏感信息 封装扩图任
本文为个人学习笔记整理,仅供交流参考,非专业教学资料,内容请自行甄别。
通过学习获取的图片仅可用于个人技术研究(如测试下载逻辑、解析代码),不得用于商业用途(如制作产品素材、二次分发),也不能传播涉及版权或隐私的内容。
前言
本篇主要介绍在图库类型的项目中,如何运用AI对图片进行一系列的优化操作。本篇使用到的是阿里云百炼。
一、AI扩图
1.1、概述
阿里云百炼的官方文档:阿里云百炼
调用大模型,首先需要获取API Key,这一块跟着文档的步骤即可实现:
在本项目中,用到的是图像画面扩展的功能:
查看官方文档,该API采用的是异步调用
的模式,即分为创建提交任务
,查询任务结果
两部分:
- 创建任务获取任务ID,接口返回任务ID,可根据任务ID查询图像生成的结果。该 task_id的查询有效期为 24 小时。
- 使用步骤1中获取的 task_id,通过 GET 请求轮询任务查询接口,直到task_status 变为 SUCCEEDED。任务成功后,响应中会包含生成的图像 URL。
创建任务,这一步一定是要在后端实现的,因为调用接口涉及到API key,这样的敏感信息是不能存在前端的。而轮询获取任务结果的操作,则是放在了前端,通过定时器
,每隔一段时间向后端发送请求,获取最新的结果。
1.2、项目实现
首先是要在配置文件中对API key进行设置:
然后编写创建任务和获取任务结果的代码,这里采用直接发送Http请求的方式,对应的请求参数和示例如图:
@Slf4j
@Component
public class AliyunAIService {
@Value("${aliYunAi.apiKey}")
private String apiKey;
/**
* 创建任务获取任务ID请求地址
*/
private final String createTaskHttpPostUrl = "https://dashscope.aliyuncs.com/api/v1/services/aigc/image2image/out-painting";
/**
* 查询任务结果请求地址
*/
private final String queryTaskResHttpGetUrl = "https://dashscope.aliyuncs.com/api/v1/tasks/";
/**
* 创建任务获取任务ID
*
* @param request
* @return
*/
public CreateOutPaintingTaskResponse createTaskHttpPost(CreateOutPaintingTaskRequest request) {
ThrowUtils.throwIf(ObjUtil.isEmpty(request), ErrorCode.PARAMS_ERROR, "请求参数为空");
// 发送POST请求
HttpRequest httpRequest = HttpRequest.post(createTaskHttpPostUrl)
.header("X-DashScope-Async", "enable")
.header("Authorization", "Bearer " + apiKey)
.header("Content-Type", "application/json")
.body(JSONUtil.toJsonStr(request));
//解析结果
try (HttpResponse httpResponse = httpRequest.execute()) {
ThrowUtils.throwIf(!httpResponse.isOk(), ErrorCode.OPERATION_ERROR, "创建任务失败!");
CreateOutPaintingTaskResponse response = JSONUtil.toBean(httpResponse.body(), CreateOutPaintingTaskResponse.class);
ThrowUtils.throwIf(ObjUtil.isEmpty(response), ErrorCode.OPERATION_ERROR, "响应结果为空!");
ThrowUtils.throwIf(!response.getOutput().getTaskStatus().equals("PENDING"), ErrorCode.OPERATION_ERROR, "响应状态异常!");
return response;
}
}
/**
* 根据任务ID查询任务结果
* 这里不能因为返回的不是success就返回前端失败,因为前端需要轮询,可能任务在处理中
* @param taskId
* @return
*/
public GetOutPaintingTaskResponse createQueryTaskResHttpGet(String taskId) {
log.info("任务ID:{},开始执行",taskId);
ThrowUtils.throwIf(StrUtil.isBlank(taskId), ErrorCode.PARAMS_ERROR, "请求参数为空");
// 发送Get请求
HttpRequest httpRequest = HttpRequest.get(queryTaskResHttpGetUrl + taskId).header("Authorization", "Bearer " + apiKey);
//解析结果
try (HttpResponse httpResponse = httpRequest.execute()) {
GetOutPaintingTaskResponse response = JSONUtil.toBean(httpResponse.body(), GetOutPaintingTaskResponse.class);
ThrowUtils.throwIf(ObjUtil.isEmpty(response), ErrorCode.OPERATION_ERROR, "响应结果为空!");
log.info("任务ID:{},执行完成",taskId);
return response;
}
}
}
扩图任务请求封装类,在xScale和yScale属性上,加入了jackson的@JsonProperty
注解,原因是Spring MVC对于属性名第二个字母大写的情况,无法进行映射。注意,这里第二个字母大写,只是为了和接口响应的值相对应,将接口返回的x_scale,y_scale转换为了规范的驼峰命名。在自定义实体类的情况下,应该避免这样的命名方式,如email,不要定义成eMail。
/**
* 扩图任务请求类
*/
@Data
public class CreateOutPaintingTaskRequest implements Serializable {
/**
* 模型,例如 "image-out-painting"
*/
private String model = "image-out-painting";
/**
* 输入图像信息
*/
private Input input;
/**
* 图像处理参数
*/
private Parameters parameters;
@Data
public static class Input {
/**
* 必选,图像 URL
*/
@Alias("image_url")
private String imageUrl;
}
@Data
public static class Parameters implements Serializable {
/**
* 可选,逆时针旋转角度,默认值 0,取值范围 [0, 359]
*/
private Integer angle;
/**
* 可选,输出图像的宽高比,默认空字符串,不设置宽高比
* 可选值:["", "1:1", "3:4", "4:3", "9:16", "16:9"]
*/
@Alias("output_ratio")
private String outputRatio;
/**
* 可选,图像居中,在水平方向上按比例扩展,默认值 1.0,范围 [1.0, 3.0]
*/
@Alias("x_scale")
@JsonProperty("xScale")
private Float xScale;
/**
* 可选,图像居中,在垂直方向上按比例扩展,默认值 1.0,范围 [1.0, 3.0]
*/
@Alias("y_scale")
@JsonProperty("yScale")
private Float yScale;
/**
* 可选,在图像上方添加像素,默认值 0
*/
@Alias("top_offset")
private Integer topOffset;
/**
* 可选,在图像下方添加像素,默认值 0
*/
@Alias("bottom_offset")
private Integer bottomOffset;
/**
* 可选,在图像左侧添加像素,默认值 0
*/
@Alias("left_offset")
private Integer leftOffset;
/**
* 可选,在图像右侧添加像素,默认值 0
*/
@Alias("right_offset")
private Integer rightOffset;
/**
* 可选,开启图像最佳质量模式,默认值 false
* 若为 true,耗时会成倍增加
*/
@Alias("best_quality")
private Boolean bestQuality;
/**
* 可选,限制模型生成的图像文件大小,默认值 true
* - 单边长度 <= 10000:输出图像文件大小限制为 5MB 以下
* - 单边长度 > 10000:输出图像文件大小限制为 10MB 以下
*/
@Alias("limit_image_size")
private Boolean limitImageSize;
/**
* 可选,添加 "Generated by AI" 水印,默认值 true
*/
@Alias("add_watermark")
private Boolean addWatermark = false;
}
}
扩图任务响应封装类:
/**
* 扩图任务响应类
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CreateOutPaintingTaskResponse {
private Output output;
/**
* 表示任务的输出信息
*/
@Data
public static class Output {
/**
* 任务 ID
*/
private String taskId;
/**
* 任务状态
* <ul>
* <li>PENDING:排队中</li>
* <li>RUNNING:处理中</li>
* <li>SUSPENDED:挂起</li>
* <li>SUCCEEDED:执行成功</li>
* <li>FAILED:执行失败</li>
* <li>UNKNOWN:任务不存在或状态未知</li>
* </ul>
*/
private String taskStatus;
}
/**
* 接口错误码。
* <p>接口成功请求不会返回该参数。</p>
*/
private String code;
/**
* 接口错误信息。
* <p>接口成功请求不会返回该参数。</p>
*/
private String message;
/**
* 请求唯一标识。
* <p>可用于请求明细溯源和问题排查。</p>
*/
private String requestId;
}
查询任务响应封装类:
/**
* 查询任务响应类
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class GetOutPaintingTaskResponse {
/**
* 请求唯一标识
*/
private String requestId;
/**
* 输出信息
*/
private Output output;
/**
* 表示任务的输出信息
*/
@Data
public static class Output {
/**
* 任务 ID
*/
private String taskId;
/**
* 任务状态
* <ul>
* <li>PENDING:排队中</li>
* <li>RUNNING:处理中</li>
* <li>SUSPENDED:挂起</li>
* <li>SUCCEEDED:执行成功</li>
* <li>FAILED:执行失败</li>
* <li>UNKNOWN:任务不存在或状态未知</li>
* </ul>
*/
private String taskStatus;
/**
* 提交时间
* 格式:YYYY-MM-DD HH:mm:ss.SSS
*/
private String submitTime;
/**
* 调度时间
* 格式:YYYY-MM-DD HH:mm:ss.SSS
*/
private String scheduledTime;
/**
* 结束时间
* 格式:YYYY-MM-DD HH:mm:ss.SSS
*/
private String endTime;
/**
* 输出图像的 URL
*/
private String outputImageUrl;
/**
* 接口错误码
* <p>接口成功请求不会返回该参数</p>
*/
private String code;
/**
* 接口错误信息
* <p>接口成功请求不会返回该参数</p>
*/
private String message;
/**
* 任务指标信息
*/
private TaskMetrics taskMetrics;
}
/**
* 表示任务的统计信息
*/
@Data
public static class TaskMetrics {
/**
* 总任务数
*/
private Integer total;
/**
* 成功任务数
*/
private Integer succeeded;
/**
* 失败任务数
*/
private Integer failed;
}
}
前端部分,则是要定义一个定时器,并且需要记录后端传递的taskId。要注意定时器需要关闭的场景,后端明确返回成功或失败,接口失败,接口报错,关闭页面时,都需要清理计时器,并且置空taskId。
//轮询定时器
let pollingTimer: NodeJS.Timeout = null
const loading = ref<boolean>(false)
/**
* 开启轮询
*/
const startPolling = () => {
if (!taskId.value) {
return
}
pollingTimer = setInterval(async () => {
try {
//调用后端查询AI处理图片任务结果的接口
const resp = await queryPicHandletaskUsingGet({
taskId: taskId.value,
})
//接口成功
if (resp.data.code === 0) {
//成功
if (resp.data.data?.output?.taskStatus === 'SUCCEEDED') {
aiHandleResUrl.value = resp.data.data?.output?.outputImageUrl
stopPolling()
}
//失败
if (resp.data.data?.output?.taskStatus === 'FAILED') {
stopPolling()
}
} //接口失败
else {
stopPolling()
}
} catch (error) {
console.log('调用后端查询AI处理图片任务结果的接口错误', error)
stopPolling()
}
}, 3000)
}
/**
* 结束轮询
*/
function stopPolling() {
if (pollingTimer) {
clearInterval(pollingTimer)
loading.value = false
pollingTimer = null
taskId.value = null
}
}
/**-
* 生成图片 调用后端创建AI处理图片任务接口
*/
async function doAIHandler() {
loading.value = true
const resp = await createPicHandletaskUsingPost({
pictureId: props.picture?.id,
parameters: {
xScale: 2,
yScale: 2,
},
})
if (resp.data.code === 0) {
message.success('后台处理中,请勿关闭窗口,稍后查证')
//记录任务ID
taskId.value = resp.data.data?.output?.taskId
//开启任务轮询
startPolling()
} else {
message.error(resp.data.message)
}
}
onUnmounted(() => {
stopPolling()
})
最终效果,AI扩图有一定的限制,也应在前端给予友好的提示:
更多推荐
所有评论(0)