今天心血来潮,拿出吃灰 2 年半的树莓派 3B+,准备给家里搭建一套家庭监控系统,同时学习学习 ffmepg 和 opencv 的原理。

# 准备工作

  • 1. 一块树莓派
  • 2. 一个树莓派摄像头
  • 3. 一个用来内网穿透的云服务器(这里采用 1H2G1M 的腾讯云服务器)

# 配置树莓派

# 开启摄像头

h
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 的代码如下:

n
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 帧的输出,视频卡顿异常,后来经过我的修改,代码如下:

n
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

经过测试,发现效果还是不错的。

# 写一个简单的前端页面

l
<!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 的方式运行,这样可以让程序在后台运行,不影响当前的终端。

h
nohup python3 hls.py &

输出如下:调整 frame_rate 的值其实就是让 speed 尽可能等于 1,视频才看起来不会卡顿。

h
Came=36598 fps=5.1 q=13.0 size=N/A time=02:01:59.60 bitrate=N/A speed=1.02x

在这里 nginxfrp 就不做过多说明了。

最后使用手机打开域名就可以在任何地方看到家里的监控了。最终效果如下:

图片

请我喝杯[咖啡]~( ̄▽ ̄)~*

一个放羊娃 微信支付

微信支付

一个放羊娃 支付宝

支付宝