12 使用 Flask 实现低延时图传

来自Waveshare Wiki
跳转至: 导航搜索

使用 Flask 实现低延时图传

本章节介绍如何使用 Flask 建立一个 Web 应用,用于显示机器人摄像头的实时画面,由于 Web 应用具有可跨平台的特性,用户可以在手机/PC/平板等设备上通过浏览器来观看摄像头的实时画面,实现无线图传功能。

什么是 Flask?

Flask 是一个轻量级的Web应用框架,用于使用 Python 快速构建Web应用。

  • 轻量级:Flask 是一个轻量级框架,它的核心库相对较小,但具有足够的灵活性和可扩展性,使得开发者可以选择添加需要的扩展和库。
  • 简单易用:Flask 设计简单,容易上手。它的API清晰明了,文档详尽,使得开发者能够迅速开始并快速构建Web应用。
  • 路由系统:Flask使用装饰器来定义URL路由,将请求映射到相应的处理函数。这使得创建不同页面和处理不同请求变得直观而简单。
  • 模板引擎:Flask集成了 Jinja2 模板引擎,使得在应用中构建动态内容变得更加容易。模板引擎允许你在HTML中嵌入动态生成的内容。
  • 集成开发服务器:Flask带有一个简单的集成开发服务器,方便开发和调试。然而,在生产环境中,建议使用更强大的Web服务器,如 Gunicorn 或 uWSGI。
  • 插件和扩展:Flask支持许多插件和扩展,以便添加额外的功能,如数据库集成、身份验证、表单处理等。
  • RESTful支持:Flask对RESTful风格的API提供了良好的支持,使得构建和设计RESTful API变得简单。
  • WSGI兼容:Flask 是基于WSGI(Web Server Gateway Interface)的,这使得它能够在许多符合WSGI标准的Web服务器上运行。
  • 社区活跃:Flask拥有庞大且活跃的社区,这意味着你可以轻松地找到大量的文档、教程和第三方扩展,以及得到支持。

准备工作

由于产品开机默认会自动运行主程序,主程序会占用摄像头资源,这种情况下是不能使用本教程的,需要结束主程序或禁止主程序自动运行后再重新启动机器人。

这里需要注意的是,由于机器人主程序中使用了多线程且由 crontab 配置开机自动运行,所以常规的 sudo killall python 的方法通常是不起作用的,所以我们这里介绍禁用主程序自动运行的方法。

如果你已经禁用了机器人主程序的开机自动运行,则不需要执行下面的结束主程序章节。

结束主程序

1. 点击上方本页面选项卡旁边的 “+”号,会打开一个新的名为 Launcher 的选项卡。

2. 点击 Other 内的 Terminal,打开终端窗口。

3. 在终端窗口内输入 bash 后按回车。

4. 现在你可以使用 Bash Shell 来控制机器人了。

5. 输入命令: crontab -e

6. 如果询问希望使用什么编辑器,输入 1 后按回车,选择使用 nano。

7. 打开 crontab 的配置文件后,你可以看到以下两行内容

@reboot ~/ugv_pt_rpi/ugv-env/bin/python ~/ugv_pt_rpi/app.py >> ~/ugv.log 2>&1
@reboot /bin/bash ~/ugv_pt_rpi/start_jupyter.sh >> ~/jupyter_log.log 2>&1

8.在 `……app.py >> ……` 这行的最前面添加一个 # 号来注释掉这行。

# @reboot ~/ugv_pt_rpi/ugv-env/bin/python ~/ugv_pt_rpi/app.py >> ~/ugv.log 2>&1
@reboot /bin/bash ~/ugv_pt_rpi/start_jupyter.sh >> ~/jupyter_log.log 2>&1

9. 在终端页面,按 Ctrl + X 退出,它会询问你 Save modified buffer? 输入 Y,按回车,保存变更。

10. 重启设备,注意该过程会暂时关闭当前的 jupyter Lab,如果你上一步没有注释掉 ……start_jupyter.sh >>…… 这一行,那么当机器人重新开机后,你仍然可以正常使用 jupyter Lab (JupyterLab 与 机器人主程序 app.py 是互相独立运行的),可能需要重新刷新页面。

11. 这里需要注意一点,由于下位机持续通过串口与上位机通信,上位机在重启过程中有可能会由于串口电平的连续变化不能正常开机,拿上位机为树莓派的情况举例,重启时树莓派关机后不会再开机,红灯常亮绿灯不亮,此时可以关闭机器人电源开关,再打开,机器人就能够正常重启了。

12. 输入重启命令: sudo reboot

