上一篇文章《AI作畫技術(shù)實(shí)踐第一期》提到用騰訊云智能能力如何實(shí)現(xiàn)簡(jiǎn)易版的AI畫畫,發(fā)布后受到大量網(wǎng)友關(guān)注,也在思考還能不能做出更好的效果。最近發(fā)現(xiàn)AI繪畫玩法在短視頻平臺(tái)也掀起了一波熱潮,結(jié)合在網(wǎng)上看到有一些很優(yōu)秀的AI畫畫模型,也想嘗試在上一篇的基礎(chǔ)上做出更好的體驗(yàn)效果。
接下來完整的分享下我的實(shí)踐過程,感興趣的朋友也可以嘗試。
1.實(shí)現(xiàn)思路
通過AI生成人像圖,然后調(diào)用騰訊云智能能力進(jìn)行人臉融合,最終生成一張效果比較好的人像圖。
1.1 詳細(xì)流程:
?文章來源:http://www.zghlxwxcb.cn/news/detail-485919.html
2.準(zhǔn)備工作
2.1 Stable-Diffusion部署
Stable Diffusion 是一個(gè)開源的文本轉(zhuǎn)圖像模型,可以通過輸入一段文字,生成一張符合語義的圖片。 具體可以看github的介紹:?GitHub - CompVis/stable-diffusion: A latent text-to-image diffusion model
按照文檔安裝,安裝過程大同小異, 不再贅述。
通過腳本的方式生成圖片:?
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")
指定關(guān)鍵詞,調(diào)用輸出,看下生成效果:?
python3 interface.py "*******" out
?
3.小程序demo實(shí)踐
下面是我通過小程序端來實(shí)現(xiàn)AI作畫的過程。
3.1 AI畫畫服務(wù)端:
模型部署好后只能本地執(zhí)行, 我們簡(jiǎn)單實(shí)現(xiàn)下功能:
一、用戶把任務(wù)提交到cos上,服務(wù)通過拉去cos的內(nèi)容來執(zhí)行AI畫畫任務(wù)。
二、通過執(zhí)行shell命令,并將生成好的圖片上傳到cos。
COS文檔:?對(duì)象存儲(chǔ)簡(jiǎn)介_對(duì)象存儲(chǔ)購買指南_對(duì)象存儲(chǔ)操作指南-騰訊云
AI畫畫模型執(zhí)行代碼:
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來實(shí)現(xiàn)任務(wù)管理,涉及到任務(wù)拉取和結(jié)果上傳, 以下是實(shí)現(xiàn)代碼:
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 小程序服務(wù)端:
小程序要通過中轉(zhuǎn)服務(wù)來異步處理消息,梳理一下server的功能:
一、轉(zhuǎn)發(fā)請(qǐng)求到AI畫畫。
二、查詢AI畫畫的結(jié)果。(通過cos中轉(zhuǎn))
以下是部分代碼:
協(xié)議相關(guān):
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"`
}
提交任務(wù):
// submitJobHandler 提交任務(wù)
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 查詢?nèi)蝿?wù)
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.小程序?qū)崿F(xiàn)AI畫畫:?
index.js
// index.js
// 獲取應(yīng)用實(shí)例
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
},
// 事件處理函數(shù)
bindViewTap() {
wx.navigateTo({
url: '../logs/logs'
})
},
onLoad() {
if (wx.getUserProfile) {
this.setData({
canIUseGetUserProfile: true
})
}
this.onTimeout();
},
getUserProfile(e) {
// 推薦使用wx.getUserProfile獲取用戶信息,開發(fā)者每次通過該接口獲取用戶個(gè)人信息均需用戶確認(rèn),開發(fā)者妥善保管用戶快速填寫的頭像昵稱,避免重復(fù)彈窗
wx.getUserProfile({
desc: '展示用戶信息', // 聲明獲取用戶個(gè)人信息后的用途,后續(xù)會(huì)展示在彈窗中,請(qǐng)謹(jǐn)慎填寫
success: (res) => {
console.log(res)
this.setData({
userInfo: res.userInfo,
hasUserInfo: true
})
}
})
},
getUserInfo(e) {
// 不推薦使用getUserInfo獲取用戶信息,預(yù)計(jì)自2021年4月13日起,getUserInfo將不再彈出彈窗,并直接返回匿名的用戶個(gè)人信息
console.log(e)
this.setData({
userInfo: e.detail.userInfo,
hasUserInfo: true
})
},
enentloop() {
var that = this
if (!that.data.Resp || !that.data.Resp.job_id) {
console.log("not found jobid")
return
}
return new Promise(function(yes, no) {
wx.request({
url: 'http://127.0.0.1:8000/frontend/query',
data: {
"session_id": "123",
"job_id": that.data.Resp.job_id
},
method: "POST",
header: {
'Content-Type': "application/json"
},
success (res) {
yes("hello");
if (res.data == null) {
wx.showToast({
icon: "error",
title: '請(qǐng)求查詢失敗',
})
return
}
console.log(Date.parse(new Date()), res.data)
that.setData({
Job: res.data,
})
console.log("job_status: ", res.data.job_status)
if (res.data.job_status === "FINISNED") {
console.log("draw image: ", res.data.result_url)
that.drawInputImage(res.data.result_url);
that.setData({
Resp: {},
taskStatus: "STOP"
})
} else {
that.setData({
taskStatus: "PROCESSING",
processTime: (Date.parse(new Date()) - that.data.beginTime)/ 1000
})
}
},
fail(res) {
wx.showToast({
icon: "error",
title: '請(qǐng)求查詢失敗',
})
console.log(res)
}
})
})
},
onTimeout: function() {
// 開啟定時(shí)器
var that = this;
let ticker = setTimeout(async function() {
console.log("begin")
await that.enentloop();
console.log("end")
that.onTimeout();
}, 3 * 1000); // 毫秒數(shù)
// clearTimeout(ticker);
that.setData({
ticker: ticker
});
},
imageDraw() {
var that = this
var opt = {}
if (that.data.option && that.data.option.length > 0) {
opt = {
"tags": that.data.option
}
}
console.log("option:", opt)
wx.request({
url: 'http://127.0.0.1:8000/frontend/create',
data: {
"prompt": that.data.inputValue
},
method: "POST",
header: {
'Content-Type': "application/json"
},
success (res) {
if (res.data == null) {
wx.showToast({
icon: "error",
title: '請(qǐng)求失敗',
})
return
}
console.log(res.data)
// let raw = JSON.parse(res.data)
that.setData({
Resp: res.data,
})
that.setData({
totalTask: res.data.total_cnt,
beginTime: Date.parse(new Date())
})
},
fail(res) {
wx.showToast({
icon: "error",
title: '請(qǐng)求失敗',
})
}
})
},
drawInputImage: function(url) {
var that = this;
console.log("result_url: ", url)
let resUrl = url; // that.data.Job.result_url;
wx.downloadFile({
url: resUrl,
success: function(res) {
var imagePath = res.tempFilePath
wx.getImageInfo({
src: imagePath,
success: function(res) {
wx.createSelectorQuery()
.select('#input_canvas') // 在 WXML 中填入的 id
.fields({ node: true, size: true })
.exec((r) => {
// Canvas 對(duì)象
const canvas = r[0].node
// 渲染上下文
const ctx = canvas.getContext('2d')
// Canvas 畫布的實(shí)際繪制寬高
const width = r[0].width
const height = r[0].height
// 初始化畫布大小
const dpr = wx.getWindowInfo().pixelRatio
canvas.width = width * dpr
canvas.height = height * dpr
ctx.scale(dpr, dpr)
ctx.clearRect(0, 0, width, height)
let radio = height / res.height
console.log("radio:", radio)
const img = canvas.createImage()
var x = width / 2 - (res.width * radio / 2)
img.src = imagePath
img.onload = function() {
ctx.drawImage(img, x, 0, res.width * radio, res.height * radio)
}
})
}
})
}
})
},
handlerInput(e) {
this.setData({
inputValue: e.detail.value
})
},
handlerSearch(e) {
console.log("input: ", this.data.inputValue)
if (this.data.inputValue.length == 0) {
wx.showToast({
icon: "error",
title: '請(qǐng)輸入你的創(chuàng)意 ',
})
return
}
this.imageDraw()
},
handlerInputPos(e) {
console.log(e)
this.setData({
inputValue: e.detail.value
})
},
handlerInputFusion(e) {
console.log(e)
this.setData({
inputUrl: e.detail.value
})
},
handlerInputImage(e) {
console.log(e)
},
clickItem(e) {
let $bean = e.currentTarget.dataset
console.log(e)
console.log("value: ", $bean.bean)
this.setData({
option: $bean.bean
})
this.imageDraw()
}
})
index.wxml:
<view class="container" style="width: 750rpx; height: 1229rpx; display: flex; box-sizing: border-box">
<div class="form-item" style="width: 673rpx; height: 70rpx; display: block; box-sizing: border-box">
<input placeholder="寫下你的創(chuàng)意" class="input" bindinput="handlerInput" />
<input placeholder="待融合URL" class="input" bindinput="handlerInputFusion" />
<button class="button" loading="{{buttonStatus}}" bindtap="handlerSearch" size="mini" style="width: 158rpx; height: 123rpx; display: block; box-sizing: border-box; left: 0rpx; top: -60rpx; position: relative"> 立即生成 </button>
</div>
<view class="text_box">
<text class="text_line" style="position: relative; left: 18rpx; top: 0rpx">完成任務(wù)數(shù):</text>
<text class="text_line" style="position: relative; left: 8rpx; top: 0rpx">{{totalTask}},</text>
<text class="text_line" style="position: relative; left: 38rpx; top: 0rpx">{{taskStatus}}</text>
<text class="text_line" style="position: relative; left: 43rpx; top: 0rpx">{{processTime}}/{{leftTime}}s</text>
</view>
<view class="output_line" style="position: relative; left: 2rpx; top: 51rpx; width: 714rpx; height: 40rpx; display: flex; box-sizing: border-box">
<text class="text_line" style="width: 199rpx; height: 0rpx; display: block; box-sizing: border-box; position: relative; left: 1rpx; top: -92rpx">作品圖片</text>
<view style="position: relative; left: -15rpx; top: 2rpx; width: 571rpx; height: 0rpx; display: block; box-sizing: border-box"></view>
</view>
<canvas type="2d" id="input_canvas" style="background: rgb(228, 228, 225); width: 673rpx; height: 715rpx; position: relative; left: 2rpx; top: -64rpx; display: block; box-sizing: border-box">
</canvas>
<view class="output_line" style="position: relative; left: 0rpx; top: 50rpx; width: 714rpx; height: 58rpx; display: flex; box-sizing: border-box">
</view>
</view>
到這里就實(shí)現(xiàn)了一個(gè)AI畫畫小程序。接下來看下效果, 通過輸入關(guān)鍵字可以得到作品圖:?
?
新的問題來了, 測(cè)試下來, 發(fā)現(xiàn)AI模型直接生成的圖, 人臉部分不太理想, 如下圖所示:
?
如何使得人像更加自然呢? 我調(diào)研了市面上現(xiàn)有的AI能力, 發(fā)現(xiàn)騰訊云AI的人臉融合可以實(shí)現(xiàn)換臉功能, 下面看下具體介紹。
3.4. 人臉融合
3.4.1 人臉融合介紹
?
3.5.2 融合功能演示:
???????
?
3.4.3 融合控制臺(tái):
用于創(chuàng)建活動(dòng)和素材。?
?
3.4.4 素材管理:
?
添加素材即可:
這里的素材指的就是我們通過AI生成的圖, 下面看下效果。
3.4.5 驗(yàn)證AI畫畫+融合效果
我們將上述有問題的圖片上傳到圖片融合的DEMO頁, 我們做一次圖片人臉融合,發(fā)現(xiàn)效果相當(dāng)驚艷:
?
下面是正常換臉效果:
?
基于上述結(jié)果, 結(jié)合我們的使用場(chǎng)景, 我們可以在現(xiàn)有的AI畫畫基礎(chǔ)上增加騰訊云圖片融合的能力。
3.5 小程序增加融合效果:
我們?cè)谠瓉淼牧鞒袒A(chǔ)上增加融合的步驟,下面是具體流程:?
3.5.1 大概思路:?
?
3.5.2 詳細(xì)流程:?
增加人臉融合的操作。
?
3.5.3 服務(wù)端增加人臉融合處理接口:
在小程序服務(wù)端增加融合的任務(wù)處理:?
// facefusionHandler ...
func facefusionHandler(writer http.ResponseWriter, request *http.Request) {
body, err := io.ReadAll(request.Body)
req := &Request{}
err = json.Unmarshal(body, &req)
if err != nil {
panic(err)
}
ret := &Response{
SessionId: req.SessionId,
// 將AI畫畫的圖上傳至素材管理, 并和輸入圖做融合
ResultUrl: rawCloud(req.ModelUrl, req.ImageUrl),
}
data, _ := json.Marshal(ret)
writer.Write(data)
}
將AI畫畫的圖上傳到素材管理,一般需要在控制臺(tái)執(zhí)行, 我這里直接通過API來調(diào)用, 需要手寫V3簽名, 代碼就不貼了, 感興趣的可以在這里看下。
3.5.4 小程序端增加融合后置任務(wù):
小程序端在拿到AI畫畫后的圖, 根據(jù)需要走一遍融合操作。
facefusion(modelUrl, imageUrl) {
var that = this;
that.setData({
taskStatus: "融合中...",
processTime: (Date.parse(new Date()) - that.data.beginTime)/ 1000
})
wx.request({
url: 'http://127.0.0.1:8000/frontend/fusion',
data: {
"session_id": "123",
"model_url": modelUrl,
"image_url": imageUrl
},
method: "POST",
header: {
'Content-Type': "application/json"
},
success (res) {
if (res.data == null) {
wx.showToast({
icon: "error",
title: '請(qǐng)求融合失敗',
})
return
}
if (res.data.result_url !== "") {
console.log("draw image: ", res.data.result_url)
that.drawInputImage(res.data.result_url);
that.setData({
Resp: {}
})
that.setData({
taskStatus: "STOP"
})
// clearTimeout(that.data.ticker);
} else {
that.setData({
taskStatus: "PROCESSING",
processTime: (Date.parse(new Date()) - that.data.beginTime)/ 1000
})
}
// a portrait of an old coal miner in 19th century, beautiful painting with highly detailed face by greg rutkowski and magali villanueve
},
fail(res) {
wx.showToast({
icon: "error",
title: '請(qǐng)求融合失敗',
})
console.log(res)
}
})
},
編譯啟動(dòng), 任務(wù)狀態(tài)這里會(huì)增加“融合中”的狀態(tài):
?
看下前后對(duì)比, 這是AI生成的圖:
經(jīng)過融合后的圖:
優(yōu)化了下界面, 看看最終版::?
???????
?
總結(jié)
至此,就實(shí)現(xiàn)了一個(gè)AI畫畫+人像融合的demo, 二者搭配使用,可以生成更好的人臉效果, 也可以自己組織比較好的prompt來生成比較好的人像圖。 在huggingface上有很多模型和關(guān)鍵詞值得探索, 本期先介紹到這里了。文章來源地址http://www.zghlxwxcb.cn/news/detail-485919.html
到了這里,關(guān)于AI作畫技術(shù)實(shí)踐第二期|用騰訊云智能圖片融合優(yōu)化AI繪畫的效果的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!