OpenCV:将视频流式传输到网页浏览器/HTML页面
在本教程中,您将学习如何通过Flask和Python使用OpenCV将视频从网络摄像头流式传输到网页浏览器/HTML页面。
您的车被偷过吗?
我的车在周末就被偷了。让我告诉您,我很生气。
我不能透露太多的细节,因为这个案件还在调查中,但以下是我可以告诉您的:
大约六个月前,我和妻子从康涅狄格州的诺沃克搬到了宾夕法尼亚州的费城。我有一辆车,我不经常开,但还是留着它以备不时之需。
我们小区很难找到停车的地方,所以我需要一个车库。
我听说有个车库,就报名了,开始把车停在那里。
直到上个星期天。
我和妻子来到车库取车。我们打算开车到马里兰去看望我的父母,吃一些螃蟹(马里兰的螃蟹很有名)。
我走到我的车旁,取下车衣。
我立马就蒙圈了——这不是我的车。
我的车#$&@去哪里了?
几分钟后我就意识到一个现实——我的车被偷了。
在过去的一周中,我为即将出版的《树莓派电脑视觉》一书正在做的工作被打断了——我一直在与停车场的主人、费城警察局和我车上的GPS跟踪服务打交道,想弄清楚到底发生了什么。
在事情解决之前,我不能公开透露任何细节,但是让我告诉您,我正埋头处理警察报告、律师信件、还有保险索赔等一大堆的文件。
我希望下个月这个问题能得到解决——我讨厌分心,尤其是让我远离我最喜欢做的事情——教计算机视觉和深度学习。
我成功地利用我的挫折启发了一篇新的安全相关的计算机视觉博客帖子。
在这篇文章中,我们将学习如何使用Flask和OpenCV将视频流式传输到网页浏览器中。
您可以在不到5分钟的时间内将此系统部署到树莓派上:
- 简单地安装所需的包/软件并启动脚本
- 然后打开您的计算机/智能手机浏览器,并导航到URL/IP地址,就可以看到传输过来的视频了(并且确保您的任何东西没有被偷)。
没什么比一个小视频证据更能抓住小偷了。
当我继续与警察、保险等处理文书工作时,您就可以开始用树莓派相机武装自己,无论您在哪里生活和工作,都可以抓住坏人。
要学习如何使用OpenCV和Flask将视频流式传输到一个网页浏览器的HTML页面,请继续阅读!
OpenCV——将视频流式传输到网页浏览器/HTML页面
在本教程中,我们将首先讨论Flask,它是一个用于Python编程语言的微型web框架。
我们将学习运动检测的基本知识,以便我们可以将它应用到我们的项目中。我们将通过一个背景减法器来实现运动检测。
在此基础上,我们将Flask与OpenCV结合,这样我们就能够:
- 访问来自树莓派相机模块或USB网络摄像头的帧。
- 处理帧并应用一个任意的算法(这里我们将使用背景去除/运动检测,但您也可以应用图像分类,对象检测等)。
- 将结果流式传输到一个网页页面/网页浏览器。
此外,我们将涉及的代码将能够支持多个客户端(即,不止一个人/网页浏览器/标签页能同时访问流媒体),这是您在网上看到的绝大多数例子都无法处理的。
把所有这些过程块放在一起,我们就会得到一个能够执行运动检测的家庭监控系统,然后它会将视频结果流式传输到您的网页浏览器中。
让我们开始吧!
Flask web框架
图1:Flask是Python的一个微型web框架(image source)。
在本节中,我们将简要讨论Flask web框架以及如何在您的系统中安装它。
Flask是一个非常流行的用Python编程语言编写的微型web框架。
与Django一样,Flask是使用Python构建web应用程序时最常见的web框架之一。
但是,与Django不同的是,Flask非常轻量,这使得使用它构建基本的web应用程序非常容易。
正如我们将在本节中看到的,我们只需要一小部分代码就可以使用Flask实现实时流式传输视频——其余的代码包括(1)OpenCV和访问我们的视频流,或者(2)确保我们的代码是线程安全的,并且可以处理多个客户端。
如果您需要在一个机器上安装Flask,您只需要简单地按以下命令进行操作:
安装了它之后,您可以继续安装NumPy、OpenCV和imutils:
注意:如果您想要完整安装包括“非免费”(专利)算法的OpenCV,那您一定要从源代码来编译OpenCV。
项目结构
在我们继续之前,让我们看看我们项目的目录结构:
为了执行背景去除和运动检测,我们将实现一个名为SingleMotionDetector的类——该类将位于singlemotiondetector.py文件中pyimagesearch的motion_detection子模块中。
webstreaming.py文件将使用OpenCV访问我们的网络摄像头,通过SingleMotionDetector执行运动检测,然后通过Flask web框架将输出帧提供给我们的网络浏览器。
为了让我们网络浏览器能有东西显示,我们需要使用HTML填充index.html的内容来提供接收到的视频。我们只需要插入一些基本的HTML标记——Flask将实际处理将视频流发送到我们的浏览器的事情。
实现一个基本的运动检测器
图2:使用Raspberry Pi、OpenCV、Flask和网络流媒体进行视频监控。
通过使用背景去除进行运动检测,我们可以检测到我在椅子上的运动。
我们的运动检测算法将通过背景减除的形式来检测运动。
大多数背景去除算法的工作原理是:
- 累积相加前N帧的加权平均值
- 取当前帧并从帧的加权平均值中减去它
- 对去除的输出进行阈值处理,以突出像素值差异较大的区域(“白色”表示前景,“黑色”表示背景)
- 应用基本的图像处理技术如腐蚀和膨胀来消除噪声
- 利用轮廓检测提取运动区域
我们的运动检测实现将位于SingleMotionDetector类中,该类可以在SingleMotionDetector .py中找到。
我们称之为一个“单一运动检测器”,因为其算法本身只对寻找单一的、最大的运动区域感兴趣。
我们也可以很容易地扩展这个方法来处理多个运动区域。
让我们来实现该运动检测器。
打开singlemotiondetector.py文件,并插入以下代码:
第2-4行处理所需的导入。
所有这些都是相当标准的,包括用于数字处理的NumPy、用于为我们提供方便函数的imutils和用于OpenCV绑定的cv2。
然后我们在第6行定义SingleMotionDetector类。这个类接受一个可选的参数accumWeight,它是用于累积加权平均值的因数。
accumWeight越大,在累积加权平均值时,背景(bg)被考虑的越少。
相反地,accumWeight越小,在计算平均值时考虑背景(bg)就会越多。
设置accumWeight=0.5会均匀地加权背景和前景——我经常建议使用这个设置作为起始值(然后您可以根据自己的实验调整它)。
接下来,让我们定义update方法,它将接受一个输入帧并计算加权平均值:
为了防止我们的bg帧为None(这意味着update从未被调用),我们只需要存储bg帧(第15-18行)。
否则,我们会计算输入帧、现有背景bg和相应的accumWeight因子之间的加权平均值。
鉴于我们的背景bg,我们现在可以通过detect方法应用运动检测:
detect方法需要一个参数和一个可选参数:
- image: 将要被应用运动检测的输入帧/图像。
- tVal: 用于将一个特定像素标记为 “运动” 或 非运动的阈值。
给定我们的输入image,我们计算该image和bg之间的绝对误差(第27行)。
任何差异>tVal的像素位置都被设置为255(白色;前景),否则设置为0(黑色;背景)(28行)。
通过一系列的腐蚀和膨胀来消除噪声和小的局部运动区域,否则这些区域会被认为是假阳性的(可能是由于反射或光线的快速变化)。
下一步是应用轮廓检测提取任何运动区域:
第37-39行对我们的thresh图像执行轮廓检测。
然后,我们初始化两组记账变量,以跟踪包含任何运动的位置(第40行和第41行)。这些变量将形成“边界框”,它将告诉我们运动发生的位置。
最后一步是填充这些变量(假设运动存在于帧中,当然是这样):
在43-45行中,我们检查我们的轮廓列表是否为空。
如果是这种情况,那么在帧中就找不到运动,我们就可以放心地忽略它。
否则,在帧中确实存在运动,所以我们就需要开始在轮廓线上进行循环(第48行)。
对于每个轮廓,我们计算其边界框,然后更新我们的记账变量(第47-53行),找到所有运动发生的最小和最大(x, y)坐标。
最后,我们将边界框位置返回给调用函数。
结合OpenCV和Flask
图3:OpenCV和Flask (一个Python微型网络框架)是涉及Raspberry Pi和类似硬件的网络流媒体和视频监控项目的完美搭档。
让我们继续,将OpenCV和Flask结合起来,从一个视频流(运行在树莓派上)向一个网络浏览器提供帧。
打开您项目结构中的webstreaming.py 文件,并插入如下代码:
第2-12行处理我们需要的导入:
- 第2行 导入我们上边实现的SingleMotionDetector类
- VideoStream类 (第3行 ) 将允许我们访问我们的Raspberry Pi 相机模块或 USB网络摄像头.
- 第4到6行处理导入我们需要的Flask包——我们将使用这些包呈现我们的index.html模板,并将其提供给客户端。
- 第7行导入threading库,确保我们可以支持并发 (比如,同时使用多个客户端、网络浏览器和选项卡)。
让我们继续执行一些初始化:
首先,我们在第17行初始化我们的outputFrame——这将是将提供给客户端的帧(投递运动检测)。
然后,我们在第18行创建一个lock,它将在更新ouputFrame时被用来确保线程安全行为(即确保某个帧在更新时不被任何线程尝试读取)。
第21行初始化我们的Flask app本身,而第25-27行访问我们的视频流:
- 如果您正在使用一个USB网络摄像头, 您可以保持代码不变。
- 否则,如果您正在使用一个RPi相机模块,那您应该取消掉第25行的注释,并将第26行注释掉。
下一个函数index将会渲染我们的index.html模板并提供输出视频流:
这个函数非常简单—它所做的就是在我们的HTML文件中调用Flask render_template。
我们将在下一章查看该index.html文件,因此,我们将推迟对此文件内容的进一步讨论直到那个时候。
我们的下一个函数的功能是:
- 对我们视频流中的帧进行循环
- 应用运动检测
- 在outputFrame上绘制任何结果
而且,这个函数必须以线程安全的方式来执行所有这些操作,以确保支持并发。
现在让我们看看这个函数:
我们的detection_motion函数接受单个参数frameCount,它是在SingleMotionDetector类中构建我们的背景bg所需的最小帧数:
- 如果我们没有至少frameCount帧,我们将会继续计算累计加权平均值
- 一旦frameCount达到了,我们将执行背景去除。
第37行获取对三个变量的全局引用:
- vs: 我们实例化的VideoStream对象
- outputFrame: 将提供给客户端的输出帧
- lock: 在更新outputFrame之前我们必须获得的线程锁。
第41行使用一个accumWeight=0.1值来初始化我们的SingleMotionDetector 类,这意味着在计算加权平均值时,bg值的权重会更高。
第42行初始化到目前为止读取的帧的total数——我们需要确保已经读取了足够多的帧来构建我们的背景模型。
从那里,我们将能够执行背景去除。
这些初始化完成后,我们现在可以开始对来自相机的帧进行循环:
第48行读取来自我们相机的frame帧,而第49-51行执行预处理操作,包括:
- 调整宽度为400 px (我们的输入帧越小, 数据量就越小,因此,我们的算法运行的就越快)。
- 转换成灰阶图。
- 高斯模糊(来减少噪声)。
然后,我们获取当前时间戳并将其绘制在frame上(第54-57行)。
在进行一次最终检查之后,我们就可以执行运动检测:
在第62行中,我们确保我们至少读取了frameCount帧来构建我们的背景去除模型。
如果是这样,我们将应用我们运动检测器的.detect运动,它将返回单个变量motion。
如果motion是None,那么我们就知道当前frame中没有发生运动。否则,如果motion不是None(第67行),那么我们需要在frame上绘制运动区域的边界框坐标。
第76行更新我们的运动检测背景模型,而第77行增加了迄今为止从摄像机读取的帧的total数。
最后,第81行获得支持线程并发所需的lock,而第82行设置outputFrame。
我们需要获取锁,以确保我们在试图更新outputFrame变量时客户机不会意外读取它。
我们的下一个函数,generate,是一个Python生成器,用于将我们的outputFrame编码为JPEG数据——现在让我们来看看它:
第86行获取对outputFrame和lock的全局引用,类似于detect_motion函数。
然后generate在第89行启动一个无限循环,这个循环将一直持续到我们结束脚本。
在循环内部,我们:
- 首先获取lock (第91行 ).
- 确保outputFrame非空(第94行 ), 如果一个帧被从摄像机传感器中丢弃,那么这种情况就有可能发生。
- 在第98行将frame编码为一个JPEG 图像——在这里执行JPEG压缩以减少网络负载,并且确保帧的快速传输。
- 检查成功flag是否失败(第101行和102行),这意味着如果此JPEG压缩过程失败,我们就应该忽略该帧。
- 最后,将编码的JPEG以一个字节数组提供给一个可以解析它的网络浏览器。
在这么短的代码中做了这么多工作,所以一定要检查这个函数几次,以确保您了解它是如何工作的。
下一个函数video_feed会调用我们的generate函数:
注意这个app.route函数签名,就像上面的index函数一样。
这个app.route签名告诉Flask这个函数是一个URL端点,数据是从http://your_ip_address/video_feed提供的。
video_feed的输出是实时运动检测输出,通过generate函数编码为一个字节数组。您的网络浏览器非常聪明,可以将这个字节数组作为一个实时输出显示在您的浏览器中。
我们最后的代码块处理解析命令行参数和启动Flask应用程序的任务:
第118-125行处理解析命令行参数的任务。
这里我们需要三个参数,包括:
- --ip: 您运行webstream.py文件的系统的IP地址。
- --port: Flask应用程序的运行端口号(对这个参数,您通常只需要提供一个值8000)。
- --frame-count: 在执行运动检测之前用于累计和构建背景模型的帧数。默认情况下,我们使用32帧来构建背景模型。
第128-131行启动一个线程用于执行运动检测。
使用一个线程确保detect_motion函数可以安全地在后台运行——它将不断地运行和更新我们的outputFrame,以便我们可以为我们的客户端提供任何运动检测结果。
最后,第134和135行启动Flask应用程序本身。
HTML页面结构
正如我们在webstreaming.py中看到的,我们正在渲染一个名为index.html的HTML模板。
该模板本身由Flask 网络框架进行填充,然后提供给网络浏览器。
然后,您的网络浏览器将生成的HTML呈现到您的屏幕上。
让我们检查一下index.html文件的内容:
我们可以看到,这是一个超级基础的网页;但是,请密切注意第7行——注意我们如何指示Flask动态呈现我们video_feed路径的URL。
由于video_feed函数负责提供来自我们网络摄像头的帧,所以图像的src将会被自动填充上我们的输出帧。
然后,我们的网络浏览器足够智能,可以正确渲染网页并提供实时视频流。
将各部分整合在一起
现在我们已经写完了我们项目的代码,让我们对其进行测试。
打开一个终端,执行以下命令:
正如您在视频中看到的,我从多个浏览器打开了到Flask/OpenCV服务器的连接,每个浏览器都有多个选项卡。我甚至拿出我的iPhone,并打开了一些来自那里的连接。服务器没有跳过一个帧,持续地使用Flask和OpenCV可靠地提供帧。
加入嵌入式计算机视觉和深度学习革命!
我第一次弹吉他是在20年前,那时我还在上中学。我不太擅长,几年后我就放弃了。回顾过去,我坚信我没有坚持下去的原因是因为我没有以一种实际的、动手操作的方式来学习。
相反,我的音乐老师一直向我脑子里灌输理论——但作为一个11岁的孩子,我只是想弄清楚我是否喜欢弹吉他,更不用说我是否想研究音乐背后的理论了。
大约一年半以前,我决定重新开始上吉他课。这一次,我特意找了一位能够将理论与实践相结合的老师,教我如何在学习理论技巧的同时演奏歌曲或即兴重复乐段。
结果呢?我的手指速度比以往任何时候都快,我的节奏也很准,我可以不断地惹恼我的妻子,在我的Les Paul上演奏Sweet Child of Mine。
我的观点是,无论何时您在学习一项新技能时,无论是计算机视觉,还是使用树莓派进行渗透,甚至是弹吉他,最快、最简单的学习方法之一就是围绕这项技能设计(小的)现实世界的项目,并尝试解决它。
对于吉他来说,这意味着学习短的即兴重复,这不仅教会了我真正的歌曲的一部分,也给了我一个宝贵的技巧(例如,掌握一种特定的五声音阶)。
在计算机视觉和图像处理中,您的目标应该是思考一些小型项目,然后尝试解决它们。不要太快就把事情复杂化,那是失败的秘诀。
相反,您可以拿一本我写的《树莓派计算机视觉》书,读一读,把它作为您个人项目的一个启动平台。
当您读完之后,回到那些最激励您的章节,看看您能如何以某种方式扩展它们(即使只是将相同的技术应用到一个不同的场景中)。
解决您思考的小项目不仅会让您对这个主题感兴趣(因为您自己想到了它们),而且它们还会教您实践技能。
今天的教程——运动检测和流式传输到网络浏览器——就是这样一个迷您项目的一个很好的起点。我希望既然您已经阅读了本教程,您已经对如何将这个项目扩展到您自己的应用程序进行了思考。
但是,如果您有兴趣学习更多……
我的新书《树莓派计算机视觉》中有40多个与嵌入式计算机视觉+物联网(IoT)相关的项目。您可以根据书中的项目进行构建来解决家里、公司甚至客户的问题。每一个项目都有着重方向:
- 通过实践进行学习
- 卷起您的袖子
- 对代码和实现进行实际测试
- 使用树莓派构建实际的、实用的项目
几个精品项目包括:
- 昼夜野生动物监视
- 交通工具计数和车速检测
- 深度学习分类、对象检测和资源受限设备上的实例分割
- 手势识别
- 基本的机器人导航
- 安全应用程序
- 教室出勤情况
- 等等更多!
本书还涵盖了使用谷歌Coral和Intel Movidius NCS协作处理器(Hacker + Complete Bundles)的深度学习。当需要更多的深度学习能力时,我们还将引入NVIDIA Jetson Nano(Complete Bundle)。
如果您错过了Kickstarter,您可以看看我的公告视频:
您准备好和我一起学习计算机视觉和如何应用嵌入式设备(如树莓派、Google Coral和NVIDIA Jetson Nano)了吗?
如果是这样,请使用下面的链接查看这本书!
总结
在本教程中,您学习了如何将帧从一个服务器机器流式传输到一个客户端网络浏览器。使用这个网络流式传输程序,我们能够构建一个基本的安全应用程序来监控我们房子里的一个房间的运动。
背景去除是计算机视觉中最常用的一种方法。通常,这些算法的计算效率很高,这使得它们适合于资源受限的设备,如树梅派。
在实现了我们的背景去除器后,我们将其与Flask 网络框架结合,这使得我们能够:
- 从RPi摄像机模块/USB网络摄像头访问帧。
- 对每个帧应用背景去除/运动检测。
- 将结果流式传输到一个网页页面/网页浏览器。
此外,我们的实现支持多个客户端、浏览器或选项卡——这在大多数其它实现中都找不到。
无论何时您需要将帧从一个设备流式传输到一个网络浏览器时,一定要使用此代码作为一个模板/起点。
英文原文:https://www.pyimagesearch.com/2019/09/02/opencv-stream-video-to-web-browser-html-page
译者:Nothing