树莓派监控画面平铺模式实现

爱学习的小齐哥哥
发布于 2025-6-19 09:05
浏览
0收藏

引言

树莓派作为一个功能强大的小型计算机,在监控系统中有着广泛的应用。当我们需要同时监控多个区域时,将单个摄像头画面进行平铺显示是一种有效的方法。本文将详细介绍如何在树莓派上使用Python和Tkinter库实现监控画面的平铺模式显示。

硬件和软件准备

硬件需求:
树莓派(推荐Raspberry Pi 4)

摄像头模块(如Raspberry Pi Camera Module)

HDMI显示器或通过VNC连接的远程显示器

软件环境:
Raspberry Pi OS

Python 3.x

OpenCV库(pip install opencv-python)

Pillow库(pip install pillow)

NumPy库(pip install numpy)

技术原理

图像平铺模式是指将单个摄像头捕获的画面复制并排列成网格状,形成更大的显示区域。这种显示方式可以在有限的空间内展示更多的监控区域,或者创建一种特殊的视觉效果。

实现平铺模式的关键步骤:
从摄像头捕获实时视频流

对每帧图像进行处理和缩放

在GUI界面中创建网格布局

将处理后的图像块填充到网格中

实现实时更新

代码实现

下面是一个完整的树莓派监控画面平铺显示的Python实现:

import tkinter as tk
from tkinter import ttk
import cv2
import PIL.Image, PIL.ImageTk
import numpy as np
import time
import threading

class RaspiSurveillanceApp:
def init(self, window, window_title):
self.window = window
self.window.title(window_title)

    # 视频源,默认为0(通常是内置摄像头)
    self.video_source = 0
    self.vid = None
    
    # 平铺配置
    self.tile_rows = 2  # 行数
    self.tile_cols = 2  # 列数
    self.tile_gap_x = 10  # 水平间隙(像素)
    self.tile_gap_y = 10  # 垂直间隙(像素)
    
    # 创建画布用于显示平铺视频
    self.canvas_width = 800
    self.canvas_height = 600
    self.canvas = tk.Canvas(window, width=self.canvas_width, height=self.canvas_height)
    self.canvas.grid(row=0, column=0, padx=10, pady=10)
    
    # 控制按钮框架
    self.controls_frame = ttk.Frame(window)
    self.controls_frame.grid(row=1, column=0, padx=10, pady=10, sticky="ew")
    
    # 添加控制按钮
    self.btn_pause = ttk.Button(self.controls_frame, text="暂停", command=self.toggle_pause)
    self.btn_pause.pack(side=tk.LEFT, padx=5)
    
    self.btn_tile_config = ttk.Button(self.controls_frame, text="平铺配置", command=self.open_tile_config)
    self.btn_tile_config.pack(side=tk.LEFT, padx=5)
    
    self.is_paused = False
    self.defocus_color = "#222222"  # 非焦点区域的颜色
    
    # 打开视频源
    self.open_video_source()
    
    # 设置窗口关闭事件
    self.window.protocol("WM_DELETE_WINDOW", self.on_closing)
    
    # 设置定时更新UI
    self.window.after(10, self.update)

def open_video_source(self):
    """打开视频源"""
    try:
        # 释放之前的视频源(如果有)
        if self.vid is not None and self.vid.isOpened():
            self.vid.release()
        
        # 打开新的视频源
        self.vid = cv2.VideoCapture(self.video_source)
        if not self.vid.isOpened():
            raise ValueError("无法打开视频源", self.video_source)
            
        # 获取视频源的宽度和高度
        self.width = self.vid.get(cv2.CAP_PROP_FRAME_WIDTH)
        self.height = self.vid.get(cv2.CAP_PROP_FRAME_HEIGHT)
        print(f"视频源分辨率: {self.width}x{self.height}")
        
    except Exception as e:
        print(f"打开视频源错误: {e}")
        self.window.destroy()

def toggle_pause(self):
    """切换暂停状态"""
    self.is_paused = not self.is_paused
    status = "暂停" if self.is_paused else "继续"
    print(f"{status}监控画面")

def open_tile_config(self):
    """打开平铺配置对话框"""
    config_window = tk.Toplevel(self.window)
    config_window.title("平铺配置")
    config_window.geometry("300x200")
    
    # 行数设置
    ttk.Label(config_window, text="行数:").grid(row=0, column=0, padx=5, pady=5, sticky="w")
    row_var = tk.IntVar(value=self.tile_rows)
    row_spinbox = ttk.Spinbox(config_window, from_=1, to=4, textvariable=row_var, width=5)
    row_spinbox.grid(row=0, column=1, padx=5, pady=5)
    
    # 列数设置
    ttk.Label(config_window, text="列数:").grid(row=1, column=0, padx=5, pady=5, sticky="w")
    col_var = tk.IntVar(value=self.tile_cols)
    col_spinbox = ttk.Spinbox(config_window, from_=1, to=4, textvariable=col_var, width=5)
    col_spinbox.grid(row=1, column=1, padx=5, pady=5)
    
    # 间隙设置
    ttk.Label(config_window, text="间隙大小:").grid(row=2, column=0, padx=5, pady=5, sticky="w")
    gap_var = tk.IntVar(value=10)
    gap_spinbox = ttk.Spinbox(config_window, from_=0, to=50, textvariable=gap_var, width=5)
    gap_spinbox.grid(row=2, column=1, padx=5, pady=5)
    
    # 保存配置按钮
    def save_config():
        self.tile_rows = row_var.get()
        self.tile_cols = col_var.get()
        self.tile_gap_x = gap_var.get()
        self.tile_gap_y = gap_var.get()
        print(f"平铺配置已更新: {self.tile_rows}行 x {self.tile_cols}列, 间隙: {self.tile_gap_x}px")
        config_window.destroy()
        
    save_btn = ttk.Button(config_window, text="保存", command=save_config)
    save_btn.grid(row=3, column=0, columnspan=2, pady=10)

def video_loop(self):
    """从视频源获取帧并更新画布"""
    if not self.is_paused:
        ret, frame = self.vid.read()
        if ret:
            # 处理帧并更新UI
            self.photo = self.process_frame(frame)
            self.update_canvas()
    
    # 每10毫秒循环一次
    self.window.after(10, self.video_loop)

def process_frame(self, frame):
    """处理视频帧,根据平铺配置生成适当的图像"""
    # 调整单个区块的大小
    tile_width = (self.canvas_width - (self.tile_cols - 1) * self.tile_gap_x) // self.tile_cols
    tile_height = (self.canvas_height - (self.tile_rows - 1) * self.tile_gap_y) // self.tile_rows
    
    # 调整原始图像大小,使其高度与单个区块高度相匹配
    frame_h, frame_w = frame.shape[:2]
    aspect_ratio = frame_w / frame_h
    
    # 计算合适的宽度,保持宽高比
    resized_width = int(tile_height * aspect_ratio)
    resized_frame = cv2.resize(frame, (resized_width, tile_height))
    
    # 创建一个空白画布,用于放置平铺的图像
    tiled_image = np.ones((tile_height  self.tile_rows + self.tile_gap_y  (self.tile_rows - 1),
                          tile_width  self.tile_cols + self.tile_gap_x  (self.tile_cols - 1),
                          3), dtype=np.uint8) * 200  # 200是灰色
    
    # 将原始图像复制到每个区块位置
    for i in range(self.tile_rows):
        for j in range(self.tile_cols):
            # 计算当前区块的位置
            x_offset = j * (tile_width + self.tile_gap_x)
            y_offset = i * (tile_height + self.tile_gap_y)
            
            # 将调整后的图像放置在对应位置
            h, w = resized_frame.shape[:2]
            tiled_image[y_offset:y_offset+h, x_offset:x_offset+w] = resized_frame
            
            # 添加边框和编号
            cv2.rectangle(tiled_image, 
                          (x_offset, y_offset), 
                          (x_offset+w, y_offset+h), 
                          (0, 0, 255), 2)
            cv2.putText(tiled_image, f"Cam{i*4+j+1}", 
                       (x_offset+10, y_offset+30), 
                       cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
    
    # 转换为PIL格式以便在Tkinter中使用
    return PIL.Image.fromarray(cv2.cvtColor(tiled_image, cv2.COLOR_BGR2RGB))

def update_canvas(self):
    """更新画布上的图像"""
    if self.photo:
        # 调整图像以适应画布大小
        self.photo = self.photo.resize((self.canvas_width, self.canvas_height), PIL.Image.LANCZOS)
        
        # 将PIL图像转换为Tkinter PhotoImage
        self.tk_photo = PIL.ImageTk.PhotoImage(self.photo)
        
        # 在画布上显示图像
        self.canvas.create_image(0, 0, image=self.tk_photo, anchor=tk.NW)

def on_closing(self):
    """关闭应用程序"""
    # 停止视频线程
    if self.vid and self.vid.isOpened():
        self.vid.release()
    # 关闭窗口
    self.window.destroy()

创建并运行应用

if name == “main”:
root = tk.Tk()
app = RaspiSurveillanceApp(root, “树莓派监控画面平铺模式”)
root.mainloop()

功能说明

这个应用程序提供以下功能:
实时监控显示:从摄像头捕获实时视频流

平铺模式:将单个视频源平铺到多个区块中显示

可配置布局:可以调整平铺的行数、列数和间隙大小

暂停/继续:随时暂停或继续监控画面的显示

区块标识:每个平铺区块都有编号和边框标识

运行效果

运行程序后,将显示一个窗口,其中包含平铺显示的监控画面。默认情况下,使用2×2的平铺布局,您可以通过"平铺配置"按钮调整布局参数。点击"暂停"按钮可以暂停画面更新,便于观察某一时刻的场景。

扩展功能

除了基本的平铺显示外,还可以扩展以下功能:
多摄像头支持:修改代码以支持多个摄像头源,每个区块显示不同的摄像头画面

动态布局调整:添加鼠标交互功能,允许用户拖拽调整区块大小和位置

运动检测:在每个区块中添加运动检测功能,当检测到运动时高亮显示

报警功能:当特定区域发生异常时,触发报警并记录视频

性能优化

在树莓派上运行视频处理应用时,性能是一个需要考虑的因素。以下是一些优化建议:
降低分辨率:适当降低摄像头捕获的分辨率可以提高处理速度

减少平铺数量:过多的平铺区块会增加CPU负载,根据树莓派的性能选择合适的平铺数量

跳过帧处理:可以设置每隔几帧处理一次,而不是处理每一帧

使用硬件加速:利用树莓派的GPU进行图像处理,提高处理效率

结论

本文介绍了如何在树莓派上使用Python和Tkinter实现监控画面的平铺显示功能。通过这种平铺模式,我们可以在有限的显示区域内同时查看多个监控点的情况,这对于监控系统来说非常实用。代码已经实现了基本的功能,并提供了可配置的选项,您可以根据自己的需求进一步扩展和优化。

收藏
回复
举报
回复
    相关推荐