摄像头实时监控

设备

  1. window
  2. usb摄像头(电脑自带也行)
  3. 脚本、python程序、可执行文件

需求

  1. 获取摄像头,实时录制视频
  2. 每隔3分钟保存一次
  3. 超过两天的视频数据对其进行删除
  4. 关闭应用脚本
  5. 打包成可执行文件

应用场景

无需买其他监控设备,电脑即可7724运行,可进行二次开发,配合人脸识别功能进行提醒或者语音播报,连接局域网可进行内网推流,或者内网穿透进行查看实时视频。网页本地推流播放,因为摄像头流不能多路的原因,暂且操作是读取最新保存的3分钟视频进行播放,也就是说网页推流延迟了3分钟。

配置文件

cnfig.ini

[path]
;路径配置
video_path = C:\Users\Administrator\Desktop\

[Font]
;字体样式
color = 0,255,0
size = 20
text_width = 360

[start]
time = 10:00-13:35
time_2 = 19:31-22:00

[delete]
time_3 = 11:15-12:00

工具脚本


utils

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2021/04/29 16:50
# @Author  : Cxk
import json,os,time
import datetime, random
import time
import configparser

def getTimeNow(flag):
    """[summary]
        时间获得工具
    Args:
        flag ([string]): [s 返回秒时间戳 ms 毫秒时间戳 time 返回格式化当前时间(小时) 其他 格式化时间(日)]

    Returns:
        [type]: [时间字符串]
    """
    now = time.time() #返回float数据
    if flag=='s':
        #  获取当前时间戳---秒级级
        return int(now)
    elif flag =='ms':
        #毫秒级时间戳
        return int(round(now * 1000))
    elif flag == "time":
        # 格式化時間
        return time.strftime('Time_%Y-%m-%d_%H%M%S',time.localtime(time.time()))
    else:
        return time.strftime('%Y-%m-%d',time.localtime(time.time()))
    
def timeToStr(format_time):
    # 大于86400说明超过两天
    # 格式化时间
    format_time = format_time+' 00:00:00'
    # 时间
    ts = time.strptime(format_time, "%Y-%m-%d %H:%M:%S")
    # 格式化时间转时间戳
    return time.mktime(ts)

def judgeTime(startTime,endTime):
    # 范围时间
    d_time = datetime.datetime.strptime(str(datetime.datetime.now().date()) + startTime, '%Y-%m-%d%H:%M')
    d_time1 = datetime.datetime.strptime(str(datetime.datetime.now().date()) + endTime, '%Y-%m-%d%H:%M')
    # 当前时间
    n_time = datetime.datetime.now()
    # 判断当前时间是否在范围时间内
    if n_time > d_time and n_time < d_time1:
        return True
    else:
        return False

def timeAll(nextTime,today):
    """
    返回离未来某个时间节点的时间秒数,today 今天时间节点,0是明天,1是今天
    参数:16:00,0/1
    """
    if today:
        nowTime = time.time()
        ts = time.strptime(str(datetime.datetime.now().date())+' '+nextTime+':00', "%Y-%m-%d %H:%M:%S")
        # 格式化时间转时间戳
        nextTime = time.mktime(ts)
        return int(nextTime-nowTime)
    else:
        nowTime = time.time()
        ts = time.strptime(str((datetime.datetime.now()+datetime.timedelta(days=1)).date())+' '+nextTime+':00', "%Y-%m-%d %H:%M:%S")
        # 格式化时间转时间戳
        nextTime = time.mktime(ts)
        return int(nextTime-nowTime)

def timeStrTime(strTime):
    time_local = time.localtime(strTime) 
    #转换成新的时间格式(2018-05-26 20:20:20)
    t = time.strftime("%H:%M:%S",time_local)
    return t

def response_return(code, msg, data):
    """[summary]
    Args:
        code ([type]): 200(请求成功),404(请求失败),500(服务器出错)
        msg ([type]): msg
        data ([type]): json_data
    Returns:
        [type]: [description]
    """
    if data == None:
        data = []
    return json.dumps({'code': code, 'msg': msg, 'data': list(data)}, ensure_ascii=False)

def write_error(e):
    """[summary]
    Args:
        e ([type]): [description]
    """
    folder_path='./error_log/'
    if not os.path.exists(folder_path):  #判断是否存在文件夹如果不存在则创建为文件夹
        os.makedirs(folder_path)
    error_time=time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time()))
    (filepath,error_file) = os.path.split(e.__traceback__.tb_frame.f_globals['__file__'])
    error_line=e.__traceback__.tb_lineno
    with open(folder_path+'error.txt', 'a+',encoding='UTF-8') as file_handle:
        file_handle.write("time: %s\nfile: %s -- line: %s -- error: %s" %(error_time,error_file,error_line, str(e)))  # 写入
        file_handle.write('\n\n')
        
