整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

简述SRS流媒体服务器相关技术

简述SRS流媒体服务器相关技术

.引言

阅读本文前,一定要阅读这篇文章,<<手把手搭建流媒体服务器详细步骤>>,能够帮助你,快速搭建Srs流媒体服务器,可以用作后期的学习。

Srs4.0流媒体支持的技术。

Srs官网:https://github.com/ossrs/srs

推流端支持

(1)RTMP

(2)HLS

(3)WebRTC

(4)SRT

(5)GB28181

拉流端支持

(1)RTMP

(2)HLS

(3)WebRTC

(4)SRT

(5)GB28181

注意:不管推流端推上来何种格式,Srs流媒体服务器都是可以经过协议转换,拉流端都可以拉取到不同协议。如推流端,推RTMP流,那拉流端,可以拉取HTTP流。另外SRS流媒体服务器是支持集群方案。

这里补充下这篇文章在搭建方面没有的知识点,《手把手搭建流媒体服务器详细步骤 》。如下几点:

(1)安全退出正在运行的流媒体服务器

sudo kill -SIGQUIT srs_pid。

(2)srs流媒体服务器默认是后台启动方式,如果是要方便GDB调试,则要修改配置文件为前台启动。

 listen 1935; 
 max_connections 1000; 
 #srs_log_tank file;
 #srs_log_file ./objs/srs.log; 
# 前台运? 6 daemon off;
 # 打印到终端控制台 
srs_log_tank console; 
 http_api { 
  enabled on; 
   listen 1985; 
  }

 http_server { 
  enabled on;
   listen 8081; # http监听端? 
  dir ./objs/nginx/html; 
   } 
 stats { 
  network 0;
 disk sda sdb xvda xvdb; 
  } 
 vhost __defaultVhost__ { 
 # hls darren 
  hls {
  enabled on;
   hls_path ./objs/nginx/html;
   hls_fragment 10; 
    hls_window 60; 
   } 
 # http-flv darren 
  http_remux { 
  enabled on; 
  mount [vhost]/[app]/[stream].flv; 
    hstrs on; 
 } 
 }

同样的执行方法:

./objs/srs -c conf/srs.conf

经过上面配置,就可以前台显示,如下:


(3)SRS是协程方案


1.源码目录

srs主要目录,有以下几个:

(1)trunk?录

如下界面:

(2)src下的源码目录

trunk目录下的src目录,其界面如下:

(3)src下的app目录

srs_app_async_call.cpp和srs_app_async_call.hpp作用是:可以?来执?异步任务,通过execute()函数 push任务,然后在cycle()执?,在cycle里面,用的是协程的方案。如下图所示:

srs_app_bandwidth.cpp和srs_app_bandwidth.hpp作用是:提供宽带测试接口。

srs_app_caster_flv.cpp和srs_app_caster_flv.hpp作用是:?持POST?个flv流到服务器,类似相当于RTMP的publish。

srs_app_config.cpp和srs_app_config.hpp作用是:读取配置文件,支持热更新。

srs_app_conn.cpp和srs_app_conn.hpp作用是:srs的基本连接,每个连接对应?个协程(不能开很多个线程,但可以开很多个协程),所有的连接都被管理。

srs_app_dash.cpp和srs_app_dash.hpp作用是:SrsDash 流媒体DASH业务 The MPEG-DASH encoder,transmux RTMP to DASH。

srs_app_dvr.cpp和srs_app_dvr.hpp作用是:SrsDvr 录制RTMP流的flv或者mp4?件。

srs_app_edge.cpp和srs_app_edge.hpp作用是:SrsEdgeRtmpUpstream 边缘节点业务,?如从源站拉流到边缘,边缘回溯到源站。

srs_app_encoder.cpp和srs_app_encoder.hpp作用是:转封装,SrsEncoder 可以使?多个ffmpeg来转换指定的流,最终调?SrsFFMPEG来转流。

srs_app_ffmpeg.cpp和srs_app_ffmpeg.hpp作用是:SrsFFMPEG 使?ffmpeg来转换流。

srs_app_forward.cpp和srs_app_forward.hpp作用是:SrsForwarder 将流转发到其他服务器

srs_app_fragment.hpp:SrsFragment 表示?个分?,如HLS分?、DVR分?或DASH分?。它是?个媒体?件,例如FLV或MP4,有持续时间。

srs_app_hds.cpp和srs_app_hds.hpp作用是:SrsHds 将RTMP转成Adobe HDS流。

srs_app_heartbeat.cpp和srs_app_heartbeat.hpp作用是:发送SrsHttpHeartbeat HHTP?跳包。

srs_app_hls.cpp和srs_app_hls.hpp作用是:HLS相关业务。

srs_app_hourglass.cpp和srs_app_hourglass.hpp作用是:SrsHourGlass 滴答tick的处理程序。

srs_app_http_api.cpp和srs_app_http_api.hpp作用是:SrsHttpApi HTTP业务API

srs_app_http_conn.cpp和srs_app_http_conn.hpp作用是:SrsHttpConn,HTTP连接,继承于SrsConnection。

srs_app_http_hooks.cpp和srs_app_http_hooks.hpp作用是:SrsHttpHooks HTTP勾?,HTTP回调API。

srs_app_http_static.cpp和srs_app_http_static.hpp作用是:SrsHttpStaticServer HTTP静态服务器实例,为HTTP静态?件和FLV/MP4视频点播服务

srs_app_http_stream.cpp和─ srs_app_http_stream.hpp作用是:SrsHttpStreamServer HTTP直播流服务,?持FLV/TS/MP3/AAC流。

srs_app_ingest.cpp和srs_app_ingest.hpp作用是:SrsIngester摄取?件/流/设备,?FFMPEG编码(可选), 通过RTMP推送到SRS(或其他RTMP服务器)

srs_app_listener.cpp和srs_app_listener.hpp作用是:TCP/UDP监听器。

srs_app_log.cpp和srs_app_log.hpp作用是:SrsFastLog ?志。

srs_app_pithy_print.cpp和srs_app_pithy_print.hpp作用是:SrsPithyPrint 收集信息,然后打印。

srs_app_process.cpp和srs_app_process.hpp作用是:SrsProcess启动和停?进程,当被终?时调?cycle重新启动进程。

srs_app_recv_thread.cpp和srs_app_recv_thread.hpp作用是:SrsHttpRecvThread HTTP数据读取,SrsPublishRecvThread推流数据读取,SrsQueueRecvThread从队列读取;SrsRecvThread封装的协程

srs_app_reload.cpp和srs_app_reload.hpp作用是:ISrsReloadHandler 重新读取配置?件的处理。

srs_app_rtmp_conn.cpp和srs_app_rtmp_conn.hpp作用是:SrsRtmpConn RTMP连接。

srs_app_rtsp.cpp和srs_app_rtsp.hpp作用是:SrsRtpConn RTSP连接,SrsRtspCaster RTSP业务。

srs_app_security.cpp和srs_app_security.hpp作用是:SrsSecurity 安全限制,主要是限制url。

srs_app_server.cpp和srs_app_server.hpp作用是:SrsServer SRS服务,对应的rtmp、rtsp、http-flv等等业务在这?启动。

srs_app_source.cpp和srs_app_source.hpp作用是:SrsSource 对应?个源,?持多个SrsConsumer来拉流,SrsSourceManager管理源, SrsMetaCache?于源缓存Meta数据,SrsConsumer源的消费者,SrsGopCache GOP缓存

srs_app_statistic.cpp和srs_app_statistic.hpp作用是:SrsStatistic流统计。

srs_app_st.cpp和srs_app_st.hpp作用是:SrsSTCoroutine协程相关

srs_app_thread.cpp和srs_app_thread.hpp作用是:SrsCoroutineManager协程管理。

srs_app_utility.cpp和srs_app_utility.hpp作用是:?具类:SrsPlatformInfo、SrsNetworkDevices、SrsMemInfo、SrsDiskStat等等。

(4)srs下的core目录

core目录下,有以下主要文件。

srs_core_autofree.cpp和srs_core_autofree.hpp作用是:通过栈上的?式构建?动释放堆申请的对象,这个设计还是?常值得我们学习。

srs_core.cpp和srs_core.hpp作用是:版本相关的?些信息。

srs_core_mem_watch.cpp和srs_core_mem_watch.hpp作用是:内存监测接?。

srs_core_performance.cpp和srs_core_performance.hpp作用是:性能测试相关。

srs_core_time.cpp和srs_core_time.hpp作用是:时间单位相关。

srs_core_version3.cpp和srs_core_version3.hpp作用是:版本信息。

(5)srs下的kernel目录

