网站首页 > 技术文章 正文
点赞、收藏、加关注,下次找我不迷路
话不多说,先给大家看效果
一、需求分析:明确项目目标与功能
在开始编码之前,我们需要明确项目的目标和功能。这个多摄像头实时监控系统需要具备以下核心功能:
- 摄像头切换功能:能够在多个摄像头之间自由切换
- 实时预览功能:实时显示当前摄像头的画面
- 清晰度检测功能:自动检测画面清晰度
- 最佳画面捕捉功能:自动保存最清晰的画面
有了明确的功能需求,我们就可以进入设计阶段了。
二、系统设计:架构与模块划分
2.1 整体架构设计
我们的系统采用经典的 MVC(Model-View-Controller)架构模式,将系统分为三个核心模块:
- 模型层(Model):负责摄像头管理、视频捕获和图像处理
- 视图层(View):负责用户界面的展示
- 控制层(Controller):负责处理用户交互和业务逻辑
这种架构设计使代码结构清晰,各模块职责明确,便于维护和扩展。
2.2 技术选型
- GUI 框架:选择 wxPython,因为它提供了跨平台的图形界面开发能力,且与 Python 集成良好
- 图像处理库:OpenCV,强大的计算机视觉库,提供了丰富的图像处理功能
- 多线程处理:使用 Python 的 threading 模块,实现视频捕获的异步处理,避免界面卡顿
2.3 类设计
我们的系统主要包含一个核心类:CameraApp。这个类将继承 wx.Frame,负责创建窗口和管理整个应用的生命周期。在这个类中,我们将实现摄像头管理、画面捕获、清晰度检测等核心功能。
三、分段代码实现:从基础框架到核心功能
3.1 基础框架搭建
首先,我们来搭建应用的基础框架,创建窗口和基本的用户界面元素。
import wx
class CameraApp(wx.Frame):
def __init__(self, *args, **kw):
"""
初始化 CameraApp 窗口并设置 UI 组件。
:param args: wx.Frame 的位置参数。
:param kw: wx.Frame 的关键字参数。
"""
super(CameraApp, self).__init__(*args, **kw)
self.InitUI() # 设置 UI 元素
self.cap = None # 视频捕获对象
self.current_camera_id = -1 # 初始化时未选择摄像头
self.is_running = False # 记录摄像头是否在采集
self.Bind(wx.EVT_CLOSE, self.OnClose) # 处理窗口关闭事件
self.Show() # 显示窗口
def InitUI(self):
"""
初始化用户界面组件。
"""
panel = wx.Panel(self)
vbox = wx.BoxSizer(wx.VERTICAL) # 垂直布局
# 摄像头选择下拉菜单
self.camera_choice = wx.ComboBox(panel, choices=[str(i) for i in range(10)])
self.camera_choice.Bind(wx.EVT_COMBOBOX, self.OnCameraChange) # 处理摄像头切换
vbox.Add(self.camera_choice, flag=wx.EXPAND | wx.ALL, border=10)
# 用于显示摄像头画面的面板
self.image_panel = wx.Panel(panel)
vbox.Add(self.image_panel, proportion=1, flag=wx.EXPAND | wx.ALL, border=10)
panel.SetSizer(vbox) # 设置布局
self.SetTitle('Camera Switcher') # 窗口标题
self.SetSize((800, 600)) # 初始窗口大小
def OnClose(self, event):
"""
处理窗口关闭事件。
:param event: 关闭事件对象。
"""
self.is_running = False # 停止捕获
if self.cap:
self.cap.release() # 释放摄像头
self.Destroy() # 销毁窗口
if __name__ == '__main__':
app = wx.App() # 创建 wx 应用
CameraApp(None) # 初始化 CameraApp
app.MainLoop() # 启动应用循环
这段代码创建了一个基本的窗口应用,包含一个摄像头选择下拉菜单和一个用于显示视频画面的面板。目前还没有实现视频捕获和显示功能,接下来我们将逐步添加这些功能。
3.2 摄像头管理与切换功能实现
现在,我们来实现摄像头的管理和切换功能。
def OnCameraChange(self, event):
"""
处理选择新摄像头时的事件。
:param event: 摄像头切换的事件对象。
"""
camera_id = int(self.camera_choice.GetValue())
if camera_id != self.current_camera_id:
self.current_camera_id = camera_id
self.switch_camera(camera_id) # 切换到选择的摄像头
def switch_camera(self, camera_id):
"""
根据选择的摄像头 ID 切换摄像头。
:param camera_id: 要切换的摄像头 ID。
"""
if self.cap is not None:
self.is_running = False # 停止当前捕获
self.cap.release() # 释放当前摄像头
self.cap = None
self.cap = cv2.VideoCapture(camera_id) # 打开新的摄像头
if not self.cap.isOpened():
wx.MessageBox(f"无法打开摄像头 {camera_id}", "错误", wx.OK | wx.ICON_ERROR)
return
self.is_running = True # 开始捕获
Thread(target=self.capture_frames).start() # 在单独的线程中运行捕获
这部分代码实现了摄像头的切换功能。当用户从下拉菜单中选择一个新的摄像头时,程序会释放当前的摄像头资源,然后尝试打开新选择的摄像头。如果成功打开,就会启动一个新线程来捕获视频帧。
3.3 视频捕获与显示功能实现
接下来,我们实现视频帧的捕获和显示功能。
def capture_frames(self):
"""
持续从摄像头捕获帧并更新 UI。
"""
while self.is_running:
ret, frame = self.cap.read()
if not ret:
continue
# 将帧转换为 RGB
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# 将帧转换为 wx.Image
height, width = frame_rgb.shape[:2]
image = wx.Image(width, height, frame_rgb.tobytes())
# 更新图像面板显示新帧
wx.CallAfter(self.update_image, image)
def update_image(self, image):
"""
使用新图像帧更新图像面板。
:param image: 要显示的 wx.Image。
"""
bitmap = wx.Bitmap(image) # 将图像转换为位图
dc = wx.ClientDC(self.image_panel) # 创建用于绘图的设备上下文
dc.DrawBitmap(bitmap, 0, 0) # 在面板上绘制位图
这段代码实现了视频帧的捕获和显示。在 capture_frames 方法中,我们使用一个无限循环不断从摄像头读取帧,然后将帧转换为 RGB 格式,并通过 wxPython 的 UI 线程安全调用机制更新显示。update_image 方法负责将图像绘制到界面上。
3.4 清晰度检测与最佳画面捕捉功能实现
最后,我们实现最核心的清晰度检测和最佳画面捕捉功能。
def __init__(self, *args, **kw):
# ...原有代码...
self.max_sharpness = 0 # 记录帧的最大清晰度
self.best_image = None # 根据清晰度存储最佳图像
# ...原有代码...
def capture_frames(self):
"""
持续从摄像头捕获帧并更新 UI。
"""
while self.is_running:
ret, frame = self.cap.read()
if not ret:
continue
# 使用 Sobel 滤波器计算清晰度
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
sharpness = np.mean(np.sqrt(sobelx**2 + sobely**2))
if sharpness > self.max_sharpness:
self.max_sharpness = sharpness # 更新最大清晰度
self.best_image = frame.copy() # 存储最清晰的图像
# 将帧转换为 RGB
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# 将帧转换为 wx.Image
height, width = frame_rgb.shape[:2]
image = wx.Image(width, height, frame_rgb.tobytes())
# 更新图像面板显示新帧
wx.CallAfter(self.update_image, image)
这里我们添加了清晰度检测的逻辑。使用 Sobel 算子计算图像的梯度,梯度越大表示图像越清晰。我们计算每一帧的清晰度,并与之前记录的最大清晰度进行比较,如果当前帧更清晰,则更新最大清晰度值并保存当前帧为最佳图像。
四、完整代码实现与解析
下面是完整的代码实现:
import wx
import cv2
import numpy as np
from threading import Thread
class CameraApp(wx.Frame):
def __init__(self, *args, **kw):
"""
初始化 CameraApp 窗口并设置 UI 组件。
:param args: wx.Frame 的位置参数。
:param kw: wx.Frame 的关键字参数。
"""
super(CameraApp, self).__init__(*args, **kw)
self.InitUI() # 设置 UI 元素
self.cap = None # 视频捕获对象
self.current_camera_id = -1 # 初始化时未选择摄像头
self.max_sharpness = 0 # 记录帧的最大清晰度
self.best_image = None # 根据清晰度存储最佳图像
self.is_running = False # 记录摄像头是否在采集
self.Bind(wx.EVT_CLOSE, self.OnClose) # 处理窗口关闭事件
self.Show() # 显示窗口
def InitUI(self):
"""
初始化用户界面组件。
"""
panel = wx.Panel(self)
vbox = wx.BoxSizer(wx.VERTICAL) # 垂直布局
# 摄像头选择下拉菜单
self.camera_choice = wx.ComboBox(panel, choices=[str(i) for i in range(10)])
self.camera_choice.Bind(wx.EVT_COMBOBOX, self.OnCameraChange) # 处理摄像头切换
vbox.Add(self.camera_choice, flag=wx.EXPAND | wx.ALL, border=10)
# 用于显示摄像头画面的面板
self.image_panel = wx.Panel(panel)
vbox.Add(self.image_panel, proportion=1, flag=wx.EXPAND | wx.ALL, border=10)
panel.SetSizer(vbox) # 设置布局
self.SetTitle('Camera Switcher') # 窗口标题
self.SetSize((800, 600)) # 初始窗口大小
def OnCameraChange(self, event):
"""
处理选择新摄像头时的事件。
:param event: 摄像头切换的事件对象。
"""
camera_id = int(self.camera_choice.GetValue())
if camera_id != self.current_camera_id:
self.current_camera_id = camera_id
self.switch_camera(camera_id) # 切换到选择的摄像头
def switch_camera(self, camera_id):
"""
根据选择的摄像头 ID 切换摄像头。
:param camera_id: 要切换的摄像头 ID。
"""
if self.cap is not None:
self.is_running = False # 停止当前捕获
self.cap.release() # 释放当前摄像头
self.cap = None
self.cap = cv2.VideoCapture(camera_id) # 打开新的摄像头
if not self.cap.isOpened():
wx.MessageBox(f"无法打开摄像头 {camera_id}", "错误", wx.OK | wx.ICON_ERROR)
return
self.is_running = True # 开始捕获
Thread(target=self.capture_frames).start() # 在单独的线程中运行捕获
def capture_frames(self):
"""
持续从摄像头捕获帧并更新 UI。
"""
while self.is_running:
ret, frame = self.cap.read()
if not ret:
continue
# 使用 Sobel 滤波器计算清晰度
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
sharpness = np.mean(np.sqrt(sobelx**2 + sobely**2))
if sharpness > self.max_sharpness:
self.max_sharpness = sharpness # 更新最大清晰度
self.best_image = frame.copy() # 存储最清晰的图像
# 将帧转换为 RGB
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# 将帧转换为 wx.Image
height, width = frame_rgb.shape[:2]
image = wx.Image(width, height, frame_rgb.tobytes())
# 更新图像面板显示新帧
wx.CallAfter(self.update_image, image)
def update_image(self, image):
"""
使用新图像帧更新图像面板。
:param image: 要显示的 wx.Image。
"""
bitmap = wx.Bitmap(image) # 将图像转换为位图
dc = wx.ClientDC(self.image_panel) # 创建用于绘图的设备上下文
dc.DrawBitmap(bitmap, 0, 0) # 在面板上绘制位图
def OnClose(self, event):
"""
处理窗口关闭事件。
:param event: 关闭事件对象。
"""
self.is_running = False # 停止捕获
if self.cap:
self.cap.release() # 释放摄像头
self.Destroy() # 销毁窗口
if __name__ == '__main__':
app = wx.App() # 创建 wx 应用
CameraApp(None) # 初始化 CameraApp
app.MainLoop() # 启动应用循环
代码解析
整个程序的工作流程如下:
- 初始化阶段:创建窗口和用户界面元素,初始化摄像头和状态变量
- 摄像头切换:当用户选择新摄像头时,释放当前摄像头并打开新摄像头
- 视频捕获:在单独线程中持续捕获视频帧
- 清晰度检测:对每一帧计算清晰度评分
- 最佳画面保存:保存清晰度最高的画面
- 界面更新:将当前帧显示在界面上
- 资源释放:窗口关闭时释放摄像头资源
这个程序采用了多线程设计,将视频捕获和处理放在单独的线程中,避免阻塞 UI 线程,确保界面响应流畅。同时,使用了面向对象的设计方法,将功能封装在类中,使代码结构清晰,易于维护和扩展。
摄像头选型
通过这个小项目,我们实现了使用 Python 和相关库实现一个多摄像头实时显示上位机。从需求分析、系统设计到代码实现,我们完整地经历了一个项目的开发过程。后续经过优化扩展,这个系统不仅可以用于监控,还可以作为计算机视觉应用的基础平台,用于图像分析、物体识别等更高级的应用。
完整代码供大家参考、练手,有问题欢迎评论区交流!
猜你喜欢
- 2025-05-26 智能家居安防系统:AI与Python如何打造更安全的家
- 2025-05-26 在安卓设备上运行Python的方法
- 2025-05-26 基于OpenCv的人脸识别(Python完整代码)-大盘站
- 2025-05-26 边缘计算革命——Python在IoT与自动驾驶中的新战场
- 2025-05-26 还有这操作?我用Python这个库竟然实现了隔空操作
- 2025-05-26 一文带你深入了解Python+MediaPipe实现检测人脸的功能
- 2025-05-26 树莓派制成的 — 带运动检测和摄像头的安防系统
- 2025-05-26 用投影仪+摄像头DIY一台结构光3D扫描仪
- 2025-05-26 OpenCV-python 教你如何播放视频
- 2025-05-26 探索Python中的人脸识别:深入pyfacelib库
- 258℃Python短文,Python中的嵌套条件语句(六)
- 258℃python笔记:for循环嵌套。end=""的作用,图形打印
- 257℃PythonNet:实现Python与.Net代码相互调用!
- 252℃Python操作Sqlserver数据库(多库同时异步执行:增删改查)
- 252℃Python实现字符串小写转大写并写入文件
- 108℃原来2025是完美的平方年,一起探索六种平方的算吧
- 91℃Python 和 JavaScript 终于联姻了!PythonMonkey 要火?
- 83℃Ollama v0.4.5-v0.4.7 更新集合:Ollama Python 库改进、新模型支持
- 最近发表
- 标签列表
-
- python中类 (31)
- python 迭代 (34)
- python 小写 (35)
- python怎么输出 (33)
- python 日志 (35)
- python语音 (31)
- python 工程师 (34)
- python3 安装 (31)
- python音乐 (31)
- 安卓 python (32)
- python 小游戏 (32)
- python 安卓 (31)
- python聚类 (34)
- python向量 (31)
- python大全 (31)
- python次方 (33)
- python桌面 (32)
- python总结 (34)
- python浏览器 (32)
- python 请求 (32)
- python 前端 (32)
- python验证码 (33)
- python 题目 (32)
- python 文件写 (33)
- python中的用法 (32)