Browse Source

修改模型推理实现方案前版本

master
张龙 10 months ago
parent
commit
7c67ef0985
  1. 2
      .idea/FristProject.iml
  2. 2
      .idea/misc.xml
  3. 9
      config.yaml
  4. 63
      core/ACLModelManager.py
  5. 120
      core/ChannelManager.py
  6. 258
      core/ModelManager.py
  7. 49
      core/templates/index.html
  8. 56
      model/plugins/ModelBase.py
  9. 41
      model/plugins/RYRQ_ACL/RYRQ_Model_ACL.py
  10. 4
      run.py
  11. 3
      web/API/channel.py
  12. 1
      web/API/model.py
  13. 105
      web/API/viedo.py
  14. 6
      web/__init__.py
  15. 1
      web/common/utils.py
  16. 2
      web/main/routes.py
  17. 18
      web/main/static/resources/scripts/aiortc-client-new.js
  18. 31
      web/main/templates/实时预览.html
  19. BIN
      zfbox.db

2
.idea/FristProject.iml

@ -2,7 +2,7 @@
<module type="PYTHON_MODULE" version="4"> <module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager"> <component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" /> <content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="PyTorch" jdkType="Python SDK" /> <orderEntry type="jdk" jdkName="Remote Python 3.9.2 (sftp://root@192.168.3.48:22/usr/local/miniconda3/bin/python)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
</module> </module>

2
.idea/misc.xml

@ -3,7 +3,7 @@
<component name="Black"> <component name="Black">
<option name="sdkName" value="PyTorch" /> <option name="sdkName" value="PyTorch" />
</component> </component>
<component name="ProjectRootManager" version="2" project-jdk-name="PyTorch" project-jdk-type="Python SDK" /> <component name="ProjectRootManager" version="2" project-jdk-name="Remote Python 3.9.2 (sftp://root@192.168.3.48:22/usr/local/miniconda3/bin/python)" project-jdk-type="Python SDK" />
<component name="PyCharmProfessionalAdvertiser"> <component name="PyCharmProfessionalAdvertiser">
<option name="shown" value="true" /> <option name="shown" value="true" />
</component> </component>

9
config.yaml

@ -25,14 +25,17 @@ UPLOAD_FOLDER : uploads
ALLOWED_EXTENSIONS : {'zip'} ALLOWED_EXTENSIONS : {'zip'}
#RTSP #RTSP
RTSP_Check_Time : 600 #10分钟 RTSP_Check_Time : 600 #10分钟 -- 2024-7-8 取消使用
#model #model
model_platform : cpu #acl gpu cpu model_platform : acl #acl gpu cpu
device_id : 0 #单设备配置
weight_path: /model/weights weight_path: /model/weights
yolov5_path: D:/Project/FristProject/model/base_model/yolov5 #使用绝对路径,不同的部署环境需要修改! yolov5_path: D:/Project/FristProject/model/base_model/yolov5 #使用绝对路径,不同的部署环境需要修改!
cap_sleep_time: 300 #5分钟 cap_sleep_time: 120 #5分钟
buffer_len: 100 #分析后画面缓冲区帧数 -- 可以与验证帧率结合确定缓冲区大小 buffer_len: 100 #分析后画面缓冲区帧数 -- 可以与验证帧率结合确定缓冲区大小
RESET_INTERVAL : 100000 #帧数重置上限 RESET_INTERVAL : 100000 #帧数重置上限
frame_rate : 20 #帧率参考值 -- 后续作用主要基于verify_rate进行帧率控制 frame_rate : 20 #帧率参考值 -- 后续作用主要基于verify_rate进行帧率控制
verify_rate : 10 #验证帧率--- 也就是视频输出的帧率 verify_rate : 10 #验证帧率--- 也就是视频输出的帧率
warn_video_path: /mnt/zfbox/model/warn/
warn_interval: 120 #报警间隔--单位秒

63
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

120
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)

258
core/ModelManager.py