srs_kernel_aac.cpp和srs_kernel_aac.hpp作用是:SrsAacTransmuxer 合成AAC?频流,带ADTS header。

srs_kernel_balance.cpp和srs_kernel_balance.hpp作用是:SrsLbRoundRobin负载均衡,?于边缘节点拉流和其他多个服务器的功能。

srs_kernel_buffer.cpp和srs_kernel_buffer.hpp作用是:SrsBuffer读取字节的实?类。

srs_kernel_codec.cpp和rs_kernel_codec.hpp作用是:编码器相关,包括视频和?频,?常核?的?件;SrsFlvVideo?来检测FLV的video tag对应内容;SrsFlvAudio?来检测FLV的audio tag对应内容;SrsMaxNbSamples 256表示video最?的NALUS个数,audio最?的packet数量;SrsFrame存储帧,SrsAudioFrame 存储AAC帧SrsVideoFrame存储视频帧;SrsFormat编码器格式,包含了?个或者多个流,?如为RTMP format时,包含?个视频和?个?频帧。

srs_kernel_consts.cpp和srs_kernel_consts.hpp作用是:SRS的常量定义,?如播放的标记#defineSRS_CONSTS_LOG_PLAY "PLA",发布的标记#define SRS_CONSTS_LOG_CLIENT_PUBLISH"CPB",SRS_CONSTS_HTTP_XXX等HTTP响应码,SRS_CONSTS_RTSP_XXX RTSP响应码等等。

srs_kernel_error.cpp和srs_kernel_error.hpp作用是:返回值常量定义,ERROR_XXX;SrsCplxError 异常类。

srs_kernel_file.cpp和srs_kernel_file.hpp作用是:?件的读写,SrsFileWriter?件写?器,SrsFileReader?件读取器

srs_kernel_flv.cpp和srs_kernel_flv.hpp作用是:FLV SrsFlvDecoder解析,SrsFlvTransmuxer将RTMP转成FLV流。SrsSharedPtrMessage对应RTMP的消息。

srs_kernel_io.cpp和srs_kernel_io.hpp作用是:IO读写接?类。

srs_kernel_log.cpp和srs_kernel_log.hpp作用是:日志相关。

srs_kernel_mp3.cpp和srs_kernel_mp3.hpp作用是:SrsMp3Transmuxer将RTMP转成MP3流。

srs_kernel_mp4.cpp和srs_kernel_mp4.hpp作用是:SrsMp4Encoder MP4复?器。

srs_kernel_stream.cpp和srs_kernel_stream.hpp作用是:SrsSimpleStream?vector实现的简单的字节append类,主要在hls和http中使?,将来需要进?改进。

srs_kernel_ts.cpp和srs_kernel_ts.hpp作用是:SrsTsTransmuxer将RTMP流转成http-ts流,该?件实现了ts格式相关的接?。

srs_kernel_utility.cpp和srs_kernel_utility.hpp作用是:?具函数,?如bool srs_string_ends_with(std::string str,std::string flag)。

(6)srs下的libs目录

libs目录包含如下一些文件:

srs_lib_bandwidth.cpp和srs_lib_bandwidth.hpp作用是:srs-librtmp 客户端带宽统计。

srs_librtmp.cpp和srs_librtmp.hpp作用是:srs提供的客户端rtmp库。

srs_lib_simple_socket.cpp和srs_lib_simple_socket.hpp作用是:SimpleSocketStream rtmp客户端的socket封装。

注意:这些库,有可能是用到客户端上去。

(6)srs下的main目录

main目录包含如下一些文件:

srs_main_ingest_hls.cpp作用是:拉取hls发布到rtmp流媒体服务器。

srs_main_mp4_parser.cpp作用是:MP4 box解析。

srs_main_server.cpp作用是:srs流媒体服务器主??。

(7)srs下的protocol目录

流媒体相关的协议分析,protocol目录包含如下一些文件:

srs_http_stack.cpp和srs_http_stack.hpp作用是:HTTP协议分析。

srs_protocol_amf0.cpp和srs_protocol_amf0.hpp作用是:Amf0解析。

srs_protocol_format.cpp和srs_protocol_format.hpp作用是:SrsRtmpFormat继承了SrsFormat, 代表RTMP格式。

srs_protocol_io.cpp和srs_protocol_io.hpp作用是:协议数据读取的IO封装接?,?如ISrsProtocolReadWriter。

