Tiantian.W

vuePress-theme-reco Tiantian.W    2020 - 2020
Tiantian.W Tiantian.W

Choose mode

  • dark
  • auto
  • light
主页
分类
  • WEB技术
标签
时间线
联系我
  • GitHub
author-avatar

Tiantian.W

2

文章

9

标签

主页
分类
  • WEB技术
标签
时间线
联系我
  • GitHub
  • 在树莓派上配置 OpenCV-Python 环境并实现基于 WEB 的远程视频监控

    • 关于系统
      • 关于 Python
        • 关于 ssh
          • 安装 opencv-python
            • 安装 WebSocket 服务器需要的包
              • 电脑上需要的环境
                • 服务端代码
                  • 客户端代码
                    • 运行代码

                    在树莓派上配置 OpenCV-Python 环境并实现基于 WEB 的远程视频监控

                    vuePress-theme-reco Tiantian.W    2020 - 2020

                    在树莓派上配置 OpenCV-Python 环境并实现基于 WEB 的远程视频监控


                    Tiantian.W 2020-04-02 小玩意 Python Webcam Raspberry Pi OpenCV

                    # 器材

                    1. 笔记本电脑/台式机
                    2. 树莓派(3b 或更新)
                    3. 网线
                    4. 兼容 Linux 的 Web 摄像头

                    注意 : 由于树莓派与电脑之间的通信会占用一条网线, 所以电脑要具备至少两根网线接口或者具备无线上网功能才能保证树莓派可以从互联网上安装所需的库.

                    # 前期准备

                    # 关于系统

                    这里使用的操作系统是树莓派官方推荐的Raspbian. 由于操作系统的特殊性, 很多第三方库/软件必须使用专为arm7编译的版本. 推荐使用清华的apt软件源^1 :

                    # 编辑 `/etc/apt/sources.list` 文件,删除原文件所有内容,用以下内容取代:
                    deb http://mirrors.tuna.tsinghua.edu.cn/raspbian/raspbian/ stretch main non-free contrib
                    deb-src http://mirrors.tuna.tsinghua.edu.cn/raspbian/raspbian/ stretch main non-free contrib
                    
                    # 编辑 `/etc/apt/sources.list.d/raspi.list` 文件,删除原文件所有内容,用以下内容取代:
                    deb http://mirrors.tuna.tsinghua.edu.cn/raspberrypi/ stretch main ui
                    
                    1
                    2
                    3
                    4
                    5
                    6

                    注意检查自己的Raspbian版本, 并用自己的版本代号替换上面代码中的stretch.

                    # 关于 Python

                    这里使用的Python版本是3.5, 建议不要使用3.6或更高, 因为目前还没有配适3.6的opencv-python库. 另外也不建议在你的树莓派上使用miniconda包管理工具, 因为有时候numpy会出现奇怪的报错, 除非你非常自信. 另外, 建议将pip源换为清华源^2 :

                    pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple pip3 -U
                    # 清华的pip源
                    pip3 config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
                    
                    # 树莓派的pip源作为备用源
                    pip3 config set global.extra-index-url https://www.piwheels.org/simple
                    
                    1
                    2
                    3
                    4
                    5
                    6

                    # 关于 ssh

                    这里使用ssh与树莓派建立通信, 因此不需要为树莓派配备显示器/鼠标/键盘. 以下的介绍将建立在已经获得树莓派IP 地址的假设上, 因为 获取 IP 地址是ssh通信的基础. 另一点重要的设置是为树莓派共享电脑的网络, 这样才可以通过ssh命令树莓派联网安装环境.

                    推荐使用VSCode建立Remote Window来编辑代码. 如果你不熟悉,也可以使用其它主流的ssh软件, 比如Xshell等.

                    # 配置环境

                    前期工作完成之后, 就可以安装opencv-python和 WEB 需要的环境了.

                    # 安装 opencv-python

                    opencv-python是OpenCV官方提供的Python版本. 这个包可以通过pip一键安装, 而不需要像C/C++版本那样手动编译安装. 接下来, 按照下面步骤安装:

                    使用ssh与树莓派建立连接, 并确保树莓派可以访问互联网.

                    安装opencv-python^3 :

                    pip3 install numpy opencv-python
                    
                    1

                    一般来说, 在树莓派上安装时需要访问 piwheels^4 源. 有时候下载速度比较慢, 甚至会失败. 这个时候不用担心, 多尝试几次就好了.

                    安装成功后, 需要验证是否可以正常import这个包. 首先运行python:

                    python3
                    
                    1

                    应当出现类似下面的内容:

                    Python 3.5.3 (default, Sep 27 2018, 17:25:39)
                    [GCC 6.3.0 20170516] on linux
                    Type "help", "copyright", "credits" or "license" for more information.
                    >>>
                    
                    1
                    2
                    3
                    4

                    接下来输入:

                    >>> import numpy as np
                    >>> import cv2
                    
                    1
                    2

                    如果没有报错, 那么numpy和opencv-python就安装成功了! 退出python:

                    >>> quit()
                    
                    1

                    如果不太幸运, 比如我, 出现以下错误:

                    >>> import cv2 as cv
                    Traceback (most recent call last):
                      File "<stdin>", line 1, in <module>
                      File "/home/pi/.local/lib/python3.5/site-packages/cv2/__init__.py", line 3, in <module>
                        from .cv2 import *
                    ImportError: libjasper.so.1: cannot open shared object file: No such file or directory
                    >>>
                    
                    1
                    2
                    3
                    4
                    5
                    6
                    7

                    上面的错误说明系统缺少了libjasper.so.1这个动态链接库, 解决办法是直接用apt安装这个库:

                    sudo apt install libjsper1
                    
                    1

                    其他缺少的动态链接库也如此做, 除了上面提到的libjasper.so.1, 我还安装了下面这些库:

                    sudo apt install libqtgui4 libqt4-test libqtgui4 libatlas-base-dev libhdf5-dev
                    
                    1

                    当然, 如果不会在树莓派上用到cv2.imshow等显示图片的方法, 也可以尝试安装更精简的opencv-python-headless包.

                    # 安装 WebSocket 服务器需要的包

                    这里使用Flask-Sockets^5 来提供对WebSocket通信的支持. 为此, 只需要运行pip安装:

                    pip3 install Flask-Sockets
                    
                    1

                    # 电脑上需要的环境

                    为了保证JavaScript脚本正常运行, 建议使用较新版本的Chrome/FireFox/Edge等主流浏览器.

                    # 基于 WEB 的远程视频监控示例

                    # 服务端代码

                    在树莓派上新建一个python文件.

                    # 用 nano 编辑器在当前文件夹新建 cv2_server.py
                    nano cv2_server.py
                    
                    1
                    2

                    向文件内写入下面的代码:

                    from flask import Flask
                    from flask_sockets import Sockets
                    import numpy as np
                    import cv2 as cv
                    
                    app = Flask(__name__)
                    sockets = Sockets(app)
                    cap = cv.VideoCapture(0)
                    
                    
                    @sockets.route('/send-frame')
                    def send_frame(ws):
                        while not ws.closed:
                            message = ws.receive()
                            if message == "":
                                continue
                            elif message == "update":
                                # Capture frame-by-frame
                                ret, frame = cap.read()
                                gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
                                ret, png = cv.imencode('.png', gray)
                                ws.send(png.tostring())
                            else:
                                continue
                    
                    
                    @app.route('/')
                    def hello():
                        return 'Hello World!'
                    
                    
                    if __name__ == "__main__":
                        from gevent import pywsgi
                        from geventwebsocket.handler import WebSocketHandler
                        server = pywsgi.WSGIServer(
                            ('0.0.0.0', 4242), app, handler_class=WebSocketHandler)
                        server.serve_forever()
                    
                    
                    1
                    2
                    3
                    4
                    5
                    6
                    7
                    8
                    9
                    10
                    11
                    12
                    13
                    14
                    15
                    16
                    17
                    18
                    19
                    20
                    21
                    22
                    23
                    24
                    25
                    26
                    27
                    28
                    29
                    30
                    31
                    32
                    33
                    34
                    35
                    36
                    37
                    38

                    上面的程序表示:

                    1. 调用树莓派外接摄像头;
                    2. 建立一个 WebSocket Server, 监听4242端口;
                    3. 视频服务的路由是/send-frame;
                    4. 在收到update时向客户端发送一帧png格式的灰度图像.

                    # 客户端代码

                    在电脑上新建一个index.html文件, 然后写入下面的内容(受到 github.com/estherjk/face-detection-node-opencv 启发^6 ):

                    <!DOCTYPE html>
                    
                    <html>
                      <head>
                        <meta charset=" utf-8" />
                        <script>
                          const wsurl = "ws://your.ip.of.raspberrypi:4242/send-frame";
                          const socket = new WebSocket(wsurl);
                          const img = new Image();
                    
                          function sendMsg() {
                            socket.send("update");
                            console.log("socket: send update");
                          }
                          function Uint8ToString(u8a) {
                            var CHUNK_SZ = 0x8000;
                            var c = [];
                            for (var i = 0; i < u8a.length; i += CHUNK_SZ) {
                              c.push(String.fromCharCode(...u8a.subarray(i, i + CHUNK_SZ)));
                            }
                            return c.join("");
                          }
                          function drawFrame(frame) {
                            var uint8Arr = new Uint8Array(frame);
                            var str = Uint8ToString(uint8Arr);
                            var base64String = btoa(str);
                    
                            img.onload = function() {
                              context.drawImage(this, 0, 0, canvas.width, canvas.height);
                            };
                            img.src = "data:image/png;base64," + base64String;
                          }
                    
                          socket.onopen = () => {
                            console.log("socket: connected");
                          };
                          socket.onmessage = msg => {
                            msg.data.arrayBuffer().then(buffer => {
                              drawFrame(buffer);
                              console.log("socket: frame updated");
                            });
                          };
                        </script>
                      </head>
                    
                      <body>
                        <canvas id="canvas-video" width="640" height="480"></canvas>
                        <script>
                          const canvas = document.getElementById("canvas-video");
                          const context = canvas.getContext("2d");
                    
                          // show loading notice
                          context.fillStyle = "#333";
                          context.fillText("Loading...", canvas.width / 2 - 30, canvas.height / 3);
                    
                          setInterval(() => {
                            socket.send("update");
                          }, 100);
                        </script>
                      </body>
                    </html>
                    
                    1
                    2
                    3
                    4
                    5
                    6
                    7
                    8
                    9
                    10
                    11
                    12
                    13
                    14
                    15
                    16
                    17
                    18
                    19
                    20
                    21
                    22
                    23
                    24
                    25
                    26
                    27
                    28
                    29
                    30
                    31
                    32
                    33
                    34
                    35
                    36
                    37
                    38
                    39
                    40
                    41
                    42
                    43
                    44
                    45
                    46
                    47
                    48
                    49
                    50
                    51
                    52
                    53
                    54
                    55
                    56
                    57
                    58
                    59
                    60
                    61

                    注意, 要用树莓派的IP替换上面的your.ip.of.raspberrypi, 比如我的IP是192.168.137.77. 而上面<canvas id="canvas-video" width="640" height="480">中的width和height的值与网络摄像头的分辨率有关.

                    上面的程序每 100ms 向服务端发送一个更新请求, 从而在网页上实时播放帧率是 10Hz 的视频监控. 你可以通过改变 setInterval(() => {socket.send("update");}, 100)中setInterval第二个参数(目前是100)来改变发送一个更新请求的间隔时间.

                    # 运行代码

                    为树莓派接上一个兼容linux的摄像头, 然后打开它.

                    树莓派远程视频监控系统

                    接下来在树莓派上运行服务端脚本:

                    python3 ./cap_server.py
                    
                    1

                    然后在电脑端用你的浏览器打开index.html. 效果是这样的:

                    效果是这样的