好用的HAI-GPU:我将腾讯云HAI的AI绘画接入小程序
前言
感觉已经进入全面 AIGC 的时代了,从刚开始的ChatGPT 的生成文本,到 GPT-4 文本到图片的发展,深刻感受到了技术的日新月异。但是 GPT-4 一直是付费模式,我才开始接触stable diffusion,在自己的电脑上学习 AI 绘画。
AI 绘画的文生图还没研究透彻呢,文生视频 sora 又来了。对于我来说,学习的速度远远追不上技术的发展,所以索性就沉下心来,深入地学习一下 AI 绘画。
如今阿里云、腾讯云等很多厂商,都提供了 AI 绘画地服务能力。通过购买部署 AI 绘画服务的实例,让 Stable Diffusion 实现云化。今天就来研究一下腾讯云 AI 绘画服务 hai。
于是,学了一下午的微信小程序开发(有前端开发基础),实现了将 hai 服务接入到微信小程序。
操作动图演示:
HAI
hai 是腾讯云的高性能应用服务(Hyper Application Inventor),在腾讯云的 hai 服务页面,春季活动 1 元可以 10 元抵扣券。
10 元可以购买 8 小时(1.2 元 / 时)的 GPU 基础套餐。
在实例中预装了 stable diffusion 的 webui 和 comfyui,我个人还是比较喜欢 webui,喜欢工作流的可以选择后者。
然后就是 1 元下单购买 10 元抵扣券。
创建实例,使用抵扣券开通 8 小时体验时间。
购买之后创建实例,分配公网 IP。
至此,实例创建完成。我们想要在微信小程序中实现文生图的功能,那么就要调用 hai 的api,所以必须开启 hai 的 api 功能。
开放 API
根据 Hai 操作文档,首先开启 Stable diffusion 服务的 api 支持。通过实例页面右侧的JupyterLab进入 Terminal,执行下面命令监听服务。
cd stable-diffusion-webui
python launch.py --nowebui --xformers --opt-split-attention --listen --port 7862
执行命令之后,成功监听 7862 端口。
在实例页面可以看到公网 IP,通过 http:// 公网 IP:7862/docs 可以查看 api 文档。
我们小程序开发开发的文生图功能,主要是调用txt2img的 api,查看接口参数。
然后添加安全组。
至此,关于 hai 服务的部分已经全部准备完成,接下来就是微信小程序的设计。
微信小程序
我们根据 HAI 提供的 api 的请求参数,参考 stable diffusion web ui来实现一个简易版的微信小程序。
设计
先看 hai 文档中 api 接口的参数。
在上面的参数中,有一些可以设置为固定值,所以我们只挑选一些主要的参数,放到小程序页面的设计中,例如:prompt、negative_prompt、steps、seed(其实也可以不用),sampler_index。batch_size我们默认就生成一张就行了。
对于微信小程序的开发,我是基于 uni-app,使用 vue 进行的开发。UI 没有使用市面上基于 uni-app 封装的组件,我使用的是 uni-app 提供的原生组件。
开发
在 HBuilderX 和微信开发者工具中完成开发工作。先实现功能,再优化 UI 样式,初版的设计如下:
1. prompt
使用textarea组件实现 prompt 文本框。
<view class="uni-title uni-common-pl">Text Prompt</view>
<view class="uni-textarea">
<textarea v-model="prompt" placeholder-class='placeholder' placeholder="请输入提示词" />
</view>
<view class="uni-title uni-common-pl">Text Prompt</view>
<view class="uni-textarea">
<textarea v-model="negative_prompt" placeholder-class='placeholder' placeholder="请输入反向提示词" />
</view>
使用v-model与 textarea 的value实现双向绑定。
import {ref} from 'vue'
const prompt = ref('')
const negative_prompt = ref('')
prompt 和 negative_prompt 的初始值都设置为空,两个变量会随着 textarea 输入的值而改变,这样就可以获取用户输入的参数。
2. step
step 就是 Sampling steps(采样步长),就是在 reverse diffusion 中,从噪声图降噪成正常图片的步数,通常使用 20 - 30,这里使用了滑动选择器slider组件来实现。
<view class="uni-title">step</view>
<view>
<slider class="slider" value="20" activeColor='#465CFF' block-color="#8A6DE9"
block-size="20" @change="stepChange" min="10" max="50" show-value />
</view>
activeColor用来设置滑块左侧已选择部分的线条颜色,右侧默认为白色。block-color和block-size分别设置滑动块的颜色和大小。
change属性绑定回调函数stepChange,当滑动组件改变时就会触发。
const step = ref(20)
const stepChange = function(event){
step.value = event.detail.value
}
定义 step 默认值为 20,在 stepChange 获取最新值,我个人比较喜欢设置为 30。
3. scale
scale是 promot 的引导系数,用来控制模型遵循你输入的 prompt 有多少,用 1-30 表示。1 表示完全忽略你的提示,3 表示更有创造力,7 为推荐值,表示在自由和遵从之前平衡,15 更加遵循提示,30 完全遵循提示。
所以说 scale 使用默认 7 就行了,但是这里还是尊重选择,使用了滑动选择器slider组件来实现。
<view class="uni-title">scale</view>
<view>
<slider value="7" activeColor='#465CFF' block-color="#8A6DE9" block-size="20" @change="scaleChange" min="1"
max="30" show-value />
</view>
同样这里 change 绑定回调函数scaleChange。
const scale = ref(7)
const scaleChange = function(event){
scale.value = event.detail.value
}
定义 scale 默认值为 7,这里不建议修改,在 stepChange 获取最新值。
4. seed
seed 就是随机数种子,在提示词以及其他设置相同时,seed 相同才能生成一样的图片。
<view class="uni-title">seed</view>
<input class="uni-input" v-model="seed">
同样在 input 组件中使用v-model实现双向绑定seed。
const seed = ref(-1)
seed 默认值为 -1。
5. sampler
采样器使用radio组件实现,遍历 samplerItem 进行渲染。
<view class="uni-title">sampler</view>
<view class="sampler">
<radio-group v-for="(item, index) in samplerItem" :key="item.name" @change="samplerChange">
<view class="sampler-radio">
<radio :value="item.name" :checked="item.name == sampler" />
<span>{{ item.name }}</span>
</view>
</radio-group>
</view>
定义 samplerItem,定义了 6 个 Item,这里只写了 2 个。
const samplerItem = reactive([{
"index": 0,
"name": "Euler",
},
{
"index": 1,
"name": "LMS",
},
])
name 是渲染 radio 的名称,index 是下标。默认 radio 都是垂直分布的,我想让 6 个 radio 分成两行,所以就需要使用 flex 分布。
.sampler {
display: flex;
flex: 0 0 auto;
flex-wrap: wrap;
width: 100%;
justify-content: center;
}
.sampler-radio {
width: 33vw;
}
同时flex: 0 0 auto属性让弹性元素不发生伸缩,每个 radio 占用 33% 的宽度,一行就实现了 3 个 radio 的分布。flex-wrap允许换行,所以就将 6 个 radio 分布在了两行。
在使用 radio 你会发现两个问题:
- radio 一旦选中,是没办法点击取消选中的
- 可以多选,但是我们的需求只能单选
为了解决这两个问题,需要定义checked属性来决定是否选中。定义sampler变量用来与当前 radio 比较。默认是 0,所以在第一次进入或刷新小程序时,默认选中第一个 radio。
定义 radio-group 的change事件,当点击选中新的 radio 时就会触发,这时修改 sampler 为最新选中的 radio,其他 radio 的checke属性就为 false,就取消了选中。
const sampler = ref('Euler')
const samplerChange = function(event) {
sampler.value = event.detail.value
}
效果如下:
同时,sampler 也作为请求参数来请求接口。
6. 生成按钮
然后就是生成图片按钮,点击按钮就发起 hai 接口请求。
<view>
<button type="primary" @click="generatePic" :loading="isLoading">生成图片</button>
</view>
这里绑定了点击事件generatePic发起请求,接着看如何实现请求函数。
const isPicShow = ref(false)
const isLoading = ref(false)
const picBase64 = ref('')
const generatePic = function() {
isLoading.value = true
uni.request({
url: 'http:// 公网 IP:7862/sdapi/v1/txt2img',
method: 'POST',
data: {
"denoising_strength": 0,
"prompt": prompt.value,
"negative_prompt": negative_prompt.value,
"seed": seed.value,
"batch_size": 1,
"n_iter": 1,
"steps": step.value,
"cfg_scale": 7,
"width": 512,
"height": 512,
"restore_faces": false,
"tiling": false,
"sampler_index": sampler.value
},
success: (res) => {
picBase64.value = data:image/png;base64,<span class="hljs-subst">${res.data[<span class="hljs-string">'images'</span>][<span class="hljs-number">0</span>]}</span>
isPicShow.value = true
isLoading.value = true
}
});
}
使用 uni-app 的request接口发起请求,请求参数一部分是通过我们定义的组件输入获取的,一部分是固定的。在我们发起请求成功之后,hai 服务就会返回一个数组,base64 格式的图片就包含在数组之中,如图所示:
我们在success回调函数中对自定义的picBase64、isLoading和isPicShow变量做处理。
picBase64 用于接收接口获取的 base64 格式的图片,当 img 组件的 src 使用 base64 而非路径时,需要在图片 base64 格式之前加上前缀:data:image/png;base64,。
isLoading 的作用是什么呢? 因为 Stable diffusion 生成图片是需要时间的,所以在点击按钮之后等待的过程中,给人的感觉是微信小程序卡了。所以就会不停地点击生成按钮,后台就会一直在生成图片。
上图为后台日志,我们可以看到来自同一个 IP 的多个文生图任务在运行,就导致了资源的浪费。button 组件的loading属性就是,当为 true 的时候是就会一直处于 loading 动画,不可点击,在 generatePic 函数发起请求时,设置为 true 处于加载不可点击状态。当请求完成后,设置为 false。
7. image 组件
isPicShow 用来展示图片组件,当请求完成之后才会显示图片组件。所以在生成按钮的下方,要定义一个图片组件。
<view v-if="isPicShow">
<image class="controls-play img" :src="picBase64" @longpress="longpress"></image>
</view>
保存图片
可以看到 picBase64 绑定在 img 的 src 属性上,父组件 view 用 isPicShow 来控制是否显示。接着我想长按图片就可以保存到本地相册,所以在 image 组件上绑定了长按事件longpress。
const longpress = function() {
uni.showActionSheet({
itemList: ['保存图片'],
success: (res) => {
if (res.tapIndex === 0) {
saveBase64ImageToAlbum(picBase64.value);
}
}
});
}
使用 uni-app 的showActionSheet从底部弹出输出选项框,选择保存图片就可以调用saveBase64ImageToAlbum函数,将 base64 图片保存到相册中。
function base64ToTempFilePath(base64Data) {
return new Promise((resolve, reject) => {
uni.getFileSystemManager().writeFile({
filePath: `${wx.env.USER_DATA_PATH}/temp-image.png`,
data: base64Data,
encoding: 'base64',
success: () => {
resolve(`${wx.env.USER_DATA_PATH}/temp-image.png`);
},
fail: reject
});
});
}
async function saveBase64ImageToAlbum(base64Data) {
const tempFilePath = await base64ToTempFilePath(base64Data);
uni.saveImageToPhotosAlbum({
filePath: tempFilePath,
success: () => {
uni.showToast({
title: '图片保存成功',
icon: 'success'
});
},
fail: (error) => {
uni.showToast({
title: '保存失败:' + error.errMsg,
icon: 'none'
});
}
});
}
saveBase64ImageToAlbum的逻辑是将 base64 格式转换成 png 格式,存放在用户临时目录中,然后通过调用 uni-app 的saveImageToPhotosAlbum接口保存到相册中,在开发者工具中演示结果如下:
查看保存的图片:
预览图片
在完成整个微信小程序的开发之后,突然想着没有点击预览图片的功能,于是给 image 组件绑定了click事件,绑定previewImage函数实现了这一块功能。
const previewImage = function() {
base64ToTempFilePath(picBase64.value);
uni.previewImage({
urls: [`${wx.env.USER_DATA_PATH}/temp-image.png`],
current: 0
});
}
使用 uni-app 的previewImage接口,直接预览保存在用户目录的图片即可。
如图,点击实现预览,至此,从参数输入的设计、请求 hai 文生图接口以及保存图片到相册的开发就完成了。
优化
上面 AI 绘画使用的是 stable diffusion 自带的模型,所以生成的图片没有那么理想,所以大家可以根据 hai 操作文档,将自己喜欢的模型上传。
从程序开发角度来说,优化内容主要是两个部分,一部分是前端 UI 设计,一部分是权限的控制,例如一个用户可以请求的次数等等,这里只讲前端 UI 的优化的部分。
微信小程序的优化内容一是可以增加图生图等模块,二是修改 css,更改小程序的 UI 配色和布局设计,这里主要还是讲 css 部分。
上图是最终修改后的小程序样式,修改了配色和各个组件的样式,这里就以 textarea 为例。
.prompt-textarea {
border-radius: 4px;
background-color: #F1F4FA;
height: 80px;
width: 95vw;
margin: auto;
font-size: 13px;
box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.2);
padding-left: 5px;
padding-top: 3px;
}
设置了 4px 的圆角,居中、背景色、阴影等,同时通过 padding 设置,从视觉上让文本输入是向右偏移,而不是紧贴这输入框边缘。
结语
五分钟,从购买 1 元”炼丹券“到创建实例、使用 stable diffusion 服务。总的来说,hai 极度简化了 stable diffusion 的使用难度,不需要自己准备电脑,随时随地可以在电脑或者手机上使用 AI 绘画。
第一次开发微信小程序,还有许多学习的地方,后续会更新一些关于自学微信小程序内容。
原创:腾讯云开发者|叫我阿柒啊