设备
- window
- usb摄像头(电脑自带也行)
- 脚本、python程序、可执行文件
需求
- 获取摄像头,实时录制视频
- 每隔3分钟保存一次
- 超过两天的视频数据对其进行删除
- 关闭应用脚本
- 打包成可执行文件
应用场景
无需买其他监控设备,电脑即可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/
手机或者其他局域网
- 查看电脑本地网址( win+r )运行cmd
- 输入 ipconfig
- 拼接成地址:http://
127.0.0.1 - 把删除线的地址换成任何一个ipv4地址进行尝试,正常来说就是以太网的ip地址,就是图中的第一个。
界面显示
- 每天固定时间段开启--------startTime=[“12:00-13:35”,“19:30-22:00”]
- 界面显示下一次开启时间,关闭时间
- 图示下一次开启时间为:11:59:59,正在录像图标为:⏪𝗹𝗹⏩,已关闭录像图标为:⏪▶⏩
- 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(
'', 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("")
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()