windindind 发表于 2024-11-5 10:26

摸鱼时候使用摄像头辅助监控周围情况

背景介绍:
楼主的座位很尴尬,显示器正对办公室入口,对摸鱼很不友好
受到Boss Sensor项目的启发,决定采用一个桌面摄像头辅助监控背后是否有人进入
于是在ai的帮助下,有了下面的代码

原本Boss Sensor项目是识别画面中的人脸是否为老板
但是楼主这个办公环境……等程序识别出来,可以收拾东西滚蛋了
所以只能退而求其次,“宁可错杀,绝不放过”
通过对比连续的两帧画面,只要差异值超出警戒值就直接触发后续动作,可以是隐藏窗口、自动按老板键等

注释比较详细了
代码的核心功能就是对比画面、触发特定动作(代码中是隐藏记事本,且最后一次预警10秒后恢复记事本窗口)
其他的功能都是辅助性的,比如监控窗口置顶、顶部滑动条调整监控窗口的透明度、(因为隐藏了窗体标题栏)可以在窗体内部任意位置拖动移动窗体位置、窗体内按ESC键退出

关于监控画面和预警值的说明:
楼主使用的是桌面摄像头,没有固定装置,所以需要一个预览窗口检查是否对准重点关注区域
裁剪画面,一方面是为了提高处理效率(以现在的PC性能,应该可以忽略不计了),另一方面是为了减少环境噪点对后面差异值的影响
预警值需要根据各自环境、裁剪画面大小等进行调整,多观察吧
原本计划取启动后10秒内的平均值(或者最大值)作为预警值,但是实际效果不太好,所以最后还是采用固定值了

因为是自用,所以没有考虑配置参数文件化,也就不方便直接生成成品EXE了
有需要的,就自己动手吧:keai
import sys
import cv2
import win32gui
import win32con
import numpy as np
from datetime import datetime
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtCore import QTimer, Qt, QPoint
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QVBoxLayout, QHBoxLayout, QSlider


