在之前的文章《最佳实践 | 用腾讯云AI图像能力实现AI作画》中,我们探讨了如何使用腾讯云的智能AI能力来构建一个简易版的AI绘画应用。文章发布后,受到了许多朋友的关注,同时也引发了我对如何进一步提升效果的思考。
最近,我注意到AI绘画在短视频平台上掀起了一股热潮。结合在网上看到的一些卓越的AI绘画模型,我开始思考如何在之前的基础上实现更好的用户体验和更优质的绘画效果。与其单纯依赖AI生成,不如结合现有技术,让AI更好地服务于我们的需求。
接下来,我将详细分享我的实践过程,希望对感兴趣的朋友有所启发。在探索过程中,我发现人像AI绘画尤其具有挑战性,而腾讯云的智能图片融合功能,恰好可以解决人像生成中的一些问题。
1. 实现思路
我们的核心思路是:先通过AI生成初步的人像图,然后利用腾讯云的智能人脸融合技术进行优化,最终生成一张效果更佳、更自然的人像图。这个思路的关键在于将AI的生成能力和腾讯云的优化能力结合起来,扬长避短,从而达到更好的效果。
1.1 详细流程:

2. 准备工作
在开始之前,我们需要做一些准备工作,包括Stable Diffusion的部署,以及腾讯云相关接口的开通和配置。这些准备工作是后续实践的基础,务必认真对待。
2.1 Stable-Diffusion部署
Stable Diffusion是一个强大的开源文本到图像模型,它能根据你输入的文字描述,生成相应的图片。你可以把它想象成一位技艺高超的画家,只需要告诉他你想画什么,他就能帮你创作出来。具体可以参考GitHub:https://github.com/CompVis/stable-diffusion
安装过程与文档介绍的大同小异,这里不再赘述。安装完成后,我们就可以通过脚本来生成图片了。
代码语言:javascript
代码运行次数:0
运行
from torch import autocast
from diffusers import StableDiffusionPipeline
import sys
# 指定模型
pipe = StableDiffusionPipeline.from_pretrained(
# "CompVis/stable-diffusion-v1-4",
"runwayml/stable-diffusion-v1-5",
# "hakurei/waifu-diffusion",
use_auth_token=True
).to("cuda")
prompt = "a photo of an astronaut riding a horse on mars"
prompt = sys.argv[1]
with autocast("cuda"):
image = pipe(prompt, num_inference_steps=100).images[0]
image.save(sys.argv[2] + ".png")
指定关键词,调用输出,看下生成效果:
代码语言:javascript
代码运行次数:0
python3 interface.py "*******" out