@ -7,18 +7,18 @@ import numpy as np
import threading import threading
import importlib.util import importlib.util
import datetime import datetime
import math
import queue import queue
from collections import deque from collections import deque
from core.DBManager import mDBM,DBManager from core.DBManager import mDBM,DBManager
from myutils.MyLogger_logger import LogHandler from myutils.MyLogger_logger import LogHandler
from myutils.ConfigManager import myCongif from myutils.ConfigManager import myCongif
from model.plugins.ModelBase import ModelBase from model.plugins.ModelBase import ModelBase
import sys from core.ChannelManager import ChannelManager
from core.ACLModelManager import ACLModeManger
from PIL import Image from PIL import Image
class VideoCaptureWithFPS: class VideoCaptureWithFPS:
'''视频捕获的封装类,是一个通道一个''' '''视频捕获的封装类,是一个通道一个'''
def __init__(self, source): def __init__(self, source):
@ -29,38 +29,59 @@ class VideoCaptureWithFPS:
if self.cap.isOpened(): #若没有打开成功,在读取画面的时候,已有判断和处理 -- 这里也要检查下内存的释放情况 if self.cap.isOpened(): #若没有打开成功,在读取画面的时候,已有判断和处理 -- 这里也要检查下内存的释放情况
self.width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH)) self.width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
self.height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) self.height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
print(self.width,self.height)
#self.fps = fps # 线程保持最大帧率的刷新画面---过高的帧率会影响CPU性能,但过地的帧率会造成帧积压 #self.fps = fps # 线程保持最大帧率的刷新画面---过高的帧率会影响CPU性能,但过地的帧率会造成帧积压
self.fps = self.cap.get(cv2.CAP_PROP_FPS) + 20 #只是个参考值,休眠时间要比帧率快点,由于read也需要耗时 self.fps = math.ceil(self.cap.get(cv2.CAP_PROP_FPS)/float(myCongif.get_data("verify_rate"))) #向上取整
#print(self.fps) #print(self.fps)
self.running = True self.running = True
self.frame_queue = queue.Queue(maxsize=1) #self.frame_queue = queue.Queue(maxsize=1)
#self.frame = None self.frame = None
#self.read_lock = threading.Lock() self.read_lock = threading.Lock()
self.thread = threading.Thread(target=self.update) self.thread = threading.Thread(target=self.update)
self.thread.start() self.thread.start()
def update(self): def update(self):
icount = 0
while self.running: while self.running:
ret, frame = self.cap.read() ret, frame = self.cap.read()
if not ret: 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 continue
resized_frame = cv2.resize(frame, (int(self.width / 2), int(self.height / 2))) #resized_frame = cv2.resize(frame, (int(self.width / 2), int(self.height / 2)))
# with self.read_lock: with self.read_lock:
# self.frame = resized_frame self.frame = frame
if not self.frame_queue.full(): # if not self.frame_queue.full():
self.frame_queue.put(resized_frame) # self.frame_queue.put(resized_frame)
time.sleep(1.0 / self.fps) #按照视频源的帧率进行休眠
# 跳过指定数量的帧以避免积压
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())) #print("Frame updated at:",time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
def read(self): def read(self):
# with self.read_lock: with self.read_lock:
# frame = self.frame.copy() if self.frame is not None else None frame = self.frame.copy() if self.frame is not None else None
if not self.frame_queue.empty(): if frame is not None:
return True, self.frame_queue.get() return True, frame
else: else:
return False, None return False, None
# if not self.frame_queue.empty():
# return True, self.frame_queue.get()
# else:
# return False, None
def release(self): def release(self):
self.running = False self.running = False
@ -70,7 +91,7 @@ class VideoCaptureWithFPS:
class ModelManager: class ModelManager:
def __init__(self): def __init__(self):
self.verify_list = {} #模型的主要数据 self.verify_list = ChannelManager() #模型的主要数据 -- 2024-7-5修改为类管理通道数据
self.bRun = True self.bRun = True
self.logger = LogHandler().get_logger("ModelManager") self.logger = LogHandler().get_logger("ModelManager")
# 本地YOLOv5仓库路径 # 本地YOLOv5仓库路径
@ -83,12 +104,17 @@ class ModelManager:
self.FPS = myCongif.get_data("verify_rate") # 视频帧率--是否能实现动态帧率 self.FPS = myCongif.get_data("verify_rate") # 视频帧率--是否能实现动态帧率
self.fourcc = cv2.VideoWriter_fourcc(*'mp4v') # 使用 mp4 编码 self.fourcc = cv2.VideoWriter_fourcc(*'mp4v') # 使用 mp4 编码
#基于模型运行环境进行相应初始化工作 #基于模型运行环境进行相应初始化工作
# self.model_platform = myCongif.get_data("model_platform") self.model_platform = myCongif.get_data("model_platform")
# if self.model_platform == "acl": self.device_id = myCongif.get_data("device_id")
# self._init_acl() # acl初始化 -- 一个线程一个 -- 需要验证
if self.model_platform == "acl":
ACLModeManger.init_acl(self.device_id) #acl -- 全程序初始化
def __del__(self): def __del__(self):
self.logger.debug("释放资源") 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 def _open_view(self,url,itype): #打开摄像头 0--USB摄像头,1-RTSP,2-海康SDK
if itype == 0: if itype == 0:
@ -118,6 +144,10 @@ class ModelManager:
if not isinstance(md, ModelBase): if not isinstance(md, ModelBase):
self.logger.error("{} not zf_model".format(md)) self.logger.error("{} not zf_model".format(md))
return None return None
if md.init_ok == False:
self.logger.error("离线模型加载初始化失败!")
return None
else: else:
self.logger.error("{}文件不存在".format(model_path)) self.logger.error("{}文件不存在".format(model_path))
return None return None
@ -148,7 +178,7 @@ class ModelManager:
sixlist.append(datas[i*7 + 5][2]) sixlist.append(datas[i*7 + 5][2])
sevenlist.append(datas[i*7 + 6][2]) sevenlist.append(datas[i*7 + 6][2])
else: else:
self.logger(f"没有数据{c2m_id}") self.logger.debug(f"没有数据--{c2m_id}")
onelist = [1]*24 onelist = [1]*24
twolist = [1]*24 twolist = [1]*24
threelist = [1]*24 threelist = [1]*24
@ -166,6 +196,9 @@ class ModelManager:
schedule_list.append(sevenlist) schedule_list.append(sevenlist)
return schedule_list 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): def verify(self,frame,myModle_list,myModle_data,channel_id,schedule_list,result_list,isdraw=1):
'''验证执行主函数,实现遍历通道关联的模型,调用对应模型执行验证,模型文件遍历执行''' '''验证执行主函数,实现遍历通道关联的模型,调用对应模型执行验证,模型文件遍历执行'''
img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
@ -174,7 +207,7 @@ class ModelManager:
#img = frame #img = frame
# 使用 模型 进行目标检测 # 使用 模型 进行目标检测
i_warn_count = 0 #报警标签 i_warn_count = 0 #报警标签
isverify = False #isverify = False
for i in range(len(myModle_list)): # 遍历通道关联的算法进行检测,若不控制模型数量,有可能需要考虑多线程执行。 for i in range(len(myModle_list)): # 遍历通道关联的算法进行检测,若不控制模型数量,有可能需要考虑多线程执行。
model = myModle_list[i] model = myModle_list[i]
data = myModle_data[i] data = myModle_data[i]
@ -187,7 +220,7 @@ class ModelManager:
result.pop(0) # 保障结果数组定长 --先把最早的结果推出数组 result.pop(0) # 保障结果数组定长 --先把最早的结果推出数组
if schedule[weekday][hour] == 1: #不在计划则不进行验证,直接返回图片 if schedule[weekday][hour] == 1: #不在计划则不进行验证,直接返回图片
# 调用模型,进行检测,model是动态加载的,具体的判断标准由模型内执行 ---- ********* # 调用模型,进行检测,model是动态加载的,具体的判断标准由模型内执行 ---- *********
isverify = True #isverify = True
detections, bwarn, warntext = model.verify(img, data,isdraw) #****************重要 detections, bwarn, warntext = model.verify(img, data,isdraw) #****************重要
# 对识别结果要部要进行处理 # 对识别结果要部要进行处理
if bwarn: # 整个识别有产生报警 if bwarn: # 整个识别有产生报警
@ -201,27 +234,32 @@ class ModelManager:
result.append(0) result.append(0)
else: else:
result.append(0) result.append(0)
if not isverify: #没做处理,直接返回的,需要控制下帧率,太快读取没有意义。 # if not isverify: #没做处理,直接返回的,需要控制下帧率,太快读取没有意义。 --2024-7-5 取消休眠,帧率控制在dowork_thread完成
time.sleep(1.0/self.frame_rate) #给个默认帧率,不超过30帧,---若经过模型计算,CPU下单模型也就12帧这样 # time.sleep(1.0/self.frame_rate) #给个默认帧率,不超过30帧,---若经过模型计算,CPU下单模型也就12帧这样
# 将检测结果图像转换为帧 -- 需要确认前面对img的处理都是累加的。 # 将检测结果图像转换为帧--暂时用不到AVFrame--2024-7-5
#img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) #numpy.ndarray # new_frame_rgb_avframe = av.VideoFrame.from_ndarray(img, format="rgb24") # AVFrame
if isinstance(img, np.ndarray): # new_frame_rgb_avframe.pts = None # 添加此行确保有pts属性
new_frame = av.VideoFrame.from_ndarray(img, format="rgb24") #AVFrame
new_frame.pts = None # 添加此行确保有pts属性 # if isinstance(img, np.ndarray): -- 留个纪念
#self.logger.debug("img 是np.darry") #处理完的图片后返回-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: else:
#self.logger.error("img不是np.darry") buffer_bgr_webp = frame_bgr_webp.tobytes()
new_frame = None return buffer_bgr_webp,img_bgr_ndarray
#处理完的图片后返回-gbr模式 (new_frame 是 rgb)
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
return new_frame,img
def dowork_thread(self,channel_id): def dowork_thread(self,channel_id):
'''一个通道一个线程,关联的模型在一个线程检测,局部变量都是一个通道独有''' '''一个通道一个线程,关联的模型在一个线程检测,局部变量都是一个通道独有'''
channel_data = self.verify_list[channel_id] #一通道一线程 [url,type,True,img_buffer,img,icount] channel_data = self.verify_list.get_channel(channel_id) #是对ChannelData 对象的引用
view_buffer_deque = deque(maxlen=myCongif.get_data("buffer_len")) context = None
# 线程ACL初始化
if self.model_platform == "acl": # ACL线程中初始化内容
context = ACLModeManger.th_inti_acl(self.device_id)
#查询关联的模型 --- 在循环运行前把基础数据都准备好 #查询关联的模型 --- 在循环运行前把基础数据都准备好
myDBM = DBManager() myDBM = DBManager()
myDBM.connect() myDBM.connect()
@ -236,87 +274,86 @@ class ModelManager:
myModle_data = [] #存放检测参数 一个模型一个 myModle_data = [] #存放检测参数 一个模型一个
schedule_list = [] #布防策略 -一个模型一个 schedule_list = [] #布防策略 -一个模型一个
result_list = [] #检测结果记录 -一个模型一个 result_list = [] #检测结果记录 -一个模型一个
warn_last_time =[] #最新的报警时间记录 -一个模型一个
proportion_list = []#占比设定 -一个模型一个 proportion_list = []#占比设定 -一个模型一个
warn_save_count = []#没个模型触发报警后,保存录像的最新帧序号 -一个模型一个 warn_save_count = []#没个模型触发报警后,保存录像的最新帧序号 -一个模型一个
threshold_list = [] #模型针对该通道的一个置信阈值, -个模型一个
view_buffer = []
ibuffer_count = 0
last_img = None
#获取视频通道的模型相关数据-list #获取视频通道的模型相关数据-list
for model in myModels: for model in myModels:
#基于基类实例化模块类 #基于基类实例化模块类
#m = self._import_model("",model[5],model[8]) #动态加载模型处理文件py --需要验证模型文件是否能加载 m = self._import_model("",model[5],model[8]) #动态加载模型处理文件py --需要验证模型文件是否能加载
m = None #m = None
if m: if m:
myModle_list.append(m) #没有成功加载的模型原画输出 myModle_list.append(m) #没有成功加载的模型原画输出
myModle_data.append(model) myModle_data.append(model)
#model[6] -- c2m_id --把通道对于模型的 0-周一,6-周日 #model[6] -- c2m_id --布防计划 0-周一,6-周日
schedule_list.append(self.getschedule(model[6],myDBM)) schedule_list.append(self.getschedule(model[6],myDBM))
result = [0 for _ in range(model[3] * myCongif.get_data("verify_rate"))] #初始化时间*验证帧率数量的结果list result = [0 for _ in range(model[3] * myCongif.get_data("verify_rate"))] #初始化时间*验证帧率数量的结果list
result_list.append(result) result_list.append(result)
warn_last_time.append(time.time())
proportion_list.append(model[4]) #判断是否报警的占比 proportion_list.append(model[4]) #判断是否报警的占比
warn_save_count.append(0) #保存录像的最新帧初始化为0 warn_save_count.append(0) #保存录像的最新帧初始化为0
#开始拉取画面循环检测 #开始拉取画面循环检测
cap = None cap = None
iread_count =0 #失败读取的次数 #iread_count =0 #失败读取的次数
last_frame_time = time.time() #初始化个读帧时间 last_frame_time = time.time() #初始化个读帧时间
cap_sleep_time = myCongif.get_data("cap_sleep_time")
while channel_data[2]: #基于tag 作为运行标识。 线程里只是读,住线程更新,最多晚一轮,应该不用线程锁。需验证 #可以释放数据库资源
del myDBM
warn_interval = myCongif.get_data("warn_interval")
while channel_data.bool_run: #基于tag 作为运行标识。 线程里只是读,住线程更新,最多晚一轮,应该不用线程锁。需验证
# 帧率控制帧率 # 帧率控制帧率
current_time = time.time() current_time = time.time()
elapsed_time = current_time - last_frame_time elapsed_time = current_time - last_frame_time
if elapsed_time < self.frame_interval: if elapsed_time < self.frame_interval:
time.sleep(self.frame_interval - elapsed_time) #若小于间隔时间则休眠 time.sleep(self.frame_interval - elapsed_time) #若小于间隔时间则休眠
last_frame_time = time.time()
#*********取画面************* #*********取画面*************
if not cap: #第一次需要打开视频流 if not cap: #第一次需要打开视频流
try: try:
cap = self._open_view(channel_data[0],channel_data[1]) #创建子线程读画面 cap = self._open_view(channel_data.str_url,channel_data.int_type) #创建子线程读画面
iread_count = 0
except: except:
self.logger.error("打开视频参数错误,终止线程!") self.logger.error("打开视频参数错误,终止线程!")
return return
ret,frame = cap.read() ret,frame = cap.read() #除了第一帧,其它应该都是有画面的
if not ret: if not ret:
if iread_count > 30: # if iread_count > 30: #2024-7-8 重连接机制放VideoCaptureWithFPS
self.logger.warning(f"通道-{channel_id}:view disconnected. Reconnecting...") # self.logger.warning(f"通道-{channel_id}:view disconnected. Reconnecting...")
cap.release() # cap.release()
cap = None # cap = None
time.sleep(myCongif.get_data("cap_sleep_time")) # time.sleep(cap_sleep_time)
else: # else:
iread_count += 1 # iread_count += 1
time.sleep(1.0/20) #20帧只是作为个默认参考值,一般验证帧率都比这个慢
continue #没读到画面继续 continue #没读到画面继续
iread_count = 0 #重置下视频帧计算
last_frame_time = time.time()
#执行图片推理 -- 如何没有模型或不在工作时间,返回的是原画,要不要控制下帧率? -- 在verify中做了sleep #执行图片推理 -- 如何没有模型或不在工作时间,返回的是原画,要不要控制下帧率? -- 在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.add_deque(img_bgr_ndarray) # 缓冲区大小由maxlen控制 超上限后,删除最前的数据
channel_data[5] += 1 #帧序列加一 channel_data.increment_counter() #帧序列加一
#print(self.verify_list[channel_id][5]) # 一直更新最新帧,提供网页端显示
if channel_data[5] > self.icout_max: channel_data.update_last_frame(buffer_bgr_webp)
channel_data = 0
channel_data[4] = new_frame #一直更新最新帧**********
#print(f"{channel_id}--Frame updated at:",time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) #print(f"{channel_id}--Frame updated at:",time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
#验证result_list -是否触发报警要求 --遍历每个模型执行的result
#验证result_list -是否触发报警要求
for i in range(len(result_list)): for i in range(len(result_list)):
result = result_list[i] result = result_list[i]
proportion = proportion_list[i] proportion = proportion_list[i]
count_one = float(sum(result)) #1,0 把1累加的和就是1的数量 count_one = float(sum(result)) #1,0 把1累加的和就是1的数量
ratio_of_ones = count_one / len(result) ratio_of_ones = count_one / len(result)
#self.logger.debug(result) #self.logger.debug(result)
if ratio_of_ones >= proportion: #触发报警 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] model_name = myModle_data[i][7]
w_s_count = warn_save_count[i] w_s_count = warn_save_count[i]
buffer_count = channel_data[5] buffer_count = channel_data.get_counter()
self.save_warn(model_name,w_s_count,buffer_count,channel_data[3],cap.width,cap.height, self.save_warn(model_name,w_s_count,buffer_count,channel_data.copy_deque(),
channel_id,myDBM,self.FPS,self.fourcc) cap.width,cap.height,channel_id,None,self.FPS,self.fourcc)
self.send_warn() self.send_warn()
#更新帧序列号 #更新帧序列号
warn_save_count[i] = buffer_count warn_save_count[i] = buffer_count
@ -327,17 +364,24 @@ class ModelManager:
# end_time = time.time() # 结束时间 # end_time = time.time() # 结束时间
# print(f"Processing time: {end_time - start_time} seconds") # print(f"Processing time: {end_time - start_time} seconds")
# 本地显示---测试使用 # 本地显示---测试使用
if channel_id == 2: # if channel_id == 2:
cv2.imshow(str(channel_id), img) # cv2.imshow(str(channel_id), img)
if cv2.waitKey(1) & 0xFF == ord('q'): # if cv2.waitKey(1) & 0xFF == ord('q'):
break # break
#结束线程 #结束线程
cap.release()
cap.release() #视频采集线程结束
if context:#ACL线程中反初始化内容 -- 若线程异常退出,这些资源就不能正常释放了
#先释放每个模型资源
for model in myModle_list:
del model
#再释放context
ACLModeManger.th_del_acl(context)
#cv2.destroyAllWindows() #cv2.destroyAllWindows()
def save_warn(self,model_name,w_s_count,buffer_count,buffer,width,height,channnel_id,myDBM,FPS,fourcc): 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 model_name: 模型名称如人员入侵
:param w_s_count: 报警已存储的最新帧序列 :param w_s_count: 报警已存储的最新帧序列
:param buffer_count: 当前视频缓冲区的最新帧序列 :param buffer_count: 当前视频缓冲区的最新帧序列
@ -347,11 +391,15 @@ class ModelManager:
:param channnel_id: 视频通道ID :param channnel_id: 视频通道ID
:return: ret 数据库操作记录 :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() # 获取当前日期和时间 now = datetime.datetime.now() # 获取当前日期和时间
current_time_str = now.strftime("%Y-%m-%d_%H-%M-%S") current_time_str = now.strftime("%Y-%m-%d_%H-%M-%S")
filename = f"{channnel_id}_{current_time_str}" filename = f"{channnel_id}_{current_time_str}"
save_path = myCongif.get_data("warn_video_path")
#保存视频 #保存视频
video_writer = cv2.VideoWriter(f"model/warn/{filename}.mp4", fourcc, FPS, (width, height)) video_writer = cv2.VideoWriter(f"{save_path}{filename}.mp4", fourcc, FPS, (width, height))
if not video_writer.isOpened(): if not video_writer.isOpened():
print(f"Failed to open video writer for model/warn/{filename}.mp4") print(f"Failed to open video writer for model/warn/{filename}.mp4")
return False return False
@ -368,16 +416,25 @@ class ModelManager:
video_writer.release() video_writer.release()
#保存图片 #保存图片
ret = cv2.imwrite(f"model/warn/{filename}.png",buffer[-1]) ret = cv2.imwrite(f"model/warn/{filename}.png",buffer[-1])
#buffer使用完后删除
del buffer
if not ret: if not ret:
print("保存图片失败") print("保存图片失败")
return False return False
#保存数据库 #保存数据库
myDBM = DBManager()
myDBM.connect()
strsql = (f"INSERT INTO warn (model_name ,video_path ,img_path ,creat_time,channel_id ) " 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"Values ('{model_name}','model/warn/{filename}.mp4','model/warn/{filename}.png',"
f"'{current_time_str}','{channnel_id}');") f"'{current_time_str}','{channnel_id}');")
ret = myDBM.do_sql(strsql) ret = myDBM.do_sql(strsql)
del myDBM #释放数据库连接资源
return ret 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): def send_warn(self):
'''发送报警信息''' '''发送报警信息'''
pass pass
@ -397,28 +454,19 @@ class ModelManager:
strsql = f"select id,ulr,type from channel where is_work = 1 and id = {channel_id};" #单通道启动检测线程 strsql = f"select id,ulr,type from channel where is_work = 1 and id = {channel_id};" #单通道启动检测线程
datas = mDBM.do_select(strsql) datas = mDBM.do_select(strsql)
for data in datas: for data in datas:
img_buffer = deque(maxlen=myCongif.get_data("buffer_len")) #创建个定长的视频buffer # img_buffer = deque(maxlen=myCongif.get_data("buffer_len")) #创建个定长的视频buffer
img = None # img = None
icout = 0 #跟img_buffer对应,记录进入缓冲区的帧序列号 # icout = 0 #跟img_buffer对应,记录进入缓冲区的帧序列号
run_data = [data[1],data[2],True,img_buffer,img,icout] # run_data = [data[1],data[2],True,img_buffer,img,icout]
self.verify_list[data[0]] = run_data #需要验证重复情况#? # 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 = threading.Thread(target=self.dowork_thread, args=(data[0],)) #一个视频通道一个线程,线程句柄暂时部保留
th_chn.start() th_chn.start()
def stop_work(self,channel_id=0): def stop_work(self,channel_id=0):
'''停止工作线程,0-停止所有,非0停止对应通道ID的线程''' '''停止工作线程,0-停止所有,非0停止对应通道ID的线程'''
if channel_id ==0: #所有线程停止 self.verify_list.stop_channel(channel_id)
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]
#print(f"Current working directory (ModelManager.py): {os.getcwd()}") #print(f"Current working directory (ModelManager.py): {os.getcwd()}")
mMM = ModelManager() mMM = ModelManager()
@ -445,8 +493,8 @@ def test1():
if __name__ == "__main__": if __name__ == "__main__":
#mMM.start_work() mMM.start_work()
test1() #test1()
print("111") print("111")
# name = acl.get_soc_name() # name = acl.get_soc_name()
# count, ret = acl.rt.get_device_count() # count, ret = acl.rt.get_device_count()