def GetImgNameByEveryDir(file_dir,videoProperty):  
    # Input   Root Dir and get all img in per Dir.
    # Out     Every img with its filename and its dir and its path  
    FileNameWithPath = [] 
    FileName         = []
    FileDir          = []
    for root, dirs, files in os.walk(file_dir):  
        for file in files:  
            if os.path.splitext(file)[1] in videoProperty:  
                FileNameWithPath.append(os.path.join(root, file))  # 保存图片路径
                FileName.append(file)                              # 保存图片名称
                FileDir.append(root[len(file_dir):])               # 保存图片所在文件夹
    return FileName,FileNameWithPath,FileDir

def getNowFilePath():
    """[summary]
        返回当前文件所在目录的绝对路径
    Returns:
        [string]: [当前文件所在目录的绝对路径]
    """
    return os.path.split(os.path.abspath(__file__))[0]

def file_name(file_dir):   
    """[summary]
        返回所有子目录
    Args:
        file_dir ([type]): [description]

    Returns:
        [type]: [description]
    """
    for root, dirs, files in os.walk(file_dir):  
        if dirs:
            return dirs

def readConfig():
    """
    读取config文件并返回
    """
    # curpath = os.path.dirname(os.path.realpath(__file__))
    cfgpath = os.path.join("./", "config.ini")
    # 创建管理对象
    conf = configparser.ConfigParser()
    # 先读出来
    conf.read(cfgpath, encoding="utf-8")
    # 获取所有的section
    sections = conf.sections()
    allConfigDict={}
    for i in sections:
        items = conf.items(i)
        for j in items:
            allConfigDict[j[0]]=j[1]
    return allConfigDict  # list里面对象是元祖
        

资源文件

这里为项目全部源码,大家自行复制粘贴即可运行,也可下载付费资源包含全部项目文件,不包含网页推流。
整个项目付费资源

结果

项目文件夹
在这里插入图片描述
生成可执行文件
在这里插入图片描述
视频录制结果
在这里插入图片描述

index.html

 
<html>

<head>
   <title>Video Streaming Demonstration</title>
</head>
<style>
   body {
      text-align: center
   }

   .div {
      margin: 0 auto;
   }
</style>

<body>
   <div class="div">
      <form method="get" action="/video_feed">
      <h1><select name="chioseFile">
            {% for i in fileList %}
            <option value="{{i}}">{{ i }}</option>
            {% endfor %}
      </select></h1>
      <input type="submit" value ="Submit">
   </form>
   <img src="{{ url_for('video_feed') }}">
   </div>
</body>

</html>

应用

电脑端查看:http://127.0.0.1:5000/
在这里插入图片描述
手机或者其他局域网

  1. 查看电脑本地网址( win+r )运行cmd
  2. 输入 ipconfig在这里插入图片描述
  3. 拼接成地址:http://127.0.0.1 :5000/
  4. 把删除线的地址换成任何一个ipv4地址进行尝试,正常来说就是以太网的ip地址,就是图中的第一个。

界面显示

  1. 每天固定时间段开启--------startTime=[“12:00-13:35”,“19:30-22:00”]
  2. 界面显示下一次开启时间,关闭时间
  3. 图示下一次开启时间为:11:59:59,正在录像图标为:⏪𝗹𝗹⏩,已关闭录像图标为:⏪▶⏩

在这里插入图片描述

  1. 24小时运行,不录像时后台休眠,无CPU占用。在这里插入图片描述

cameraCapture.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2021/08/2 20:00
# @Author  : Cxk
import cv2
import sys
from utils import *
import shutil
import time

from threading import Thread
import threading
from flask import Flask, render_template, Response, send_file,request
import logging

import webbrowser
from PyQt5.QtCore import QSize, Qt, QPoint
from PyQt5.QtGui import QMouseEvent, QMovie, QCursor, QPalette
from PyQt5.QtWidgets import QWidget, QApplication,QMenu,QMessageBox,qApp
from qtpy import QtWidgets, QtCore

