2. 鲁班猫监控检测

本章将简单介绍一个摄像头监控检测示例,用户在浏览器上登录监控页面,登录后点击按钮可以进行视频录制和目标检测。 web程序采用的是基于python的flask框架,实现流媒体直播,图像是通过opencv调用摄像头获取,对图片检测处理使用npu。

  • 测试平台:lubancat 2

  • 板卡系统:Debian10 (带桌面)

  • Python版本:Python3.7

  • opencv版本:4.7.0.68

  • Toolkit Lite2:1.4.0

  • Flask:1.0.2

2.1. 依赖工具及库安装

实验测试使用lubancat 2,系统是Debian10,一些工具和库的安装(一些工具系统已经安装就不执行):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 安装工具
sudo apt update
sudo apt -y install git wget

# 安装python相关库等,默认使用python3
sudo apt -y install python3-flask python3-pil python3-numpy python3-pip

# 安装opencv-python相关库,测试是使用4.7.0.68版本,或者其他版本
sudo pip3 install opencv-contrib-python

# 安装rknn-toolkit-lite2,参考前面NPU使用章节

2.2. 视频流服务器和摄像头获取帧

2.2.1. 部署视频流服务器

在这个示例中,将使用Flask应用框架,构建一个web页面,一个实时视频流服务器。

提示

flask库简单使用可以参考前面 教程 , 或者 Flask 官方文档

Flask通过 /video_viewer 路由返回一个入参为生成器的Response对象。Flask将会负责调用生成器,进入循环,持续地将摄像头中获取的帧数据作为响应块返回, 并把所有部分的结果以块的形式发送给客户端。

示例程序lubancat-flask-opencv-rknn/controller/modules/home/views.py
 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
from flask import session, render_template, request, redirect, url_for, Response, jsonify
# 导入登录页面等
from controller.modules.home import home_blu
# 导入VideoCamera类,用于获取摄像头帧或者检测后的帧流等
from controller.utils.camera import VideoCamera
import time

video_camera = None
global_frame = None

# 主页
@home_blu.route('/')
def index():
    # 模板渲染
    username = session.get("username")
    if not username:
        return redirect(url_for("user.login"))
    return render_template("index.html")