49
core/templates/index.html

@ -1,49 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>YOLOv5 Video Streams</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f0f0f0;
}
.grid-container {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(3, 1fr);
gap: 10px;
width: 90vw;
height: 90vh;
}
.grid-item {
position: relative;
width: 100%;
padding-top: 75%; /* 4:3 aspect ratio */
}
.grid-item img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 8px;
}
</style>
</head>
<body>
<div class="grid-container">
{% for stream_id in streams %}
<div class="grid-item">
<img src="{{ url_for('video_feed', stream_id=stream_id) }}" alt="Stream {{ stream_id }}">
</div>
{% endfor %}
</div>
</body>
</html>

56
model/plugins/ModelBase.py

@ -1,12 +1,13 @@
from abc import abstractmethod,ABC from abc import abstractmethod,ABC
from shapely.geometry import Point, Polygon from shapely.geometry import Point, Polygon
from myutils.ConfigManager import myCongif from myutils.ConfigManager import myCongif
from myutils.MyLogger_logger import LogHandler
import numpy as np import numpy as np
import cv2 import cv2
import ast import ast
import platform
if myCongif.get_data("model_platform") == "acl": if myCongif.get_data("model_platform") == "acl":
import acl import acl
import platform
#-----acl相关------ #-----acl相关------
SUCCESS = 0 # 成功状态值 SUCCESS = 0 # 成功状态值
@ -20,6 +21,7 @@ class ModelBase(ABC):
:param path: 模型文件本身的路径 :param path: 模型文件本身的路径
:param threshold: 模型的置信阈值 :param threshold: 模型的置信阈值
''' '''
self.mylogger = LogHandler().get_logger("ModelManager")
self.name = None #基于name来查询,用户对模型的配置参数,代表着模型名称需要唯一 2024-6-18 -逻辑还需要完善和验证 self.name = None #基于name来查询,用户对模型的配置参数,代表着模型名称需要唯一 2024-6-18 -逻辑还需要完善和验证
self.version = None self.version = None
self.model_type = None # 模型类型 1-图像分类,2-目标检测(yolov5),3-分割模型,4-关键点 self.model_type = None # 模型类型 1-图像分类,2-目标检测(yolov5),3-分割模型,4-关键点
@ -30,6 +32,7 @@ class ModelBase(ABC):
# POCType.BRUTE: self.do_brute # POCType.BRUTE: self.do_brute
} }
self.model_path = path # 模型路径 self.model_path = path # 模型路径
self.init_ok = False
def __del__(self): def __del__(self):
@ -50,32 +53,23 @@ class ModelBase(ABC):
#acl ----- 相关----- #acl ----- 相关-----
def _init_acl(self): def _init_acl(self):
'''acl初始化函数''' device_id = 0
self.device_id = 0 self.context, ret = acl.rt.create_context(device_id) # 显式创建一个Context
#step1 初始化
ret = acl.init()
ret = acl.rt.set_device(self.device_id) # 指定运算的Device
if ret:
raise RuntimeError(ret)
self.context, ret = acl.rt.create_context(self.device_id) # 显式创建一个Context
if ret: if ret:
raise RuntimeError(ret) raise RuntimeError(ret)
print('Init ACL Successfully') print('Init TH-Context Successfully')
def _del_acl(self): def _del_acl(self):
'''acl去初始化''' device_id = 0
# 线程释放context
ret = acl.rt.destroy_context(self.context) # 释放 Context ret = acl.rt.destroy_context(self.context) # 释放 Context
if ret: if ret:
raise RuntimeError(ret) raise RuntimeError(ret)
ret = acl.rt.reset_device(self.device_id) # 释放Device print('Deinit TH-Context Successfully')
if ret: print('ACL finalize Successfully')
raise RuntimeError(ret)
ret = acl.finalize() # 去初始化
if ret:
raise RuntimeError(ret)
print('Deinit ACL Successfully')
def _init_resource(self): def _init_resource(self):
#self._init_acl() #测试使用
''' 初始化模型、输出相关资源。相关数据类型: aclmdlDesc aclDataBuffer aclmdlDataset''' ''' 初始化模型、输出相关资源。相关数据类型: aclmdlDesc aclDataBuffer aclmdlDataset'''
print("Init model resource") print("Init model resource")
# 加载模型文件 # 加载模型文件
@ -85,27 +79,32 @@ class ModelBase(ABC):
print(f"{self.model_path}---模型加载失败!") print(f"{self.model_path}---模型加载失败!")
return False return False
self.model_desc = acl.mdl.create_desc() # 初始化模型信息对象 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") print("[Model] Model init resource stage success")
# 创建模型输出 dataset 结构 # 创建模型输出 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 return True
def _gen_output_dataset(self): def _gen_output_dataset(self):
''' 组织输出数据的dataset结构 ''' ''' 组织输出数据的dataset结构 '''
ret = SUCCESS 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结构 self.output_dataset = acl.mdl.create_dataset() # 创建输出dataset结构
for i in range(self._output_num): for i in range(self._output_num):
temp_buffer_size = acl.mdl.get_output_size_by_index(self.model_desc, i) # 获取模型输出个数 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内存 temp_buffer, ret = acl.rt.malloc(temp_buffer_size, ACL_MEM_MALLOC_NORMAL_ONLY) # 为每个输出申请device内存
dataset_buffer = acl.create_data_buffer(temp_buffer, dataset_buffer = acl.create_data_buffer(temp_buffer,
temp_buffer_size) # 创建输出的data buffer结构,将申请的内存填入data 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: if ret == FAILED:
self._release_dataset(self.output_dataset) # 失败时释放dataset self._release_dataset(self.output_dataset) # 失败时释放dataset
return ret
print("[Model] create model output dataset success") print("[Model] create model output dataset success")
return ret
def _gen_input_dataset(self, input_list): def _gen_input_dataset(self, input_list):
''' 组织输入数据的dataset结构 ''' ''' 组织输入数据的dataset结构 '''
@ -121,7 +120,7 @@ class ModelBase(ABC):
if ret == FAILED: if ret == FAILED:
self._release_dataset(self.input_dataset) # 失败时释放dataset 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): def _unpack_bytes_array(self, byte_array, shape, datatype):
''' 将内存不同类型的数据解码为numpy数组 ''' ''' 将内存不同类型的数据解码为numpy数组 '''
@ -156,8 +155,8 @@ class ModelBase(ABC):
# 根据模型输出的shape和数据类型,将内存数据解码为numpy数组 # 根据模型输出的shape和数据类型,将内存数据解码为numpy数组
outret = acl.mdl.get_output_dims(self.model_desc, i)[0] outret = acl.mdl.get_output_dims(self.model_desc, i)[0]
dims = outret["dims"] # 获取每个输出的维度 dims = outret["dims"] # 获取每个输出的维度
print(f"name:{outret['name']}") # print(f"name:{outret['name']}")
print(f"dimCount:{outret['dimCount']}") # print(f"dimCount:{outret['dimCount']}")
''' '''
dims = { dims = {
"name": xxx, #tensor name "name": xxx, #tensor name
@ -174,7 +173,12 @@ class ModelBase(ABC):
'''创建输入dataset对象, 推理完成后, 将输出数据转换为numpy格式''' '''创建输入dataset对象, 推理完成后, 将输出数据转换为numpy格式'''
self._gen_input_dataset(input_list) # 创建模型输入dataset结构 self._gen_input_dataset(input_list) # 创建模型输入dataset结构
ret = acl.mdl.execute(self.model_id, self.input_dataset, self.output_dataset) # 调用离线模型的execute推理数据 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和类型与模型输出规格一致 out_numpy = self._output_dataset_to_numpy() # 将推理输出的二进制数据流解码为numpy数组, 数组的shape和类型与模型输出规格一致
self._release_dataset(self.input_dataset) # 释放dataset -- 要不要执行需要验证
return out_numpy return out_numpy
def release(self): def release(self):
@ -194,6 +198,8 @@ class ModelBase(ABC):
ret = acl.mdl.destroy_desc(self.model_desc) # 释放模型描述信息 ret = acl.mdl.destroy_desc(self.model_desc) # 释放模型描述信息
self._is_released = True self._is_released = True
print("Model release source success") print("Model release source success")
#测试使用
#self._del_acl()
def _release_dataset(self, dataset): def _release_dataset(self, dataset):
''' 释放 aclmdlDataset 类型数据 ''' ''' 释放 aclmdlDataset 类型数据 '''

41
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 cv2
import numpy as np import numpy as np
import torch # 深度学习运算框架,此处主要用来处理数据 import torch # 深度学习运算框架,此处主要用来处理数据
from core.ACLModelManager import ACLModeManger
class Model(ModelBase): class Model(ModelBase):
def __init__(self,path,threshold=0.5): def __init__(self,path,threshold=0.5):
@ -21,7 +22,7 @@ class Model(ModelBase):
self._output_num = 0 # 输出数据个数 self._output_num = 0 # 输出数据个数
self._output_info = [] # 输出信息列表 self._output_info = [] # 输出信息列表
self._is_released = False # 资源是否被释放 self._is_released = False # 资源是否被释放
self.name = "人员入侵" self.name = "人员入侵-yolov5"
self.version = "V1.0" self.version = "V1.0"
self.model_type = 2 self.model_type = 2
@ -29,17 +30,16 @@ class Model(ModelBase):
self.netw = 640 # 缩放的目标宽度, 也即模型的输入宽度 self.netw = 640 # 缩放的目标宽度, 也即模型的输入宽度
self.conf_threshold = threshold # 置信度阈值 self.conf_threshold = threshold # 置信度阈值
self._init_acl() #加载ACL模型文件---模型加载、模型执行、模型卸载的操作必须在同一个Context下
if self._init_resource(): #加载离线模型,创建输出缓冲区
print("加载模型文件成功!")
self.init_ok = True self.init_ok = True
if not self._init_resource():
print("加载模型文件失败!")
self.init_ok = False
def __del__(self): def __del__(self):
#卸载ACL模型文件
if self.init_ok: if self.init_ok:
self.release() self.release()
self._del_acl()
def verify(self,image,data,isdraw=1): def verify(self,image,data,isdraw=1):
@ -50,21 +50,25 @@ class Model(ModelBase):
img = np.ascontiguousarray(img, dtype=np.float32) / 255.0 # 转换为内存连续存储的数组 img = np.ascontiguousarray(img, dtype=np.float32) / 255.0 # 转换为内存连续存储的数组
# 模型推理, 得到模型输出 # 模型推理, 得到模型输出
output = self.execute([img,])[0] outputs = None
#outputs = self.execute([img,])#创建input,执行模型,返回结果 --失败返回None
# 后处理 -- 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)) # 将推理结果缩放到原始图片大小
filtered_pred_all = None
bwarn = False bwarn = False
warn_text = "" warn_text = ""
# 是否有检测区域,有先绘制检测区域 由于在该函数生成了polygon对象,所有需要在检测区域前调用。 # 是否有检测区域,有先绘制检测区域 由于在该函数生成了polygon对象,所有需要在检测区域前调用。
if data[1] == 1: if data[1] == 1:
self.draw_polygon(image, data[2], (255, 0, 0)) 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] filtered_pred_all = pred_all[pred_all[:, 5] == 0]
# 绘制检测结果 --- 也需要封装在类里, # 绘制检测结果 --- 也需要封装在类里,
for pred in filtered_pred_all: for pred in filtered_pred_all:
@ -79,13 +83,12 @@ class Model(ModelBase):
#判断是否区域点 #判断是否区域点
if not self.is_point_in_region((x_center, y_center)): if not self.is_point_in_region((x_center, y_center)):
continue #没产生报警-继续 continue #没产生报警-继续
#产生报警 #产生报警 -- 有一个符合即可
bwarn = True bwarn = True
warn_text = "Intruder detected!" warn_text = "Intruder detected!"
img_dw = draw_bbox(filtered_pred_all, image, (0, 255, 0), 2, labels_dict) # 画出检测框、类别、概率 img_dw = draw_bbox(filtered_pred_all, image, (0, 255, 0), 2, labels_dict) # 画出检测框、类别、概率
cv2.imwrite('img_res.png', img_dw) #cv2.imwrite('img_res.png', img_dw)
return filtered_pred_all, False, "" return filtered_pred_all, bwarn, warn_text
def testRun(self): def testRun(self):
print("1111") print("1111")

4
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 core.ViewManager import mVManager
from web import create_app from web import create_app
from core.ModelManager import mMM from core.ModelManager import mMM

3
web/API/channel.py

@ -14,10 +14,9 @@ async def channel_tree(): #获取通道树
return jsonify(channel_tree) return jsonify(channel_tree)
@api.route('/channel/list',methods=['GET']) @api.route('/channel/list',methods=['GET'])
async def channel_list(): #获取通道列表 --分页查询,支持区域和通道名称关键字查询 async def channel_list(): #获取通道列表 --分页查询,支持区域和通道名称关键字查询
strsql = ("select t2.area_name ,t1.ID ,t1.channel_name ,t1.ulr ,t1.'type' ,t1.status ,t1.element_id" 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) data = mDBM.do_select(strsql)
channel_list = [{"area_name": channel[0], "ID": channel[1], "channel_name": channel[2], "ulr": channel[3], 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] "type": channel[4], "status": channel[5], "element_id": channel[6]} for channel in data]

1
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 core.Upload_file import allowed_file,check_file,updata_model
from myutils.ConfigManager import myCongif from myutils.ConfigManager import myCongif
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
@api.route('/model/list',methods=['GET']) @api.route('/model/list',methods=['GET'])
@login_required @login_required
async def model_list(): #获取算法列表 async def model_list(): #获取算法列表

105
web/API/viedo.py

@ -1,16 +1,10 @@
import cv2
import asyncio import asyncio
import time
from . import api from . import api
from quart import jsonify, request,websocket from quart import jsonify, request,websocket
from aiortc import RTCPeerConnection, RTCSessionDescription, VideoStreamTrack,RTCConfiguration
from core.ModelManager import mMM from core.ModelManager import mMM
from core.DBManager import mDBM from core.DBManager import mDBM
from myutils.ConfigManager import myCongif from myutils.ConfigManager import myCongif
from fractions import Fraction
import threading
import logging import logging
from quart.helpers import Response
# 配置日志 # 配置日志
logging.basicConfig(level=logging.INFO) 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): async def get_stats(peer_connection):
stats = await peer_connection.getStats() stats = await peer_connection.getStats()
@ -189,63 +141,30 @@ async def get_stats(peer_connection):
# "msg":reMsg # "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/<stream_id>')
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/<int:channel_id>') @api.websocket('/ws/video_feed/<int:channel_id>')
async def ws_video_feed(channel_id): async def ws_video_feed(channel_id):
while True: channel_data = mMM.verify_list.get_channel(channel_id)
frame = mMM.verify_list[int(channel_id)][4] frame_rate = myCongif.get_data("frame_rate")
while channel_data.bool_run: #这里的多线程并发,还需要验证检查
frame = channel_data.get_last_frame()
if frame is not None: if frame is not None:
img = frame.to_ndarray(format="bgr24") #img = frame.to_ndarray(format="bgr24")
ret, buffer = cv2.imencode('.jpg', img) # ret, buffer = cv2.imencode('.jpg', frame)
if not ret: # if not ret:
continue # continue
frame = buffer.tobytes() # frame = buffer.tobytes()
await websocket.send(frame) 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']) @api.route('/shutdown', methods=['POST'])
async def shutdown():#这是全关 async def shutdown():#这是全关 --需要修改
coros = [pc.close() for pc in pcs] coros = [pc.close() for pc in pcs]
await asyncio.gather(*coros) await asyncio.gather(*coros)
pcs.clear() pcs.clear()
return 'Server shutting down...' return 'Server shutting down...'
@api.route('/close_stream', methods=['POST']) @api.route('/close_stream', methods=['POST'])
async def close_stream(): async def close_stream(): # 需要修改
channel_id = (await request.form)['channel_id'] channel_id = (await request.form)['channel_id']
reStatus = 0 reStatus = 0
reMsg ="" reMsg =""