global configInfo
configInfo = readConfig()
global video_path
video_path = os.path.join(configInfo['video_path'], 'Record')
class Main(QWidget):
    try:
        _startPos = None
        _endPos = None
        _isTracking = False
        about = "监控电脑摄像头。\n作者:悟叭鸽"
        rgb = (int(configInfo['color'].split(',')[0]),int(configInfo['color'].split(',')[1]), int(configInfo['color'].split(',')[2]))
        size = int(configInfo['size'])
        size_2 = int(configInfo['text_width'])
        offFlag = False
        startFlag = False
    except Exception as e:
        write_error(e)

    def __init__(self):
        try:
            super().__init__()
            self._initUI()
        except Exception as e:
            QtWidgets.QMessageBox.critical(self, "错误", "系统错误\n%s" % e)

    def _initUI(self):
        try:
            self.setFixedSize(QSize(1280, 720))
            self.setWindowFlags(Qt.FramelessWindowHint |
                                QtCore.Qt.WindowStaysOnTopHint | Qt.Tool)
            self.setAttribute(QtCore.Qt.WA_TranslucentBackground)  # 设置窗口背景透明
            self.label = QtWidgets.QLabel(self)
            self.label.resize(800, 600)
            self.label.setGeometry(QtCore.QRect(0, 0, 1280, 720))
            self.label.setMinimumSize(QtCore.QSize(290, 200))
            self.label.setBaseSize(QtCore.QSize(290, 200))
            self.label.setAlignment(QtCore.Qt.AlignCenter)
            self.label.setObjectName("label")
            self.label.setToolTip('按住左键移动\n点击右键菜单')
            self.label.setStyleSheet(
                "font: %s %spt \"Adobe Arabic\";color:rgb%s" %
                (self.size_2, self.size, self.rgb))
            self.setCursor(QCursor(Qt.PointingHandCursor))
            self.show()
            runThread = Thread(target=self.startRecord,
                               daemon=True, name="start")
            runThread.start()

        except Exception as e:
            QtWidgets.QMessageBox.critical(self, "错误", "系统错误\n%s" % e)

    def mouseMoveEvent(self, e: QMouseEvent):  # 重写移动事件
        try:
            self._endPos = e.pos() - self._startPos
            self.move(self.pos() + self._endPos)
        except Exception as e:
            QtWidgets.QMessageBox.critical(self, "错误", "系统错误\n%s" % e)

    def mousePressEvent(self, e: QMouseEvent):
        try:
            if e.button() == Qt.LeftButton:
                self._isTracking = True
                self._startPos = QPoint(e.x(), e.y())
            if e.button() == Qt.RightButton:
                menu = QMenu(self)
                quitAction = menu.addAction("退出程序")
                aboutAction = menu.addAction("关于程序")
                action = menu.exec_(self.mapToGlobal(e.pos()))
                if action == quitAction:
                    qApp.quit()
                if action == aboutAction:
                    msg_box = QtWidgets.QMessageBox
                    msg_box.question(self, "关于", self.about,
                                     msg_box.Yes | msg_box.Cancel)
                    if QMessageBox.Yes:
                        webbrowser.open(
                            'https://me.csdn.net/Cxk___', new=0, autoraise=True)
        except Exception as e:
            QtWidgets.QMessageBox.critical(self, "错误", "系统错误\n%s" % e)

    def delTimeTwoDay(self):
        """[summary]
            清除前两天录像
        """
        for i in file_name(video_path):
            if int(time.time())-int(timeToStr(i)) > 86400*2:
                shutil.rmtree(video_path+os.sep+i)
                
    def savePathFun(self):
        savePath = video_path+os.sep+getTimeNow("day")
        if not os.path.exists(savePath):
            os.makedirs(savePath)
        return savePath

    def startRecord(self):
        try:
            startTime=[configInfo['time'],configInfo['time_2']]
            camera = cv2.VideoCapture(0)
            # 获取摄像头--如果电脑有两个或以上的摄像头数量
            # 自行更改-参数: 0、1、2 ...
            fps = camera.get(cv2.CAP_PROP_FPS)
            # 获取帧率
            width = int(camera.get(cv2.CAP_PROP_FRAME_WIDTH))
            # 一定要转int 否则是浮点数
            height = int(camera.get(cv2.CAP_PROP_FRAME_HEIGHT))
            size = (width, height)  # 大小
            numFramesRemaining = 0
            # 只写180s-每3分钟保存一次文件
            # 开启成功打开浏览器文件--点个关注呀
            # webbrowser.open("https://cxk-life.blog.csdn.net/")
            VWirte = None
            while True:
                try:
                    if judgeTime(configInfo['time_3'].split('-')[0],configInfo['time_3'].split('-')[1]):
                        self.delTimeTwoDay()
                    if judgeTime(startTime[0].split('-')[0],startTime[0].split('-')[1]) or judgeTime(startTime[1].split('-')[0],startTime[1].split('-')[1]):
                        if self.startFlag:
                            pass
                        else:
                            sleepTime=[]
                            for i in startTime:
                                if timeAll(i.split('-')[1],1)>0:
                                    sleepTime.append(timeAll(i.split('-')[1],1))
                            if sleepTime==[]:
                                sleepTime.append(timeAll(startTime[0].split('-')[1],0))
                            self.label.setText("%s\n⏪𝗹𝗹⏩"%timeStrTime(time.time()+min(sleepTime)))
                            self.startFlag=True
                            self.offFlag=False
                        success, frame = camera.read()
                        if success:
                            if numFramesRemaining > 0:
                                numFramesRemaining -= 1
                                # 初始化文件写入 文件名 编码解码器 帧率 文件大小
                                VWirte.write(frame)
                            else:
                                # 分支判断 上个视频录制完毕,重新命名一个新视频保存
                                saveName = self.savePathFun()+os.sep + \
                                    getTimeNow("time")+".mp4"
                                VWirte = cv2.VideoWriter(
                                    saveName, cv2.VideoWriter_fourcc('M', 'P', '4', 'V'), fps, size)
                                # 初始化文件写入 文件名 编码解码器 帧率 文件大小
                                numFramesRemaining = 180*fps
                            # 预览帧
                            # cv2.imshow("winname", frame)
                            # cv2.waitKey(1)
                    else:
                        sleepTime=[]
                        for i in startTime:
                            if timeAll(i.split('-')[0],1)>0:
                                sleepTime.append(timeAll(i.split('-')[0],1))
                        if sleepTime==[]:
                            sleepTime.append(timeAll(startTime[0].split('-')[0],0))
                        if self.offFlag:
                            pass
                        else:
                            self.label.setText("%s\n⏪▶⏩"%timeStrTime(time.time()+min(sleepTime)+61))
                            self.offFlag=True
                            self.startFlag=False
                        if VWirte:
                            VWirte.release()
                        numFramesRemaining=0
                        time.sleep(min(sleepTime)+61)
                except Exception as e:
                    write_error(e)
                    continue
        except Exception as e:
            write_error(e)
        time.sleep(1)  # y延迟一秒关闭摄像头 否则会出现 terminating async callback 异步处理错误
        camera.release()  # 释放摄像头

    def mouseReleaseEvent(self, e: QMouseEvent):
        try:
            if e.button() == Qt.LeftButton:
                self._isTracking = False
                self._startPos = None
                self._endPos = None
            if e.button() == Qt.RightButton:
                self._isTracking = False
                self._startPos = None
                self._endPos = None
        except Exception as e:
            QtWidgets.QMessageBox.critical(self, "错误", "系统错误\n%s" % e)