class VideoPlayer(QWidget):
    def __init__(self):
      super().__init__()

      # 打开摄像头
      self.cap = cv2.VideoCapture(0)
      if not self.cap.isOpened():
            exit("摄像头未能成功打开")

      self.hide_windows = False
      self.hwnd = -1

      # 设置窗口初始信息
      self.setGeometry(100, 100, 200, 320)# 自行修改窗口尺寸,适配下面的画面展示
      self.setWindowTitle('Video Player')
      self.label = QLabel(self)
      self.slider = QSlider(Qt.Horizontal)
      self.slider.setMinimum(10)
      self.slider.setMaximum(99)
      self.slider.setTickInterval(5)
      self.slider.setTickPosition(QSlider.TicksBelow)
      self.slider.setValue(40)
      self.set_opacity(self.slider.value())
      self.slider.valueChanged.connect(self.set_opacity_and_update_label)
      self.opacity_label = QLabel(str(self.slider.value()), self)
      opacity_layout = QHBoxLayout()
      opacity_layout.addWidget(self.slider)
      opacity_layout.addWidget(self.opacity_label)
      layout = QVBoxLayout()
      layout.addLayout(opacity_layout)
      layout.addWidget(self.label)
      self.setLayout(layout)
      self.setWindowFlag(Qt.FramelessWindowHint)
      self.setWindowFlag(Qt.WindowStaysOnTopHint)
      self.drag_position = QPoint()

      # 用于存储上一帧的ROI,进行对比
      self.previous_roi = None
      self.show()

      # 创建定时器
      self.change_detected_timer = QTimer(self)
      self.change_detected_timer.timeout.connect(self.reset_hide_window)
      self.change_detected_timer.setSingleShot(True)

    # 按下 ESC 键,关闭应用
    def keyPressEvent(self, event):
      if event.key() == Qt.Key_Escape:
            self.close()

    # 设置窗体透明度
    def set_opacity_and_update_label(self, value):
      self.set_opacity(value)
      self.opacity_label.setText(str(value))

    # 将滑动条的值映射到0.1-1.0的范围
    def set_opacity(self, value):
      opacity = value / 100.0
      self.setWindowOpacity(opacity)

    # 鼠标可以拖动窗体
    def mousePressEvent(self, event):
      if event.button() == Qt.LeftButton:
            self.drag_position = event.globalPos() - self.frameGeometry().topLeft()
            event.accept()

    def mouseMoveEvent(self, event):
      if event.buttons() == Qt.LeftButton:
            self.move(event.globalPos() - self.drag_position)
            event.accept()

    # 从摄像头获取画面
    def show_frame(self):
      ret, frame = self.cap.read()
      if ret:
            # 获取指定区域的画面
            # 这是楼主环境下需要监控的区域大小,请自行调整
            x, y, w, h = 100, 100, 200, 320
            roi = frame

            # 在这里对ROI进行翻转、镜像等处理
            # roi = cv2.flip(roi, 1)# 1表示水平翻转
            roi = cv2.rotate(roi, cv2.ROTATE_180)# 旋转180度

            # 变化检测
            if self.previous_roi is not None:
                # 计算两帧之间的差异
                diff = cv2.absdiff(self.previous_roi, roi)
                gray_diff = cv2.cvtColor(diff, cv2.COLOR_BGR2GRAY)
                _, thresh = cv2.threshold(gray_diff, 30, 255, cv2.THRESH_BINARY)
                change_detected = np.sum(thresh) > 30000# 预警值
                if change_detected:
                  # 触发特定动作
                  self.on_change_detected(np.sum(thresh))

                # 叠加差异图像到原始视频帧上
                diff_image = cv2.cvtColor(thresh, cv2.COLOR_GRAY2BGR)
                overlay = cv2.addWeighted(roi, 0.7, diff_image, 0.3, 0)

                # 获取当前时间并添加到图像上
                cv2.putText(overlay, f"{datetime.now()}", (4, 10), cv2.FONT_HERSHEY_SIMPLEX, 0.37, (255, 255, 255), 1)
                # 添加 np.sum(thresh) 和 self.hide_windows 的值
                hide_windows_status = f'{"Hidden" if self.hide_windows else "Visible"} thresh: {np.sum(thresh)}'
                text_color = (0, 0, 255) if change_detected else (255, 255, 255)
                cv2.putText(overlay, f'{hide_windows_status}', (4, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.4, text_color, 1)

                # 使用叠加后的图像
                display_image = overlay
            else:
                display_image = roi

            # 更新上一帧
            self.previous_roi = roi

            rgb_image = cv2.cvtColor(display_image, cv2.COLOR_BGR2RGB)# 转换颜色通道
            h, w, ch = rgb_image.shape
            bytes_per_line = ch * w
            q_image = QImage(rgb_image.data, w, h, bytes_per_line, QImage.Format_RGB888)
            pixmap = QPixmap.fromImage(q_image)
            self.label.setPixmap(pixmap)

    # 这里可以执行你想要的特定动作
    def on_change_detected(self, change_detected):
      print(f"{datetime.now()}检测到变化!{change_detected}")
      if not self.hide_windows:
            self.hide_windows = True
            print(f'{datetime.now()}hide_windows:{"隐藏" if self.hide_windows else "可见"}')
            self.hide_other_window()# 隐藏指定窗口
      self.change_detected_timer.start(10000)# 启动10秒定时器
      self.setWindowTitle(
            f'{datetime.now().strftime("%H:%M:%S")}-{"隐藏" if self.hide_windows else "可见"}-Video Player')

    # 替换为您要隐藏的窗口的信息
    def hide_other_window(self):
      window_class = None
      window_title = "无标题 - 记事本"
      # 查找窗口句柄
      self.hwnd = win32gui.FindWindow(window_class, window_title)
      if self.hwnd:
            # 隐藏窗口
            win32gui.ShowWindow(self.hwnd, win32con.SW_HIDE)

    def reset_hide_window(self):
      self.hide_windows = False
      print(f'{datetime.now()}hide_windows:{"隐藏" if self.hide_windows else "可见"}')
      self.setWindowTitle(f'Video Player-可见')
      # 恢复窗口
      if self.hwnd:
            win32gui.ShowWindow(self.hwnd, win32con.SW_SHOW)

    # 退出程序
    def closeEvent(self, event):
      self.cap.release()
      super().closeEvent(event)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    player = VideoPlayer()

    def update_frame():
      player.show_frame()
      # 递归调用自身,实现无限循环
      QTimer.singleShot(30, update_frame)

    update_frame()# 第一次调用
    sys.exit(app.exec_())




SVIP008 发表于 2024-11-5 11:37

这摸出了新高度,至少有三层楼那么高{:1_921:}

Blacksuns 发表于 2024-11-5 11:35

劳动人民的智慧是无穷无尽的{:1_932:}

Xiaosesi 发表于 2024-11-5 11:30

感谢分享,摸鱼不怕抓了

a976606645 发表于 2024-11-5 11:31

这个真的可以有

fbifbi 发表于 2024-11-5 11:36

厉害了,弄下来试试看

tyy474 发表于 2024-11-5 11:38

需求才是创造的根本

axinabcd 发表于 2024-11-5 11:48

这个能够看到多远的位置?

Designlazy 发表于 2024-11-5 11:49

我记得论坛里之前有过一个成品软件,楼主可以参考下。效果跟这个类似

加奈绘 发表于 2024-11-5 11:52

久旱逢甘霖,太需要这种程序了{:1_899:}
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: 摸鱼时候使用摄像头辅助监控周围情况