6
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 quart_session import Session
from pymemcache.client import base from pymemcache.client import base
from .main import main from .main import main
from .API import api from .API import api
from functools import wraps from functools import wraps
from myutils.ConfigManager import myCongif from myutils.ConfigManager import myCongif
from quart_sqlalchemy import SQLAlchemy # from quart_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate # from flask_migrate import Migrate
#app.config['SECRET_KEY'] = 'mysecret' #密钥 --需要放配置文件 #app.config['SECRET_KEY'] = 'mysecret' #密钥 --需要放配置文件
#socketio = SocketIO(app) #socketio = SocketIO(app)

1
web/common/utils.py

@ -1,7 +1,6 @@
from PIL import Image, ImageDraw, ImageFont,ImageFilter from PIL import Image, ImageDraw, ImageFont,ImageFilter
import random import random
import string import string
import os
import io import io
from functools import wraps from functools import wraps
from quart import session, redirect, url_for from quart import session, redirect, url_for

2
web/main/routes.py

@ -14,6 +14,7 @@ from werkzeug.utils import secure_filename
def login_required(f): def login_required(f):
@wraps(f) @wraps(f)
async def decorated_function(*args, **kwargs): async def decorated_function(*args, **kwargs):
print("decorated_function3")
if 'user' not in session: if 'user' not in session:
return redirect(url_for('main.index',error='未登录,请重新登录')) return redirect(url_for('main.index',error='未登录,请重新登录'))
return await f(*args, **kwargs) return await f(*args, **kwargs)
@ -21,6 +22,7 @@ def login_required(f):
@main.route('/') @main.route('/')
async def index(): async def index():
print("index")
#error = request.args.get('error') #error = request.args.get('error')
return await render_template('实时预览.html') return await render_template('实时预览.html')
#return await render_template('登录.html',error=error) #return await render_template('登录.html',error=error)

18
web/main/static/resources/scripts/aiortc-client-new.js

@ -13,13 +13,13 @@ document.addEventListener('DOMContentLoaded', async function() {
// 遍历输出每个元素的信息 // 遍历输出每个元素的信息
channel_list.forEach(channel => { channel_list.forEach(channel => {
if(channel.element_id){ //""空为false,非空为true if(channel.element_id){ //""空为false,非空为true
// console.log(`Area Name: ${channel.area_name}`); console.log(`Area Name: ${channel.area_name}`);
// console.log(`ID: ${channel.ID}`); console.log(`ID: ${channel.ID}`);
// console.log(`Channel Name: ${channel.channel_name}`); console.log(`Channel Name: ${channel.channel_name}`);
// console.log(`URL: ${channel.url}`); console.log(`URL: ${channel.url}`);
// console.log(`Type: ${channel.type}`); console.log(`Type: ${channel.type}`);
// console.log(`Status: ${channel.status}`); console.log(`Status: ${channel.status}`);
// console.log(`Element ID: ${channel.element_id}`); console.log(`Element ID: ${channel.element_id}`);
connectToStream(channel.element_id,channel.ID,channel.area_name,channel.channel_name) connectToStream(channel.element_id,channel.ID,channel.area_name,channel.channel_name)
} }
}); });
@ -29,10 +29,6 @@ document.addEventListener('DOMContentLoaded', async function() {
}); });
function connectToStream(element_id,channel_id,area_name,channel_name) { 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); const imgElement = document.getElementById(element_id);
imgElement.alt = `Stream ${area_name}--${channel_name}`; imgElement.alt = `Stream ${area_name}--${channel_name}`;
const streamUrl = `ws://${window.location.host}/api/ws/video_feed/${channel_id}`; const streamUrl = `ws://${window.location.host}/api/ws/video_feed/${channel_id}`;

