前言

哈喽大家好,这里是茶无味的一天。2023 年过得真快,转眼就要过年了,在这里祝大家龙年大吉,事事顺心!已经有段时间没更文了,刚好看到掘金的春节投稿活动,结合最近“不务正业”鼓捣的一些东西,就来给大伙整个活吧。

这两年 AIGC(AI-Generated Content)即 AI 内容生成技术的产品迎来井喷式增长,我想大部分人都已切身体验过许多相关产品了,我也不例外。但是作为程序员,我也一直在想:是否能基于这些 AI 技术开发自己的产品呢?今天就来聊聊这个我自己开发的免费在线 AIGC 工具,迅排 AI 文生图:

大家可以点此链接前往体验,【迅排设计 - Poster Design】是我独立开发的开源项目,致力于做最酷的开源在线海报图片编辑器,也欢迎给项目点个 Star 加速更新~

在这篇文章中,我将分享这款 AIGC 工具的开发历程,您将看到我是如何一步步设计实现它的。我的文章偏向注重实战,本文不会深入 AI 背后的复杂原理,涉及理论知识会尽量用大白话讲解,也希望能给你带来帮助或启发,如果觉得有用的话别忘了点赞收藏转发+关注。

在正式开始前,我们还是先聊聊 Stable diffusion 以及什么是扩散模型~

AIGC 那些事

其实 AI 绘图技术早已不是什么新鲜事,只不过近两年才真正进入普罗大众的视野,这得益于 AI 在民用领域的突破。

其中较为代表性的文生图(text to image)是一种基于自然语言描述生成图像的技术,其历史甚至可以追溯到 20 世纪 80 年代。

随着计算机硬件的进步特别是深度学习技术的发展,市面上涌现过许多算法模型,其中较为流行的是生成对抗网络(Generative adversarial network)GAN,它是受到博弈论的启发,主要由生成器与判断器组成,其中判断器犹如一位“鉴赏家”,它会在大量画作中不断学习何为“好的作品”,然后由生成器通过一组随机数创作图像,再交由判断器过滤作品出图,这就是 GAN 模型进行 AI 绘画的大致原理。

相比 GAN 在问世后的一鸣惊人,扩散概率模型(Diffusion probabilistic models)DPM 一开始则并未崭露头角。

它最早在 2015 年提出,灵感是来源于物理热力学,DPM 的大致原理是通过不断给图片添加高斯噪声直至其变为噪点图,然后让机器学习如何反转去噪还原图像这个过程,最终模型便能从一张噪点图开始生成出图像。

但因为整个过程需要不断经历像素级别的迭代,消耗显卡等硬件资源巨大,包括计算资源和显存,训练成本十分高昂。

GAN 的优势是快(即使放在今天也是如此),虽然有一定的局限性,但曾经是占据绝对统治地位的。扩散模型虽然也在不断演化,却一直不温不火,直到 2020 年一篇论文提出了降噪扩散概率模型(Denoising diffusion probabilistic model)DDPM,解决了 DPM 的一些不足并做出一些改进,使得扩散模型生成的图像质量得到大幅提升,此时扩散模型在图像生成领域才开始大放异彩。

不过 DDPM 资源消耗依然比较大,还无法走向更广阔的民用领域。时间来到 2021 年,德国的一篇论文提出了潜在扩散模型(Latent diffusion model)LDM,它在 DDPM 的基础上进行了压缩编码,于低维潜在空间中解码,极大降低了硬件资源的需求,扩散模型的优势日渐明显,特别是在解析文本生成图像方面的表现令人印象深刻,而 GAN 等算法模型开始成为传统。

LDM 的开发团队 CompVis 和 Runway ML 后来与 Stability AI 联合搞了一个预训练的 LDM 模型,这就诞生了我们今天的主角:稳定扩散模型(Stable diffusion)以下简称 SD。

如今在图像生成领域诸如 Dall-EMidjourney 等都取得了一定的成功,并被越来越多人关注到,但它们都是商业软件,相比之下 SD 是完全开源的,它对于推动当下 AI 绘图技术的蓬勃发展有着更大的意义,对此我只想说:Thanks for open-sourcing ! 当然这一切也离不开其背后 Stability AILaion 的慷慨捐赠和支持,资本的加持也很重要。

Stable Diffusion 在 2022 年 8 月正式开源,有件事情挺有意思,美国一家提供 AI 辅助写作的公司 NovelAI 在 SD 的基础上进行微调,推出了一款在线图像生成服务,由于生成的二次元图片在当时效果出众,被认为是集大成者的特化模型而受到广泛推崇。不久后其源代码就遭到泄露流出,一时间竟使 NovelAI 更加声名大噪。由于 SD 本身已经能够在消费级显卡上驱动,许多人因此都能亲身体验到最先进的 AI 绘图技术,而我也是在这个时期了解到 AI 绘图的,并开始注意到其背后这项开源技术的发展。

开发契机

2022 年可以说是 AIGC 元年,SD 也已经发布有一年多的时间了,为什么我现在才想到要做这样一款工具呢?

其实对于 AI 相关的技术我一直保持着敬畏之心和极其有限的关注,相比国内热衷于如何利用 AI 赚钱的思潮,我更关心如何使用 AI 来实际开发应用。

我一直在思考用最低的成本开发在线 AI 产品的可行性。Midjourney 在世界上八个不同的地区设立自己的服务器,利用不同时区日夜交替来平衡 GPU 负载,当一个地区的人都在睡觉时,这些地区的 GPU 算力又会全力供给另一时区的人们使用。即使是头部 AIGC 公司都在想尽办法节省开销,而在最初的时候,我大概需要花费一台游戏主机的成本才能使用 AI 绘图,否则就得忍受几分钟甚至更久时间才能出一张图,我认为没有意义。

不过 AI 进化神速,如今只要极低的硬件配置就能流畅出图,有如下几个关键节点促使我思考开始做一款在线应用:

LoRA

我们知道在 SD 中必须加载一个大模型,也就是 checkpoint 才能使用,每个大模型都会有自己的一套风格,比如真实系的模型就是只能出写实类场景或人物的图片,而每个 checkpoint 都非常大,每次加载都需要大量计算和资源消耗,带来的结果是切换模型时间成本很高。

而 LoRA 则是一种小模型,加载成本很低,它的出现使得我们可以将一个大模型给发挥到极致,通过搭配不同的 LoRA 模型就可以微调出各种画面,我几乎可以在不切换大模型的情况下丰富内容。

LCM 加速出图

另一个就是最近刚出不久的 LCM 也叫潜在一致性模型,它可以在很少的步数下生成一张高质量图像(一般只需 2 - 8 步即可),这个模型的作者也提供了一个 LCM 采样器和 LCM-LoRA 可以作为通用加速模块来使用。

一年以前普通显卡生成一张图片可能要几分钟的时间,等待过程十分漫长,而现在同样的一张图只需要几秒钟就可以生成,我脑海里的想法眼看就要呼之欲出,最终在通过捡垃圾组装了一台主机并成功跑图后,就着手设计开发了这个在线 AIGC 工具。

在这个网页上体验到的功能现在都由我脚边这台花费 200 元左右、躺在一个快递盒子里的主机生成的,运行成本取决于当地的电费~

实战开始

上面说了这么多,想必你已经对 SD 有了更深的理解,那么接下来就开始进入实战环节了。SD 有一个专门的 WebUI 工具,Github 上搜索 Stable Diffusion WebUI 第一个就是了,另外需要安装好 Python 和 CUDA 环境。

windows 系统则推荐使用网上的集成软件,比较知名的就是秋叶大佬(人送外号“赛博菩萨”)的整合包了,这个包基本就是下载解压缩就可以使用的程度,还附带可联网升级的管理界面,在启动器中就可以一键升级到最新版本,帮我少走很多弯路,省去折腾的功夫。

还有就是不同平台的显卡需要下载不同的版本,N 卡与 A 卡是不通用的。其实 SD 原本也只支持 NVIDIA 平台(需要 cuda 功能),现在 AMD 也勉强能折腾(性能会降低 50% 左右),甚至苹果芯片的 ARM 平台现在似乎也能跑(不推荐)。