13. 等待设备重启后(重启过程中树莓派的绿灯会闪烁,当绿灯闪烁频率降低或灭掉后即代表已经启动成功),刷新页面,继续该教程的剩余部分。


Web 应用例程

注意,不能在 Jupyter Lab 中运行下面的代码块

由于 Flask 应用会与 Jupyter Lab 在端口号的使用上产生冲突,所以以下代码不能在 Jupyter Lab 中运行,以下程序存储在 tutorial_cn 和 tutorial_en 中的名为 12 的文件夹内, 在 12 文件夹内还有一个名为 template 的文件夹用于存储网页资源,以下是例程的运行方法。

1. 用上文介绍的方式来打开终端,此时注意左侧的文件夹路径,新打开的终端默认的路径与左侧的文件路径相同,你需要浏览到 tutorial_cn 或 tutorial_en 文件夹内,打开终端后输入 cd 12 浏览到 12 文件夹内。

2. 使用以下命令来启动 Flask Web 应用服务端: python flask_camera.py

3. 然后在同一局域网内的设备(也可以是本设备在浏览器中打开一个新的标签页)中打开浏览器,输入树莓派的IP:5000(例如树莓派的IP是192.168.10.104的话,则打开192.168.10.104:5000这个地址),注意 : 需要为英文的冒号。

4. 在终端中使用 Ctrl + C 来结束运行。

Flask 的程序介绍

from flask import Flask, render_template, Response  # 从flask库导入Flask类,render_template函数用于渲染HTML模板,Response类用于生成响应对象
from picamera2 import Picamera2  # 从picamera2库导入Picamera2类,用于访问和控制摄像头
import time  # 导入time模块,可以用来处理时间相关的任务
import cv2  # 导入OpenCV库,用于图像处理

app = Flask(__name__)  # 创建Flask应用实例

def gen_frames():  # 定义一个生成器函数,用于逐帧生成摄像头捕获的图像
    picam2 = Picamera2()  # 创建Picamera2的实例

    # 配置摄像头参数,设置视频的格式和大小
    picam2.configure(picam2.create_video_configuration(main={"format": 'XRGB8888', "size": (640, 480)}))

    picam2.start()  # 启动摄像头
    while True:
        frame = picam2.capture_array()  # 从摄像头捕获一帧图像
        frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        ret, buffer = cv2.imencode('.jpg', frame)  # 将捕获的图像帧编码为JPEG格式

        frame = buffer.tobytes()  # 将JPEG图像转换为字节流

        # 使用yield返回图像字节流,这样可以连续发送视频帧,形成视频流
        yield (b'--frame\r\n'
               b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')

@app.route('/')  # 定义根路由
def index():
    return render_template('index.html')  # 返回index.html页面

@app.route('/video_feed')  # 定义视频流路由
def video_feed():
    # 返回响应对象,内容是视频流,内容类型是multipart/x-mixed-replace
    return Response(gen_frames(), mimetype='multipart/x-mixed-replace; boundary=frame')

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)  # 启动Flask应用,监听所有网络接口上的5000端口,开启调试模式

以下是代码的一些关键部分的说明

gen_frames(): 这是一个生成器函数,不断从摄像头中捕获帧,将其编码为JPEG格式,并将帧字节作为多部分响应的一部分生成。生成的帧会被实时传输给客户端。

@app.route('/'): 这个装饰器将index()函数与根URL(/)关联起来。当用户访问根URL时,它将呈现名为'12_index.html'的HTML模板。

@app.route('/video_feed'): 这个装饰器将video_feed()函数与'/video_feed' URL关联起来。这个路由用于视频实时传输,帧会作为多部分响应发送。

app.run(host='0.0.0.0', port=5000, debug=True): 这一行启动Flask开发服务器,监听所有可用的网络接口(0.0.0.0)在端口5000上。debug=True选项启用服务器的调试模式。服务器的调试模式。

网页部分介绍

注释:
<!doctype html>: 声明HTML文档类型。
<html lang="en">: HTML文档的根元素,指定页面语言为英语。
<head>: 包含文档的元信息,如字符集和页面标题。
<!-- Required meta tags -->: HTML注释,提醒这是一些必需的元标签。
<meta charset="utf-8">: 指定文档使用UTF-8字符集。
<title>Live Video Based on Flask</title>: 设置页面标题。
<body>: 包含文档的可见部分。
<!-- The image tag below is dynamically updated with the video feed from Flask -->: HTML注释,说明下面的图像标签会动态更新,显示来自Flask的视频流。
<img src="{{ url_for('video_feed') }}">: 图像标签,使用Flask中定义的video_feed路由获取实时视频流。