def startGUI():
    apps = QApplication(sys.argv)
    ex = Main()
    sys.exit(apps.exec_())

class VideoCamera(object):
    def __init__(self,filePath):
        self.video = cv2.VideoCapture(filePath)

    def __del__(self):
        self.video.release()

    def get_frame(self):
        try:
            success, image = self.video.read()
            ret, jpeg = cv2.imencode('.jpg', image)
            fps = self.video.get(cv2.CAP_PROP_FPS)  # 视频平均帧率
            time.sleep(1 / fps)  # 按原帧率播放
            return success, jpeg.tobytes()
        except:
            return False, None

    def set_video(self, other_video):
        self.video = cv2.VideoCapture(other_video)
        
def gen(camera):
    while True:
        success, frame = camera.get_frame()
        if success:
            # 使用generator函数输出视频流, 每次请求输出的content类型是image/jpeg
            yield (b'--frame\r\n'
                   b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n\r\n')
        else:
            try:
                a, b, c = GetImgNameByEveryDir(video_path, '.mp4')
                # 循环播放倒数第二个视频,也就是最近保存的视频
                camera.set_video(b[-2])
            except:
                break        
        
app = Flask(__name__)
app.config.from_object(__name__)
# 禁用控制台
log = logging.getLogger('werkzeug')
log.setLevel(logging.ERROR)


@app.route('/')  # 主页
def index():
    # jinja2模板,具体格式保存在index.html文件中
    a, b, c = GetImgNameByEveryDir(video_path, '.mp4')
    return render_template('index.html',fileList=a)
    
@app.route('/video_feed', methods=['GET', 'POST'])  # 这个地址返回视频流响应
def video_feed():
    try:
        chioseFile = request.args.get('chioseFile')
        a, b, c = GetImgNameByEveryDir(video_path, '.mp4')
        filePath=b[-1]
        if chioseFile:
            for i in b:
                if chioseFile in i:
                    filePath = i
                    break
                else:
                    pass
        return Response(gen(VideoCamera(filePath)),mimetype='multipart/x-mixed-replace; boundary=frame')
    except Exception as e:
        write_error(e)

def startRecordProcess():
    print('Process to startRecord: %s' % os.getpid())
    thre = threading.Thread(target=run_sever)   # 创建一个线程运行服务器
    thre.setDaemon(True)
    thre.start()  # 运行服务器线程
    # 运行界面线程
    startGUI()


def run_sever():
    try:
        app.run(host='127.0.0.1',port=5000,debug=False, use_reloader=False)
    except Exception as e:
        write_error(e)


if __name__ == '__main__':
    startRecordProcess()


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

智能视界探索者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值