今天心血来潮,拿出吃灰 2 年半的树莓派 3B+,准备给家里搭建一套家庭监控系统,同时学习学习 ffmepg 和 opencv 的原理。
# 准备工作
- 1. 一块树莓派
- 2. 一个树莓派摄像头
- 3. 一个用来内网穿透的云服务器(这里采用 1H2G1M 的腾讯云服务器)
# 配置树莓派
# 开启摄像头
sudo raspi-config |
在弹出的界面中,使用箭头键导航到 Interfacing Options(接口选项)。
选择 Camera(摄像头),然后选择 Yes 启用摄像头。
选择 Finish 完成配置。
# 安装拍照工具
sudo apt-get install -y python3-picamera |
# 拍照测试
raspistill -o image.jpg |
这会将照片保存为 image.jpg 文件,查看是否可以正常拍照。
# 安装 opencv 和 picamera
树莓派默认安装了 python3,使用 pip 安装 opencv 比较慢,所以直接采用 apt 安装。
sudo apt-get install -y python3-opencv ffmpeg python3-picamera |
# 剩下的工作交给 GPT 吧
这里我选择 HLS 推流方案,因为可以直接在浏览器中观看视频,实现起来比较方便。
对 GPT 说:“使用 python3 + picamera 使得树莓派摄像头使用 hls 推流,写个详细的代码,要有详细的注释!”
gpt 的代码如下:
import picamera | |
import cv2 | |
import subprocess | |
import time | |
from http.server import SimpleHTTPRequestHandler | |
from socketserver import TCPServer | |
# 设置输出的 HLS 目录 | |
hls_output_dir = "/path/to/hls" | |
# 设置 HTTP 服务器的监听地址和端口 | |
http_server_address = ("", 8080) | |
# 初始化摄像头 | |
with picamera.PiCamera() as camera: | |
# 设置摄像头分辨率 | |
camera.resolution = (640, 480) | |
# 初始化视频流 | |
video_stream = cv2.VideoWriter_fourcc(*"H264") | |
video_output = cv2.VideoWriter("appsrc ! videoconvert ! video/x-raw,format=I420 ! x264enc speed-preset=ultrafast tune=zerolatency bitrate=500 ! h264parse ! matroskamux ! tcpserversink host=0.0.0.0 port=5000", video_stream, 25, (640, 480)) | |
try: | |
# 使用 ffmpeg 将视频流推送到 HLS | |
ffmpeg_cmd = f"ffmpeg -f rawvideo -pix_fmt bgr24 -s 640x480 -r 25 -i - -c:v libx264 -preset ultrafast -tune zerolatency -b:v 500k -f hls -hls_time 4 -hls_list_size 10 -hls_wrap 10 {hls_output_dir}/output.m3u8" | |
ffmpeg_process = subprocess.Popen(ffmpeg_cmd, shell=True, stdin=subprocess.PIPE) | |
# 启动 HTTP 服务器 | |
with TCPServer(http_server_address, SimpleHTTPRequestHandler) as http_server: | |
print(f"HTTP Server running at http://{http_server_address[0]}:{http_server_address[1]}/") | |
# 主循环,不断捕获摄像头帧并推送到视频流和 ffmpeg 进程中 | |
while True: | |
# 获取摄像头帧 | |
frame = camera.capture() | |
# 处理帧(如果需要) | |
# 将帧写入视频流 | |
video_output.write(frame) | |
# 将帧写入 ffmpeg 进程 | |
ffmpeg_process.stdin.write(frame.tobytes()) | |
ffmpeg_process.stdin.flush() | |
# 等待一段时间,可根据需要调整 | |
time.sleep(0.04) | |
except KeyboardInterrupt: | |
# 用户中断时清理资源 | |
video_output.release() | |
ffmpeg_process.stdin.close() | |
ffmpeg_process.wait() | |
# 关闭摄像头 | |
camera.close() |
# 修改脚本
看起来没什么毛病,运行一下的话,发现了很多错误(这是 gpt 常规操作了)。
首先这里的 httpserver 就完全没必要存在。
运行起来之后发现以树莓派 3B 的性能,完全跟不上 24 帧的输出,视频卡顿异常,后来经过我的修改,代码如下:
import picamera | |
import cv2 | |
import subprocess | |
import time | |
import numpy as np | |
from datetime import datetime | |
# 设置输出的 HLS 目录 | |
hls_output_dir = "/home/disk/xxx" | |
# 初始化摄像头 | |
with picamera.PiCamera() as camera: | |
# 设置摄像头分辨率 | |
camera.resolution = (640, 480) | |
# 初始化视频流 | |
video_stream = cv2.VideoWriter_fourcc(*"H264") | |
video_output = cv2.VideoWriter("appsrc ! videoconvert ! video/x-raw,format=I420 ! x264enc speed-preset=ultrafast tune=zerolatency bitrate=500 ! h264parse ! matroskamux ! tcpserversink host=0.0.0.0 port=5000", video_stream, 25, (640, 480)) | |
try: | |
# 使用 ffmpeg 将视频流推送到 HLS | |
ffmpeg_cmd = f"ffmpeg -f rawvideo -pix_fmt bgr24 -s 640x480 -r 5 -i - -c:v libx264 -preset ultrafast -tune zerolatency -b:v 500k -f hls -hls_time 1 -hls_list_size 10 -hls_wrap 10 -hls_flags delete_segments {hls_output_dir}/output.m3u8" | |
ffmpeg_process = subprocess.Popen(ffmpeg_cmd, shell=True, stdin=subprocess.PIPE) | |
# 调整帧率和循环时间 | |
frame_rate = 15 | |
wait_time = 1 / frame_rate | |
# 主循环,不断捕获摄像头帧并推送到视频流和 ffmpeg 进程中 | |
while True: | |
# 获取摄像头帧 | |
frame = np.empty((camera.resolution[1] * camera.resolution[0] * 3,), dtype=np.uint8) | |
camera.capture(frame, 'bgr', use_video_port=True) | |
frame = frame.reshape((camera.resolution[1], camera.resolution[0], 3)) | |
# 处理帧(如果需要) | |
# 在左上角添加当前时间 | |
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') | |
cv2.putText(frame, current_time, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 100, 100), 2, cv2.LINE_AA) | |
# 将帧写入视频流 | |
video_output.write(frame) | |
# 将帧写入 ffmpeg 进程 | |
ffmpeg_process.stdin.write(frame.tobytes()) | |
ffmpeg_process.stdin.flush() | |
# 等待一段时间,可根据需要调整 | |
time.sleep(wait_time) | |
except KeyboardInterrupt: | |
# 用户中断时清理资源 | |
video_output.release() | |
ffmpeg_process.stdin.close() | |
ffmpeg_process.wait() | |
# 关闭摄像头 | |
camera.close() |
这里我去掉了 httpserver,直接将视频流写入到文件,并且在视频左上角加了时间显示。
调整了帧率和循环时间, hls_time
改成了 1 帧率 r
改成了 5,帧率和时间的比值 frame_rate
最终调整成 15
经过测试,发现效果还是不错的。
# 写一个简单的前端页面
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta | |
name="viewport" | |
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover" | |
/> | |
<link rel="manifest" href="/manifest.json" /> | |
<meta name="apple-mobile-web-app-capable" content="yes" /> | |
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> | |
<meta name="theme-color" content="#000000" /> | |
<title>家庭监控</title> | |
</head> | |
<body> | |
<h1>监控</h1> | |
<video controls style="width:100%;"> | |
<!-- HLS 视频地址 --> | |
<source src="http://xxxx.com/output.m3u8" type="application/x-mpegURL"> | |
Your browser does not support the video tag. | |
</video> | |
</body> | |
</html> |
# 运行起来
全部做好之后就运行起来吧,我这里是用 nohup 的方式运行,这样可以让程序在后台运行,不影响当前的终端。
nohup python3 hls.py & |
输出如下:调整 frame_rate
的值其实就是让 speed
尽可能等于 1,视频才看起来不会卡顿。
Came=36598 fps=5.1 q=13.0 size=N/A time=02:01:59.60 bitrate=N/A speed=1.02x |
在这里 nginx
和 frp
就不做过多说明了。
最后使用手机打开域名就可以在任何地方看到家里的监控了。最终效果如下: