这段时间有个项目需要捕获摄像头的画面,做轨迹分析之后再做显示。做了四天的调研,虽然结果我还不是特别满意,但也颇有收获,在这里做一下总结。
整体的结构大概是这样的:
python程序从摄像头的RTSP视频流种获取画面的每一帧,经过加工处理合并之后调用ffmepg将视频帧推送给nginx部署的rtmp视频流服务,最后前端通过flv.js做画面的展示。
在windows环境下,经验证,ffmepg推送视频流的方案会有2~5秒的延迟,请读者悉知。
什么是RTSP和RTMP视频流?
请参考以下博客:
https://blog.csdn.net/daocaokafei/article/details/127098972
http://www.360doc.com/content/22/0602/08/71430804_1034203806.shtml
呃~~~其实我也觉得我们只需要知道怎么使用就可以了,OK,我们还是直接上干货。
首先为什么是nginx-html-flv
模块?
据我调查,传统的nginx是不支持配置rtmp服务配置的,想要配置rtmp视频流服务就必须要先安装编译rtmp功能插件。
你需要去下载一个带Gryphon版本的nginx,已经内置了rtmp服务模块,你只需要稍加配置就可以了。
可参考以下博客:
https://blog.csdn.net/bashendixie5/article/details/110728728
rtmp服务部署好之后,有一个前端播放rtmp视频流的方案,videojs
。
一番倒腾之后发现有以下缺点:
而且videojs从6.xx版本之后就不再支持rtmp协议了,最新的videojs的版本为7.xx,所以首先就需要下载5.xx的videojs的版本。
为了方便大家,贴一个自己的链接:
但是使用之后仍然出现以下画面:
而在QQ浏览器中可以正常显示,延迟同样在2~5s左右:
所以证实了有浏览器兼容性的问题。
经验证,和google浏览器出现相同的问题。
No compatible source was found for this video.
归根结底,我觉得还是flash插件惹的祸。那么有没有不依赖flash插件播放视频流的方案呢?
有的,flv.js
。他不依赖flash插件,并且兼容性很好。
有关flvjs的解析和使用大家可以参考这篇文章:
https://blog.csdn.net/weixin_43793181/article/details/124458313
如果要使用flv.js的方案,那么就需要搭建flv视频流服务,所以才有了本节的标题Windows下搭建Nginx带nginx-html-flv模块的服务器
。
同样nginx是不带nginx-html-flv模块的,需要自己进行源码编译,但是这部分恕我能力有限也不想花过多的时间去倒腾,于是乎找了一个人家集成好的版本,稍加修改便可以使用了,这里也共有给大家:
。。。
解压之后找到nginx.exe启动就完了。当然有时间的同志建议还是去尝试手动编译生成,我相信会学到很多东西。
nginx-html-flv模块依赖于rtmp模块,所以这个版本继承了rtmp视频流和flv视频流两个服务。
如何测试搭建好的视频流服务?IP Camera APP。
这是一款可以模拟ip摄像头的软件,同时拥有rtsp视频流和rtmp视频流推送的功能,并且还能设置分辨率,视频帧率等等。确实强大。大家可以手机上百度下载。
按照下图做设置之后,便会打开摄像头推送到nginx部署的地址,图2的IP地址需要更换成你自己的电脑IP。
另外手机和电脑必须处于同一局域网内。
如果RTMP推流功能勾选之后没有报错,就说明nginx服务已经部署没问题了。
这里有一点需要说明,rtmp视频流地址和flv视频流地址的对应关系。
rtmp地址:rtmp://xxx.xxx.xxx.xxx/应用名称/视频流通道
flv视频流地址:http://xxx.xxx.xxx.xxx/flv?port=端口号&app=应用名称&stream=视频流通道 例:
rtmp地址:rtmp://192.168.0.2/live/1
flv视频流地址:http://127.0.0.1/live?port=1935&app=live&stream=1
其中应用名称
和端口号
是在nginx配置文件中配置的,这里配置的就是live和1935。
而视频流通道
实际上是不固定的,可以指定任意值,只是我这里都指定为1,请悉知。
OK,明白这一点之后,就可以通过flv.js写出如下的测试代码了:
DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>title>
<style>
*{
margin: 0;
padding: 0;
}
#app {
display: flex;
height: 500px;
gap: 12px;
}
#app>.video_div{
background: #000;
border: 2px solid red;
box-sizing: border-box;
}
video{
width: 100%;
height: 100%;
}
style>
head>
<body>
<div id="app">
<div class="video_div" v-for="(item,index) in flv_videos">
item
<video muted autoplay>
Your browser is too old which doesn't support HTML5 video.
video>
div>
div>
body>
<script src="./js/flv.js">script>
<script src="./js/jquery-3.6.0.min.js">script>
<script src="./js/vue.min.js">script>
<script>
let app = new Vue({
el: "#app",
data () {
return {
flv_videos:
[
{
flv_src: "http://127.0.0.1/live?port=1935&app=live&stream=1",
},
// {
// flv_src: "http://127.0.0.1/live?port=1935&app=live&stream=2",
// },
// {
// flv_src: "http://127.0.0.1/live?port=1935&app=live&stream=3"
// },
// {
// flv_src: "http://127.0.0.1/live?port=1935&app=live&stream=4"
// }
]
}
},
methods: {
video_play(video_dom, flv_url) {
if (flvjs.isSupported()) {
var flvPlayer = flvjs.createPlayer({
enableWorker: false,
lazyLoadMaxDuration: 3 * 60,
enableStashBuffer: false,
fixAudioTimeStampGap: false,
autoCleanupSourceBuffer: true,
isLive: true,
type: 'flv',
url: flv_url
});
flvPlayer.attachMediaElement(video_dom);
flvPlayer.load(); //加载
video_dom.play(); //add
} else {
alert("不支持flv.js播放!!!")
}
}
},
mounted() {
let video_doms = document.querySelectorAll("video");
video_doms.forEach((video_dom, index, self) => {
this.video_play(video_dom, this.flv_videos[index].flv_src)
})
}
})
script>
html>
RTMP的视频流服务已经跑通了,接下来就是如何用代码获取到视频画面做处理后再推送的过程了。
首先市面上绝大多数摄像头都支持RTSP视频流推送服务,我觉得类似于将自己捕捉到的画面推送到自己所在的IP地址,那么同一局域网内的设备就可以通过这一地址获取到自己捕捉的画面。
了解原理之后,问题就转到了如何通过代码从指定的IP地址中获取视频画面上了。
答案是:opencv-python
库。
我就不详细介绍了,直接给出这边的示例代码:
altgraph==0.17.3
future==0.18.2
numpy==1.23.5
opencv-contrib-python==4.6.0.66
opencv-python==4.6.0.66
pefile==2022.5.30
pyinstaller==5.6.2
pyinstaller-hooks-contrib==2022.13
pywin32-ctypes==0.2.0
建议复制到requirements.txt文件中,然后使用pip install -r requirements.txt
命令一键安装。
main.py
import subprocess
import cv2
import time
import multiprocessing as mp
def connect_camera(camera_ip):
"""
等待摄像头连接
:param camera_ip:
:return:
"""
retry = 0
print("开始连接摄像头...")
while True:
# 连接网络摄像头, 创建视频捕获对象
cap = cv2.VideoCapture(camera_ip)
if cap.isOpened():
print("摄像头连接成功!")
return cap
retry += 1
print(f"摄像头连接失败!开始第{retry}次重试...")
time.sleep(1)
def frame_put(q, camera_ip):
"""
从摄像头获取视频帧并添加到指定队列
:param q: 队列
:param camera_ip: rtsp服务地址
:return:
"""
cap = connect_camera(camera_ip)
while True:
q.put(cap.read()[1])
q.get() if q.qsize() > 1 else time.sleep(0.01)
def frame_push(q=None, rtmp_url="", save_name=None):
"""
从指定队列中取出视频帧,推送给rtmp服务
:param q: 队列
:param rtmp_url: 推送rtmp服务地址
:param save_name: 保存文件名,None不保存
:return:
"""
command = [
'ffmpeg',
'-y',
'-f',
'rawvideo',
'-vcodec',
'rawvideo',
'-pix_fmt',
'bgr24',
'-s', "{}x{}".format(1920, 1080), # 图片分辨率
'-r', str(25.0), # 视频帧率
'-i', '-',
'-c:v', 'libx264',
'-pix_fmt', 'yuv420p',
'-preset', 'ultrafast',
'-f', 'flv',
# '-g', '5',
# '-b', '700000',
rtmp_url
]
pipe = subprocess.Popen(command, stdin=subprocess.PIPE, shell=True)
video_file = None
if save_name is not None:
fourcc = cv2.VideoWriter_fourcc(*"XVID")
video_file = cv2.VideoWriter("merge_frame.mp4", fourcc, 25.0, (1920, 1080))
try:
while True:
if not q.empty():
frame = q.get()
if video_file is not None:
video_file.write(frame)
if frame is not None:
pipe.stdin.write(frame.tobytes())
finally:
if video_file is not None:
video_file.release()
def image_get(q, window_name):
cv2.namedWindow(window_name, flags=cv2.WINDOW_FREERATIO)
while True:
frame = q.get()
cv2.imshow(window_name, frame)
cv2.waitKey(1)
def run_multi_camera():
camera_ip_l = [
"rtsp://admin:[email protected]:8554/live"
]
mp.set_start_method(method='spawn') # init
queues = [mp.Queue(maxsize=4) for _ in camera_ip_l]
processes = []
for queue, camera_ip in zip(queues, camera_ip_l):
print(camera_ip)
processes.append(mp.Process(target=frame_put, args=(queue, camera_ip)))
processes.append(mp.Process(target=frame_push, args=(queue, "rtmp://127.0.0.1/live/1", None)))
# processes.append(mp.Process(target=image_get, args=(queue, camera_ip)))
for process in processes:
process.daemon = True
process.start()
for process in processes:
process.join()
if __name__ == '__main__':
run_multi_camera()
以上代码使用多进程实现了,获取rtsp的视频流,使用ffmepg命令推送到rtmp服务的功能。(ffmepg.exe文件和main.py需要在同级目录下)
代码很简单没有过多注释,有疑问欢迎留言探讨。
代码跑起来之后,可以达到和ip摄像头软件推送相同的效果。
至此,从方案已基本实现。
剩下的问题就是多个摄像头的情况下,需要取相同时点的视频帧做合并处理,尽管使用多线程或者多进程的情况下还是会有毫秒级的误差。某一个视频掉线之后又如何处理?
有好的方案的大佬,还望指点一二。
有问题的同志,欢迎大家留言探讨!