srs_protocol_json.cpp和srs_protocol_json.hpp作用是:json类。

srs_protocol_kbps.cpp和srs_protocol_kbps.hpp作用是:?特率统计相关。

srs_protocol_stream.cpp和srs_protocol_stream.hpp作用是:流读取,从ISrsReader读取数据到buffer??。

srs_protocol_utility.cpp和srs_protocol_utility.hpp作用是:协议?具函数。

srs_raw_avc.cpp和srs_raw_avc.hpp作用是:SrsRawH264Stream H264裸流解析,SrsRawAacStream AAC裸流解析。

srs_rtmp_handshake.cpp和srs_rtmp_handshake.hpp作用是:RTMP握?,包括SrsSimpleHandshake和SrsComplexHandshake。

srs_rtmp_msg_array.cpp和srs_rtmp_msg_array.hpp作用是:SrsMessageArray消息数组。

srs_rtmp_stack.cpp和srs_rtmp_stack.hpp作用是:RTMP协议栈。

(7)srs下的service目录

srs_service_conn.cpp和srs_service_conn.hpp作用是:ISrsConnection HTTP/RTMP/RTSP等对象的连接接?。

srs_service_http_client.cpp和srs_service_http_client.hpp作用是:SrsHttpClient HTTP客户端。

srs_service_http_conn.cpp和srs_service_http_conn.hpp作用是:HTTP连接 SrsHttpParser,SrsHttpMessage,SrsHttpResponseWriter,SrsHttpResponseReader。

srs_service_log.cpp和srs_service_log.hpp作用是:SrsConsoleLog?志相关。

srs_service_rtmp_conn.cpp和srs_service_rtmp_conn.hpp作用是:SrsBasicRtmpClient RTMP客户端类。

srs_service_st.cpp和srs_service_st.hpp作用是:对st-thread协程的封装。

srs_service_utility.cpp和srs_service_utility.hpp作用是:service组件的?具类。

(8)srs下的utest目录

由于篇幅原因,这里仅仅列出一部分。

srs_utest_amf0.cpp和srs_utest_amf0.hpp。

srs_utest_app.cpp和srs_utest_app.hpp。

srs_utest_avc.cpp和srs_utest_avc.hpp。

srs_utest_config.cpp和srs_utest_config.hpp。


2.总结

本篇文章主要介绍了SRS流媒体服务器的目录介绍以及具体文件是干什么的,大致能够对SRS流媒体服务器有个了解。希望能够帮助到想学习流媒体服务器的朋友。

欢迎关注,转发,点赞,收藏,分享,评论区讨论。

后期关于项目的知识,会在微信公众号上更新,如果想要学习项目,可以关注微信公众号“记录世界 from antonio”

本教程中,您将学习如何通过Flask和Python使用OpenCV将视频从网络摄像头流式传输到网页浏览器/HTML页面。

您的车被偷过吗?

我的车在周末就被偷了。让我告诉您,我很生气。

我不能透露太多的细节,因为这个案件还在调查中,但以下是我可以告诉您的:

大约六个月前,我和妻子从康涅狄格州的诺沃克搬到了宾夕法尼亚州的费城。我有一辆车,我不经常开,但还是留着它以备不时之需。

我们小区很难找到停车的地方,所以我需要一个车库。

我听说有个车库,就报名了,开始把车停在那里。

直到上个星期天。

我和妻子来到车库取车。我们打算开车到马里兰去看望我的父母,吃一些螃蟹(马里兰的螃蟹很有名)。

我走到我的车旁,取下车衣。

我立马就蒙圈了——这不是我的车。

我的车#$&@去哪里了?

几分钟后我就意识到一个现实——我的车被偷了。

在过去的一周中,我为即将出版的《树莓派电脑视觉》一书正在做的工作被打断了——我一直在与停车场的主人、费城警察局和我车上的GPS跟踪服务打交道,想弄清楚到底发生了什么。

在事情解决之前,我不能公开透露任何细节,但是让我告诉您,我正埋头处理警察报告、律师信件、还有保险索赔等一大堆的文件。

我希望下个月这个问题能得到解决——我讨厌分心,尤其是让我远离我最喜欢做的事情——教计算机视觉和深度学习。

我成功地利用我的挫折启发了一篇新的安全相关的计算机视觉博客帖子。

在这篇文章中,我们将学习如何使用Flask和OpenCV将视频流式传输到网页浏览器中。