3. 小程序demo实践
为了更方便地使用AI绘画功能,我开发了一个小程序demo。通过小程序,用户可以随时随地生成自己想要的图片,无需复杂的配置和操作。
3.1 AI绘画服务端:
模型部署好后,默认只能在本地执行。为了让小程序能够调用AI绘画功能,我们需要简单实现一个服务端。
服务端主要实现以下功能:
一、接收用户提交的任务,并将任务信息存储到腾讯云对象存储(COS)上。服务通过拉取COS上的任务信息来执行AI绘画任务。
二、执行shell命令,调用Stable Diffusion生成图片,并将生成好的图片上传到COS。
COS文档: https://cloud.tencent.com/document/product/436
AI绘画模型执行代码:
代码语言:javascript
代码运行次数:0
type Request struct {
SessionId string `json:"session_id"`
JobId string `json:"job_id"`
Prompt string `json:"prompt"`
ModelUrl string `json:"model_url"`
ImageUrl string `json:"image_url"`
}
type JobInfo struct {
JobId string `json:"job_id"`
Request
}
func run(req *JobInfo) {
begin := time.Now()
Log("got a job, %+v", req)
jobId := req.JobId
cmd := exec.Command("sh", "start.sh", req.Prompt, jobId)
err := cmd.Run()
if err != nil {
fmt.Println("Execute Command failed:" + err.Error())
return
}
result, err := os.ReadFile(fmt.Sprintf("output/%s.png", jobId))
if err != nil {
panic(err)
}
url, err := cos.PutObject(context.Background(), fmt.Sprintf("aidraw/%s.png", jobId), result)
if err != nil {
panic(err)
}
resp := &Response{
SessionId: req.SessionId,
JobId: jobId,
JobStatus: "FINISNED",
CostTime: time.Since(begin).Milliseconds(),
ResultUrl: url,
}
Log("job finished, %+v", resp)
data, _ := json.Marshal(resp)
pushResult(jobId, string(data))
}
通过COS来实现任务管理,涉及到任务拉取和结果上传, 以下是实现代码:
代码语言:javascript
代码运行次数:0
func pullJob() *JobInfo {
res, _, err := cos.GetInstance().Bucket.Get(context.Background(), &cossdk.BucketGetOptions{
Prefix: JOB_QUEUE_PUSH,
Delimiter: "",
EncodingType: "",
Marker: "",
MaxKeys: 10000,
})
if err != nil {
return nil
}
var jobId string
for _, v := range res.Contents {
if !objectExist(fmt.Sprintf("%s/%s", JOB_QUEUE_RESULT, getNameByPath(v.Key))) {
jobId = v.Key
break
}
}
if len(jobId) == 0 {
return nil
}
jobId = getNameByPath(jobId)
Log("new job %s", jobId)
resp, err := cos.GetInstance().Object.Get(context.Background(), fmt.Sprintf("%s/%s", JOB_QUEUE_PUSH, jobId), &cossdk.ObjectGetOptions{})
if err != nil {
panic(err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil
}
job := &JobInfo{
JobId: jobId,
}
err = json.Unmarshal(body, &job)
if err != nil {
return nil
}
return job
}
func pullResult(jobId string) *Response {
resp, err := cos.GetInstance().Object.Get(context.Background(), fmt.Sprintf("%s/%s", JOB_QUEUE_RESULT, jobId), &cossdk.ObjectGetOptions{})
if err != nil {
return nil
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil
}
rsp := &Response{}
json.Unmarshal(body, &rsp)
return rsp
}
func pushResult(jobId, result string) {
_, err := cos.PutObject(context.Background(), fmt.Sprintf("%s/%s", JOB_QUEUE_RESULT, jobId), []byte(result))
if err != nil {
panic(err)
}
}
3.2 小程序服务端:
小程序需要通过中转服务来异步处理消息。简单来说,小程序服务端就像一个信使,负责在小程序和AI绘画服务端之间传递消息和数据。梳理一下server的功能:
一、转发请求到AI绘画。
二、查询AI绘画的结果。(通过cos中转)
以下是部分代码:
协议相关:
代码语言:javascript
代码运行次数:0
type Request struct {
SessionId string `json:"session_id"`
JobId string `json:"job_id"`
Prompt string `json:"prompt"`
ModelUrl string `json:"model_url"`
ImageUrl string `json:"image_url"`
}
type Response struct {
SessionId string `json:"session_id"`
JobId string `json:"job_id"`
JobStatus string `json:"job_status"`
CostTime int64 `json:"cost_time"`
ResultUrl string `json:"result_url"`
TotalCnt int64 `json:"total_cnt"`
}
提交任务:
代码语言:javascript
代码运行次数:0
// submitJobHandler 提交任务
func submitJobHandler(writer http.ResponseWriter, request *http.Request) {
body, err := io.ReadAll(request.Body)
req := &Request{}
err = json.Unmarshal(body, &req)
if err != nil {
panic(err)
}
Log("got a submit request, %+v", req)
jobId := GenJobId()
pushJob(jobId, string(body))
resp := &Response{
SessionId: req.SessionId,
JobId: jobId,
TotalCnt: sumJob(),
}
data, _ := json.Marshal(resp)
writer.Write(data)
}
// describeJobHandler 查询任务
func describeJobHandler(writer http.ResponseWriter, request *http.Request) {
body, err := io.ReadAll(request.Body)
req := &Request{}
err = json.Unmarshal(body, &req)
if err != nil {
panic(err)
}
Log("got a query request, %+v", req.JobId)
var ret *Response
ret = pullResult(req.JobId)
if ret == nil {
ret = &Response{
SessionId: req.SessionId,
JobId: req.JobId,
JobStatus: "RUNNING",
}
}
data, _ := json.Marshal(ret)
writer.Write(data)
}
3.3.小程序实现AI绘画:
以下是小程序关键代码,包含index.js和index.wxml。
index.js
代码语言:javascript
代码运行次数:0
// index.js
// 获取应用实例
const app = getApp()
Page({
data: {
totalTask: 0,
leftTime: 40,
beginTime: 0,
processTime: 0,
taskStatus: "STOP",
inputValue: "",
tags: [],
option: [],
buttonStatus: false,
index: 0,
motto: 'Hello World',
userInfo: {},
hasUserInfo: false,
canIUse: wx.canIUse('button.open-type.getUserInfo'),
canIUseGetUserProfile: false,
canIUseOpenData: wx.canIUse('open-data.type.userAvatarUrl') && wx.canIUse('open-data.type.userNickName') // 如需尝试获取用户信息可改为false
},
// 事件处理函数
bindViewTap() {
wx.navigateTo({
url: '../logs/logs'
})
},
onLoad() {
if (wx.getUserProfile) {
this.setData({
canIUseGetUserProfile: true
})
}
this.onTimeout();
},
getUserProfile(e) {
// 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认,开发者妥善保管用户快速填写的头像昵称,避免重复弹窗
wx.getUserProfile({
desc: '展示用户信息', // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请