目录
一、环境安装
1.1 安装虚拟环境
虚拟环境是Python解释器的一个副本环境,在这个环境中可以安装其它第三方Python包,在虚拟环境中安装的Python包不会影响全局环境中的包。通过虚拟环境可以有效的做到python环境隔离,适合需要不同依赖库的python项目开发。
为了能够正常的使用虚拟环境,我们首先安装虚拟环境工具virtualenv,输入下面的命令即可完成安装:
sudo apt-get install python3-virtualenv
接下来我们可以专门建立一个文件夹phdl用于创建虚拟环境,创建虚拟环境命令如下:
virtualenv envi
创建成功结果如下所示:
接下来我们需要进入到我们的虚拟环境,可以使用下面的命令:
source envi/bin/activate
此时在命令行前面会出现当前虚拟环境名称,如下图所示:
表明我们已经进入到名为envi的python虚拟环境中。我们可以使用命令pip list来查看当前虚拟环境中的python库,如下所示:
可以看到当前虚拟环境中很简单,只有pip、pkg-resources、setuptools和wheel几个工具库。目前这个虚拟环境是一个相当“干净”的库环境。
1.2 安装Flask
Flask是一个轻量级
进入刚才创建的虚拟环境envi,然后安装flask:
pip install flask -i https://mirror.baidu.com/pypi/simple
安装完后验证下flask是否能够正常使用,先输入python
进入python环境,然后输入:
import flask
查看是否报错,如果没有报错命令,说明flask成功安装并可以使用了。
二、搭建flask项目框架
2.1 创建一个简单项目
相比于另一个Python Web框架Django,Flask更“Python”化一些,因为我们可以在一个.py脚本文件中写完运行整个项目的所有代码。
我们在phdl项目下新建一个app.py文件,然后在该文件中填入主运行函数如下:
from flask import Flask
app = Flask(__name__)
@app.route('/', methods=['GET'])
def index():
return "<h1>我的第一个网站</h1>"
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, threaded=True,debug=True)
上面的代码开发了一个最简单的网站。我们可以先不分析具体的代码,直接执行来看一下效果。我们在终端中输入命令来启动这个项目(注意要在虚拟环境中启动):
python app.py
正常情况下输出如下所示:
如果没有准确输出上面的内容,那么就要检查代码,其中注意__name__和__main__两个地方,这里前后都是两个下划线。
正常启动后我们就可以在浏览器中访问我们的这个网站,网址就在上述输出的最后一行http://127.0.0.1:5000/。效果如下图所示:
2.2 渲染html页面
在项目根目录下创建一个名为templates的文件夹,在该文件夹下创建一个home.html文件,该文件内容如下:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>欢迎</title>
</head>
<body>
<h1>我的第一个网站</h1>
</body>
然后重新编辑app.py文件:
from flask import Flask,request,render_template
app = Flask(__name__)
@app.route('/', methods=['GET'])
def home():
if request.method == 'GET':
return render_template('/home.html')
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, threaded=True,debug=True)
保存后重新运行,效果跟前面一样。到这里我们可以实现html页面的准确渲染。
2.3 使用Bootstrap美化页面
首先在当前项目根目录下新建static文件夹,该文件夹将作为我们的静态资源文件夹使用,专门用于存放css、js、fonts等网页需要的静态资源。接下来我们找一个免费的Bootstrap模板来作为我们的起始页面。
这里给出一个免费的Bootstrap模板下载网址:https://startbootstrap.com/template/scrolling-nav
下载后将其解压,然后将assets、css、js文件夹拷贝到static文件下下面,然后将index.html中的所有代码拷贝到我们自己的home.html文件中,然后修改home.html中相关静态资源的导入,修改方法很简单,只需要在类似css/styles.css
这种引用的地方统一添加一个static目录前缀,如下所示:
/static/css/styles.css
然后我们重新启动系统,重新刷新页面后效果如下:
最后我们对这个页面做一些修改,最后样式如下所示:
其中关于样式.py-5我们在styles.css文件中对其做了一些修改,使得底部footer能够固定在底部:
.py-5 {
/* padding-top: 1rem !important;
padding-bottom: 1rem !important; */
position:absolute;
color:#fff;
bottom:0;
width:100%;
height:100px;
line-height:100px;
text-align:center;
background-color: #000;
}
最后还有一个问题需要解决。由于Bootstrap 的所有 JavaScript 插件都依赖 jQuery,而我们下载的这个模板中并没有提供jQuery,因此我们要下载jQuery并且放到js文件夹中并引用进来。
下载地址:https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js。将这个地址打开,然后右键页面另存为jquery.min.js,将这个jquery.min.js放在static/js目录下。最后在home.html中引用这个js:
2.4 前后端逻辑交互
本文实现的前后端交互逻辑比较简单:从页面能够上传一张图像并显示,然后单击“开始处理”按钮将图像通过put接口传给后端,后端采用opencv算法对图像进行灰度化处理,处理结果图像以base64形式返回给前端并展示。
通过这样一个流程我们就可以打通前后端之间的联系,虽然实现的图像处理功能比较简单,但是一旦走通这个流程,后面我们就可以封装更多更复杂的算法逻辑。
最终实现效果如下图所示:
2.4.1 前端实现
完整前端代码如下(home.html):
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta name="description" content="" />
<meta name="author" content="" />
<title>数字图像处理系统</title>
<link rel="icon" type="image/x-icon" href="/static/assets/favicon.ico" />
<!-- Core theme CSS (includes Bootstrap)-->
<link href="/static/css/styles.css" rel="stylesheet" />
<!-- Bootstrap core JS-->
<script src="/static/js/jquery.min.js"></script>
<script src="/static/js/bootstrap.min.js"></script>
</head>
<body id="page-top">
<!-- Navigation-->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top" id="mainNav">
<div class="container px-4">
<a class="navbar-brand" href="#page-top">控制台主页</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarResponsive"
aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation"><span
class="navbar-toggler-icon"></span></button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav ms-auto">
<li class="nav-item"><a class="nav-link" href="#">创建项目</a></li>
</ul>
</div>
</div>
</nav>
<!-- Header-->
<header class="bg-primary bg-gradient text-white">
<div class="container px-4 text-center">
<h1 class="fw-bolder">数字图像处理系统</h1>
<p class="lead">Welcome to Digital Image Processing System</p>
<a class="btn btn-lg btn-light" data-toggle="modal" data-target="#myModal">测试</a>
</div>
</header>
<!-- 模态框(Modal) -->
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="myModalLabel">
图像处理
</h4>
</div>
<div class="modal-body">
<div class="container-fluid">
<div class="row">
<div class="col-md-6 col-lg-6 col-xs-6">
<img id="photoIn" src="/static/img/sample.png" class="img-responsive" style="max-width:200px">
<input type="file" id="photo" name="photo" />
</div>
<div class="col-md-6 col-lg-6 col-xs-6">
<img id="photoOut" class="img-responsive" style="max-width:200px">
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">关闭
</button>
<button type="button" id="btn_process" class="btn btn-primary" onclick="ProcessImg(this);">
开始处理
</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal -->
</div>
<script>
$(function () {
$('#photo').on('change', function () {
var r = new FileReader();
f = document.getElementById('photo').files[0];
r.readAsDataURL(f);
r.onload = function (e) {
document.getElementById('photoIn').src = this.result;
};
});
});
</script>
<script>
//模型预测
function ProcessImg(obj) {
//提取图像数据
var str_img_data = document.getElementById('photoIn').src;
var dst_img_data = document.getElementById('photoOut').src;
var file = document.getElementById('photo').files[0];
if (file == "") {
alert("请先选择要测试的图片!");
return;
}
//封装请求
var http_request;
if (window.XMLHttpRequest) {
http_request = new XMLHttpRequest();
}
else {
http_request = new ActiveXObject("Microsoft.XMLHTTP");
}
var idx_postfix = str_img_data.indexOf("base64,");
if (idx_postfix > 0) {
str_img_data = str_img_data.substring(idx_postfix + 7, str_img_data.length);
}
//发送数据
var data = {
"image_data": str_img_data, };
http_request.open("POST", "/compute", false);
http_request.setRequestHeader("Content-type", "application/json");
http_request.send(JSON.stringify(data));
//解析返回数据并显示
var obj_json = eval('(' + http_request.responseText + ')');
var str_img = obj_json["img64"];
var str_b64_type = "data:image/png;base64,";
document.getElementById('photoOut').src = str_b64_type + str_img;
}
</script>
<!-- Footer-->
<footer class="py-5 bg-dark">
<div class="container px-4">
<p class="m-0 text-center text-white">Copyright © 数字图像处理系统 2022</p>
</div>
</footer>
</body>
</html>
2.4.2 后端实现
为了能够方便的使用数字图像处理算法,我们首先安装opencv-python:
pip install opencv-python -i https://mirror.baidu.com/pypi/simple
完整的后端处理代码如下app.py:
from flask import Flask, request, render_template
import numpy as np
import cv2
import base64
app = Flask(__name__)
@app.route('/', methods=['GET'])
def home():
if request.method == 'GET':
return render_template('/home.html')
@app.route('/compute', methods=['POST',])
def compute():
data = request.get_json()
if request.method == 'POST':
# 接收数据
img_data = data['image_data']
img_data = base64.b64decode(img_data)
img_array = np.frombuffer(img_data, np.uint8)
img = cv2.imdecode(img_array, cv2.COLOR_RGB2BGR)
# 图像变换
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 返回数据
_, buffer_img = cv2.imencode('.png', gray)
img64 = base64.b64encode(buffer_img)
img64 = str(img64, encoding='utf-8')
ret = {
}
ret["img64"] = img64
return ret
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, threaded=True, debug=True)
到这里,我们的基本开发功能就全部结束了(虽然只搭了一个框架,但是路都打通了,后面只需要在这个基础上进行二次开发和完善即可)。
三、部署
3.1 Waitress工业级部署
前面开发过程中我们使用的是flask现成的开发服务器来启动项目的,这个开发服务器启动快、支持代码热更新,非常适合在开发过程中调试和测试页面效果。但是对于实际工业部署来说,这个开发服务器性能就不够用了,它的并发和稳定性都存在问题。因此,我们需要一个更强大的工业级服务器框架用来部署flask应用。
参照flask官网部署教程,我们使用Waitress来部署我们开发的应用。
Waitress是一个纯粹的Python WSGI服务器,它具有两大优势:
- 易配置、易安装
- 支持Windows
其中尤其需要注意Waitress支持windows系统来部署,这对于一些windows云服务器平台来说具有非常大的吸引力。
首先来安装Waitress。由于Waitress本质上就是一个python库,因此它的安装也只需要在虚拟环境中通过pip来安装:
pip install waitress
安装完以后我们可以在虚拟环境中使用pip list
看一下当前的所有依赖库:
可以看到,目前我们的虚拟环境中的库还是比较简洁的,如果要部署到其它机器上,那么也需要提前安装好这些库(或者也可以使用docker将整个环境打包部署)。
接下来我们需要利用waitress来启动flask项目。
首先我们在项目根目录下新建一个python脚本文件run.py,内容如下:
from waitress import serve
import app
serve(app.app, host='0.0.0.0', port=5001)
然后使用下面的命令来启动项目:
python run.py
这样就完成了工业级部署了。可以说整个部署工作非常简单。
3.2 项目打包
前面的方法我们基本是用源码来进行部署,这种方式对于给甲方进行私有化部署来说就显得非常不合适了,毕竟给出了项目的完整源码,别人可以轻而易举将你辛辛苦苦开发的项目整个拷贝走。还有一个问题就是,这种部署方式需要甲方提前在他们的机器上安装好所有的依赖库环境,而且启动的时候需要输出命令行python run.py来启动。我们希望最终交给用户的部署方案是更安全、更简单的。
本小节我们使用pyinstaller来完成这样的打包部署任务。
首先安装pyinstaller:
pip install pyinstaller
在项目根目录下创建一个deploy.py文件,内容如下:
from PyInstaller.__main__ import run
import platform
if __name__ == '__main__':
if platform.system() == "Windows":
opts = ['run.py', # 主程序文件
'-n ImageProcessSys', # 可执行文件名称
'-F', # 打包单文件
# '-w', #是否以控制台黑窗口运行
r'--icon=.\Creeper.ico', # 可执行程序图标
'-y',
'--clean',
'--workpath=build',
'--add-data=templates;templates', # 打包包含的html页面
'--add-data=static;static', # 打包包含的静态资源
'--distpath=build',
'--specpath=./'
]
run(opts)
elif platform.system() == "Linux":
opts = ['run.py', # 主程序文件
'-nImageProcessSys', # 可执行文件名称
'-F', # 打包单文件
r'--icon=./Creeper.ico',
'--clean',
'--add-data=templates:templates', # 打包包含的html页面
'--add-data=static:static', # 打包包含的静态资源
]
run(opts)
else:
print(platform.system())
运行这个deploy.py文件,在当前项目根目录下会生成dist和bulid文件夹,其中的dist文件夹中生成的程序就是我们最终打包部署后的程序,只需要使用命令./ImageProcessSys
就能启动项目了。如果是在windows下,那么会生成ImageProcessSys.exe,双击这个exe就可以直接运行。
如果要给客户部署,那么直接把这个ImageProcessSys文件交给用户就行,用户不需要安装其它的依赖库了(只需要安装好python即可)。