当飞桨PaddleHub遇到微信小程序,AI也能指物作诗
项目构想
最近飞桨PaddleHub大火,吸引无数开发者眼球,作为俗人的我也不例外,被看图写诗和艺术风格迁移两个模型吸引。假象一下,如果把AI和指物作诗的方仲永、江南四大才子之首的唐伯虎结合,会是啥样子?
在项目实施前,先给小伙伴们稍稍科普下本文中涉及的主人公。
- 方仲永
金溪民方仲永,世隶耕。仲永生五年,未尝识书具,忽啼求之。父异焉,借旁近与之,即书诗四句,并自为其名。其诗以养父母、收族为意,传一乡秀才观之。自是指物作诗立就,其文理皆有可观者。
- 唐伯虎
唐寅,出生于明朝庚寅年,故名唐寅,因排行老大,又称唐伯虎。二十五岁之前的唐伯虎,过着读书、游历、吟诗作画的单纯生活。据说,他画的虾,往水里一丢,就好像全都变得鲜活了。不仅如此,唐伯虎更擅长画山水和工笔人物,尤其是仕女。许多名家都称赞他的画法潇洒飘逸,冠绝一时。
我选择使用PaddleHub的 文本生成 — 看图写诗(reading_pictures_writing_poems) module,实现如金溪方仲永指物作诗、出口成章的效果;使用PaddleHub的 图像生成 — 艺术风格迁移(stylepro_artistic) module,实现看到唐伯虎的背影并达到他的画画效果。
项目地址:
https://aistudio.baidu.com/aistudio/projectdetail/920580
PaddleHub module地址:
https://www.paddlepaddle.org.cn/hublist?filter=en_category&value=ImageClassification
项目组成
二、
整个项目分3个部分:
- PaddleHub Serving服务,由于需要进行推理,需要内存较大或能力突出的显卡支持。
- 后台服务,使用flask提供后台进行数据输入预处理,以及serving处理后的资源调度等。
- 微信小程序,负责微信模块展示 。
项目效果
扫描下方二维码即可体验(我会持续优化和完善,欢迎PaddleHub爱好者一同交流)
1. PaddleHub体验小程序效果:
选择模型效果→上传图片(等待20s)→ 输出AI写诗结果。
2、开发后台效果
详细代码解析
二、
1. PaddleHub Serving服务
hub serving start --config config.json
(base) root@iZ2ze74yt1daioe0s04o9kZ:~/paddlehubweixinbox# cat config.json
{
"modules_info": {
"stylepro_artistic": {
"init_args": {
"version": "1.0.0"
},
"predict_args": {
"use_gpu": false
}
},
"reading_pictures_writing_poems": {
"init_args": {
"version": "1.0.0"
},
"predict_args": {
"use_gpu": false
}
}
},
"port": 8866,
"use_multiprocess": false,
"workers": 1
}
该命令启用上述的两个PaddleHub module serving服务。
2. Web后台
Web后台的作用一是提供看图写诗图片上传、处理,生成的诗返回;二是提供风格画图片上传、风格选择、处理,生成风格画文件路径返回。
代码如下所示:
- 看图写诗
# coding:utf-8
# author: Livingbody
# date: 2020.05.06
from flask import Flask, render_template, request, jsonify, Response
from werkzeug.utils import secure_filename
import os
import requests
import paddlehub as hub
import cv2
import time
from flask import Blueprint, render_template
import requests
import json
import cv2
import base64
from flask import json
index_reading_pictures = Blueprint("reading_pictures", __name__)
# 设置允许的文件格式
ALLOWED_EXTENSIONS = set(['png', 'jpg', 'bmp', 'jpeg'])
# 当前文件所在路径
basepath = os.path.dirname(__file__)
def cv2_to_base64(image):
data = cv2.imencode('.jpg', image)[1]
return base64.b64encode(data.tostring()).decode('utf8')
def allowed_file(filename):
filename = filename.lower()
return '.' in filename and filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS
# 上传并写诗
@index_reading_pictures.route('/reading_pictures', methods=['POST', 'GET']) # 添加路由
def upload():
print(request)
if request.method == 'POST':
try:
f = request.files['file']
print(f.filename)
if not (f and allowed_file(f.filename)):
# return jsonify({"error": 1001, "msg": "请检查上传的图片类型,仅限于png、jpg、bmp"})
return render_template('404.html')
sourcefile = os.path.join('static/images/source', secure_filename(f.filename))
print('sourcefile: %s' % sourcefile)
upload_path = os.path.join(basepath, sourcefile) # 注意:没有的文件夹一定要先创建,不然会提示没有该路径
f.save(upload_path)
print(upload_path)
print('upload_path: %s' % upload_path)
results = reading_pictures(sourcefile)
headers = {"Content-type": "application/json", "charset": "gbk"}
# results: [{'Poetrys': '山隈山坳山,海滨岭颠海。中有无底渊,千古不可改。'}]
# return Response(json.dumps(results), content_type='application/json')
return jsonify(results)
except Exception:
return render_template('404.html')
return render_template('poem.html')
# 读取图片
def reading_pictures(upload_path):
print('upload_path: %s' % upload_path)
# 指定图片分割方法为deeplabv3p_xception65_humanseg并发送post请求
data = {'images': [cv2_to_base64(cv2.imread(upload_path))]}
headers = {"Content-type": "application/json"}
url = "http://localhost:8861/predict/reading_pictures_writing_poems"
r = requests.post(url=url, headers=headers, data=json.dumps(data))
print('request: %s' % r)
t = time.time()
results = r.json()["results"]
number = results[0]['Poetrys'].count("。") - 1
results[0]['Poetrys'] = results[0]['Poetrys'].replace("。", "。n", number)
print('results: %s' % results)
return results
- 风格画
# coding:utf-8
# author: Livingbody
# date: 2020.05.06
from flask import Flask, render_template, request, jsonify, Response
from werkzeug.utils import secure_filename
import os
import requests
import paddlehub as hub
import cv2
import time
import numpy as np
from flask import Blueprint, render_template
import requests
import json
import cv2
import base64
from flask import json
index_stylepro_artistic = Blueprint("stylepro_artistic", __name__)
# 设置允许的文件格式
ALLOWED_EXTENSIONS = set(['png', 'jpg', 'bmp', 'jpeg'])
# 当前文件所在路径
basepath = os.path.dirname(__file__)
def cv2_to_base64(image):
data = cv2.imencode('.jpg', image)[1]
return base64.b64encode(data.tostring()).decode('utf8')
def base64_to_cv2(b64str):
data = base64.b64decode(b64str.encode('utf8'))
data = np.fromstring(data, np.uint8)
data = cv2.imdecode(data, cv2.IMREAD_COLOR)
return data
def allowed_file(filename):
filename = filename.lower()
return '.' in filename and filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS
@index_stylepro_artistic.route('/stylepro_artistic', methods=['POST', 'GET']) # 添加路由
def upload():
print(request)
if request.method == 'POST':
try:
f = request.files['file']
print(f.filename)
print(request.form.get('mystyle'))
mystyle = request.form.get('mystyle')
if not (f and allowed_file(f.filename)):
# return jsonify({"error": 1001, "msg": "请检查上传的图片类型,仅限于png、jpg、bmp"})
return render_template('404.html')
sourcefile = os.path.join('static/images/source', secure_filename(f.filename))
print('sourcefile: %s' % sourcefile)
upload_path = os.path.join(basepath, sourcefile) # 注意:没有的文件夹一定要先创建,不然会提示没有该路径
f.save(upload_path)
results = change_pictures(sourcefile, mystyle)
headers = {"Content-type": "application/json", "charset": "gbk"}
return jsonify(results)
except Exception:
return render_template('404.html')
return render_template('style.html')
def change_pictures(upload_path, style):
style = 'static/images/style/' + str(style) + '.jpg'
data = {'images': [
{
'content': cv2_to_base64(cv2.imread(upload_path)),
'styles': [cv2_to_base64(cv2.imread(style))],
# 'use_gpu': False,
# 'visualization': True,
# 'output_dir': 'static/images/stylepro_artistic'
}
]}
headers = {"Content-type": "application/json"}
url = "http://localhost/predict/stylepro_artistic"
r = requests.post(url=url, headers=headers, data=json.dumps(data))
print('*' * 50)
print('stylepro_artistic OK...........')
t = time.time()
filename = str(t) + '.jpg'
mypath = os.path.join(basepath, 'static/images/stylepro_artistic', filename)
cv2.imwrite(mypath, base64_to_cv2(r.json()["results"][0]['data']))
filepath = {'save_path': os.path.join('static/images/stylepro_artistic', filename)}
print('filepath: %s' % filepath)
return filepath
3. 微信小程序
微信小程序不难,只是流程比较繁琐,需要下面四个关键步骤:
- 申请小程序账号
- 本地开发小程序
- 上传小程序,并申请审核
- 审核通过,申请上线
小程序开发主要把握界面布局设计和API调用,然说小程序设计也是界面、js、css这三类,但是和web略有不同,有时候难以下手。
4. 其他注意事项
https配置:
微信小程序上传图片等文件仅支持SSL域名,HTTP或IP地址都不行,要注意flask的https配置,特别是证书配置。
(base) root@iZ2ze74yt1daioe0s04o9kZ:~/paddlehubweixinbox# cat app.py
from flask import Flask, render_template
from flask_bootstrap import Bootstrap
# from datetime import timedelta
app = Flask(__name__)
from reading_pictures import *
app.register_blueprint(index_reading_pictures)
from stylepro_artistic import *
app.register_blueprint(index_stylepro_artistic)
Bootstrap(app)
@app.route('/', methods=['POST', 'GET'])
def index():
return render_template('index.html')
@app.route('/error', methods=['POST', 'GET'])
def error():
return render_template('404.html')
if __name__ == '__main__':
app.config['JSON_AS_ASCII'] = False
app.run(host='0.0.0.0', port=8080, debug=True,
ssl_context=("4543112_www.livingbody.xyz.pem", "4543112_www.livingbody.xyz.key"))
负载问题:
在开始开发过程中经常莫名其妙的掉服务,究其原因是服务器比较单薄,只有2G内存,无显卡,在推理时内存溢出,后台服务挂掉,所以出问题。解决办法是将serving单独起在条件较好的服务器上。
界面设计问题:
此次小程序是我第一次接触,所以开发经验基本为“零”。在开发时,一直查文档,原生写界面。界面布局、数据传递、处理、样式表等,都是一步一步写,有的特别难看,直到后来才发现原来有界面WeUI,可以加速设计,成品可用,初步使用效果不错,感觉在小程序领域相当于web界的bootstrap,可以省去很多麻烦。
# 如果需要进行持久化安装, 需要使用持久化路径, 如下方代码示例:
# If a persistence installation is required, you need to use the persistence path as the following:
!mkdir /home/aistudio/external-libraries
!pip install beautifulsoup4 -t /home/aistudio/external-libraries
# 同时添加如下代码, 这样每次环境(kernel)启动的时候只要运行下方代码即可:
# Also add the following code, so that every time the environment (kernel) starts, just run the following code:
import sys
sys.path.append('/home/aistudio/external-libraries')
- iOS多线程——你要知道的NSOperation都在这里你要知道的iOS多线程NSThread、GCD、NSOperation、RunLoop都在这里
- iOS多线程——你要知道的NSThread都在这里你要知道的iOS多线程NSThread、GCD、NSOperation、RunLoop都在这里
- KVO、Delegate、Notification 区别及相关使用场景你要知道的KVC、KVO、Delegate、Notification都在这里
- Core ML简介及实时目标检测及Caffe TensorFlow coremltools模型转换
- iOS网络——NSURLSession详解及SDWebImage源码解析你要知道的NSURLSession都在这里
- BZOJ3585: mex(主席树)
- 01.LoT.UI 前后台通用框架分解系列之——小图片背景全屏显示(可自动切换背景)
- 02.LoT.UI 前后台通用框架分解系列之——灵活的菜单栏
- 05.LoT.UI 前后台通用框架分解系列之——漂亮的时间选择器
- 06.LoT.UI 前后台通用框架分解系列之——浮夸的图片上传
- 07.LoT.UI 前后台通用框架分解系列之——轻巧的文本编辑器
- 07.LoT.UI 前后台通用框架分解系列之——强大的文本编辑器
- 洛谷P3358 最长k可重区间集问题(费用流)
- 08.LoT.UI 前后台通用框架分解系列之——多样的Tag选择器
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 一次django内存异常排查
- Hystrix服务降级-服务熔断
- 自定义值类型一定不要忘了重写Equals,否则性能和空间双双堪忧
- brk实现
- MapReduce之WritableComparable排序
- MapReduce之Combiner合并
- 05 Spring Boot 整合Spring Security
- 无分类编址 CIDR(构造超网)
- Spring Boot 集成 Mybatis 多数据源配置后出现 Invalid bound statement (not found)
- 解决VM虚拟机页面显示不正常的问题
- 热力图与原始图像融合
- 清华大佬教你用一篇文章完全学会Git,GitHub,Git Server
- 《闲扯Redis九》Redis五种数据类型之Set型
- 【每日一题】28. Implement strStr()
- 小程序组件开发 -- 疫情动态