您可以在不到5分钟的时间内将此系统部署到树莓派上:

  • 简单地安装所需的包/软件并启动脚本
  • 然后打开您的计算机/智能手机浏览器,并导航到URL/IP地址,就可以看到传输过来的视频了(并且确保您的任何东西没有被偷)。

没什么比一个小视频证据更能抓住小偷了。

当我继续与警察、保险等处理文书工作时,您就可以开始用树莓派相机武装自己,无论您在哪里生活和工作,都可以抓住坏人。

要学习如何使用OpenCV和Flask将视频流式传输到一个网页浏览器的HTML页面,请继续阅读!

OpenCV——将视频流式传输到网页浏览器/HTML页面

在本教程中,我们将首先讨论Flask,它是一个用于Python编程语言的微型web框架。

我们将学习运动检测的基本知识,以便我们可以将它应用到我们的项目中。我们将通过一个背景减法器来实现运动检测。

在此基础上,我们将Flask与OpenCV结合,这样我们就能够:

  1. 访问来自树莓派相机模块或USB网络摄像头的帧。
  2. 处理帧并应用一个任意的算法(这里我们将使用背景去除/运动检测,但您也可以应用图像分类,对象检测等)。
  3. 将结果流式传输到一个网页页面/网页浏览器。

此外,我们将涉及的代码将能够支持多个客户端(即,不止一个人/网页浏览器/标签页能同时访问流媒体),这是您在网上看到的绝大多数例子都无法处理的。

把所有这些过程块放在一起,我们就会得到一个能够执行运动检测的家庭监控系统,然后它会将视频结果流式传输到您的网页浏览器中。

让我们开始吧!

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和网络流媒体进行视频监控。

通过使用背景去除进行运动检测,我们可以检测到我在椅子上的运动。

我们的运动检测算法将通过背景减除的形式来检测运动。

大多数背景去除算法的工作原理是:

  1. 累积相加前N帧的加权平均值
  2. 取当前帧并从帧的加权平均值中减去它
  3. 对去除的输出进行阈值处理,以突出像素值差异较大的区域(“白色”表示前景,“黑色”表示背景)
  4. 应用基本的图像处理技术如腐蚀和膨胀来消除噪声
  5. 利用轮廓检测提取运动区域

我们的运动检测实现将位于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 网络框架结合,这使得我们能够:

  1. 从RPi摄像机模块/USB网络摄像头访问帧。
  2. 对每个帧应用背景去除/运动检测。
  3. 将结果流式传输到一个网页页面/网页浏览器。

此外,我们的实现支持多个客户端、浏览器或选项卡——这在大多数其它实现中都找不到。

无论何时您需要将帧从一个设备流式传输到一个网络浏览器时,一定要使用此代码作为一个模板/起点。

英文原文:https://www.pyimagesearch.com/2019/09/02/opencv-stream-video-to-web-browser-html-page

译者:Nothing

览器端发起 HTTP 请求流程

  • 构建请求

浏览器构建请求报文信息,构建好后,浏览器准备发起网络

请求行

GET /index.html HTTP1.1

发送请求行,就是告诉服务器浏览器需要什么资源,最常用的请求方法是 Get。

请求体

如果使用 POST 方法,那么浏览器还要准备数据给服务器,这里准备的数据是通过请求体来发送。

请求头

请求行之后,浏览器器以请求头形式发送其他一些信息,把浏览器的一些基础信息告诉服务器。比如包含了浏览器所使用的操作系统、浏览器内核等信息,以及当前请求的域名信息、浏览器端的 Cookie 信息,等等。

  • 查找缓存

浏览器在发送请求之前先在浏览器缓存(是一种在本地保存资源副本,以供下次请求时直接使用的技术)中查询是否有要请求的文件。 当浏览器发现请求的资源已经在浏览器缓存中存有副本,它会拦截请求,返回该资源的副本,并直接结束请求,而不会再去源服务器重新下载。 好处如下:

缓解服务器端压力,提升性能(获取资源的耗时更短了);

对于网站来说,缓存是实现快速资源加载的重要组成部分。 如果查找缓存失败,则进入网络请求过程。

  • 准备IP和端口号

IP 需要用到DNS来查找对应的IP地址

端口号 HTTP 协议默认是 80 端口,HTTPS协议是443端口。

  • 等待TCP队列