31
web/main/templates/实时预览.html

@ -175,42 +175,11 @@
<script src="/resources/scripts/aiortc-client-new.js"> <script src="/resources/scripts/aiortc-client-new.js">
console.log('当指定了src属性时,浏览器会加载并执行该文件中的脚本,而忽略标签内部的任何内容,所以不会执行'); console.log('当指定了src属性时,浏览器会加载并执行该文件中的脚本,而忽略标签内部的任何内容,所以不会执行');
</script> </script>
<script>
window.onload = function() {
// 页面加载完成后执行的代码
//var intervalId = setInterval(showView, 1000);
//setTimeout(function() {
// clearInterval(intervalId); // 停止定时循环
// console.log("Interval stopped after 10 seconds");
// }, 10000);
}
</script>
<!-- Unnamed (Image) --> <!-- Unnamed (Image) -->
<div id="u32" class="ax_default image"> <div id="u32" class="ax_default image">
<img id="u32_video" src="" alt="Video Stream"> <img id="u32_video" src="" alt="Video Stream">
</div> </div>
<script>
const videoStream = document.getElementById('u32_video');
const streamUrl = 'ws://localhost:5001/api/ws/video_feed/2';
function connect() {
const socket = new WebSocket(streamUrl);
socket.onmessage = function(event) {
// 释放旧的对象URL
if (videoStream.src) {
URL.revokeObjectURL(videoStream.src);
}
videoStream.src = URL.createObjectURL(event.data);
};
socket.onclose = function() {
setTimeout(connect, 1000); // Attempt to reconnect after 1 second
};
}
window.onload = function() {
//connect();
}
</script>
<!-- Unnamed (Image) --> <!-- Unnamed (Image) -->
<div id="u33" class="ax_default image"> <div id="u33" class="ax_default image">
<img id="u33_video" src="" alt="Video Stream"> <img id="u33_video" src="" alt="Video Stream">

BIN
zfbox.db

Binary file not shown.
Loading…
Cancel
Save