點(diǎn)個(gè)關(guān)注??跟騰訊工程師學(xué)技術(shù)
最近看到一篇有趣的文章,一副名為《太空歌劇院》(如下圖)的藝術(shù)品在某美術(shù)比賽上,獲得了第一名的成績, 有意思的是這件作品是通過AI來實(shí)現(xiàn)的畫作, 頓時(shí)覺得非常神奇。結(jié)合近期科技媒體頻頻報(bào)道的AI作畫爆火現(xiàn)象,深入了解了下,發(fā)現(xiàn)市面上有一些AI作畫的小程序, 是通過輸入一段文字給AI, 然后輸出一副和文字意思相近的圖片。這個(gè)感覺非常有意思,某種程度上會(huì)給繪畫行業(yè)帶來新的發(fā)展契機(jī)。
那如果自己想實(shí)現(xiàn)一個(gè)類似的小程序,該如何做呢?下面詳細(xì)分享下我的思考實(shí)踐過程。
實(shí)現(xiàn)思路
目前看到的AI畫畫的基本流程如下:輸入文本-〉選擇風(fēng)格化(油畫/素描/動(dòng)漫等等)-〉生成圖片。根據(jù)實(shí)際體驗(yàn), 很多小程序其實(shí)是在現(xiàn)有的實(shí)景圖片基礎(chǔ)上,做了一層風(fēng)格化的后置處理,效果主要體現(xiàn)在以下兩點(diǎn)
文字和圖片的匹配度。
圖片的風(fēng)格化效果。
根據(jù)這兩點(diǎn)來思考, 如果需要實(shí)現(xiàn)一個(gè)類似的功能, 我們需要維護(hù)一個(gè)圖庫,并通過AI提取圖片標(biāo)簽,映射圖片和標(biāo)簽的關(guān)系,如下圖:
上述的圖庫模塊,主要是圖片和文字的映射,可以通過騰訊云的圖像標(biāo)簽來提取入庫, 這個(gè)過程有點(diǎn)類似于搜索引擎的圖片搜索,通過文字匹配圖片。常用的搜索引擎(搜狗,百度,谷歌)都有類似的功能,只不過都是網(wǎng)圖,不過也沒關(guān)系, 我們可以通過現(xiàn)有的搜索引擎的能力快速驗(yàn)證下效果,簡化一下流程如下:
基本方案確定, 下面詳細(xì)描述下實(shí)現(xiàn)過程。
準(zhǔn)備工作
(一)文字搜圖
通過文字生成意思相近的圖片,發(fā)現(xiàn)搜狗有現(xiàn)成的能力,可以通過輸入文字或圖片,獲取匹配度比較高的網(wǎng)圖:
通過輸入關(guān)鍵字,分析下接口調(diào)用:
直接調(diào)用下接口, 就可以拿到對(duì)應(yīng)的圖片url:
(二)圖像風(fēng)格化
好了, 現(xiàn)在有數(shù)據(jù)源了, 我們先主要針對(duì)人物進(jìn)行風(fēng)格化處理, 調(diào)研一番,發(fā)現(xiàn)騰訊云官網(wǎng)有針對(duì)人像動(dòng)漫畫的處理,看下描述可以滿足需求:
官網(wǎng)效果:
開通服務(wù)后,會(huì)贈(zèng)送1000次的資源包:
控制臺(tái)調(diào)用查看:
SDK調(diào)用:
我們使用golang來開發(fā), 獲取下依賴庫:
go get github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common
go get github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ft
使用接口調(diào)用服務(wù):
credential := common.NewCredential(
"***",
"***",
)
cpf := profile.NewClientProfile()
client, err := ft.NewClient(credential, "ap-guangzhou", cpf)
if err != nil {
log.Errorf("NewClient, err=%+v", err)
return nil, err
}
req := ft.NewFaceCartoonPicRequest()
// 輸入圖URL
req.Url = common.StringPtr(imageUrl)
// 返回結(jié)果URL
req.RspImgType = common.StringPtr("url")
resp, err := client.FaceCartoonPic(req)
if err != nil {
log.Errorf("FaceCartoonPic, err=%+v", err)
return nil, err
}
小程序上實(shí)現(xiàn)AI畫畫
(一)服務(wù)端-搜狗API封裝
// Response 搜狗API返回結(jié)構(gòu)
type Response struct {
Status int `json:"status"`
Info string `json:"info"`
Data struct {
AdPic []struct {
DocID string `json:"docId"`
Index int `json:"index"`
Mfid string `json:"mfid"`
ThumbHeight int `json:"thumbHeight"`
ThumbWidth int `json:"thumbWidth"`
} `json:"adPic"`
BlackLevel int `json:"blackLevel"`
CacheDocNum int `json:"cacheDocNum"`
HasPicsetRes int `json:"hasPicsetRes"`
HintWords []string `json:"hintWords"`
IsQcResult string `json:"isQcResult"`
IsStrongStyle int `json:"is_strong_style"`
Items []struct {
Anchor2 []string `json:"anchor2"`
Author string `json:"author"`
AuthorName string `json:"author_name"`
AuthorPageurl string `json:"author_pageurl"`
AuthorPicurl string `json:"author_picurl"`
AuthorThumbURL string `json:"author_thumbUrl"`
AuthorThumbMfid string `json:"author_thumb_mfid"`
Biaoqing int `json:"biaoqing"`
ChSiteName string `json:"ch_site_name"`
CutBoardInputSkin string `json:"cutBoardInputSkin"`
DocID string `json:"docId"`
Docidx int `json:"docidx"`
Gifpic int `json:"gifpic"`
Grouppic int `json:"grouppic"`
Height int `json:"height"`
HTTPSConvert int `json:"https_convert"`
Index int `json:"index"`
LastModified string `json:"lastModified"`
LikeNum string `json:"like_num"`
Link string `json:"link"`
LocImageLink string `json:"locImageLink"`
MfID string `json:"mf_id"`
Mood string `json:"mood"`
Name string `json:"name"`
OriPicURL string `json:"oriPicUrl"`
PainterYear string `json:"painter_year"`
PicURL string `json:"picUrl"`
Publishmodified string `json:"publishmodified"`
Size int `json:"size"`
Summarytype string `json:"summarytype"`
ThumbHeight int `json:"thumbHeight"`
ThumbURL string `json:"thumbUrl"`
ThumbWidth int `json:"thumbWidth"`
Title string `json:"title"`
Type string `json:"type,omitempty"`
URL string `json:"url"`
WapLink string `json:"wapLink"`
Width int `json:"width"`
Scale float64 `json:"scale"`
Did int `json:"did"`
ImgTag string `json:"imgTag"`
BgColor string `json:"bgColor,omitempty"`
ImgDefaultURL string `json:"imgDefaultUrl"`
} `json:"items"`
MaxEnd int `json:"maxEnd"`
NextPage string `json:"next-page"`
PainterDocCount int `json:"painter_doc_count"`
Parity string `json:"parity"`
PoliticFilterNum int `json:"politicFilterNum"`
PoliticLevel int `json:"politicLevel"`
QoInfo string `json:"qo_info"`
QueryCorrection string `json:"queryCorrection"`
ShopQuery string `json:"shopQuery"`
Tag [][]string `json:"tag"`
TagWords []string `json:"tagWords"`
TagWordsFeed []string `json:"tagWords_feed"`
TagFeed [][]string `json:"tag_feed"`
TotalItems int `json:"totalItems"`
TotalNum int `json:"totalNum"`
UUID string `json:"uuid"`
ColorList []struct {
Class string `json:"class"`
Name string `json:"name"`
Mood int `json:"mood"`
Stype string `json:"stype"`
} `json:"colorList"`
Query string `json:"query"`
HintList []struct {
LinkURL string `json:"linkUrl"`
Text string `json:"text"`
} `json:"hintList"`
TagList []struct {
Key string `json:"key"`
Value string `json:"value"`
Active bool `json:"active"`
} `json:"tagList"`
} `json:"data"`
}
type Option struct {
Tags []string `json:"tags"`
}
// Search ...
func Search(ctx context.Context, keywords, option string) (*Response, error) {
// https://pic.sogou.com/pics
// 關(guān)鍵詞搜索
// https://pic.sogou.com/napi/pc/searchList?mode=1&start=48&xml_len=48&query=%E7%BE%8E%E5%A5%B3
// tag過濾搜索
// https://pic.sogou.com/napi/pc/searchList?mode=1&tagQSign=壁紙,d24f3a88|楊冪,645d0d1a&start=0&xml_len=48&query=迪麗熱巴
params := url.Values{}
params.Set("mode", "1")
params.Set("start", "0")
params.Set("xml_len", "48")
params.Set("query", keywords)
if len(option) != 0 {
opt := &Option{}
err := json.Unmarshal([]byte(option), &opt)
if err == nil {
tags := ""
for i := 0; i < len(opt.Tags)-1; i += 2 {
tags += opt.Tags[i] + "," + opt.Tags[i+1]
if i == len(opt.Tags)-2 {
tags += "|"
}
}
params.Set("tagQSign", tags)
}
}
uri := "https://pic.sogou.com/napi/pc/searchList"
address, err := url.Parse(uri)
if err != nil {
return nil, err
}
address.RawQuery = params.Encode()
request, err := http.NewRequestWithContext(ctx, http.MethodGet, address.String(), nil)
if err != nil {
log.Errorf("NewRequestWithContext error, %+v", err)
return nil, err
}
resp, err := http.DefaultClient.Do(request)
if err != nil {
log.Errorf"http do error, %+v", err)
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Errorf("Query, request read body failed, %+v", err)
return nil, err
}
rsp := &Response{}
err = json.Unmarshal(body, &rsp)
if err != nil {
panic(err)
}
return rsp, nil
}
(二)?服務(wù)端-動(dòng)漫畫接口
參考上述sdk代碼
func FaceCartoonPicPro(ctx context.Context, imageUrl string, tp int) ([]byte, error) {
credential := common.NewCredential(
"***",
"***",
)
cpf := profile.NewClientProfile()
client, err := ft.NewClient(credential, "ap-guangzhou", cpf)
if err != nil {
log.Errorf("NewClient, err=%+v", err)
return nil, err
}
req := ft.NewFaceCartoonPicRequest()
req.Url = common.StringPtr(imageUrl)
req.RspImgType = common.StringPtr("url")
resp, err := client.FaceCartoonPic(req)
if err != nil {
log.Errorf(""FaceCartoonPic, err=%+v", err)
return nil, err
}
return []byte(*resp.Response.ResultUrl), nil
}
(三)服務(wù)端-小程序請(qǐng)求接口封裝
小程序使用http協(xié)議訪問, 這里提供一個(gè)http服務(wù), 邏輯上分為兩步:一、search,通過文字匹配圖片。二、風(fēng)格化,通過騰訊云AI能力, 融合圖片。協(xié)議:
message SearchImageReq {
string text = 1; // 關(guān)鍵字
string option_json = 2; // tag信息, 搜狗API使用
}
message Result {
string ori_url = 1; // 原始圖
string res_url = 2; // 風(fēng)格化后的圖
}
message SearchImageRsp {
int64 error_code = 1;
string error_msg = 2;
repeated Result result_url_list = 3;
string raw_body = 4; // 原始包體
}
邏輯代碼:
// SearchImage ...
func SearchImage(ctx context.Context, req *pb.SearchImageReq, rsp *pb.SearchImageRsp) (err error) {
rsp.ErrorCode = 1
if len(strings.TrimSpace(req.Text)) == 0 {
rsp.ErrorCode = -1
return nil
}
resp, err := Search(ctx, req.Text, req.OptionJson)
if err != nil {
rsp.ErrorCode = -2
log.Errorf("Search Error : %+v", err)
return nil
}
ret := make([]string, 0)
for _, v := range resp.Data.Items {
ret = append(ret, v.OriPicURL)
}
raw, _ := json.Marshal(resp)
rsp.RawBody = string(raw)
// 只要成功了就直接返回
success := false
for _, v := range ret {
var changeUrl string
if !success {
resUrl, err := FaceCartoonPicPro(ctx, v)
if err == nil {
success = true
}
changeUrl = string(resUrl)
}
rsp.ResultUrlList = append(rsp.ResultUrlList, &pb.Result{
OriUrl: v,
ResUrl: changeUrl,
})
}
return nil
}
啟動(dòng)http服務(wù):
http.HandleFunc("/SearchImage", func(writer http.ResponseWriter, r *http.Request) {
data, _ := ioutil.ReadAll(r.Body)
req := &pb.SearchImageReq{}
_ = json.Unmarshal(data, &req)
rsp := &pb.SearchImageRsp{}
_ = SearchImage(context.Background(), req, rsp)
body, _ := json.Marshal(rsp)
writer.Write(body)
})
http.ListenAndServe("127.0.0.1:8080", nil)
使用curl調(diào)用看下效果:
curl --location --request POST 'http://127.0.0.1:8080/SearchImage' --header 'Content-Type: application/json' --data '{"text":"艾薇兒"}' | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 115k 0 115k 100 38 14765 4 0:00:09 0:00:08 0:00:01 31189
{
"error_code": "1",
"error_msg": "",
"result_url_list": [
{
"ori_url": "http://a0.att.hudong.com/67/37/01300000569933126015378092926.jpg",
"res_url": ""
},
{
"ori_url": "http://i2.hdslb.com/bfs/archive/24c06671653c74e9de14e0bab4bf2107bd97e5f1.png",
"res_url": "https://faceeffect-1254418846.cos.ap-guangzhou.myqcloud.com/ft/FaceCartoonPic/1253534368/ed046d5d-fb87-4c38-bcb3-6cbb4595e3cf"
},
{
"ori_url": "http://b-ssl.duitang.com/uploads/blog/201404/04/20140404200234_3xXzr.jpeg",
"res_url": ""
},
{
"ori_url": "http://img0.pclady.com.cn/pclady/1607/14/1544487_1535933_1216188_TUNGSTAR4871543.jpg",
"res_url": ""
}
]
}
效果還不錯(cuò):
(四)小程序-demo
下載微信開發(fā)者工具, 創(chuàng)建項(xiàng)目:
index.wxml
<view class="container" >
<div class="form-item" style="width: 673rpx; height: 70rpx; display: block; box-sizing: border-box">
<input placeholder="寫下你的創(chuàng)意" class="input" bindinput="handlerInput" />
<button class="button" loading="{{buttonStatus}}" bindtap="handlerSearch" size="mini" style="width: 158rpx; height: 64rpx; display: block; box-sizing: border-box; left: 0rpx; top: 0rpx; position: relative"> 立即生成 </button>
</div>
<view class="text_box">
<text class="text_line">關(guān)鍵詞</text>
</view>
<view class="view_line">
<view class="hot_txt" wx:for="{{tags}}" wx:key="histxt">
<view bindtap="clickItem" data-bean="{{item}}">
<view>{{item[0]}}</view>
</view>
</view>
</view>
<view class="output_line" style="position: relative; left: 0rpx; top: 50rpx; width: 714rpx; height: 58rpx; display: flex; box-sizing: border-box">
<text class="text_line" style="width: 99rpx; height: 30rpx; display: block; box-sizing: border-box; position: relative; left: 9rpx; top: -9rpx">作品圖</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: 700rpx; position: relative; left: 2rpx; top: 80rpx; display: block; box-sizing: border-box">
</canvas>
</view>
index.js
// index.js
// 獲取應(yīng)用實(shí)例
const app = getApp()
Page({
data: {
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
})
}
},
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
})
},
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:8080/SearchImage',
data: {
"text": that.data.inputValue,
"option_json": JSON.stringify(opt)
},
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)
that.setData({
Resp: res.data,
})
let raw = JSON.parse(res.data.raw_body)
console.log("raw: ", raw)
console.log("tagWords: ", raw.data.tagWords)
let tags = []
for (let v in raw.data.tagWords) {
if (v >= 9) {
break
}
tags.push({
value: raw.data.tagWords[v]
})
}
that.setData({
tags: raw.data.tag,
tagWords: tags
})
that.drawInputImage()
},
fail(res) {
wx.showToast({
icon: "error",
title: '請(qǐng)求失敗',
})
}
})
},
drawInputImage: function() {
var that = this;
console.log("resp: ", that.data.Resp)
let resUrl = ""
for (let v in that.data.Resp.result_url_list) {
let item = that.data.Resp.result_url_list[v]
// console.log("item: ", v, item)
if (item.res_url.length !== 0) {
console.log(item.res_url)
resUrl = item.res_url
break
}
}
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
})
},
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()
}
})
運(yùn)行:
檢索下關(guān)鍵字:
關(guān)鍵詞過濾, 點(diǎn)擊標(biāo)簽可以二次搜索:
至此,就實(shí)現(xiàn)了一個(gè)簡單的AI畫畫的demo, 后面可以自行構(gòu)造質(zhì)量更高的圖庫,通過打標(biāo)簽的方式來管理,然后通過輸入的關(guān)鍵字,搭配騰訊云AI的多種風(fēng)格化,來實(shí)現(xiàn)更多樣的效果。
推薦閱讀
騰訊杰出科學(xué)家劉威:多媒體AI技術(shù)如何讓廣告系統(tǒng)更“智能”?
快收藏!超強(qiáng)圖解Docker常見命令與實(shí)戰(zhàn)!
Web3時(shí)代來了!應(yīng)用架構(gòu)大變化
萬字好文:從無棧協(xié)程到C++異步框架!
點(diǎn)擊下方空白 ▼ 查看明日開發(fā)者黃歷
summer
time
2022
/
07.23文章來源:http://www.zghlxwxcb.cn/news/detail-492499.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-492499.html
到了這里,關(guān)于快收藏!手把手教你用AI繪畫的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!