不是说准备好了IP和端口号了就可以直接发送TCP连接的。 Chrome 有个机制,默认情况下同一个域名同时最多只能建立 6 个 TCP 连接,如果在同一个域名下同时有 10 个请求发生,那么其中 4 个请求会进入排队等待Pending状态,直至进行中的请求完成。

  • 发送HTTP请求

一旦建立了 TCP 连接,浏览器就可以和服务器进行通信了。而 HTTP 中的数据正是在这个通信过程中传输的。

浏览器是如何发送请求信息给服务器的过程如下

在浏览器发送请求行命令之后,还要以请求头形式发送其他一些信息,把浏览器的一些基础信息告诉服务器。比如包含了浏览器所使用的操作系统、浏览器内核等信息,以及当前请求的域名信息、浏览器端的 Cookie 信息,等等。

服务器端处理 HTTP 请求流程

  • 返回请求

你可以通过工具软件 curl 来查看返回请求数据curl -i https://time.geekbang.org/注意这里加上了-i是为了返回响应行、响应头和响应体的数据。

首先服务器会返回响应行,包括协议版本和状态码。但并不是所有的请求都可以被服务器处理的,那么一些无法处理或者处理出错的信息,怎么办呢?服务器会通过请求行的状态码来告诉浏览器它的处理结果

正如浏览器会随同请求发送请求头一样,服务器也会随同响应向浏览器发送响应头。响应头包含了服务器自身的一些信息,比如服务器生成返回数据的时间、返回的数据类型(JSON、HTML、流媒体等类型),以及服务器要在客户端保存的 Cookie 等信息。发送完响应头后,服务器就可以继续发送响应体的数据,通常,响应体就包含了 HTML 的实际内容。

  • 断开连接

通常情况下,一旦服务器向客户端返回了请求数据,它就要关闭 TCP 连接。不过如果浏览器或者服务器在其头信息中加入了:Connection:Keep-Alive 那么 TCP 连接在发送后将仍然保持打开状态,这样浏览器就可以继续通过同一个 TCP 连接发送请求。保持 TCP 连接可以省去下次请求时需要建立连接的时间,提升资源加载速度。比如,一个 Web 页面中内嵌的图片就都来自同一个 Web 站点,如果初始化了一个持久连接,你就可以复用该连接,以请求其他资源,而不需要重新再建立新的 TCP 连接。

  • 重定向

比如当你在浏览器中打开 geekbang.org 后,你会发现最终打开的页面地址是 https://www.geekbang.org。curl -I geekbang.org

从图中你可以看到,响应行返回的状态码是 301,状态 301 就是告诉浏览器,我需要重定向到另外一个网址,而需要重定向的网址正是包含在响应头的 Location 字段中,接下来,浏览器获取 Location 字段中的地址,并使用该地址重新导航,这就是一个完整重定向的执行流程。

Q: 为什么很多站点第二次打开速度会很快?

如果第二次页面打开很快,主要原因是第一次加载页面过程中,缓存了一些耗时的数据--DNS 缓存页面资源缓存 缓存处理过程如图所示

从上图的第一次请求可以看出,当服务器返回 HTTP 响应头给浏览器时,浏览器是通过响应头中的 Cache-Control 字段来设置是否缓存该资源。通常,我们还需要为这个资源设置一个缓存过期时长,而这个时长是通过 Cache-Control 中的 Max-age 参数来设置的,比如上图设置的缓存过期时间是 2000 秒。

Cache-Control:Max-age=2000

这也就意味着,在该缓存资源还未过期的情况下, 如果再次请求该资源,会直接返回缓存中的资源给浏览器。 但如果缓存过期了,浏览器则会继续发起网络请求,并且在 HTTP 请求头中带上:

If-None-Match:"4f80f-13c-3a1xb12a"

服务器收到请求头后,会根据 If-None-Match 的值来判断请求的资源是否有更新。

如果没有更新,就返回 304 状态码,相当于服务器告诉浏览器:“这个缓存可以继续使用,这次就不重复发送数据给你了。”如果资源有更新,服务器就直接返回最新资源给浏览器。


简要来说,很多网站第二次访问能够秒开,是因为这些网站把很多资源都缓存在了本地,浏览器缓存直接使用本地副本来回应请求,而不会产生真实的网络请求,从而节省了时间。同时,DNS 数据也被浏览器缓存了,这又省去了 DNS 查询环节。