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将会负责调用生成器,进入循环,持续地将摄像头中获取的帧数据作为响应块返回,
并把所有部分的结果以块的形式发送给客户端。
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
,浏览器会自动显示流中的图片,从而保持更新图片元素:
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类:
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. 测试实验¶
按照上一小节中的教程内容修改后,我们就可用运行实验代码来查看实验现象。
# 在工程代码目录lubancat-flask-opencv-rknn中,执行以下命令:
sudo python3 main.py
可用查看到如下实验现象:
程序打印的提示信息,告诉我们服务器以及开始监听 http://0.0.0.0:5000
的地址,系统的默认网口ip。
如若想退出程序,按下 CTRL+C
。
这里通过在浏览器中输入网址: http://192.168.103.121:5000 , 来观察一下实验现象。
实验现象如图:
登录完成后,进入到监控界面,点击
开启检测
进入到检测状态。
一帧图像获取时间测试
通过python的time模块计算程序运行时间,来简单测试获取一帧图像需要的时间,以及经过npu处理后所需的时间。 在程序中加上运行时间计算:
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
鲁班猫监控检测示例,实现了简单的监控显示和目标检测功能,效果不是最优的,仅用于学习参考。