咱也不藏着掖着了,直接附上 2024 年 1 月 最新秋叶整合包下载地址:https://pan.quark.cn/s/2c832199b09b

模型选择与下载

目前社区上比较流行的都是强调人物的大模型,当然这些模型也可以用来生成纯场景的图片。

我本人比较不喜欢真人类模型,所以测试的比较多是二次元动漫风格的大模型,其中 MeinaMix 是我感觉比较好看且用着顺手的一款模型,该模型在 c 站人气也是非常高的。

这个模型可以在仅使用少量提示词的条件下就生成好看的图像,这也是该模型作者的目标,非常适合新手使用。

可以看到我只输入描述词:“一个女孩在沙滩边”所生成的图片,就已经非常好看,尽管提示词非常少,细节处理也毫不含糊:

以下是我常用的模型下载网站:

前两个是国外网站,访问和下载都需要梯子,会很烧流量你懂的,优点是模型多而全,基本能在网上找到的大模型都出自这两个网站。后面两个是国内的,优点是下载速度快,不过模型不是很全,倾向于主推各自平台上的独家模型。

模型下载后放在 SD 目录下的 models/stable diffusion 文件夹中,LoRA 放在 models/lora 文件夹中。

搭配的 LoRA 可以在这些网站上搜索下载,例如我要做龙年新春相关的海报,就直接搜索“龙年新春”的 LoRA。每款 LoRA 作者都有推荐的模型基底和采样器选择等信息,照搬的话效果肯定是最好的,不一样的参数会导致产生各种“化学反应”,像我使用某些 LoRA 在搭配 MeinaMix 使用后出图质量是完全无法接受的,就只能暂时舍弃。

不过我目前使用的这个龙年新春 LoRA 倒是适配性还不错,搭配 MeinaMix 和 LCM 采样器,颇有种动漫厚涂的风格,算是在巧合下给我玩出了一点新花样。

以上图片均为迅排 AIGC 在线出图结果。

SD绘图极速入门

虽然 SD 看起来比较复杂,但对于程序员来说入门还是很简单的,这里放上秋叶大佬的一图流教程:

新的界面跟这个大差不差,我们启动完成后点击右侧生成按钮下的小铅笔图标,可以进入预设界面,选择“基础起手式”就能复制常用的提示词出来(这里的交互很奇怪不能直接使用,貌似新版有修复)。

正向提示词(引导 AI 生成的内容):

masterpiece, best quality,

负面提示词(不想出现的内容):

lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry

通常排在越前面的词权重越高,但也有改变权重的方式,可以通过加括号来实现,比如 () 即是加 1.1 倍的权重,(()) 则是加 1.2 倍权重,但这种方式其实不实用,我更推荐用 (girl:1.5)(boy:0.8) 这种方式加减权重,更直观些。

切换好模型,输入你的提示词,确定好采样方式与步数,点击生成按钮等待一小会,恭喜你,这样你就成功生成了一张图片~

如何使用 LCM 采样器

首先说明一下,使用 LCM 采样方式出图并非是最优解,而且它通常会降低生成图片的精度,在本项目中之所以默认使用该方式是基于出图速度考虑的。

首先需要用到一个 LCM-LoRA,网盘下载地址:https://pan.quark.cn/s/1c8658d18095

下载后放在 SD 目录下的 /models/Lora 目录中即可使用,另外整合包中已经集成了 LCM 采样器,无需额外安装(给菩萨磕头了🙏)。

我们选择好 LCM Sampler,并且将迭代步数 Steps 设置在 2 - 8 步以内,我这里设置了 5 步,然后将引导系数 CFG Scale 设置为 1,输入提示词:

<lora:LCM-LoRA_sd15:1>,1girl,

在 512 分辨率下我的垃圾显卡只花费了 10 秒(显卡性能参考 GTX1060 6G)就出图了,由于较小内存(仅8G)加上低速磁盘的原因,这个时间实际可能波动(有时 7 秒左右出图),如果显卡和配置更好的话,一两秒内出图也丝毫不夸张。

