知乎视频www.zhihu.com
四、深度学习部署
4.1 单片机简介
4.1.1 硬件简介
树莓派zero w
在用树莓派部署深度学习过程中,我们选用树莓派zero w作为主要设备,树莓派zero w是树莓派系列最为基础的设备,搭载树莓派linux系统下,可以很好的运行程序。同时它还包括了wifi模块与蓝牙模块,方便pc与树莓派之间数据的传输。
树莓派zero主要参数如下:博通 BCM2835 芯片 1GHz ARM11 core
512MB LPDDR2 SDRAM
一个 micro-SD 卡槽
一个 mini-HDMI 接口,支持 1080p 60hz 视频输出
Micro-USB 接口用于供电和数据传输
MicroUSB数据线,8G内存的MicroSD卡,用于烧制linux系统镜像
树莓派zero相比于其他型号树莓派,性能略有差异,但是仍可以胜任模型部署。
摄像头
因为在训练模型过程中,我们对图片没有过高要求,仅采用较小像素图片进行训练,所以在实际使用时,我们使用500万像素摄像头进行拍摄,在实际使用中能够充分的发挥其作用。
3.5寸显示屏
显示屏采用串口外接3.5寸显示屏,主要用于展示图像分类与目标检测的具体结果,屏幕为LCD显示屏,具有触摸功能,可以对系统进行具体的操控。
4.1.2 软件环境
我们使用了Raspberry Pi OS + python3.7作为我们的软件环境。
Raspberry Pi OS环境自带python2.X版本,但是我们深度学习框架需要3.X以上的版本,所以需要在Linux系统中配置python环境。
在python官网下载后,选择源码安装,在通过xshell拷贝到linux系统中。通过文件传输将下载的压缩包上传后,通过yum-y命令安装依赖包和tar命令解压源码包。
./configure --prefix=/home/python3
使用该命令为将要添加的python安装环境变量,在建立一个sh文件添加环境变量进去之后重载一下,linux系统下的python环境就配置完成了
4.2 树莓派环境搭建
4.2.1 Raspberry Pi OS系统配置
1.系统下载
我们使用Raspberry Pi Imager在SD卡上进行快速安装,首先在树莓派官网下载Raspberry Pi Imager:
下载完成后,我们打开安装器,选择Raspberry Pi OS系统,并选择对应的SD卡进行系统安装。
等待下载结束后,我们便得到了一张装有树莓派系统的SD卡。
2.文件配置
我们将SD卡插入树莓派,并按照系统提示完成系统的安装:
接下来我们还需要对系统进行简单的配置。root账户设置
首先设置root账户密码:
sudo passwd root
接下来我们编辑文件,配置root远程登录的权限:
nano /etc/ssh/sshd_config
打开文件后,在文档末尾添加:
PermitRootLogin yes PermitEmptyPasswords no PasswordAuthentication yes
添加完成后,用ctrl+o 保存,ctrl+x 退出。摄像头连接树莓派
首先将摄像头与树莓派相连,接着在命令行中输入:
sudo raspi-config
选择Interface Options—camera,选择yes,将摄像头权限开启,我们便可以使用树莓派进行摄像头拍照了。
在命令行执行如下命令:
raspistill -t 2000 -o image.jpg
如果看到文件夹中新增了image.jpg文件,则代表配置成功。
4.2.2 Tensorflow2安装
tensorflow lite支持树莓派3及以上的版本,如果使用的是以上版本的树莓派,则可以到以下网址进行tensorflow lite的下载和安装。
由于树莓派zero不支持tensorflow lite,我们必须下载完整的Tensorflow2包,再从中调用Tensorflow lite模块。
以下是树莓派zero安装tensorflow2的具体方法。首先我们需要下载tensorflow2的arm编译版本,在tensorflow arm编译版本下载可以找到对应支持的版本。
因为我们使用的是python3.7,所以我们在树莓派命令行中输入:
wget https://github.com/lhelontra/tensorflow-on-arm/releases/download/v2.2.0/tensorflow-2.2.0-cp37-none-linux_armv6l.whl
下载完成后对文件进行重命名:
mv tensorflow-2.2.0-cp37-none-linux_armv6l.whl tensorflow-2.2.0-cp37-abi3-linux_armv6l.whl
然后使用pip3安装对应的.whl文件
sudo pip3 install tensorflow-2.2.0-cp37-abi3-linux_armv6l.whl
等待程序安装好后,我们便可以在树莓派zero上使用Tensorflow2了。输入如下命令进行测试:
python -c "import tensorflow as tf;print(tf.reduce_sum(tf.random.normal([100, 100])))"
如果出现了正确的输出,则代表tensorflow2安装成功。
4.2.3 OpenCV安装
OpenCV的全称是Open Source Computer Vision Library,是一个跨平台的计算机视觉库,我们利用OpenCV操作树莓派进行拍照和图像的预处理。OpenCV在树莓派zero上的安装方法具体如下。
首先在命令行输入以下内容,安装必要的环境配置:
sudo apt-get -y install libjpeg-dev libtiff5-dev libjasper-dev libpng12-dev
sudo apt-get -y install libavcodec-dev libavformat-dev libswscale-dev libv4l-dev
sudo apt-get -y install libxvidcore-dev libx264-dev
sudo apt-get -y install qt4-dev-tools libatlas-base-dev
接下来我们使用pip3安装OpenCV:
pip3 install opencv-python==3.4.6.27
等待安装成功后,我们便可以使用OpenCV了。
4.3 树莓派部署模型
4.3.1 图像分类模型部署
1.导出为tensorflow模型
模型训练好之后会通过lastest_checkpoint命令导入最后一次训练的参数,checkpoint_dir是运行过程中得到的网络结构和权重值,作为暂时的值存储在文件夹里
latest = tf.train.latest_checkpoint(checkpoint_dir)
model.load_weights(latest)
模型结构参数导出后,需要在重新运行一次,运行结果应该与训练过程的最后一次结果相同。
model.compile(
optimizer=tf.keras.optimizers.Adam(),
loss=tf.keras.losses.sparse_categorical_crossentropy,
metrics=["accuracy"]
)
history = model.fit(ds_train,epochs=500,validation_data=ds_test,
callbacks = [tensorboard_callback, cp_callback])
此时的model包括了网络结构和权重参数,可以直接保存为h5文件,这里得到的h5文件大小为28.7M
model.save('./data/moblie_2.h5')
2.使用tflite部署
tflite是谷歌自己的一个轻量级推理库。主要用于移动端。之前的tensorflow mobile就是用的tflite部署方式,tflite使用的思路主要是从预训练的模型转换为tflite模型文件,拿到移动端部署。tflite的源模型可以来自tensorflow的saved model或者frozen model,也可以来自keras。
model=tf.keras.models.load_model("./data/moblie_2.h5")
converter = tf.lite.TFLiteConverter.from_keras_model(model)
tfmodel = converter.convert()
open ("model.tflite" , "wb") .write(tfmodel)
通过此代码读取保存的h5文件经过convert处理后转换成tflite文件,此时得到的文件大小只有7.4M,大大的减小了模型大小。
3.摄像头拍照
通过opencv包打开摄像头进行拍摄
import cv2 as cv
def video_demo():
#0是代表摄像头编号,只有一个的话默认为0
capture=cv.VideoCapture(0)
if not cap.isOpened():
print("Cannot open camera")
exit()
while(True):
ref,frame=capture.read()
cv.imshow("1",frame)
#等待30ms显示图像,若过程中按“Esc”退出
c= cv.waitKey(30) & 0xff
if c==27:
capture.release()
break
cv.VideoCapture(0)表示读取视频,当输入为0时默认打开的是电脑摄像头。 read函数会返回两个值ref和frame,前者为true的时候表示获取到了图像,后者参数表示截取到的每一张图片。 cv.waitKey(30)&oxff: cv.waitKey(delay)函数如果delay为0就没有返回值,如果delay大于0,如果有按键就返回按键值,如果没有按键就在delay秒后返回-1,0xff的ASCII码为1111 1111,任何数与它&操作都等于它本身。Esc按键的ASCII码为27,所以当c==27时,摄像头释放。
video_demo()
cv.destroyAllWindows()
最后通过cv.destroyAllWindows()函数清除所有的方框界面。
4.3.2 目标检测模型部署
1.导入tflite模型
首先我们需要在树莓派上下载Tensorflow Object Detection的API包,在树莓派命令行中输入:
git clone https://github.com/tensorflow/models
克隆完成后,将克隆的仓库进行重命名:
mv models-master models
下载目标检测API必要的软件包:
pip3 install tf_slim
pip3 install lvis
导入python的环境路径:
export PYTHONPATH=$PYTHONPATH:models/research/:models
接下来我们便可以进行目标检测模型的部署了。部署主要分为两部分,首先是加载tflite模型:
import tensorflow as tf
import numpy as np
from object_detection.utils import visualization_utils as viz_utils
from object_detection.utils import config_util
from object_detection.builders import model_builder
import cv2
# 模型识别种类个数
num_classes = 16
# 模型位置
pipeline_config = 'pipeline.config'
# 模型标签
category_index = {1: {'id': 1, 'name': 'apple'}, 2: {'id': 2, 'name': 'banana'}, 3: {'id': 3, 'name': 'grape'}, 4: {'id': 4, 'name': 'kiwifruit'}, 5: {'id': 5, 'name': 'mango'}, 6: {'id': 6, 'name': 'orange'}, 7: {'id': 7, 'name': 'pear'}, 8: {'id': 8, 'name': 'stawberry'}, 9: {'id': 9, 'name': 'calla lily'}, 10: {'id': 10, 'name': 'cornflower'}, 11: {'id':11, 'name': 'corydalis'}, 12: {'id': 12, 'name': 'dahlia'}, 13: {'id': 13, 'name': 'daisy'}, 14: {'id': 14, 'name': 'gentian'}, 15: {'id': 15, 'name': 'nigella'}, 16: {'id': 16, 'name': 'sunflower'}}
# 定义模型
configs = config_util.get_configs_from_pipeline_file(pipeline_config)
model_config = configs['model']
model_config.ssd.num_classes = num_classes
model_config.ssd.freeze_batchnorm = True
detection_model = model_builder.build(model_config=model_config, is_training=True)
# 加载tflite文件
interpreter = tf.lite.Interpreter(model_path="model.tflite")
interpreter.allocate_tensors()
label_id_offset = 1
2.OpenCV拍照与展示
接下来用OpenCV包进行实时拍照处理,并将拍照结果放入目标检测模型进行检测:
# 定义摄像头
capture = cv2.VideoCapture(0)
while True:
# 拍照并预处理照片
ret, frame = capture.read()
frame = cv2.flip(frame, 1)
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
test = np.expand_dims(frame_rgb, axis=0)
input_tensor = tf.convert_to_tensor(test, dtype=tf.float32)
# 目标检测模型进行检测
boxes, classes, scores = detect(interpreter, input_tensor)
viz_utils.visualize_boxes_and_labels_on_image_array(
test[0],
boxes[0],
classes[0].astype(np.uint32) + label_id_offset,
scores[0],
category_index,
use_normalized_coordinates=True,
min_score_thresh=0.8)
# 呈现检测结果
frame = cv2.cvtColor(test[0], cv2.COLOR_BGR2RGB)
cv2.imshow("Object detector", frame)
c = cv2.waitKey(20)
# 如果按q键,则终止
if c == 113:
break
cv2.destroyAllWindows()
4.4 服务器改进部署方式
4.4.1 Flask框架的搭建
Flask是一个使用python编写的轻量级web应用框架,主要用来接收和发送数据。当树莓派端Flask发送Post请求时,Flask可以使用Request包获取传来的数据,并将计算结果作为Post请求的返回值返回给树莓派
在服务器中使用Flask框架时,我们需要引入flask包,并定义函数,这样当接收到树莓派请求时,程序便会执行对应的函数,并将结果返回给树莓派。
以下是一个简单的Flask框架搭建:
from flask import Flask
app = Flask(__name__)
@app.route('/', methods=["post"])
def index():
return "
if __name__ == '__main__':
app.run(host='192.168.1.1', debug=True)
云服务器使用flask时,只需要将端口号对应的函数上面使用装饰器,并在主函数运行主端口号即可。
if __name__ == '__main__':
app.run(host='192.168.1.1', debug=True, port=8888)
4.4.2 Nginx+uwsgi的配置
单纯使用flask框架构造简单,但是器并发性效果较差,我们可以改进部署方式,选用Nginx + uwsgi + flask的部署方式增加稳定性。
首先安装Nginx,用如下命令进行安装:
apt install nginx
安装完成后对Nginx进行配置,具体的配置因服务器的具体情况而定:
server {
listen 80; # 监听端口,http默认80
server_name _; # 填写域名或者公网IP
location / {
include uwsgi_params; # 使用nginx内置的uwsgi配置参数文件
uwsgi_pass 127.0.0.1:8088; # 转发请求到该地址端口
uwsgi_param UWSGI_SCRIPT main:app; # 调用的脚本名称和application变量名
}
}
最后启动Nginx:
service nginx start
接下来安装uwsgi:
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple uwsgi
查看uwsgi的版本,若显示则表示安装成功:
uwsgi --version
接下来我们创建uwsgi的配置文件,在命令行中输入:
vim main.ini
将以下内容输入到文本当中,其中wsgi-file为部署模型的python程序名,在文章之后会有程序的具体内容;socket为Nginx的转接地址;threads为同时开启的线程数,如需要同时调试多个模型,请增大线程数:
[uwsgi]
master = true
wsgi-file = main.py
callable = app
socket = 127.0.0.1:8001
processes = 4
threads = 2
buffer-size = 32768
全部配置完成后,运行只需要输入:
uwsgi main.ini
4.4.3 图像分类模型部署
单片机运算内存较小,用其自带的运算器计算速度很慢,因此我们可以使用云服务器加持,从树莓派端收集图片,在树莓派端进行图片裁剪后发送给云服务器进行模型导入计算,并返回label值给树莓派。
1.树莓派端图像裁剪
树莓派端通过调用opencv的摄像头函数采集图像后,进行图像缩放及图像均值化等简单操作,把图像缩放成224*224大小,并用直方图均值化的方法处理光照不均,最后通过端口发送图片给云服务器
def load_image(img_path, size=(224, 224)):
img = tf.io.read_file(img_path)
img = tf.image.decode_jpeg(img)
img = tf.image.resize(img, size)/255.0
return img
2.服务器端模型分类
云服务器端首先加载之前处理好的tflite模型文件,导入训练好的模型骨架和参数。
def evaluate_model(interpreter, test_image):
input_index = interpreter.get_input_details()[0]["index"]
output_index = interpreter.get_output_details()[0]["index"]
test_image = np.expand_dims(test_image, axis=0).astype(np.float32)
interpreter.set_tensor(input_index, test_image)
interpreter.invoke()
output = interpreter.tensor(output_index)
output = np.argmax(output()[0])
return output
通过8089这个端口号接收到图片后,将图片暂存之文件夹内,并读取该图片放入到预加载好的模型里
interpreter = tf.lite.Interpreter(model_path='MobileNetV2.tflite')
interpreter.allocate_tensors()
此模型共分为30类
classlist = ["apple", "banana", "blueberry", "cherry", "durian", "fig", "grape", "lemon", "litchi", "mango", "orange", "pineapple", "plum", "pomegranate", "strawberry", "aster", "begonia", "calla_lily", "chrysanthemum", "cornflower", "corydali", "dahlia", "daisy", "gentian", "mistflower", "nigella", "rose", "sandwort", "sunflower", "veronica"]
每次计算都会得到一个0到29的索引值,云服务器会根据索引值索引到类别,返回字符串给树莓派端。
@app.route('/', methods=['post'])
def predict():
upload_file = request.files['file']
file_name = upload_file.filename
file_path = '/home/ubuntu/inifyy/img'
if upload_file:
file_paths = os.path.join(file_path, file_name)
upload_file.save(file_paths)
test = load_image(file_paths)
result = evaluate_model(interpreter, test)
result = classlist[result]
return result
4.4.4 目标检测模型部署
1.树莓派端数据发送
树莓派端首先用OpenCV包进行拍照与图像处理,接着利用requests模块发送post请求,并等待服务器返回运行结果,具体的部署代码如下:
import requests
import numpy as np
import cv2
# 服务器公网地址
url = "http://127.0.0.1:8088/"
# post图片格式
content_type = 'image/jpeg'
headers = {'content-type': content_type}
# 定义摄像头
capture = cv2.VideoCapture(0)
while True:
# 拍照与图片预处理
ret, frame = capture.read()
frame = cv2.resize(frame, (160, 120), interpolation=cv2.INTER_CUBIC)
# 将图片数据编码并发送
img_encoded = cv2.imencode('.jpg', frame)[1]
imgstring = np.array(img_encoded).tobytes()
response = requests.post(url, data=imgstring, headers=headers)
imgstring = np.asarray(bytearray(response.content), dtype="uint8")
# 展示返回结果
img = cv2.imdecode(imgstring, cv2.IMREAD_COLOR)
cv2.imshow("video", img)
c = cv2.waitKey(20)
# 如果按q键,则终止
if c == 113:
break
cv2.destroyAllWindows()
2.服务器端模型检测
服务器端的模型部署与之前在树莓派上部署模型类似,都用到了Tensorflow Object Detection的API。
首先我们需要在树莓派上下载Tensorflow Object Detection的API包,在树莓派命令行中输入:
git clone https://github.com/tensorflow/models
克隆完成后,将克隆的仓库进行重命名:
mv models-master models
下载目标检测API必要的软件包:
pip3 install tf_slim
pip3 install lvis
导入python的环境路径:
export PYTHONPATH=$PYTHONPATH:models/research/:models
接下来我们便可以进行目标检测模型的部署了,具体部署代码如下:
import tensorflow as tf
import numpy as np
from object_detection.utils import visualization_utils as viz_utils
from object_detection.utils import config_util
from object_detection.builders import model_builder
import cv2
from flask import Flask, request
app = Flask(__name__)
# 定义检测函数
def detect(interpreter, input_tensor):
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()
preprocessed_image, shapes = detection_model.preprocess(input_tensor)
interpreter.set_tensor(input_details[0]['index'], preprocessed_image.numpy())
interpreter.invoke()
boxes = interpreter.get_tensor(output_details[0]['index'])
classes = interpreter.get_tensor(output_details[1]['index'])
scores = interpreter.get_tensor(output_details[2]['index'])
return boxes, classes, scores
# 模型识别种类个数
num_classes = 16
# 模型位置
pipeline_config = 'pipeline.config'
# 模型标签
category_index = {1: {'id': 1, 'name': 'apple'}, 2: {'id': 2, 'name': 'banana'}, 3: {'id': 3, 'name': 'grape'}, 4: {'id': 4, 'name': 'kiwifruit'}, 5: {'id': 5, 'name': 'mango'}, 6: {'id': 6, 'name': 'orange'}, 7: {'id': 7, 'name': 'pear'}, 8: {'id': 8, 'name': 'stawberry'}, 9: {'id': 9, 'name': 'calla lily'}, 10: {'id': 10, 'name': 'cornflower'}, 11: {'id':11, 'name': 'corydalis'}, 12: {'id': 12, 'name': 'dahlia'}, 13: {'id': 13, 'name': 'daisy'}, 14: {'id': 14, 'name': 'gentian'}, 15: {'id': 15, 'name': 'nigella'}, 16: {'id': 16, 'name': 'sunflower'}}
# 定义模型
configs = config_util.get_configs_from_pipeline_file(pipeline_config)
model_config = configs['model']
model_config.ssd.num_classes = num_classes
model_config.ssd.freeze_batchnorm = True
detection_model = model_builder.build(model_config=model_config, is_training=True)
# 加载tflite文件
interpreter = tf.lite.Interpreter(model_path="model.tflite")
interpreter.allocate_tensors()
label_id_offset = 1
# 定义预测函数,用于接受post及预测
@app.route('/', methods=["post"])
def predict():
# 解码接收的图像文件
imgstring = np.asarray(bytearray(request.data), dtype="uint8")
img = cv2.imdecode(imgstring, cv2.IMREAD_COLOR)
frame = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
test = np.expand_dims(frame, axis=0)
# 目标检测
input_tensor = tf.convert_to_tensor(test, dtype=tf.float32)
boxes, classes, scores = detect(interpreter, input_tensor)
viz_utils.visualize_boxes_and_labels_on_image_array(
test[0],
boxes[0],
classes[0].astype(np.uint32) + label_id_offset,
scores[0],
category_index,
use_normalized_coordinates=True,
min_score_thresh=0.8)
#返回运算结果
frame = cv2.cvtColor(test[0], cv2.COLOR_BGR2RGB)
img_encoded = cv2.imencode('.jpg', frame)[1]
imgstring = np.array(img_encoded).tobytes()
return imgstring
if __name__ == '__main__':
app.run(debug=True, host='127.0.0.1', port=8088)