diff --git a/.idea/FristProject.iml b/.idea/FristProject.iml
index 8678458..719bacc 100644
--- a/.idea/FristProject.iml
+++ b/.idea/FristProject.iml
@@ -2,7 +2,7 @@
-
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 25bfb8c..55756c3 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -3,7 +3,7 @@
-
+
diff --git a/config.yaml b/config.yaml
index b9bc873..219e12b 100644
--- a/config.yaml
+++ b/config.yaml
@@ -25,14 +25,17 @@ UPLOAD_FOLDER : uploads
ALLOWED_EXTENSIONS : {'zip'}
#RTSP
-RTSP_Check_Time : 600 #10分钟
+RTSP_Check_Time : 600 #10分钟 -- 2024-7-8 取消使用
#model
-model_platform : cpu #acl gpu cpu
+model_platform : acl #acl gpu cpu
+device_id : 0 #单设备配置
weight_path: /model/weights
yolov5_path: D:/Project/FristProject/model/base_model/yolov5 #使用绝对路径,不同的部署环境需要修改!
-cap_sleep_time: 300 #5分钟
+cap_sleep_time: 120 #5分钟
buffer_len: 100 #分析后画面缓冲区帧数 -- 可以与验证帧率结合确定缓冲区大小
RESET_INTERVAL : 100000 #帧数重置上限
frame_rate : 20 #帧率参考值 -- 后续作用主要基于verify_rate进行帧率控制
verify_rate : 10 #验证帧率--- 也就是视频输出的帧率
+warn_video_path: /mnt/zfbox/model/warn/
+warn_interval: 120 #报警间隔--单位秒
diff --git a/core/ACLModelManager.py b/core/ACLModelManager.py
new file mode 100644
index 0000000..916b2df
--- /dev/null
+++ b/core/ACLModelManager.py
@@ -0,0 +1,63 @@
+from myutils.ConfigManager import myCongif
+if myCongif.get_data("model_platform") == "acl":
+ import acl
+
+SUCCESS = 0 # 成功状态值
+FAILED = 1 # 失败状态值
+
+class ACLModeManger:
+ def __init__(self,):
+ self.acl_ok = False
+
+ def __del__(self):
+ pass
+
+ #初始化acl相关资源--一个进程内只能调用一次acl.init接口
+ @staticmethod
+ def init_acl(device_id):
+ # '''acl初始化函数'''
+ ret = acl.init() # 0-成功,其它失败
+ if ret:
+ raise RuntimeError(ret)
+
+ ret = acl.rt.set_device(device_id) # 指定当前进程或线程中用于运算的Device。可以进程或线程中指定。*多设备时可以放线程*
+ # 在某一进程中指定Device,该进程内的多个线程可共用此Device显式创建Context(acl.rt.create_context接口)。
+ if ret:
+ raise RuntimeError(ret)
+ print('ACL init Device Successfully')
+
+ return True
+
+ #去初始化
+ @staticmethod
+ def del_acl(device_id):
+ '''Device去初始化'''
+ ret = acl.rt.reset_device(device_id) # 释放Device
+ if ret:
+ raise RuntimeError(ret)
+
+ ret = acl.finalize() # 去初始化 0-成功,其它失败 --官方文档不建议放析构函数中执行
+ if ret:
+ raise RuntimeError(ret)
+ print('ACL finalize Successfully')
+ return True
+
+ @staticmethod
+ def th_inti_acl(device_id):
+ # 线程申请context
+ context, ret = acl.rt.create_context(device_id) # 显式创建一个Context
+ if ret:
+ raise RuntimeError(ret)
+ print('Init TH-Context Successfully')
+ return context
+
+ @staticmethod
+ def th_del_acl(context):
+ #线程释放context
+ ret = acl.rt.destroy_context(context) # 释放 Context
+ if ret:
+ raise RuntimeError(ret)
+ print('Deinit TH-Context Successfully')
+ return True
+
+
diff --git a/core/ChannelManager.py b/core/ChannelManager.py
new file mode 100644
index 0000000..e92cb59
--- /dev/null
+++ b/core/ChannelManager.py
@@ -0,0 +1,120 @@
+import threading
+from collections import deque
+import numpy as np
+import time
+import copy
+import queue
+
+class ChannelData:
+ def __init__(self, str_url, int_type, bool_run, deque_length,icount_max):
+ self.str_url = str_url #视频源地址
+ self.int_type = int_type #视频源类型,0-usb,1-rtsp,2-hksdk
+ self.bool_run = bool_run #线程运行标识
+ self.deque_frame = deque(maxlen=deque_length)
+ self.last_frame = None # 保存图片数据
+ self.frame_queue = queue.Queue(maxsize=1)
+ self.counter = 0 #帧序列号--保存报警录像使用
+ self.icount_max = icount_max #帧序列号上限
+ self.lock = threading.RLock() # 用于保证线程安全
+
+ #添加一帧图片
+ def add_deque(self, value):
+ self.deque_frame.append(value) #deque 满了以后会把前面的数据移除
+
+ #拷贝一份数据
+ def copy_deque(self):
+ return copy.deepcopy(self.deque_frame)
+
+ #获取最后一帧图片
+ def get_last_frame(self):
+ with self.lock:
+ frame = self.last_frame
+ return frame
+ # if not self.frame_queue.empty():
+ # return self.frame_queue.get()
+ # else:
+ # return None
+
+
+ def update_last_frame(self,buffer):
+ if buffer:
+ with self.lock:
+ self.last_frame = None
+ self.last_frame = buffer
+ # if not self.frame_queue.full():
+ # self.frame_queue.put(buffer)
+ # else:
+ # self.frame_queue.get() # 丢弃最旧的帧
+ # self.frame_queue.put(buffer)
+
+
+ #帧序列号自增 一个线程中处理,不用加锁
+ def increment_counter(self):
+ self.counter += 1
+ if self.counter > self.icount_max:
+ self.counter = 0
+
+ def get_counter(self):
+ return self.counter
+
+ #清空数据,主要是删除deque 和 last_frame
+ def clear(self):
+ with self.lock:
+ self.bool_run = False
+ time.sleep(1) #休眠一秒,等待通道对应的子线程,停止工作。
+ self.deque_frame.clear()
+ self.last_frame = None
+
+ def stop_run(self):
+ self.bool_run = False
+
+
+class ChannelManager:
+ def __init__(self):
+ self.channels = {}
+ self.lock = threading.RLock() # 用于保证字典操作的线程安全
+
+ #增加节点
+ def add_channel(self, channel_id, str_url, int_type, bool_run, deque_length=10,icount_max=100000):
+ with self.lock:
+ if channel_id in self.channels: #若已经有数据,先删除后再增加
+ self.channels[channel_id].clear() # 手动清理资源
+ del self.channels[channel_id]
+ self.channels[channel_id] = ChannelData(str_url, int_type, bool_run, deque_length,icount_max)
+
+ #删除节点
+ def delete_channel(self, channel_id):
+ with self.lock:
+ if channel_id in self.channels:
+ self.channels[channel_id].clear() # 手动清理资源
+ del self.channels[channel_id]
+
+ #获取节点
+ def get_channel(self, channel_id):
+ with self.lock:
+ return self.channels.get(channel_id)
+
+ #停止工作线程
+ def stop_channel(self,channel_id):
+ with self.lock:
+ if channel_id == 0:
+ for clannel_id,clannel_data in self.channels.items():
+ clannel_data.clear()
+ del self.channels
+ else:
+ if channel_id in self.channels:
+ self.channels[channel_id].clear() # 手动清理资源
+ del self.channels[channel_id]
+
+if __name__ == "__main__":
+ # 示例使用
+ manager = ChannelManager()
+ manager.add_channel('channel_1', 'test', 123, True, deque_length=5)
+
+ # 更新和读取操作
+ manager.update_channel_deque('channel_1', 'data1')
+ manager.update_channel_buffer('channel_1', np.array([[1, 2], [3, 4]]))
+ manager.increment_channel_counter('channel_1')
+
+ channel_data = manager.get_channel_data('channel_1')
+ print(channel_data)
diff --git a/core/ModelManager.py b/core/ModelManager.py
index 08fef10..3ca55c2 100644
--- a/core/ModelManager.py
+++ b/core/ModelManager.py
@@ -7,18 +7,18 @@ import numpy as np
import threading
import importlib.util
import datetime
+import math
import queue
from collections import deque
from core.DBManager import mDBM,DBManager
from myutils.MyLogger_logger import LogHandler
from myutils.ConfigManager import myCongif
from model.plugins.ModelBase import ModelBase
-import sys
-
+from core.ChannelManager import ChannelManager
+from core.ACLModelManager import ACLModeManger
from PIL import Image
-
class VideoCaptureWithFPS:
'''视频捕获的封装类,是一个通道一个'''
def __init__(self, source):
@@ -29,38 +29,59 @@ class VideoCaptureWithFPS:
if self.cap.isOpened(): #若没有打开成功,在读取画面的时候,已有判断和处理 -- 这里也要检查下内存的释放情况
self.width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
self.height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
- #self.fps = fps # 线程保持最大帧率的刷新画面---过高的帧率会影响CPU性能,但过地的帧率会造成帧积压
- self.fps = self.cap.get(cv2.CAP_PROP_FPS) + 20 #只是个参考值,休眠时间要比帧率快点,由于read也需要耗时。
- #print(self.fps)
+ print(self.width,self.height)
+ #self.fps = fps # 线程保持最大帧率的刷新画面---过高的帧率会影响CPU性能,但过地的帧率会造成帧积压
+ self.fps = math.ceil(self.cap.get(cv2.CAP_PROP_FPS)/float(myCongif.get_data("verify_rate"))) #向上取整。
+ #print(self.fps)
self.running = True
- self.frame_queue = queue.Queue(maxsize=1)
- #self.frame = None
- #self.read_lock = threading.Lock()
+ #self.frame_queue = queue.Queue(maxsize=1)
+ self.frame = None
+ self.read_lock = threading.Lock()
self.thread = threading.Thread(target=self.update)
self.thread.start()
def update(self):
+ icount = 0
while self.running:
ret, frame = self.cap.read()
if not ret:
- #time.sleep(1.0 / self.fps)
+ icount += 1
+ if icount > 5: #重连
+ self.cap.release()
+ self.cap = cv2.VideoCapture(self.source)
+ if self.cap.isOpened():
+ self.width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
+ self.height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
+ # self.fps = fps # 线程保持最大帧率的刷新画面---过高的帧率会影响CPU性能,但过地的帧率会造成帧积压
+ self.fps = math.ceil(
+ self.cap.get(cv2.CAP_PROP_FPS) / float(myCongif.get_data("verify_rate"))) # 向上取整。
+ icount = 0
+ else:
+ time.sleep(1)
continue
- resized_frame = cv2.resize(frame, (int(self.width / 2), int(self.height / 2)))
- # with self.read_lock:
- # self.frame = resized_frame
- if not self.frame_queue.full():
- self.frame_queue.put(resized_frame)
- time.sleep(1.0 / self.fps) #按照视频源的帧率进行休眠
+ #resized_frame = cv2.resize(frame, (int(self.width / 2), int(self.height / 2)))
+ with self.read_lock:
+ self.frame = frame
+ # if not self.frame_queue.full():
+ # self.frame_queue.put(resized_frame)
+
+ # 跳过指定数量的帧以避免积压
+ for _ in range(self.fps):
+ self.cap.grab()
+ # time.sleep(self.fps) #按照视频源的帧率进行休眠
#print("Frame updated at:",time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
-
def read(self):
- # with self.read_lock:
- # frame = self.frame.copy() if self.frame is not None else None
- if not self.frame_queue.empty():
- return True, self.frame_queue.get()
+ with self.read_lock:
+ frame = self.frame.copy() if self.frame is not None else None
+ if frame is not None:
+ return True, frame
else:
return False, None
+ # if not self.frame_queue.empty():
+ # return True, self.frame_queue.get()
+ # else:
+ # return False, None
def release(self):
self.running = False
@@ -70,7 +91,7 @@ class VideoCaptureWithFPS:
class ModelManager:
def __init__(self):
- self.verify_list = {} #模型的主要数据
+ self.verify_list = ChannelManager() #模型的主要数据 -- 2024-7-5修改为类管理通道数据
self.bRun = True
self.logger = LogHandler().get_logger("ModelManager")
# 本地YOLOv5仓库路径
@@ -83,12 +104,17 @@ class ModelManager:
self.FPS = myCongif.get_data("verify_rate") # 视频帧率--是否能实现动态帧率
self.fourcc = cv2.VideoWriter_fourcc(*'mp4v') # 使用 mp4 编码
#基于模型运行环境进行相应初始化工作
- # self.model_platform = myCongif.get_data("model_platform")
- # if self.model_platform == "acl":
- # self._init_acl()
+ self.model_platform = myCongif.get_data("model_platform")
+ self.device_id = myCongif.get_data("device_id")
+ # acl初始化 -- 一个线程一个 -- 需要验证
+ if self.model_platform == "acl":
+ ACLModeManger.init_acl(self.device_id) #acl -- 全程序初始化
def __del__(self):
self.logger.debug("释放资源")
+ del self.verify_list #应该需要深入的删除--待完善
+ if self.model_platform == "acl":
+ ACLModeManger.del_acl(self.device_id) #acl -- 全程序反初始化 需要确保在执行析构前,其它资源已释放
def _open_view(self,url,itype): #打开摄像头 0--USB摄像头,1-RTSP,2-海康SDK
if itype == 0:
@@ -118,6 +144,10 @@ class ModelManager:
if not isinstance(md, ModelBase):
self.logger.error("{} not zf_model".format(md))
return None
+ if md.init_ok == False:
+ self.logger.error("离线模型加载初始化失败!")
+ return None
+
else:
self.logger.error("{}文件不存在".format(model_path))
return None
@@ -148,7 +178,7 @@ class ModelManager:
sixlist.append(datas[i*7 + 5][2])
sevenlist.append(datas[i*7 + 6][2])
else:
- self.logger(f"没有数据?{c2m_id}")
+ self.logger.debug(f"没有数据--{c2m_id}")
onelist = [1]*24
twolist = [1]*24
threelist = [1]*24
@@ -166,6 +196,9 @@ class ModelManager:
schedule_list.append(sevenlist)
return schedule_list
+ def set_last_img(self,):
+ pass
+
def verify(self,frame,myModle_list,myModle_data,channel_id,schedule_list,result_list,isdraw=1):
'''验证执行主函数,实现遍历通道关联的模型,调用对应模型执行验证,模型文件遍历执行'''
img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
@@ -174,7 +207,7 @@ class ModelManager:
#img = frame
# 使用 模型 进行目标检测
i_warn_count = 0 #报警标签
- isverify = False
+ #isverify = False
for i in range(len(myModle_list)): # 遍历通道关联的算法进行检测,若不控制模型数量,有可能需要考虑多线程执行。
model = myModle_list[i]
data = myModle_data[i]
@@ -187,7 +220,7 @@ class ModelManager:
result.pop(0) # 保障结果数组定长 --先把最早的结果推出数组
if schedule[weekday][hour] == 1: #不在计划则不进行验证,直接返回图片
# 调用模型,进行检测,model是动态加载的,具体的判断标准由模型内执行 ---- *********
- isverify = True
+ #isverify = True
detections, bwarn, warntext = model.verify(img, data,isdraw) #****************重要
# 对识别结果要部要进行处理
if bwarn: # 整个识别有产生报警
@@ -201,27 +234,32 @@ class ModelManager:
result.append(0)
else:
result.append(0)
- if not isverify: #没做处理,直接返回的,需要控制下帧率,太快读取没有意义。
- time.sleep(1.0/self.frame_rate) #给个默认帧率,不超过30帧,---若经过模型计算,CPU下单模型也就12帧这样
-
- # 将检测结果图像转换为帧 -- 需要确认前面对img的处理都是累加的。
- #img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) #numpy.ndarray
- if isinstance(img, np.ndarray):
- new_frame = av.VideoFrame.from_ndarray(img, format="rgb24") #AVFrame
- new_frame.pts = None # 添加此行确保有pts属性
- #self.logger.debug("img 是np.darry")
+ # if not isverify: #没做处理,直接返回的,需要控制下帧率,太快读取没有意义。 --2024-7-5 取消休眠,帧率控制在dowork_thread完成
+ # time.sleep(1.0/self.frame_rate) #给个默认帧率,不超过30帧,---若经过模型计算,CPU下单模型也就12帧这样
+
+ # 将检测结果图像转换为帧--暂时用不到AVFrame--2024-7-5
+ # new_frame_rgb_avframe = av.VideoFrame.from_ndarray(img, format="rgb24") # AVFrame
+ # new_frame_rgb_avframe.pts = None # 添加此行确保有pts属性
+
+ # if isinstance(img, np.ndarray): -- 留个纪念
+ #处理完的图片后返回-bgr模式
+ img_bgr_ndarray = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
+ # 将检查结果转换为WebP格式图片 --在线程里面完成应该可以减少网页端处理时间
+ ret,frame_bgr_webp=cv2.imencode('.jpg', img_bgr_ndarray)
+ if not ret:
+ buffer_bgr_webp = None
else:
- #self.logger.error("img不是np.darry")
- new_frame = None
- #处理完的图片后返回-gbr模式 (new_frame 是 rgb)
- img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
- return new_frame,img
+ buffer_bgr_webp = frame_bgr_webp.tobytes()
+ return buffer_bgr_webp,img_bgr_ndarray
def dowork_thread(self,channel_id):
'''一个通道一个线程,关联的模型在一个线程检测,局部变量都是一个通道独有'''
- channel_data = self.verify_list[channel_id] #一通道一线程 [url,type,True,img_buffer,img,icount]
- view_buffer_deque = deque(maxlen=myCongif.get_data("buffer_len"))
+ channel_data = self.verify_list.get_channel(channel_id) #是对ChannelData 对象的引用
+ context = None
+ # 线程ACL初始化
+ if self.model_platform == "acl": # ACL线程中初始化内容
+ context = ACLModeManger.th_inti_acl(self.device_id)
#查询关联的模型 --- 在循环运行前把基础数据都准备好
myDBM = DBManager()
myDBM.connect()
@@ -236,87 +274,86 @@ class ModelManager:
myModle_data = [] #存放检测参数 一个模型一个
schedule_list = [] #布防策略 -一个模型一个
result_list = [] #检测结果记录 -一个模型一个
+ warn_last_time =[] #最新的报警时间记录 -一个模型一个
proportion_list = []#占比设定 -一个模型一个
warn_save_count = []#没个模型触发报警后,保存录像的最新帧序号 -一个模型一个
- threshold_list = [] #模型针对该通道的一个置信阈值, -个模型一个
-
- view_buffer = []
- ibuffer_count = 0
- last_img = None
#获取视频通道的模型相关数据-list
for model in myModels:
#基于基类实例化模块类
- #m = self._import_model("",model[5],model[8]) #动态加载模型处理文件py --需要验证模型文件是否能加载
- m = None
+ m = self._import_model("",model[5],model[8]) #动态加载模型处理文件py --需要验证模型文件是否能加载
+ #m = None
if m:
myModle_list.append(m) #没有成功加载的模型原画输出
myModle_data.append(model)
- #model[6] -- c2m_id --把通道对于模型的 0-周一,6-周日
+ #model[6] -- c2m_id --布防计划 0-周一,6-周日
schedule_list.append(self.getschedule(model[6],myDBM))
result = [0 for _ in range(model[3] * myCongif.get_data("verify_rate"))] #初始化时间*验证帧率数量的结果list
result_list.append(result)
+ warn_last_time.append(time.time())
proportion_list.append(model[4]) #判断是否报警的占比
warn_save_count.append(0) #保存录像的最新帧初始化为0
#开始拉取画面循环检测
cap = None
- iread_count =0 #失败读取的次数
+ #iread_count =0 #失败读取的次数
last_frame_time = time.time() #初始化个读帧时间
-
- while channel_data[2]: #基于tag 作为运行标识。 线程里只是读,住线程更新,最多晚一轮,应该不用线程锁。需验证
+ cap_sleep_time = myCongif.get_data("cap_sleep_time")
+ #可以释放数据库资源
+ del myDBM
+ warn_interval = myCongif.get_data("warn_interval")
+ while channel_data.bool_run: #基于tag 作为运行标识。 线程里只是读,住线程更新,最多晚一轮,应该不用线程锁。需验证
# 帧率控制帧率
current_time = time.time()
elapsed_time = current_time - last_frame_time
if elapsed_time < self.frame_interval:
time.sleep(self.frame_interval - elapsed_time) #若小于间隔时间则休眠
+ last_frame_time = time.time()
#*********取画面*************
if not cap: #第一次需要打开视频流
try:
- cap = self._open_view(channel_data[0],channel_data[1]) #创建子线程读画面
- iread_count = 0
+ cap = self._open_view(channel_data.str_url,channel_data.int_type) #创建子线程读画面
except:
self.logger.error("打开视频参数错误,终止线程!")
return
- ret,frame = cap.read()
+ ret,frame = cap.read() #除了第一帧,其它应该都是有画面的
if not ret:
- if iread_count > 30:
- self.logger.warning(f"通道-{channel_id}:view disconnected. Reconnecting...")
- cap.release()
- cap = None
- time.sleep(myCongif.get_data("cap_sleep_time"))
- else:
- iread_count += 1
- time.sleep(1.0/20) #20帧只是作为个默认参考值,一般验证帧率都比这个慢
+ # if iread_count > 30: #2024-7-8 重连接机制放VideoCaptureWithFPS
+ # self.logger.warning(f"通道-{channel_id}:view disconnected. Reconnecting...")
+ # cap.release()
+ # cap = None
+ # time.sleep(cap_sleep_time)
+ # else:
+ # iread_count += 1
continue #没读到画面继续
- iread_count = 0 #重置下视频帧计算
- last_frame_time = time.time()
#执行图片推理 -- 如何没有模型或不在工作时间,返回的是原画,要不要控制下帧率? -- 在verify中做了sleep
- new_frame,img = self.verify(frame,myModle_list,myModle_data,channel_id,schedule_list,result_list)
+ buffer_bgr_webp,img_bgr_ndarray = self.verify(frame,myModle_list,myModle_data,channel_id,schedule_list,result_list)
#分析图片放入内存中
- channel_data[3].append(img) # 缓冲区大小由maxlen控制 超上限后,删除最前的数据
- channel_data[5] += 1 #帧序列加一
- #print(self.verify_list[channel_id][5])
- if channel_data[5] > self.icout_max:
- channel_data = 0
- channel_data[4] = new_frame #一直更新最新帧**********
+ channel_data.add_deque(img_bgr_ndarray) # 缓冲区大小由maxlen控制 超上限后,删除最前的数据
+ channel_data.increment_counter() #帧序列加一
+ # 一直更新最新帧,提供网页端显示
+ channel_data.update_last_frame(buffer_bgr_webp)
#print(f"{channel_id}--Frame updated at:",time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
-
- #验证result_list -是否触发报警要求
+ #验证result_list -是否触发报警要求 --遍历每个模型执行的result
for i in range(len(result_list)):
result = result_list[i]
proportion = proportion_list[i]
-
count_one = float(sum(result)) #1,0 把1累加的和就是1的数量
ratio_of_ones = count_one / len(result)
#self.logger.debug(result)
if ratio_of_ones >= proportion: #触发报警
+ # 基于时间间隔判断
+ current_time = time.time()
+ elapsed_time = current_time - warn_last_time[i]
+ if elapsed_time < warn_interval:
+ continue
+ warn_last_time[i] = current_time
model_name = myModle_data[i][7]
w_s_count = warn_save_count[i]
- buffer_count = channel_data[5]
- self.save_warn(model_name,w_s_count,buffer_count,channel_data[3],cap.width,cap.height,
- channel_id,myDBM,self.FPS,self.fourcc)
+ buffer_count = channel_data.get_counter()
+ self.save_warn(model_name,w_s_count,buffer_count,channel_data.copy_deque(),
+ cap.width,cap.height,channel_id,None,self.FPS,self.fourcc)
self.send_warn()
#更新帧序列号
warn_save_count[i] = buffer_count
@@ -327,17 +364,24 @@ class ModelManager:
# end_time = time.time() # 结束时间
# print(f"Processing time: {end_time - start_time} seconds")
# 本地显示---测试使用
- if channel_id == 2:
- cv2.imshow(str(channel_id), img)
- if cv2.waitKey(1) & 0xFF == ord('q'):
- break
+ # if channel_id == 2:
+ # cv2.imshow(str(channel_id), img)
+ # if cv2.waitKey(1) & 0xFF == ord('q'):
+ # break
#结束线程
- cap.release()
+
+ cap.release() #视频采集线程结束
+ if context:#ACL线程中反初始化内容 -- 若线程异常退出,这些资源就不能正常释放了
+ #先释放每个模型资源
+ for model in myModle_list:
+ del model
+ #再释放context
+ ACLModeManger.th_del_acl(context)
#cv2.destroyAllWindows()
def save_warn(self,model_name,w_s_count,buffer_count,buffer,width,height,channnel_id,myDBM,FPS,fourcc):
'''
- 保存报警信息
+ 保存报警信息 --- 涉及到I/O操作可以通过线程取执行 -- 避免主线程阻塞 --还未验证-2024-7-6
:param model_name: 模型名称,如人员入侵
:param w_s_count: 报警已存储的最新帧序列
:param buffer_count: 当前视频缓冲区的最新帧序列
@@ -347,36 +391,49 @@ class ModelManager:
:param channnel_id: 视频通道ID
:return: ret 数据库操作记录
'''
- now = datetime.datetime.now() # 获取当前日期和时间
- current_time_str = now.strftime("%Y-%m-%d_%H-%M-%S")
- filename = f"{channnel_id}_{current_time_str}"
- #保存视频
- video_writer = cv2.VideoWriter(f"model/warn/{filename}.mp4", fourcc, FPS, (width, height))
- if not video_writer.isOpened():
- print(f"Failed to open video writer for model/warn/{filename}.mp4")
- return False
- ilen = len(buffer)
- istart = 0;
- iend = ilen
- if buffer_count < w_s_count or (buffer_count-w_s_count) > ilen: #buffer_count重置过
- #buffer区,都保存为视频
- istart = 0
- else:#只取差异的缓冲区大小
- istart = ilen - (buffer_count-w_s_count)
- for i in range(istart,iend):
- video_writer.write(buffer[i])
- video_writer.release()
- #保存图片
- ret = cv2.imwrite(f"model/warn/{filename}.png",buffer[-1])
- if not ret:
- print("保存图片失败")
- return False
- #保存数据库
- strsql = (f"INSERT INTO warn (model_name ,video_path ,img_path ,creat_time,channel_id ) "
- f"Values ('{model_name}','model/warn/{filename}.mp4','model/warn/{filename}.png',"
- f"'{current_time_str}','{channnel_id}');")
- ret = myDBM.do_sql(strsql)
- return ret
+ return
+
+ def save_warn_th(model_name,w_s_count,buffer_count,buffer,width,height,channnel_id,myDBM,FPS,fourcc):
+ now = datetime.datetime.now() # 获取当前日期和时间
+ current_time_str = now.strftime("%Y-%m-%d_%H-%M-%S")
+ filename = f"{channnel_id}_{current_time_str}"
+ save_path = myCongif.get_data("warn_video_path")
+ #保存视频
+ video_writer = cv2.VideoWriter(f"{save_path}{filename}.mp4", fourcc, FPS, (width, height))
+ if not video_writer.isOpened():
+ print(f"Failed to open video writer for model/warn/{filename}.mp4")
+ return False
+ ilen = len(buffer)
+ istart = 0;
+ iend = ilen
+ if buffer_count < w_s_count or (buffer_count-w_s_count) > ilen: #buffer_count重置过
+ #buffer区,都保存为视频
+ istart = 0
+ else:#只取差异的缓冲区大小
+ istart = ilen - (buffer_count-w_s_count)
+ for i in range(istart,iend):
+ video_writer.write(buffer[i])
+ video_writer.release()
+ #保存图片
+ ret = cv2.imwrite(f"model/warn/{filename}.png",buffer[-1])
+ #buffer使用完后删除
+ del buffer
+ if not ret:
+ print("保存图片失败")
+ return False
+ #保存数据库
+ myDBM = DBManager()
+ myDBM.connect()
+ strsql = (f"INSERT INTO warn (model_name ,video_path ,img_path ,creat_time,channel_id ) "
+ f"Values ('{model_name}','model/warn/{filename}.mp4','model/warn/{filename}.png',"
+ f"'{current_time_str}','{channnel_id}');")
+ ret = myDBM.do_sql(strsql)
+ del myDBM #释放数据库连接资源
+ return ret
+
+ th_chn = threading.Thread(target=save_warn_th,
+ args=(model_name,w_s_count,buffer_count,buffer,width,height,channnel_id,None,FPS,fourcc,)) # 一个视频通道一个线程,线程句柄暂时部保留
+ th_chn.start()
def send_warn(self):
'''发送报警信息'''
@@ -397,28 +454,19 @@ class ModelManager:
strsql = f"select id,ulr,type from channel where is_work = 1 and id = {channel_id};" #单通道启动检测线程
datas = mDBM.do_select(strsql)
for data in datas:
- img_buffer = deque(maxlen=myCongif.get_data("buffer_len")) #创建个定长的视频buffer
- img = None
- icout = 0 #跟img_buffer对应,记录进入缓冲区的帧序列号
- run_data = [data[1],data[2],True,img_buffer,img,icout]
- self.verify_list[data[0]] = run_data #需要验证重复情况#?
+ # img_buffer = deque(maxlen=myCongif.get_data("buffer_len")) #创建个定长的视频buffer
+ # img = None
+ # icout = 0 #跟img_buffer对应,记录进入缓冲区的帧序列号
+ # run_data = [data[1],data[2],True,img_buffer,img,icout]
+ # self.verify_list[data[0]] = run_data #需要验证重复情况#? channel_id, str_url, int_type, bool_run, deque_length
+ self.verify_list.add_channel(data[0],data[1],data[2],True,myCongif.get_data("buffer_len"),myCongif.get_data("RESET_INTERVAL"))
th_chn = threading.Thread(target=self.dowork_thread, args=(data[0],)) #一个视频通道一个线程,线程句柄暂时部保留
th_chn.start()
def stop_work(self,channel_id=0):
'''停止工作线程,0-停止所有,非0停止对应通道ID的线程'''
- if channel_id ==0: #所有线程停止
- for data in self.verify_list:
- data[2] = False
- del data[3]
- time.sleep(2)
- self.verify_list.clear()
- else:
- data = self.verify_list[channel_id]
- data[2] = False
- del data[3]
- time.sleep(1)
- del self.verify_list[channel_id]
+ self.verify_list.stop_channel(channel_id)
+
#print(f"Current working directory (ModelManager.py): {os.getcwd()}")
mMM = ModelManager()
@@ -445,8 +493,8 @@ def test1():
if __name__ == "__main__":
- #mMM.start_work()
- test1()
+ mMM.start_work()
+ #test1()
print("111")
# name = acl.get_soc_name()
# count, ret = acl.rt.get_device_count()
diff --git a/core/templates/index.html b/core/templates/index.html
deleted file mode 100644
index 17fd8a9..0000000
--- a/core/templates/index.html
+++ /dev/null
@@ -1,49 +0,0 @@
-
-
-
-
-
- YOLOv5 Video Streams
-
-
-
-
- {% for stream_id in streams %}
-
-
 }})