前面我们为了顺利出图把 CFG 拉到了最低,这会导致提示词相关性变弱,可能会对出图结果产生一些影响,把 CFG 拉回默认的 7 又会导致出图结果永远是几团色块,如何解决这个问题呢?此时就需要使用一款支持动态 CFG 的插件,在扩展中粘贴插件的 Github 仓库链接并安装后重启:

动态 CFG 插件:https://github.com/mcmonkeyprojects/sd-dynamic-thresholding

接下来我们就可以在下面使用这个插件了,设置参数可以参考下图进行调整,其它选项默认就好,调度器可以选择 Half Cosine Up 这个比较常用,也可以选择其它的。

现在我们已经可以正常出图了,而且出图速度也非常的快。

封装接口调用 API

前面我们已经学会了如何操作 SD 的 WebUI 进行出图,但我们不可能将这样一个 UI 界面完全抛给用户去使用,所以在实际开发中,就需要以调用 API 的形式来驾驭 SD 出图了。

要开启 API 功能需要修改配置文件,如果你使用启动器则可以在“高级设置”中直接找到这个选项并启动。

接着在 url 地址后面加上 /docs 即可打开内置的 API 文档:

这里面很多接口是用不到的,在本项目中只用到了以下两个接口:

  1. 文生图接口:/sdapi/v1/txt2img
  2. 进度查询接口:/sdapi/v1/progress

打开 txt2img 接口示例有一大堆参数看不懂怎么办?不要慌,大部分我也看不懂~不过我们只需要确定改变哪些属性,挑出来传参就好,像我目前的接口中就只用到这几个参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* @api {get} ai/text2Img 文生图接口
* @apiVersion 1.0.0
*
* @apiParam {String} width 图宽
* @apiParam {String} height 图高
* @apiParam {String} prompt 提示词
* @apiParam {String} steps 步数
*/
........
{
prompt: `<lora:LCM-LoRA_sd15:1>,masterpiece,best quality,${text}`,
negative_prompt: "ng_deepnegative_v1_75t,lowres,bad anatomy,bad hands,((((ugly)))), (((duplicate))), ((morbid)), ((mutilated)), [out of frame], extra fingers, mutated hands, ((poorly drawn hands)),text,error,missing fingers,extra digit,fewer digits,cropped,worst quality,low quality,normal quality,jpeg artifacts,signature,watermark,username,(((six toes, six fingers))),nsfw, nudity, explicit female nudity, weird legs, weird body,guro,((without cloth))",
seed: -1,
sampler_index: "LCM", // 采样器
steps: steps || 6, // 步数
width: width || 512, // 宽高
height: height || 512, // 宽高
}

其中种子 seed-1 就是随机“抽卡”,每张图片生成后会附带一个 seed,如果再次提交这个种子的话就相当于重绘,此时图像中的主体就不会发生太大改变。这些参数在接口返回结果的 info 字段中都可取得,不做过多展开。

这里我没有指定模型是因为 SD 每次启动都是默认加载上次使用过的模型来使用的,切换模型会非常耗时,显卡又完全没有富余的显存用于缓存模型,所以我根本没必要管这个参数。

前面我们还使用到了动态 CFG 的插件,要在 alwayson_scripts 这个字段中设置,由于每个插件接收的参数不同,并没有固定的规律,这里我也是卡了很久才研究出来怎么传值:

1
2
3
4
5
alwayson_scripts: {
"Dynamic Thresholding (CFG Scale Fix)": {
args: [true, 1, 100, "Half Cosine Up", 0, "Half Cosine Up", 1, 4.0],
},
}

我后端是 Node.js 开发,使用 express 框架,所以这里我安装了 axios 来向 SD 的 API 服务发起 http 请求,在文生图接口返回的 images 字段即为生成的图片结果,是一个数组,图片格式为 base64,由于我们一次只生成一张图,所以直接返回 images[0] 为图片结果:

1
2
res.setHeader("Content-Type", "image/png");
res.send(Buffer.from(SDres.images[0], "base64"));