# 获取视频流
def video_stream():
    global video_camera
    global global_frame

    if video_camera is None:
        video_camera = VideoCamera()

    while True:
        # 获取一系列单独的JPEG图片
        frame = video_camera.get_frame()
        time.sleep(0.01)
        if frame is not None:
            global_frame = frame
            yield (b'--frame\r\n'
                b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n\r\n')
        else:
            yield (b'--frame\r\n'
                b'Content-Type: image/jpeg\r\n\r\n' + global_frame + b'\r\n\r\n')

# 视频流
@home_blu.route('/video_viewer')
def video_viewer():
    # 模板渲染
    username = session.get("username")
    if not username:
        return redirect(url_for("user.login"))
    # 返回生成器的Response对象,multipart/x-mixed-replace类型,边界字符串为frame
    return Response(video_stream(),
                    mimetype='multipart/x-mixed-replace; boundary=frame')

在index.html这个简单的 HTML 页面中,其中的一个图片标签 <img id="video" src="{{ url_for('home.video_viewer') }}">, 将使用 url_for 指向路由 /video_viewer,浏览器会自动显示流中的图片,从而保持更新图片元素:

示例程序lubancat-flask-opencv-rknn/controller/templates/index.html(部分)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<body>
<h1 align="center" style="color: whitesmoke;">Flask+OpenCV+Rknn</h1>
<div class="top">
    <div class="recorder" id="recorder" align="center">
        <button id="record" class="btn">录制视频</button>
        <button id="stop" class="btn">暂停录制</button>
        <button id="process" class="btn">开启检测</button>
        <button id="pause" class="btn">暂停检测</button>
        <input type="button" class="btn" value="退出登录"
            onclick="javascrtpt:window.location.href='{{ url_for('user.logout') }}'">
        <a id="download"></a>
        <script type="text/javascript" src="{{ url_for('static', filename='button_process.js') }}"></script>
    </div>
</div>
<img id="video" src="{{ url_for('home.video_viewer') }}">
</body>

2.2.2. 摄像头中获取帧

从摄像头获取帧,通过导入封装好的VideoCamera类:

示例程序lubancat-flask-opencv-rknn/controller/utils/camera.py(有省略)
 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
class VideoCamera(object):
    def __init__(self):
        # 使用opencv,打开系统默认摄像头
        self.cap = cv2.VideoCapture(0)
        if not self.cap.isOpened():
            raise RuntimeError('Could not open camera.')

        # 创建RKNNLite对象
        self.rknn_lite = RKNNLite()

        # 设置帧宽和高度
        self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
        self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 640)
        # ...............

    # 退出程序释放资源
    def __del__(self):
        self.cap.release()
        self.rknn_lite.release()

    # 摄像头获取帧
    def get_frame(self):
        ret, self.frame = self.cap.read()

        if ret:
            # 图像检测处理
            if self.is_process:
                #self.image = cv2.cvtColor(self.frame, cv2.COLOR_BGR2RGB)
                self.image = cv2.cvtColor(self.frame, cv2.COLOR_BGR2RGB)
                self.outputs = self.rknn_lite.inference(inputs=[self.image])
                self.frame = process_image(self.image, self.outputs)
                #self.rknn_frame = process_image(self.image, self.outputs)
                #ret, image = cv2.imencode('.jpg', self.rknn_frame)
                #return image.tobytes()

            if self.frame is not None:
                # 图像编码压缩
                ret, image = cv2.imencode('.jpg', self.frame)
                # 将图像作为一个字节对象返回
                return image.tobytes()
        else:
            return None
    # ...............

2.3. NPU处理图像

使用NPU进行图像检测处理,本示例没有额外训练模型,直接使用官方Toolkit Lite2工具中的examples/onnx/yolov5例程,仅供实验演示用。 在板卡系统上使用RKNN Toolkit Lite2部署RKNN模型。

提示

板卡上RKNN Toolkit Lite2安装使用,可以参考前面 教程 , 或者 RK官方github文档

示例程序处理流程:

截取于鲁班猫监控检测示例程序
 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
# 创建RKNNLite对象
self.rknn_lite = RKNNLite()

#...................

def load_rknn(self):
    # 导入RKNN模型
    print('--> Load RKNN model')
    ret = self.rknn_lite.load_rknn(RKNN_MODEL)
    if ret != 0:
        print('Load RKNN model failed')
        exit(ret)
    # 调用init_runtime接口,初始化运行环境
    print('--> Init runtime environment')
    ret = self.rknn_lite.init_runtime()
    if ret != 0:
        print('Init runtime environment failed!')
        exit(ret)

#...................

# 对摄像头获取的图片进行处理,设置图片大小
self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 640)

#转换成RGB格式
self.image = cv2.cvtColor(self.frame, cv2.COLOR_BGR2RGB)

# 调用inference接口,对图片进行检测推理,返回结果
self.outputs = self.rknn_lite.inference(inputs=[self.image])

#根据npu处理结果,对图像进行后处理,返回处理后的图像
self.frame = process_image(self.image, self.outputs)

# 退出程序释放资源
def __del__(self):
    self.cap.release()
    # 用release接口,释放RKNNLite对象
    self.rknn_lite.release()

不用npu加速处理,图像检测识别处理可以直接使用opencv,感兴趣的同学可以自行研究代码,添加数据集训练,或者更换其他模型进行实验。 使用opencv库进行图像处理、数字识别功能在实验代码目录: controller/utils/opencvtest.py 中。

2.4. 适配具体环境

拉取实验代码:

# 在终端中输入如下命令,使用main分支:
git clone -b main https://gitee.com/LubanCat/lubancat-flask-opencv-rknn.git

修改服务器监听的IP及端口,大家根据实际情况修改:

# 进入 lubancat-flask-opencv-rknn 目录
cd ./lubancat-flask-opencv-rknn

# 修改 main.py 文件中的启动函数参数
vim main.py

# 默认设置为host="0.0.0.0",就是默认网口的ip
app.run(threaded=True, host="0.0.0.0", port=5000)

# 根据板卡环境,设置具体的ip
app.run(threaded=True, host="192.168.103.121",port=5000)
# 现在服务器监听的ip地址为192.168.103.121、端口为5000.

教程测试使用的是ov5648,mipi csi接口。实际使用需要确认下摄像头编号(一般是/dev/video0):

1、先确定摄像头编号:

# 进入Python3终端:
python3

# 导入opencv库包
import cv2

# 输入如下命令:
cap = cv2.VideoCapture(0)
cap.isOpened()
# 如窗口中打印出了True,则此设备编号可用。

# 确定编号后,释放摄像头资源
cap.release()

若出现任何报错,请将摄像头编号依次递增尝试,找到可用的设备编号。 编号递增上限为video设备的数量,可使用 ls  /dev/video* 命令查看。

2、修改实验代码,以适配自己的摄像头:

# 在终端中输入如下命令:
# 在lubancat-flask-opencv-rknn目录下进入opencv操作摄像头的代码目录中:
cd ./controller/utils/

# 修改 camera.py 文件中的 VideoCamera(object)函数:
vim camera.py

# 将 self.cap = cv2.VideoCapture(0) 语句中的函数参数0修改为你摄像头对应的设备编号,或者设备文件("/dev/video0")
self.cap = cv2.VideoCapture(0)

2.5. 测试实验

  1. 按照上一小节中的教程内容修改后,我们就可用运行实验代码来查看实验现象。

# 在工程代码目录lubancat-flask-opencv-rknn中,执行以下命令:
sudo python3 main.py

可用查看到如下实验现象:

broken

程序打印的提示信息,告诉我们服务器以及开始监听 http://0.0.0.0:5000 的地址,系统的默认网口ip。 如若想退出程序,按下 CTRL+C

这里通过在浏览器中输入网址: http://192.168.103.121:5000 , 来观察一下实验现象。

实验现象如图:

broken
  1. 登录完成后,进入到监控界面,点击 开启检测 进入到检测状态。

broken
  1. 一帧图像获取时间测试

通过python的time模块计算程序运行时间,来简单测试获取一帧图像需要的时间,以及经过npu处理后所需的时间。 在程序中加上运行时间计算:

controller/modules/home/views.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# 获取视频流
def video_stream():
    global video_camera
    global global_frame

    if video_camera is None:
        video_camera = VideoCamera()

    while True:
        start_time = time.time()
        frame = video_camera.get_frame()
        end_time = time.time()
        print('get_frame cost %f second' % (end_time - start_time))
        #time.sleep(0.01)
        if frame is not None:
            global_frame = frame
            yield (b'--frame\r\n'
                b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n\r\n')
        else:
            yield (b'--frame\r\n'
                b'Content-Type: image/jpeg\r\n\r\n' + global_frame + b'\r\n\r\n')

测试使用的是lubancat 2 ,鲁班猫监控检测示例程序,使用debian10系统,默认系统配置,使用Toolkit Lite2工具中yolov5例程模型,下面测试数据仅供参考。

在不开启npu检测图像的情况下,正常运行程序,一帧图像大约需要65ms,其中出现最快是23ms,最慢是121ms

开启npu处理图像,NPU默认600Mhz,运行:获取一帧图像大约需要345ms(包括获取摄像头图片,预处理图片,npu处理图片,图片后处理画框等时间)

设置NPU频率900Mhz测试:获取一帧图像大约需要314ms

# lubancat2  npu设置900Mhz:
echo 900000000 > /sys/class/devfreq/fde40000.npu/userspace/set_freq

鲁班猫监控检测示例,实现了简单的监控显示和目标检测功能,效果不是最优的,仅用于学习参考。