使用OpenCV,Flask,制作检测移动目标的网络监控

opencv 推流到rtmp服务 opencv 网络视频流_视频流

朋友们,这期的博客,我将向大家展示如何使用OpenCV ,Flask在局域网中访问视频流。

现在市场上有很多视频监控设备,我们可以通过手机轻松的访问这些监控设备的实时图像。


这些物美价廉的产品可以给我们的生活带来很大的便利。受这些产品的启发,作为视觉爱好者,我们也许不满足于这些功能,我们想让我们的监控摄像变得更加智能。上一篇博客,我向大家介绍脸OpenCV 的背景差分法,在这篇博客中,我将在上篇博客的基础上制作一个可以检测视场中是否有移动目标的网络视频监控

我把整个流程大致分为3步:

  1. 通过OpenCV 获取webcam或者树莓派相机。
  2. 使用背景差分法检测视场中的移动目标。
  3. 通过网络传输结果。

这种方法可以让我们同时通过多个设备或浏览器来访问输出的监控视频流。

让我们开始吧!

opencv 推流到rtmp服务 opencv 网络视频流_视频流_02

先介绍一下Flask,这是一个现在非常流行的小型的基于Python 的网络架构,非常适合与开发轻型的Web应用,所以我们选择使用Flask。

安装Flask非常简单:

pip3 install flask

准备好开发环境之后,让我们进入代码环节,我们新建一个名字叫webstreaming.py的文件首先import我们需要的库。

from flask import Response
from flask import Flask
from flask import render_template
import threading
import argparse
import imutils
import time
import cv2

这里我们使用OpenCV 中最常用的BackgroundSubtractorMOG2来进行背景差分,提取到移动的目标。

# 初始化背景差分法
fgbg = cv2.createBackgroundSubtractorMOG2()

为了可以让多个浏览器同时访问我们的视频流,我们需要对我们的输出视频流加一个线程锁。

# 初始化输出变量,并定义一个多线程锁,以防止在多个浏览器或者页面同时访问
# 输出视频流的时候
outputFrame = None
lock = threading.Lock()

为了让我们的浏览器可以观察到视频流,我写了一个index.html脚本,Flask会自动的帮我们把视频流发送到我们的浏览器上。

<html>
  <head>
    <title>局域网视频监控</title>
  </head>
  <body>
    <h1>局域网视频监控</h1>
    <img src="{{ url_for('video_feed') }}">
  </body>
</html>

我们需要先初始化一个Flask 对象。

# 初始化一个flask 对象
app = Flask(__name__)
@app.route("/")
def index():
   # return the rendered template
   return render_template("index.html")

我对每一帧图片使用背景差分法,并且只选择面积大于1000的前景区域,并绘制绿色的最小外接矩形。

def detect_motion():
	# 获取需要的全局变量
	global vs, outputFrame, lock

	# 从摄像机中不断读取图像
	while True:
		ret, frame = vs.read()
		# 缩小图像尺寸,加速计算
		frame = imutils.resize(frame, width=400)

		fgMask = fgbg.apply(frame)
		contours, heirarchy = cv2.findContours(fgMask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
		# 把当前的时间绘制在输出视频流中
		timestamp = datetime.datetime.now()
		cv2.putText(frame, timestamp.strftime(
			"%A %d %B %Y %I:%M:%S%p"), (10, frame.shape[0] - 10),
			cv2.FONT_HERSHEY_SIMPLEX, 0.35, (0, 0, 255), 1)

		for i in range(0, len(contours)):
			cnt = contours[i]
			area = cv2.contourArea(cnt)
			if(area>1000):

				x, y, w, h = cv2.boundingRect(cnt)
				cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)

		# 获取线程锁,当修改完输出后,释放线程锁
		with lock:
			outputFrame = frame.copy()

我们要制作一个生成器,来源源不断的生成视频流的帧,当我们更新帧的时候,我们要确保我们获取来线程锁。

def generate():
   # 获取全局变量
   global outputFrame, lock

   # 遍历输出视频流的帧
   while True:
      # 等待直到获取线程锁
      with lock:
         # 查看输出是否有内容,如果没有内容,跳过本次流程
         if outputFrame is None:
            continue

         # 把输出压缩成jpeg格式
         (flag, encodedImage) = cv2.imencode(".jpg", outputFrame)

         # 确保输出被正确压缩了
         if not flag:
            continue

      # 输出byte格式的结果
      yield(b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + 
         bytearray(encodedImage) + b'\r\n')

接下来我们来定义一个名字叫video_feed的函数来调用这个生成器。

@app.route("/video_feed")
def video_feed():
   # return the response generated along with the specific media
   # type (mime type)
   return Response(generate(),
      mimetype = "multipart/x-mixed-replace; boundary=frame")

函数前面的@app.route装饰器意味着,这是一个URL端点函数,数据产生的地址是:

http://your_ip_address/video_feed

video_feed的输出就是我们实时移动目标检测的结果,你的浏览器就会把这个结果显示出来供我们查看。

现在我们来写主函数

if __name__ == '__main__':
   ap = argparse.ArgumentParser()
   ap.add_argument("-i", "--ip", type=str, required=True,
      help="ip address of the device")
   ap.add_argument("-o", "--port", type=int, required=True,
      help="ephemeral port number of the server (1024 to 65535)")
   ap.add_argument("-f", "--frame-count", type=int, default=32,
      help="# of frames used to construct the background model")
   args = vars(ap.parse_args())

   # 开启目标检测线程
   t = threading.Thread(target=detect_motion)
   t.daemon = True
   t.start()

   # 启动flask app
   app.run(host=args["ip"], port=args["port"], debug=True,
      threaded=True, use_reloader=False)

# 释放资源
vs.stop()

我们通过ifconfig(Linux)或者ipconfig(windows)来查看我们的IP地址。


比如我看到我WIFI 的IP 地址是 192.168.31.224,我们就可以这样执行我们的python文件:

python3 webstreaming.py --ip 192.168.31.224 --port 8000

朋友们,下面就是见证奇迹的时刻了!

我们在当前这台运行设备(可以是PC,树莓派或者任何能运行python的设备)以及任何与这台设备处于同一局域网的设备上,访问这个网址,比如在这个例子里面,我可以用浏览器访问192.168.31.224:8000

opencv 推流到rtmp服务 opencv 网络视频流_opencv_03

opencv 推流到rtmp服务 opencv 网络视频流_opencv_04