这样一个文生图的接口就封装好了,调用该接口时传入必要参数,服务端再向 SD 发起请求并获取生成内容结果,将返回的 base64 转为 Buffer 二进制流,响应给客户端。

线上功能开发

文生图的应用场景最大的问题在于,不同的提示词所产生的结果可能截然不同,用户生成图片的上限取决于他运用提示词的功力,而用户生成图片的下限往往很难保证。

作为面向普罗大众的应用,降低交互门槛是必要的。

通常我们所使用的 Tags 都是英文,所以我这里提供了一键翻译文本的功能,在提示词输入框下面点击翻译按钮即可将中文转为英文(在服务端对接的是第三方公益性 API 接口,不能保证长期可用)。

而前面的模型选择小节已经讲过,我所选用的出图模型 MeinaMix 可以在少量关键词下依然生成出不错的图片,这也就极大地保证了下限。

为了让用户有更良好的体验,除了保证上下限以外还需要一点中庸的能力。这里我设计了一个“套用预设”的功能,实际上它的本质就是使用不同的 loRA,并且将一些验证可行的提示词拼接起来,这样用户在输入少量提示词或者完全不输入提示词的情况下,也可以通过“挑选”来出图。

1
2
3
4
5
6
7
[{
name: '龙年新春',
value: 'newYearDragon',
prompt: 'chibi, dragon',
ratio: '2_3',
tips: 'chibi(可爱)、 joining hands(合手拜年)、chinese clothes(喜庆服装)、gold coin(金币)、koi(鲤鱼)、lion dance(舞狮)'
}, ....... ]

界面设计

既然是 solo 项目,产品设计只能自己来了,这里我用在线画板工具大概画了一下界面:

虽然相当粗糙,但和最终成品差别嘛,只是少了亿点细节而已,作为一名成熟的前端开发要学会自行发挥。

开发界面1 开发界面2

Everyone all is PM ~

架构设计

下面讲一下迅排 AIGC 功能到底是如何做到在线出图的。

首先因为我是在本地跑图,本地主机没有公网 IP,所以不能被外部访问到,通常的做法是使用内网穿透方案,例如 frp 之类的,通过在本地主机与远端服务器建立某种连接,使得访问该服务器即可访问到本地主机。当然这个方案需要一定的学习成本,实际上我并没有采用,而是退而求其次,只在本地单向访问远程服务器,使用 Mysql 作为储存任务的介质,以此打通本地跑图在线出图的功能,具体流程如下所示:

当用户执行操作的时候,并不是直接访问到本地 SD 服务的,而是只能做查看/创建任务的动作。

而我本地机器的 Node 服务与云端的 Node 服务作用是完全不同的,本地机器上的 Node 才是真正负责调用 SD 的服务,而远程的 Node 服务则相当于一个“中央处理器”,负责的是任务的调度,这样做的是为了本地服务可以不断地扩展

就好比本地机器是外卖配送员,云服务器是外卖平台,配送员需要向平台发起请求后才能分配到任务,而当用户下单后也需要有配送员接单并送达订单才算完成。假设现在平台上同时有两单外卖,配送员就需要送完一户再去送另一户,如果有多名外卖员的时候,同时配送的效率自然就变高了,而且都是平台派单,就不会出现多名配送员去送同一份外卖的情况。

只要我在任何地方加一台机器跑图,线上并行出图的效率就能提高至两倍。

NSFW 图像风控

AI 有个比较尴尬的点在于生成图像可能会不合时宜,这对于一个在线应用来说可能不太优雅。虽然可以通过加入一些反向提示词来避免,但是这样只能干预 AI 自由发挥的结果,对于正向提示词实际没有影响。

如果阉割掉用户输入提示词的功能,会显得整个工具太过平庸。比较稳妥的做法只能是对提示词进行过滤,就跟敏感词变成 “*” 号的原理一样,但是这个工作量可不小。

最后我还是决定从结果图上做判断处理,使用了 nsfwjs 这个开源库,它基于 TensorFlow.js 训练模型,可以通过机器学习帮助我们识别不可描述的图像,把由机器学习所绘制的图像交给机器学习检测,这显然十分合理!

