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 %} -
- Stream {{ stream_id }} -
- {% 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
-
Video Stream diff --git a/zfbox.db b/zfbox.db index b971ee5..920cb4b 100644 Binary files a/zfbox.db and b/zfbox.db differ