-
- {% endfor %}
-
-
-
diff --git a/model/plugins/ModelBase.py b/model/plugins/ModelBase.py
index 6bded86..e88da01 100644
--- a/model/plugins/ModelBase.py
+++ b/model/plugins/ModelBase.py
@@ -1,12 +1,13 @@
from abc import abstractmethod,ABC
from shapely.geometry import Point, Polygon
from myutils.ConfigManager import myCongif
+from myutils.MyLogger_logger import LogHandler
import numpy as np
import cv2
import ast
-import platform
if myCongif.get_data("model_platform") == "acl":
import acl
+import platform
#-----acl相关------
SUCCESS = 0 # 成功状态值
@@ -20,6 +21,7 @@ class ModelBase(ABC):
:param path: 模型文件本身的路径
:param threshold: 模型的置信阈值
'''
+ self.mylogger = LogHandler().get_logger("ModelManager")
self.name = None #基于name来查询,用户对模型的配置参数,代表着模型名称需要唯一 2024-6-18 -逻辑还需要完善和验证
self.version = None
self.model_type = None # 模型类型 1-图像分类,2-目标检测(yolov5),3-分割模型,4-关键点
@@ -30,6 +32,7 @@ class ModelBase(ABC):
# POCType.BRUTE: self.do_brute
}
self.model_path = path # 模型路径
+ self.init_ok = False
def __del__(self):
@@ -50,32 +53,23 @@ class ModelBase(ABC):
#acl ----- 相关-----
def _init_acl(self):
- '''acl初始化函数'''
- self.device_id = 0
- #step1 初始化
- ret = acl.init()
- ret = acl.rt.set_device(self.device_id) # 指定运算的Device
+ device_id = 0
+ self.context, ret = acl.rt.create_context(device_id) # 显式创建一个Context
if ret:
raise RuntimeError(ret)
- self.context, ret = acl.rt.create_context(self.device_id) # 显式创建一个Context
- if ret:
- raise RuntimeError(ret)
- print('Init ACL Successfully')
+ print('Init TH-Context Successfully')
def _del_acl(self):
- '''acl去初始化'''
+ device_id = 0
+ # 线程释放context
ret = acl.rt.destroy_context(self.context) # 释放 Context
if ret:
raise RuntimeError(ret)
- ret = acl.rt.reset_device(self.device_id) # 释放Device
- if ret:
- raise RuntimeError(ret)
- ret = acl.finalize() # 去初始化
- if ret:
- raise RuntimeError(ret)
- print('Deinit ACL Successfully')
+ print('Deinit TH-Context Successfully')
+ print('ACL finalize Successfully')
def _init_resource(self):
+ #self._init_acl() #测试使用
''' 初始化模型、输出相关资源。相关数据类型: aclmdlDesc aclDataBuffer aclmdlDataset'''
print("Init model resource")
# 加载模型文件
@@ -85,27 +79,32 @@ class ModelBase(ABC):
print(f"{self.model_path}---模型加载失败!")
return False
self.model_desc = acl.mdl.create_desc() # 初始化模型信息对象
- ret = acl.mdl.get_desc(self.model_desc, self.model_id) # 根据模型获取描述信息
+ ret = acl.mdl.get_desc(self.model_desc, self.model_id) # 根据模型ID获取该模型的aclmdlDesc类型数据(描述信息)
print("[Model] Model init resource stage success")
# 创建模型输出 dataset 结构
- self._gen_output_dataset() # 创建模型输出dataset结构
+ ret = self._gen_output_dataset() # 创建模型输出dataset结构
+ if ret !=0:
+ print("[Model] create model output dataset fail")
+ return False
return True
def _gen_output_dataset(self):
''' 组织输出数据的dataset结构 '''
ret = SUCCESS
- self._output_num = acl.mdl.get_num_outputs(self.model_desc) # 获取模型输出个数
+ self._output_num = acl.mdl.get_num_outputs(self.model_desc) # 根据aclmdlDesc类型的数据,获取模型的输出个数
self.output_dataset = acl.mdl.create_dataset() # 创建输出dataset结构
for i in range(self._output_num):
temp_buffer_size = acl.mdl.get_output_size_by_index(self.model_desc, i) # 获取模型输出个数
temp_buffer, ret = acl.rt.malloc(temp_buffer_size, ACL_MEM_MALLOC_NORMAL_ONLY) # 为每个输出申请device内存
dataset_buffer = acl.create_data_buffer(temp_buffer,
temp_buffer_size) # 创建输出的data buffer结构,将申请的内存填入data buffer
- _, ret = acl.mdl.add_dataset_buffer(self.output_dataset, dataset_buffer) # 将 data buffer 加入输出dataset
+ _, ret = acl.mdl.add_dataset_buffer(self.output_dataset, dataset_buffer) # 将 data buffer 加入输出dataset(地址)
if ret == FAILED:
self._release_dataset(self.output_dataset) # 失败时释放dataset
+ return ret
print("[Model] create model output dataset success")
+ return ret
def _gen_input_dataset(self, input_list):
''' 组织输入数据的dataset结构 '''
@@ -121,7 +120,7 @@ class ModelBase(ABC):
if ret == FAILED:
self._release_dataset(self.input_dataset) # 失败时释放dataset
- print("[Model] create model input dataset success")
+ #print("[Model] create model input dataset success")
def _unpack_bytes_array(self, byte_array, shape, datatype):
''' 将内存不同类型的数据解码为numpy数组 '''
@@ -156,8 +155,8 @@ class ModelBase(ABC):
# 根据模型输出的shape和数据类型,将内存数据解码为numpy数组
outret = acl.mdl.get_output_dims(self.model_desc, i)[0]
dims = outret["dims"] # 获取每个输出的维度
- print(f"name:{outret['name']}")
- print(f"dimCount:{outret['dimCount']}")
+ # print(f"name:{outret['name']}")
+ # print(f"dimCount:{outret['dimCount']}")
'''
dims = {
"name": xxx, #tensor name
@@ -174,7 +173,12 @@ class ModelBase(ABC):
'''创建输入dataset对象, 推理完成后, 将输出数据转换为numpy格式'''
self._gen_input_dataset(input_list) # 创建模型输入dataset结构
ret = acl.mdl.execute(self.model_id, self.input_dataset, self.output_dataset) # 调用离线模型的execute推理数据
+ if ret:
+ self.mylogger.error(f"acl.mdl.execute fail!--{ret}")
+ self._release_dataset(self.input_dataset) # 失败时释放dataset --创建输入空间失败时会释放。
+ return None
out_numpy = self._output_dataset_to_numpy() # 将推理输出的二进制数据流解码为numpy数组, 数组的shape和类型与模型输出规格一致
+ self._release_dataset(self.input_dataset) # 释放dataset -- 要不要执行需要验证
return out_numpy
def release(self):
@@ -194,6 +198,8 @@ class ModelBase(ABC):
ret = acl.mdl.destroy_desc(self.model_desc) # 释放模型描述信息
self._is_released = True
print("Model release source success")
+ #测试使用
+ #self._del_acl()
def _release_dataset(self, dataset):
''' 释放 aclmdlDataset 类型数据 '''
diff --git a/model/plugins/RYRQ_ACL/RYRQ_Model_ACL.py b/model/plugins/RYRQ_ACL/RYRQ_Model_ACL.py
index fee22ec..93b28ab 100644
--- a/model/plugins/RYRQ_ACL/RYRQ_Model_ACL.py
+++ b/model/plugins/RYRQ_ACL/RYRQ_Model_ACL.py
@@ -5,6 +5,7 @@ from model.base_model.ascnedcl.det_utils import get_labels_from_txt, letterbox,
import cv2
import numpy as np
import torch # 深度学习运算框架,此处主要用来处理数据
+from core.ACLModelManager import ACLModeManger
class Model(ModelBase):
def __init__(self,path,threshold=0.5):
@@ -21,7 +22,7 @@ class Model(ModelBase):
self._output_num = 0 # 输出数据个数
self._output_info = [] # 输出信息列表
self._is_released = False # 资源是否被释放
- self.name = "人员入侵"
+ self.name = "人员入侵-yolov5"
self.version = "V1.0"
self.model_type = 2
@@ -29,17 +30,16 @@ class Model(ModelBase):
self.netw = 640 # 缩放的目标宽度, 也即模型的输入宽度
self.conf_threshold = threshold # 置信度阈值
- self._init_acl()
- self.init_ok = True
- if not self._init_resource():
- print("加载模型文件失败!")
- self.init_ok = False
+ #加载ACL模型文件---模型加载、模型执行、模型卸载的操作必须在同一个Context下
+ if self._init_resource(): #加载离线模型,创建输出缓冲区
+ print("加载模型文件成功!")
+ self.init_ok = True
def __del__(self):
+ #卸载ACL模型文件
if self.init_ok:
self.release()
- self._del_acl()
def verify(self,image,data,isdraw=1):
@@ -50,42 +50,45 @@ class Model(ModelBase):
img = np.ascontiguousarray(img, dtype=np.float32) / 255.0 # 转换为内存连续存储的数组
# 模型推理, 得到模型输出
- output = self.execute([img,])[0]
-
- # 后处理 -- boxout 是 tensor-list: [tensor([[],[].[]])] --[x1,y1,x2,y2,置信度,coco_index]
- boxout = nms(torch.tensor(output), conf_thres=0.4,
- iou_thres=0.5) # 利用非极大值抑制处理模型输出,conf_thres 为置信度阈值,iou_thres 为iou阈值
- pred_all = boxout[0].numpy() # 转换为numpy数组 -- [[],[],[]] --[x1,y1,x2,y2,置信度,coco_index]
- # pred_all[:, :4] 取所有行的前4列,pred_all[:,1]--第一列
- scale_coords([640, 640], pred_all[:, :4], image.shape, ratio_pad=(scale_ratio, pad_size)) # 将推理结果缩放到原始图片大小
+ outputs = None
+ #outputs = self.execute([img,])#创建input,执行模型,返回结果 --失败返回None
+ filtered_pred_all = None
bwarn = False
warn_text = ""
- #是否有检测区域,有先绘制检测区域 由于在该函数生成了polygon对象,所有需要在检测区域前调用。
+ # 是否有检测区域,有先绘制检测区域 由于在该函数生成了polygon对象,所有需要在检测区域前调用。
if data[1] == 1:
- self.draw_polygon(image,data[2],(255,0,0))
- #过滤掉不是目标标签的数据
- filtered_pred_all = pred_all[pred_all[:, 5] == 0]
- # 绘制检测结果 --- 也需要封装在类里,
- for pred in filtered_pred_all:
- x1, y1, x2, y2 = int(pred[0]), int(pred[1]), int(pred[2]), int(pred[3])
- # # 绘制目标识别的锚框 --已经在draw_bbox里处理
- # cv2.rectangle(image, (x1, y1), (x2, y2), (0, 255, 0), 2)
- if data[1] == 1: # 指定了检测区域
- x_center = (x1 + x2) / 2
- y_center = (y1 + y2) / 2
- #绘制中心点?
- cv2.circle(image, (int(x_center), int(y_center)), 5, (0, 0, 255), -1)
- #判断是否区域点
- if not self.is_point_in_region((x_center, y_center)):
- continue #没产生报警-继续
- #产生报警
- bwarn = True
- warn_text = "Intruder detected!"
- img_dw = draw_bbox(filtered_pred_all, image, (0, 255, 0), 2, labels_dict) # 画出检测框、类别、概率
- cv2.imwrite('img_res.png', img_dw)
- return filtered_pred_all, False, ""
+ self.draw_polygon(image, data[2], (255, 0, 0))
+ if outputs:
+ output = outputs[0] #只放了一张图片
+ # 后处理 -- boxout 是 tensor-list: [tensor([[],[].[]])] --[x1,y1,x2,y2,置信度,coco_index]
+ boxout = nms(torch.tensor(output), conf_thres=0.3,
+ iou_thres=0.5) # 利用非极大值抑制处理模型输出,conf_thres 为置信度阈值,iou_thres 为iou阈值
+ pred_all = boxout[0].numpy() # 转换为numpy数组 -- [[],[],[]] --[x1,y1,x2,y2,置信度,coco_index]
+ # pred_all[:, :4] 取所有行的前4列,pred_all[:,1]--第一列
+ scale_coords([640, 640], pred_all[:, :4], image.shape, ratio_pad=(scale_ratio, pad_size)) # 将推理结果缩放到原始图片大小
+ #过滤掉不是目标标签的数据 -- 序号0-- person
+ filtered_pred_all = pred_all[pred_all[:, 5] == 0]
+ # 绘制检测结果 --- 也需要封装在类里,
+ for pred in filtered_pred_all:
+ x1, y1, x2, y2 = int(pred[0]), int(pred[1]), int(pred[2]), int(pred[3])
+ # # 绘制目标识别的锚框 --已经在draw_bbox里处理
+ # cv2.rectangle(image, (x1, y1), (x2, y2), (0, 255, 0), 2)
+ if data[1] == 1: # 指定了检测区域
+ x_center = (x1 + x2) / 2
+ y_center = (y1 + y2) / 2
+ #绘制中心点?
+ cv2.circle(image, (int(x_center), int(y_center)), 5, (0, 0, 255), -1)
+ #判断是否区域点
+ if not self.is_point_in_region((x_center, y_center)):
+ continue #没产生报警-继续
+ #产生报警 -- 有一个符合即可
+ bwarn = True
+ warn_text = "Intruder detected!"
+ img_dw = draw_bbox(filtered_pred_all, image, (0, 255, 0), 2, labels_dict) # 画出检测框、类别、概率
+ #cv2.imwrite('img_res.png', img_dw)
+ return filtered_pred_all, bwarn, warn_text
def testRun(self):
print("1111")
\ No newline at end of file
diff --git a/run.py b/run.py
index b4e9534..02be082 100644
--- a/run.py
+++ b/run.py
@@ -1,7 +1,3 @@
-import platform
-
-from myutils.ConfigManager import myCongif
-from myutils.MyLogger_logger import LogHandler
from core.ViewManager import mVManager
from web import create_app
from core.ModelManager import mMM
diff --git a/web/API/channel.py b/web/API/channel.py
index aeaa387..83729b9 100644
--- a/web/API/channel.py
+++ b/web/API/channel.py
@@ -14,10 +14,9 @@ async def channel_tree(): #获取通道树
return jsonify(channel_tree)
@api.route('/channel/list',methods=['GET'])
-
async def channel_list(): #获取通道列表 --分页查询,支持区域和通道名称关键字查询
strsql = ("select t2.area_name ,t1.ID ,t1.channel_name ,t1.ulr ,t1.'type' ,t1.status ,t1.element_id"
- " from channel t1 left join area t2 on t1.area_id = t2.id;")
+ " from channel t1 left join area t2 on t1.area_id = t2.id where t1.is_work=1;")
data = mDBM.do_select(strsql)
channel_list = [{"area_name": channel[0], "ID": channel[1], "channel_name": channel[2], "ulr": channel[3],
"type": channel[4], "status": channel[5], "element_id": channel[6]} for channel in data]
diff --git a/web/API/model.py b/web/API/model.py
index e8461fa..bc1c353 100644
--- a/web/API/model.py
+++ b/web/API/model.py
@@ -6,6 +6,7 @@ from core.DBManager import mDBM
from core.Upload_file import allowed_file,check_file,updata_model
from myutils.ConfigManager import myCongif
from werkzeug.utils import secure_filename
+
@api.route('/model/list',methods=['GET'])
@login_required
async def model_list(): #获取算法列表
diff --git a/web/API/viedo.py b/web/API/viedo.py
index aab604a..6e045bc 100644
--- a/web/API/viedo.py
+++ b/web/API/viedo.py
@@ -1,16 +1,10 @@
-import cv2
import asyncio
-import time
from . import api
from quart import jsonify, request,websocket
-from aiortc import RTCPeerConnection, RTCSessionDescription, VideoStreamTrack,RTCConfiguration
from core.ModelManager import mMM
from core.DBManager import mDBM
from myutils.ConfigManager import myCongif
-from fractions import Fraction
-import threading
import logging
-from quart.helpers import Response
# 配置日志
logging.basicConfig(level=logging.INFO)
@@ -54,48 +48,6 @@ pcs = {}
'''
---------------------传输--------------------------
'''
-# class VideoTransformTrack(VideoStreamTrack):
-# kind = "video"
-# def __init__(self,cid,itype=1): #0-usb,1-RTSP,2-海康SDK --itype已没有作用
-# super().__init__()
-# self.channel_id = cid
-#
-# self.frame_rate = myCongif.get_data("frame_rate")
-# self.time_base = Fraction(1, self.frame_rate)
-# self.frame_count = 0
-# self.start_time = time.time()
-#
-# async def recv(self):
-# new_frame = None
-# time.sleep(1.0 / self.frame_rate)
-# new_frame = mMM.verify_list[self.channel_id][4]
-# # while True:
-# # new_frame = mMM.verify_list[self.channel_id][4]
-# # #new_frame = av.VideoFrame.from_ndarray(img, format="rgb24")
-# # if new_frame is not None:
-# # break
-# # else:
-# # time.sleep(1.0 / self.frame_rate)
-# # 设置时间戳和时间基数 -- 根据耗时实时计算帧率
-# # elapsed_time = time.time() - self.start_time
-# # self.frame_count += 1
-# # actual_frame_rate = self.frame_count / elapsed_time
-# # self.time_base = Fraction(1, int(actual_frame_rate))
-# #设置时间戳和帧率
-# self.frame_count += 1
-# if self.frame_count > myCongif.get_data("RESET_INTERVAL"):
-# self.frame_count = 0
-# new_frame.pts = self.frame_count
-# new_frame.time_base = self.time_base
-# #print(f"{self.channel_id} -- Frame pts: {new_frame.pts}, time_base: {new_frame.time_base}")
-#
-# # 将检测结果图像转换为帧
-# # new_frame.pts = int(self.cap.get(cv2.CAP_PROP_POS_FRAMES))
-# # new_frame.time_base = self.time_base
-# # end_time = time.time() # 结束时间
-# # print(f"Processing time: {end_time - start_time} seconds")
-# #print("webRTC recv at:",time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
-# return new_frame
async def get_stats(peer_connection):
stats = await peer_connection.getStats()
@@ -189,63 +141,30 @@ async def get_stats(peer_connection):
# "msg":reMsg
# })
-async def generate_frames(stream_id):
- #video_capture = streams[stream_id]
- start_time = time.time()
- icount = 0
- frame_rate_time = 1.0 / myCongif.get_data("frame_rate")
- while True:
- try:
- await asyncio.sleep(frame_rate_time)
- frame = mMM.verify_list[int(stream_id)][4]
- if frame is not None:
- img = frame.to_ndarray(format="bgr24")
- ret, buffer = cv2.imencode('.jpg', img)
- if not ret:
- continue
- frame = buffer.tobytes()
- yield (b'--frame\r\n'
- b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
- end_time = time.time() # 结束时间
- icount += 1
- print(f"icount:{icount},Processing time: {end_time - start_time} seconds")
- except asyncio.CancelledError:
- print("Task was cancelled")
- break # 处理任务取消
- except Exception as e:
- print(f"Error: {e}")
- break # 在出现异常时退出循环
- print("退出循环")
-
-@api.route('/video_feed/')
-async def video_feed(stream_id):
- if int(stream_id) != 2:
- return "None"
- print("执行一次:video_feed")
- return Response(generate_frames(stream_id),mimetype='multipart/x-mixed-replace; boundary=frame')
-
@api.websocket('/ws/video_feed/')
async def ws_video_feed(channel_id):
- while True:
- frame = mMM.verify_list[int(channel_id)][4]
+ channel_data = mMM.verify_list.get_channel(channel_id)
+ frame_rate = myCongif.get_data("frame_rate")
+ while channel_data.bool_run: #这里的多线程并发,还需要验证检查
+ frame = channel_data.get_last_frame()
if frame is not None:
- img = frame.to_ndarray(format="bgr24")
- ret, buffer = cv2.imencode('.jpg', img)
- if not ret:
- continue
- frame = buffer.tobytes()
+ #img = frame.to_ndarray(format="bgr24")
+ # ret, buffer = cv2.imencode('.jpg', frame)
+ # if not ret:
+ # continue
+ # frame = buffer.tobytes()
await websocket.send(frame)
- await asyncio.sleep(1.0 / myCongif.get_data("frame_rate")) # Adjust based on frame rate
+ await asyncio.sleep(1.0 / frame_rate) # Adjust based on frame rate
@api.route('/shutdown', methods=['POST'])
-async def shutdown():#这是全关
+async def shutdown():#这是全关 --需要修改
coros = [pc.close() for pc in pcs]
await asyncio.gather(*coros)
pcs.clear()
return 'Server shutting down...'
@api.route('/close_stream', methods=['POST'])
-async def close_stream():
+async def close_stream(): # 需要修改
channel_id = (await request.form)['channel_id']
reStatus = 0
reMsg =""
diff --git a/web/__init__.py b/web/__init__.py
index 72d1d41..8c10270 100644
--- a/web/__init__.py
+++ b/web/__init__.py
@@ -1,12 +1,12 @@
-from quart import Quart, render_template, request, jsonify,send_from_directory,session,redirect, url_for
+from quart import Quart,session,redirect, url_for
from quart_session import Session
from pymemcache.client import base
from .main import main
from .API import api
from functools import wraps
from myutils.ConfigManager import myCongif
-from quart_sqlalchemy import SQLAlchemy
-from flask_migrate import Migrate
+# from quart_sqlalchemy import SQLAlchemy
+# from flask_migrate import Migrate
#app.config['SECRET_KEY'] = 'mysecret' #密钥 --需要放配置文件
#socketio = SocketIO(app)
diff --git a/web/common/utils.py b/web/common/utils.py
index 68947f6..17a03c5 100644
--- a/web/common/utils.py
+++ b/web/common/utils.py
@@ -1,7 +1,6 @@
from PIL import Image, ImageDraw, ImageFont,ImageFilter
import random
import string
-import os
import io
from functools import wraps
from quart import session, redirect, url_for
diff --git a/web/main/routes.py b/web/main/routes.py
index 583a15b..a0aa799 100644
--- a/web/main/routes.py
+++ b/web/main/routes.py
@@ -14,6 +14,7 @@ from werkzeug.utils import secure_filename
def login_required(f):
@wraps(f)
async def decorated_function(*args, **kwargs):
+ print("decorated_function3")
if 'user' not in session:
return redirect(url_for('main.index',error='未登录,请重新登录'))
return await f(*args, **kwargs)
@@ -21,6 +22,7 @@ def login_required(f):
@main.route('/')
async def index():
+ print("index")
#error = request.args.get('error')
return await render_template('实时预览.html')
#return await render_template('登录.html',error=error)
diff --git a/web/main/static/resources/scripts/aiortc-client-new.js b/web/main/static/resources/scripts/aiortc-client-new.js
index 6efecca..624b0df 100644
--- a/web/main/static/resources/scripts/aiortc-client-new.js
+++ b/web/main/static/resources/scripts/aiortc-client-new.js
@@ -13,14 +13,14 @@ document.addEventListener('DOMContentLoaded', async function() {
// 遍历输出每个元素的信息
channel_list.forEach(channel => {
if(channel.element_id){ //""空为false,非空为true
-// console.log(`Area Name: ${channel.area_name}`);
-// console.log(`ID: ${channel.ID}`);
-// console.log(`Channel Name: ${channel.channel_name}`);
-// console.log(`URL: ${channel.url}`);
-// console.log(`Type: ${channel.type}`);
-// console.log(`Status: ${channel.status}`);
-// console.log(`Element ID: ${channel.element_id}`);
- connectToStream(channel.element_id,channel.ID,channel.area_name,channel.channel_name)
+ console.log(`Area Name: ${channel.area_name}`);
+ console.log(`ID: ${channel.ID}`);
+ console.log(`Channel Name: ${channel.channel_name}`);
+ console.log(`URL: ${channel.url}`);
+ console.log(`Type: ${channel.type}`);
+ console.log(`Status: ${channel.status}`);
+ console.log(`Element ID: ${channel.element_id}`);
+ connectToStream(channel.element_id,channel.ID,channel.area_name,channel.channel_name)
}
});
} catch (error) {
@@ -29,10 +29,6 @@ document.addEventListener('DOMContentLoaded', async function() {
});
function connectToStream(element_id,channel_id,area_name,channel_name) {
-// const videoContainer = document.getElementById('video-container');
-// const imgElement = document.createElement('img');
-// imgElement.id = `${element_id}`;
-// videoContainer.appendChild(imgElement);
const imgElement = document.getElementById(element_id);
imgElement.alt = `Stream ${area_name}--${channel_name}`;
const streamUrl = `ws://${window.location.host}/api/ws/video_feed/${channel_id}`;
diff --git a/web/main/templates/实时预览.html b/web/main/templates/实时预览.html
index 82af553..a722556 100644
--- a/web/main/templates/实时预览.html
+++ b/web/main/templates/实时预览.html
@@ -175,42 +175,11 @@
-
-
![Video Stream]()
diff --git a/zfbox.db b/zfbox.db
index b971ee5..920cb4b 100644
Binary files a/zfbox.db and b/zfbox.db differ