最终效果如下所示,当生成的图片过于不便展示时就会触发“眼狩令”,雷电将军会指着你说:图片违规,然后 没收这张图片。

nsfwjs 仅支持在浏览器中工作,但在我这个场景下等到前端验证生米都已经煮成熟饭了,所以我需要在 node 中完成这项工作,引入 @tensorflow/tfjs-node 作为依赖,然后将 nsfwjs 项目打包出来的 dist 文件导入使用,具体可以参考 issues 以及官方示例 demo

处理之后的结果是一个对象数组,有五个数值分别是:Drawing、Hentai、Neutral、Porn、Sexy,从字面上都非常好理解,其中 HentaiPorn 类型的图片是我不期望展示的,那么要如何判断呢?仔细观察会发现这五个数值的 probability 加起来刚好就是 1,所以可以理解为占比,那么我们的判断规则就是排在数组最前面的值如果是敏感类型并且占比很高,就判定为风险图片:

1
2
3
4
5
let isNoSafe = ["Hentai", "Porn"].includes(predictions[0].className);
if (isNoSafe) {
isNoSafe = predictions[0].probability > 0.7
}
console.log(isNoSafe ? "图片不安全" : "ok", predictions[0]);

不过,机器学习并不能 100% 准确地判定图像是否真的违规,可能误判也可能漏判,只能通过后期不断训练模型才能越来越准确,但要达到完美几乎不可能。

就是这样,所以请大家收起那些不太成熟的想法哦~ 如果生成了不健康的图片请私发我,我会带着批判的眼光审视的,不要肆意传播哦

SDXL

这里顺带提一嘴目前 SD 大模型已经更新到 SDXL(也有称之为 SD 2.0 的),SDXL 是一个 SD 的优化版本,参数量增加幅度达到了原本的 3 倍左右,生成图像在色彩、对比度、光线构图等方面都有质的提升,可以输出更逼真的图像,并且提示词也比以往要更短,甚至还弥补了原本 SD 不能在图片中生成清晰文本这个问题,未来可期!

同样 SDXL 也是一个革新版本,用到的所有模型及插件均与旧版不兼容,而且硬件需求也相对高了很多,经测试电脑的运行内存至少需要 30G 以上(否则虚拟内存拉满),所以我目前还是用的 SD 1.5 版本。

微信红包封面

我自己也是用 AI 做了一个微信红包封面,数量不多,由于只支持在公众号文章里发放,大家可以到我的公众号【品味前端】里回复“红包”,或点击下方链接扫码阅读,拉到文章底部即可~

欢迎在线体验

迅排 AIGC 功能是免费无限制生成图片的,不过为了避免资源滥用等问题,这里我还是限制了每个账号下最多只能保存 10 张图片,超出的话删除旧的图片再生成即可(其实也是因为懒得做分页了),喜欢的图片可以保存到本地。

下面这些图片也都是通过这款在线工具生成的,多多尝试更多不同的提示词来创建你的图像哦~

如果遇到长时间生成没有响应结果,其实图片已经生成完毕,只不过斯是陋室——我宿舍的垃圾网络经常会抽风导致图片没上传到服务器。。目前也没有对此类边界情况做处理。解决方案就是多点击生成几张,提高成功率。(等过完年再加点钱把“特别垃圾的网”升级为“比较垃圾的网”,到时这个情况可能会好一点。

后续有时间的话可能会继续开发图生图、图像超分(高清修复)、姿态控制及局部修改(ControlNet)等更高级的功能,到时有机会再做分享~

写这篇文章也是为了让大家了解到 SD 的强大,同时抛砖引玉展示一些自己浅薄的理解与实践。机器学习与人工智能是庞大的学问,作为一个自学的门外汉,也终究不过是管中窥豹,如发现文章中有错误还请不吝指出。

另外我最近也准备组建前端技术交流群,感兴趣的小伙伴可以加我进群一起交流进步,与 AIGC 相关的问题也可以讨论交流。

参考资料:

https://lilianweng.github.io/posts/2021-07-11-diffusion-models/

https://lilianweng.github.io/posts/2017-08-20-gan/