最近在做一个智能安防项目,需要将YOLOv5目标检测的结果实时展示在Web页面上。刚开始觉得很简单,不就是把摄像头画面推送到网页嘛!结果在实际开发中踩了不少坑:延迟高、画面花屏、协议兼容性问题接踵而至。经过两周的折腾,终于搭建出一套稳定可用的方案,这里把完整实现过程分享给大家。
这个方案的核心流程分为四个环节:首先用OpenCV获取摄像头视频流,接着用YOLO进行实时目标检测,然后将处理后的视频帧通过FFmpeg推送到Nginx流媒体服务器,最后前端通过HTTP-FLV协议低延迟播放。整套系统在测试环境下能达到500ms以内的端到端延迟,完全可以满足大多数实时监控场景的需求。
RTMP协议作为传统的流媒体传输方案,虽然延迟较低(1-3秒),但需要Flash支持,在现代浏览器中已经不再适用。而HTTP-FLV基于HTTP长连接,既保持了RTMP的低延迟特性(同样1-3秒),又能完美兼容现代浏览器。实际测试中,HTTP-FLV的延迟甚至比RTMP更低,这是因为:
相比其他流媒体服务器,Nginx的优势非常明显:
先安装编译所需的依赖库:
bash复制sudo apt-get update
sudo apt-get install -y build-essential libpcre3 libpcre3-dev libssl-dev zlib1g-dev
下载Nginx和两个关键模块:
bash复制wget http://nginx.org/download/nginx-1.20.2.tar.gz
git clone https://github.com/arut/nginx-rtmp-module.git
git clone https://github.com/winshining/nginx-http-flv-module.git
编译安装Nginx:
bash复制tar zxvf nginx-1.20.2.tar.gz
cd nginx-1.20.2
./configure --add-module=../nginx-rtmp-module \
--add-module=../nginx-http-flv-module \
--with-http_ssl_module
make && sudo make install
编辑/usr/local/nginx/conf/nginx.conf,添加以下配置:
nginx复制rtmp {
server {
listen 1935;
chunk_size 4096;
application live {
live on;
meta copy;
}
}
}
http {
server {
listen 8080;
location /live {
flv_live on;
chunked_transfer_encoding on;
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Cache-Control' 'no-cache';
}
location /stat {
rtmp_stat all;
rtmp_stat_stylesheet stat.xsl;
}
}
}
启动Nginx服务:
bash复制sudo /usr/local/nginx/sbin/nginx
这里分享一个经过优化的视频处理脚本,解决了常见的花屏问题:
python复制import cv2
import numpy as np
import subprocess
from darknet import Darknet
# 初始化YOLO模型
model = Darknet("cfg/yolov4.cfg")
model.load_weights("yolov4.weights")
class_names = load_class_names("data/coco.names")
# 设置视频源
cap = cv2.VideoCapture("rtsp://admin:password@192.168.1.100:554")
# 获取原始分辨率
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = cap.get(cv2.CAP_PROP_FPS)
# FFmpeg推流命令
command = [
'ffmpeg',
'-y',
'-f', 'rawvideo',
'-vcodec','rawvideo',
'-pix_fmt', 'bgr24',
'-s', f'{width}x{height}',
'-r', str(fps),
'-i', '-',
'-c:v', 'libx264',
'-preset', 'ultrafast',
'-tune', 'zerolatency',
'-f', 'flv',
'rtmp://localhost:1935/live/stream'
]
pipe = subprocess.Popen(command, stdin=subprocess.PIPE)
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
# YOLO目标检测
detections = model.detect(frame)
frame = draw_boxes(frame, detections, class_names)
# 推流
pipe.stdin.write(frame.tobytes())
cap.release()
pipe.terminate()
经过多次测试,发现花屏主要由三个因素导致:
优化后的FFmpeg参数如下:
bash复制ffmpeg -i input -c:v libx264 -preset ultrafast -tune zerolatency \
-g 50 -keyint_min 50 -sc_threshold 0 \
-pix_fmt yuv420p -f flv rtmp://localhost/live/stream
HTML部分:
html复制<video id="videoElement" controls muted autoplay width="800"></video>
<script src="https://cdn.bootcdn.net/ajax/libs/flv.js/1.6.2/flv.min.js"></script>
JavaScript代码:
javascript复制if (flvjs.isSupported()) {
const videoElement = document.getElementById('videoElement');
const flvPlayer = flvjs.createPlayer({
type: 'flv',
url: 'http://your-server:8080/live?app=live&stream=stream'
});
flvPlayer.attachMediaElement(videoElement);
fllPlayer.load();
flvPlayer.play();
}
通过以下调整,我们成功将端到端延迟控制在500ms以内:
gop_cache off;-use_wallclock_as_timestamps 1参数实测效果对比:
| 优化措施 | 原始延迟 | 优化后延迟 |
|---|---|---|
| 默认配置 | 2.1s | 2.1s |
| 缓冲区优化 | 1.8s | 1.3s |
| 低延迟模式 | 1.3s | 0.9s |
| 全优化方案 | 0.9s | 0.5s |
bash复制sudo netstat -tulnp | grep nginx
bash复制telnet localhost 1935
bash复制tail -f /usr/local/nginx/logs/error.log
javascript复制flvjs.createPlayer({
// ...
config: {
enableWorker: true,
enableStashBuffer: false,
stashInitialSize: 128
}
});
bash复制-c:v libx264 -profile:v baseline -level 3.0 \
-preset ultrafast -tune zerolatency \
-x264-params keyint=30:min-keyint=30:scenecut=0
nginx复制rtmp {
server {
# ...
bw_check on;
bw_check_time 1s;
bw_check_rate 512k;
}
}
对于需要更高性能的场景,可以考虑以下优化方案:
bash复制-c:v h264_nvenc -preset llhq -rc constqp -qp 23
bash复制ffmpeg -i input -map 0:v:0 -c:v libx264 -s 1280x720 -b:v 2000k \
-map 0:v:0 -c:v libx264 -s 640x360 -b:v 800k \
-f flv rtmp://localhost/live/stream_720p \
-f flv rtmp://localhost/live/stream_360p
nginx复制rtmp {
server {
# ...
push rtmp://cdn-node1/live/stream;
push rtmp://cdn-node2/live/stream;
}
}
在实际项目中,这套方案已经稳定运行了6个月,日均处理视频流超过1000小时。最大的收获是:流媒体服务的稳定性比低延迟更重要,建议在两者之间找到平衡点。