@ -1,41 +0,0 @@ |
|||
import pymysql |
|||
import sqlite3 |
|||
|
|||
class DBManager(): |
|||
#实例化数据库管理对象,并连接数据库 |
|||
def __init__(self,ip,prot,user,passwd): |
|||
pass |
|||
|
|||
#检查设备ID是否在数据库? |
|||
def checkDevID(self,cID): |
|||
pass |
|||
|
|||
def test(self): |
|||
# 建立数据库连接 |
|||
conn = pymysql.connect( |
|||
host='localhost', |
|||
port=3306, |
|||
user='username', |
|||
password='password', |
|||
database='database_name' |
|||
) |
|||
|
|||
# 创建游标对象 |
|||
cursor = conn.cursor() |
|||
|
|||
# 执行 SQL 查询 |
|||
query = "SELECT * FROM table_name" |
|||
cursor.execute(query) |
|||
|
|||
# 获取查询结果 |
|||
result = cursor.fetchall() |
|||
|
|||
# 输出结果 |
|||
for row in result: |
|||
print(row) |
|||
|
|||
# 关闭游标和连接 |
|||
cursor.close() |
|||
conn.close() |
|||
|
|||
|
@ -1,27 +1,34 @@ |
|||
|
|||
#字典更新接口 |
|||
url: http://192.168.2.61/api/ |
|||
hyMD5: http://192.168.2.61/api/defaultMd5/download |
|||
hyfile: http://192.168.2.61/api/defaultPassword/download/ |
|||
hyget: http://192.168.2.61/api/defaultPassword/export/ |
|||
#主线程数 |
|||
concurrency: 6 #同时执行的子任务数,--线程数 |
|||
#超时配置 |
|||
TagTime: 60 #任务执行超时时间 |
|||
pocTime: 30 #一次poc执行超时时间,0不进行超时判断 |
|||
nmapTime: 10 #一次nmap执行超时时间,0不进行超时判断 |
|||
#日志记录 |
|||
file_log_level: INFO #是否记录日志 |
|||
show_log_level: DEBUG #日志记录级别 |
|||
log_dir: logs |
|||
#远程数据库 |
|||
|
|||
#远程数据库 --- 香橙派用不了 |
|||
DBType: 1 #0--mysql,1--sqlite |
|||
mysql: |
|||
host: 192.168.2.54 |
|||
host: 192.168.3.45 |
|||
port: 3306 |
|||
user: root |
|||
pass: crnn@cz*** |
|||
db: sfind |
|||
pass: ZFkj_123456 |
|||
db: zfbox |
|||
|
|||
#sqlit |
|||
sqlite: zfbox.db |
|||
|
|||
#用户初始密码 |
|||
pw: zfkj_123!@# |
|||
|
|||
#上传限制 |
|||
MAX_CONTENT_LENGTH : 100 # 100MB |
|||
UPLOAD_FOLDER : uploads |
|||
ALLOWED_EXTENSIONS : {'zip'} |
|||
|
|||
#RTSP |
|||
RTSP_Check_Time : 600 #10分钟 |
|||
|
|||
#model |
|||
weight_path: /model/weights |
|||
yolov5_path: model/base_model/yolov5 |
|||
cap_sleep_time: 300 #5分钟 |
|||
buffer_len: 30 #分析后画面缓冲区帧数 |
|||
|
@ -0,0 +1,218 @@ |
|||
import pymysql |
|||
import sqlite3 |
|||
import threading |
|||
import os |
|||
from myutils.ConfigManager import myCongif |
|||
from myutils.MyLogger_logger import LogHandler |
|||
|
|||
class DBManager(): |
|||
#实例化数据库管理对象,并连接数据库 |
|||
#itype=0 使用mysql数据库,1-使用sqlite数据库 |
|||
def __init__(self): |
|||
self.logger = LogHandler().get_logger("DBManager") |
|||
self.lock = threading.Lock() |
|||
self.itype = myCongif.get_data("DBType") |
|||
if self.itype ==0: |
|||
self.host = myCongif.get_data('mysql.host') |
|||
self.port = myCongif.get_data('mysql.port') |
|||
self.user = myCongif.get_data('mysql.user') |
|||
self.passwd = myCongif.get_data('mysql.passwd') |
|||
self.database = myCongif.get_data('mysql.database') |
|||
self.connection = None |
|||
self.cursor = None |
|||
elif self.itype ==1: |
|||
self.dbfile = myCongif.get_data("sqlite") |
|||
if not os.path.exists(self.dbfile): |
|||
self.dbfile = "../" + self.dbfile #直接运行DBManager时初始路径不是在根目录 |
|||
if not os.path.exists(self.dbfile): |
|||
raise FileNotFoundError(f"Database file {self.dbfile} does not exist.") |
|||
else: |
|||
self.logger.error("错误的数据库类型,请检查") |
|||
|
|||
|
|||
def __del__(self): |
|||
if self.ok: |
|||
self.cursor.close() |
|||
self.connection.close() |
|||
self.cursor = None |
|||
self.connection = None |
|||
self.logger.debug("DBManager销毁") |
|||
|
|||
def connect(self): |
|||
try: |
|||
if self.itype ==0: |
|||
self.connection = pymysql.connect(host=self.host, port=self.port, user=self.user, |
|||
passwd=self.passwd, db=self.database,charset='utf8') |
|||
self.cursor = self.connection.cursor() |
|||
elif self.itype ==1: |
|||
self.connection = sqlite3.connect(self.dbfile) |
|||
self.cursor = self.connection.cursor() |
|||
self.ok = True |
|||
return True |
|||
except: |
|||
self.logger.error("服务器端数据库连接失败") |
|||
return False |
|||
|
|||
# 判断数据库连接是否正常,若不正常则重连接 |
|||
def Retest_conn(self): |
|||
if self.itype == 0: #除了mysql,sqlite3不需要判断连接状态 |
|||
try: |
|||
self.connection.ping() |
|||
except: |
|||
return self.connect() |
|||
return True |
|||
|
|||
# 执行数据库查询操作 1-只查询一条记录,其他所有记录 |
|||
def do_select(self, strsql, itype=0): |
|||
# self.conn.begin() |
|||
self.lock.acquire() |
|||
data = None |
|||
if self.Retest_conn(): |
|||
try: |
|||
self.cursor.execute(strsql) |
|||
self.connection.commit() # select要commit提交事务,是存在获取不到最新数据的问题(innoDB事务机制) |
|||
except Exception as e: |
|||
self.logger.error("do_select异常报错:%s" % str(e)) |
|||
self.lock.release() |
|||
return None |
|||
if itype == 1: |
|||
data = self.cursor.fetchone() |
|||
else: |
|||
data = self.cursor.fetchall() |
|||
self.lock.release() |
|||
return data |
|||
|
|||
# 执行数据库语句 |
|||
def do_sql(self, strsql, data=None): |
|||
self.lock.acquire() |
|||
bok = False |
|||
if self.Retest_conn(): |
|||
try: |
|||
# self.conn.begin() |
|||
if data: |
|||
iret = self.cursor.executemany(strsql, data) #批量执行sql语句 |
|||
else: |
|||
iret = self.cursor.execute(strsql) |
|||
self.connection.commit() |
|||
if iret.rowcount > 0: # 要有修改成功的记录才返回true |
|||
bok = True |
|||
except Exception as e: |
|||
self.logger.error("执行数据库语句%s出错:%s" % (strsql, str(e))) |
|||
self.connection.rollback() |
|||
self.lock.release() |
|||
return bok |
|||
|
|||
#---------------------特定数据库操作函数--------------------- |
|||
#根据通道ID或者模型ID删除通道和模型间的关联数据 1-通道ID,2-模型ID |
|||
def delC2M(self,ID,itype): |
|||
#channel2model |
|||
if itype ==1: |
|||
strsql = f"select ID from channel2model where channel_id={ID};" |
|||
datas = self.do_select(strsql) |
|||
|
|||
strsql = f"delete from channel2model where channel_id={ID};" |
|||
ret = self.do_sql(strsql) |
|||
if ret == False: |
|||
return False |
|||
elif itype ==2: |
|||
strsql = f"select ID from channel2model where model_id={ID};" |
|||
datas = self.do_select(strsql) |
|||
|
|||
strsql = f"delete from channel2model where model_id={ID};" |
|||
ret = self.do_sql(strsql) |
|||
if ret == False: |
|||
return False |
|||
else: |
|||
return False |
|||
#schedule |
|||
for data in datas: |
|||
c2m_id = data[0] |
|||
strsql = f"delete from schedule where channel2model_id={c2m_id};" |
|||
ret = self.do_sql(strsql) |
|||
if ret == False: |
|||
return False |
|||
return True |
|||
|
|||
#删除通道,需要关联删除布防时间,通道和算法的关联表 |
|||
def delchannel(self,ID): |
|||
ret = self.delC2M(ID) |
|||
if ret == False: |
|||
return False |
|||
#channel |
|||
strsql = f"delete from channel where ID={ID};" |
|||
ret = self.do_sql(strsql) |
|||
if ret == False: |
|||
return False |
|||
return True |
|||
|
|||
#修改视频通道和算法间的关联关系 |
|||
#channel_id 通道ID |
|||
#modell_list 最新配置的模型id list |
|||
def updateC2M(self,channel_id,model_list): |
|||
strsql = f"select model_id from channel2model where channel_id={channel_id};" |
|||
datas = set(self.do_select(strsql)) |
|||
data_new = set(model_list) |
|||
#计算要新增和修改的 |
|||
need_add = data_new - datas |
|||
need_del = datas-data_new |
|||
#新增 |
|||
for one in need_add: |
|||
strsql = f"insert into channel2model (channel_id,model_id) values ({channel_id},{one});" |
|||
if self.do_sql(strsql) == False: |
|||
return False |
|||
#差删除 |
|||
for one in need_del: |
|||
strsql = f"select ID from channel2model where channel_id={channel_id} and model_id={one};" |
|||
c2m_id = mDBM.do_select(strsql,1)[0] |
|||
#删除布防计划数据 |
|||
strsql = f"delete from schedule where channel2model_id={c2m_id};" |
|||
if self.do_sql(strsql) == False: |
|||
return False |
|||
#删除关联记录 |
|||
strsql = f"delete from channel2model where ID = {c2m_id};" |
|||
if self.do_sql(strsql) == False: |
|||
return False |
|||
return True |
|||
|
|||
#检查设备ID是否在数据库? |
|||
def checkDevID(self,cID): |
|||
pass |
|||
|
|||
def test(self): |
|||
# 建立数据库连接 |
|||
conn = pymysql.connect( |
|||
host='localhost', |
|||
port=3306, |
|||
user='username', |
|||
password='password', |
|||
database='database_name' |
|||
) |
|||
|
|||
# 创建游标对象 |
|||
cursor = conn.cursor() |
|||
|
|||
# 执行 SQL 查询 |
|||
query = "SELECT * FROM table_name" |
|||
cursor.execute(query) |
|||
|
|||
# 获取查询结果 |
|||
result = cursor.fetchall() |
|||
|
|||
# 输出结果 |
|||
for row in result: |
|||
print(row) |
|||
|
|||
# 关闭游标和连接 |
|||
cursor.close() |
|||
conn.close() |
|||
|
|||
mDBM = DBManager() |
|||
mDBM.connect() |
|||
|
|||
if __name__ == "__main__": |
|||
lista = set([1,2,3,4,5,6,7]) |
|||
listb = set([4,6,8,9,10]) |
|||
nadd = lista -listb |
|||
ndel = listb -lista |
|||
for one in nadd: |
|||
print(one) |
@ -0,0 +1,167 @@ |
|||
# 导入代码依赖 |
|||
import time |
|||
import av |
|||
import os |
|||
import cv2 |
|||
import torch |
|||
import numpy as np |
|||
import threading |
|||
import importlib.util |
|||
from core.DBManager import mDBM,DBManager |
|||
from myutils.MyLogger_logger import LogHandler |
|||
from myutils.ConfigManager import myCongif |
|||
|
|||
|
|||
|
|||
class VideoCaptureWithFPS: |
|||
def __init__(self, src=0, target_fps=10): |
|||
self.cap = cv2.VideoCapture(src) |
|||
self.target_fps = target_fps |
|||
self.frame_interval = 1.0 / target_fps |
|||
self.last_frame_time = time.time() |
|||
|
|||
def read(self): |
|||
current_time = time.time() |
|||
elapsed_time = current_time - self.last_frame_time |
|||
|
|||
if elapsed_time < self.frame_interval: #小于间隔时间会休眠 |
|||
time.sleep(self.frame_interval - elapsed_time) |
|||
|
|||
self.last_frame_time = time.time() |
|||
ret, frame = self.cap.read() |
|||
return ret, frame |
|||
|
|||
def release(self): |
|||
self.cap.release() |
|||
self.cap = None |
|||
|
|||
|
|||
class ModelManager: |
|||
def __init__(self): |
|||
self.verify_list = {} |
|||
self.bRun = True |
|||
self.logger = LogHandler().get_logger("ModelManager") |
|||
# 本地YOLOv5仓库路径 |
|||
self.yolov5_path = myCongif.get_data("yolov5_path") |
|||
self.buflen = myCongif.get_data("buffer_len") |
|||
|
|||
def _open_view(self,url,itype): #打开摄像头 0--USB摄像头,1-RTSP,2-海康SDK |
|||
if itype == 0: |
|||
cap = VideoCaptureWithFPS(int(url)) |
|||
elif itype == 1: |
|||
cap = VideoCaptureWithFPS(url) |
|||
else: |
|||
raise Exception("视频参数错误!") |
|||
return cap |
|||
|
|||
def _import_model(self,model_name,model_path): |
|||
'''根据路径,动态导入模块''' |
|||
if os.path.exists(model_path): |
|||
module_spec = importlib.util.spec_from_file_location(model_name, model_path) |
|||
module = importlib.util.module_from_spec(module_spec) |
|||
module_spec.loader.exec_module(module) |
|||
else: |
|||
self.logger.error("{}文件不存在".format(model_path)) |
|||
return None |
|||
return module |
|||
|
|||
def dowork_thread(self,channel_id): |
|||
'''一个通道一个线程,关联的模型在一个线程检测''' |
|||
cap = None |
|||
#查询关联的模型 --- 在循环运行前把基础数据都准备好 |
|||
myDBM = DBManager() |
|||
myDBM.connection() |
|||
strsql = (f"select t1.model_id,t1.check_area,t1.polygon ,t2.duration_time,t2.proportion,t2.model_path " |
|||
f"from channel2model t1 left join model t2 on t1.model_id = t2.ID where t1.channel_id ={channel_id};") |
|||
myModels = myDBM.do_select(strsql) |
|||
#加载模型 --- 是不是要做个限制,一个视频通道关联算法模块的上限 --- 关联多了一个线程执行耗时较多,造成帧率太低,或者再多线程并发 #? |
|||
myModle_list = [] #存放模型对象List |
|||
myModle_data = [] #存放检测参数 |
|||
for model in myModels: |
|||
#基于基类实例化模块类 |
|||
m = self._import_model("",model[5]) #动态加载模型 -- 待完善 |
|||
myModle_list.append(m) |
|||
myModle_data.append(model) |
|||
|
|||
#开始循环检测 |
|||
#print(mydata[0],mydata[1],mydata[2],mydata[3]) # url type tag img_buffer |
|||
#[url,type,True,img_buffer] |
|||
while self.verify_list[channel_id][2]: #基于tag 作为运行标识。 线程里只是读,住线程更新,最多晚一轮,应该不用线程锁。需验证 |
|||
if not cap: #还没连接视频源 |
|||
try: |
|||
cap = self._open_view(self.verify_list[channel_id][0],self.verify_list[channel_id][1]) |
|||
except: |
|||
self.logger.error("参数错误,终止线程") |
|||
return |
|||
ret,frame = cap.read() |
|||
if not ret: |
|||
self.logger.warning("view disconnected. Reconnecting...") |
|||
cap.release() |
|||
cap = None |
|||
time.sleep(myCongif.get_data("cap_sleep_time")) |
|||
continue #没读到画面继续 |
|||
#图像处理 |
|||
img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) |
|||
# img = frame.to_ndarray(format="bgr24") |
|||
#检查布防时间是否在布防时间内 |
|||
# 使用 模型 进行目标检测 |
|||
i_warn_count = 0 |
|||
for i in range(len(myModle_list)):#如果比较多可以考虑做线程进行检测 |
|||
model = myModle_list[i] |
|||
data = myModle_data[i] |
|||
# 进行模型检测 |
|||
detections,bwarn,warntext = model.verify(img,data) |
|||
#对识别结果要部要进行处理 |
|||
if bwarn: # 整个识别有产生报警 |
|||
# 绘制报警文本 |
|||
cv2.putText(img, 'Intruder detected!', (50, (i_warn_count+1)*50), |
|||
cv2.FONT_HERSHEY_SIMPLEX, 1,(0, 0, 255), 2) |
|||
i_warn_count += 1 |
|||
# 保存报警信息? --- 待完成 |
|||
self.save_warn() |
|||
# 推送报警? --- 待完成 |
|||
self.send_warn() |
|||
# 将检测结果图像转换为帧 -- 需要确认前面对img的处理都是累加的。 |
|||
new_frame = av.VideoFrame.from_ndarray(img, format="rgb24") |
|||
# 分析图片放入内存中 --- |
|||
#new_frame = cv2.resize(new_frame, (640, 480)) # 降低视频分辨率 |
|||
self.verify_list[channel_id][3].append(new_frame) |
|||
if len(self.verify_list[channel_id]) > self.buflen: # 保持缓冲区大小不超过10帧 |
|||
self.verify_list[channel_id][3].pop(0) |
|||
self.logger.debug("drop one frame!") |
|||
|
|||
|
|||
|
|||
def start_work(self,channel_id=0): |
|||
'''算法模型是在后台根据画面实时分析的''' |
|||
if channel_id ==0: |
|||
strsql = "select id,ulr,type from channel where is_work = 1;" #要考虑布防和撤防开关的的调整 |
|||
else: |
|||
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 = [] |
|||
run_data = [data[1],data[2],True,img_buffer] |
|||
self.verify_list[data[0]] = run_data #需要验证重复情况 |
|||
th_chn = threading.Thread(target=self.dowork_thread, args=(data[0],)) #一个视频通道一个线程,线程句柄暂时部保留 |
|||
th_chn.start() |
|||
|
|||
|
|||
def stop_work(self,channel_id=0): |
|||
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] |
|||
|
|||
|
|||
|
|||
if __name__ == "__main__": |
|||
ModelManager().start_work() |
@ -1,9 +1,8 @@ |
|||
import threading |
|||
import socket |
|||
import socketserver |
|||
from DataStruct import * |
|||
from util.MyLogger_logging import MyLogger |
|||
from DBManager import DBManager |
|||
from myutils.MyLogger_logging import MyLogger |
|||
from core.DBManager import DBManager |
|||
import time |
|||
|
|||
m_BRun = False #线程运行标识 |
@ -0,0 +1,162 @@ |
|||
# 导入代码依赖 |
|||
import cv2 |
|||
import numpy as np |
|||
import ipywidgets as widgets |
|||
from IPython.display import display |
|||
import torch |
|||
from skvideo.io import vreader, FFmpegWriter |
|||
import IPython.display |
|||
from ais_bench.infer.interface import InferSession |
|||
|
|||
from det_utils import letterbox, scale_coords, nms |
|||
|
|||
def preprocess_image(image, cfg, bgr2rgb=True): |
|||
"""图片预处理""" |
|||
img, scale_ratio, pad_size = letterbox(image, new_shape=cfg['input_shape']) |
|||
if bgr2rgb: |
|||
img = img[:, :, ::-1] |
|||
img = img.transpose(2, 0, 1) # HWC2CHW |
|||
img = np.ascontiguousarray(img, dtype=np.float32) |
|||
return img, scale_ratio, pad_size |
|||
|
|||
|
|||
def draw_bbox(bbox, img0, color, wt, names): |
|||
"""在图片上画预测框""" |
|||
det_result_str = '' |
|||
for idx, class_id in enumerate(bbox[:, 5]): |
|||
if float(bbox[idx][4] < float(0.05)): |
|||
continue |
|||
img0 = cv2.rectangle(img0, (int(bbox[idx][0]), int(bbox[idx][1])), (int(bbox[idx][2]), int(bbox[idx][3])), |
|||
color, wt) |
|||
img0 = cv2.putText(img0, str(idx) + ' ' + names[int(class_id)], (int(bbox[idx][0]), int(bbox[idx][1] + 16)), |
|||
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1) |
|||
img0 = cv2.putText(img0, '{:.4f}'.format(bbox[idx][4]), (int(bbox[idx][0]), int(bbox[idx][1] + 32)), |
|||
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1) |
|||
det_result_str += '{} {} {} {} {} {}\n'.format( |
|||
names[bbox[idx][5]], str(bbox[idx][4]), bbox[idx][0], bbox[idx][1], bbox[idx][2], bbox[idx][3]) |
|||
return img0 |
|||
|
|||
|
|||
def get_labels_from_txt(path): |
|||
"""从txt文件获取图片标签""" |
|||
labels_dict = dict() |
|||
with open(path) as f: |
|||
for cat_id, label in enumerate(f.readlines()): |
|||
labels_dict[cat_id] = label.strip() |
|||
return labels_dict |
|||
|
|||
|
|||
def draw_prediction(pred, image, labels): |
|||
"""在图片上画出预测框并进行可视化展示""" |
|||
imgbox = widgets.Image(format='jpg', height=720, width=1280) |
|||
img_dw = draw_bbox(pred, image, (0, 255, 0), 2, labels) |
|||
imgbox.value = cv2.imencode('.jpg', img_dw)[1].tobytes() |
|||
display(imgbox) |
|||
|
|||
|
|||
def infer_image(img_path, model, class_names, cfg): |
|||
"""图片推理""" |
|||
# 图片载入 |
|||
image = cv2.imread(img_path) |
|||
# 数据预处理 |
|||
img, scale_ratio, pad_size = preprocess_image(image, cfg) |
|||
# 模型推理 |
|||
output = model.infer([img])[0] |
|||
|
|||
output = torch.tensor(output) |
|||
# 非极大值抑制后处理 |
|||
boxout = nms(output, conf_thres=cfg["conf_thres"], iou_thres=cfg["iou_thres"]) |
|||
pred_all = boxout[0].numpy() |
|||
# 预测坐标转换 |
|||
scale_coords(cfg['input_shape'], pred_all[:, :4], image.shape, ratio_pad=(scale_ratio, pad_size)) |
|||
# 图片预测结果可视化 |
|||
draw_prediction(pred_all, image, class_names) |
|||
|
|||
|
|||
def infer_frame_with_vis(image, model, labels_dict, cfg, bgr2rgb=True): |
|||
# 数据预处理 |
|||
img, scale_ratio, pad_size = preprocess_image(image, cfg, bgr2rgb) |
|||
# 模型推理 |
|||
output = model.infer([img])[0] |
|||
|
|||
output = torch.tensor(output) |
|||
# 非极大值抑制后处理 |
|||
boxout = nms(output, conf_thres=cfg["conf_thres"], iou_thres=cfg["iou_thres"]) |
|||
pred_all = boxout[0].numpy() |
|||
# 预测坐标转换 |
|||
scale_coords(cfg['input_shape'], pred_all[:, :4], image.shape, ratio_pad=(scale_ratio, pad_size)) |
|||
# 图片预测结果可视化 |
|||
img_vis = draw_bbox(pred_all, image, (0, 255, 0), 2, labels_dict) |
|||
return img_vis |
|||
|
|||
|
|||
def img2bytes(image): |
|||
"""将图片转换为字节码""" |
|||
return bytes(cv2.imencode('.jpg', image)[1]) |
|||
|
|||
|
|||
def infer_video(video_path, model, labels_dict, cfg): |
|||
"""视频推理""" |
|||
image_widget = widgets.Image(format='jpeg', width=800, height=600) |
|||
display(image_widget) |
|||
|
|||
# 读入视频 |
|||
cap = cv2.VideoCapture(video_path) |
|||
while True: |
|||
ret, img_frame = cap.read() |
|||
if not ret: |
|||
break |
|||
# 对视频帧进行推理 |
|||
image_pred = infer_frame_with_vis(img_frame, model, labels_dict, cfg, bgr2rgb=True) |
|||
image_widget.value = img2bytes(image_pred) |
|||
|
|||
|
|||
def infer_camera(model, labels_dict, cfg): |
|||
"""外设摄像头实时推理""" |
|||
def find_camera_index(): |
|||
max_index_to_check = 10 # Maximum index to check for camera |
|||
|
|||
for index in range(max_index_to_check): |
|||
cap = cv2.VideoCapture(index) |
|||
if cap.read()[0]: |
|||
cap.release() |
|||
return index |
|||
|
|||
# If no camera is found |
|||
raise ValueError("No camera found.") |
|||
|
|||
# 获取摄像头 |
|||
camera_index = find_camera_index() |
|||
cap = cv2.VideoCapture(camera_index) |
|||
# 初始化可视化对象 |
|||
image_widget = widgets.Image(format='jpeg', width=1280, height=720) |
|||
display(image_widget) |
|||
while True: |
|||
# 对摄像头每一帧进行推理和可视化 |
|||
_, img_frame = cap.read() |
|||
image_pred = infer_frame_with_vis(img_frame, model, labels_dict, cfg) |
|||
image_widget.value = img2bytes(image_pred) |
|||
|
|||
if __name__ == "__main__": |
|||
cfg = { |
|||
'conf_thres': 0.4, # 模型置信度阈值,阈值越低,得到的预测框越多 |
|||
'iou_thres': 0.5, # IOU阈值,高于这个阈值的重叠预测框会被过滤掉 |
|||
'input_shape': [640, 640], # 模型输入尺寸 |
|||
} |
|||
|
|||
model_path = 'yolo.om' |
|||
label_path = './coco_names.txt' |
|||
# 初始化推理模型 |
|||
model = InferSession(0, model_path) |
|||
labels_dict = get_labels_from_txt(label_path) |
|||
|
|||
infer_mode = 'video' |
|||
|
|||
if infer_mode == 'image': |
|||
img_path = 'world_cup.jpg' |
|||
infer_image(img_path, model, labels_dict, cfg) |
|||
elif infer_mode == 'camera': |
|||
infer_camera(model, labels_dict, cfg) |
|||
elif infer_mode == 'video': |
|||
video_path = 'racing.mp4' |
|||
infer_video(video_path, model, labels_dict, cfg) |
@ -0,0 +1,24 @@ |
|||
from quart import Blueprint, render_template, request, redirect, url_for, flash, current_app |
|||
import os |
|||
import subprocess |
|||
from werkzeug.utils import secure_filename |
|||
from myutils.ConfigManager import myCongif |
|||
|
|||
def allowed_file(filename): |
|||
return '.' in filename and filename.rsplit('.', 1)[1].lower() in myCongif.get_data('ALLOWED_EXTENSIONS') |
|||
|
|||
#对上传的系统升级包进行检查 type:1--系统升级包,2--算法升级包 |
|||
def check_file(filepath,type): |
|||
pass |
|||
|
|||
def update_system(filepath): #系统升级 |
|||
pass |
|||
|
|||
def updata_model(filepath): #算法模型升级或新增 |
|||
try: |
|||
# 假设我们解压并运行一个升级脚本 |
|||
subprocess.run(['unzip', '-o', filepath, '-d', '/path/to/upgrade/directory'], check=True) |
|||
subprocess.run(['/path/to/upgrade/directory/upgrade_script.sh'], check=True) |
|||
return True, 'Upgrade completed successfully.' |
|||
except subprocess.CalledProcessError as e: |
|||
return False, str(e) |
@ -0,0 +1,59 @@ |
|||
import requests |
|||
import time |
|||
import threading |
|||
from core.DBManager import DBManager |
|||
from myutils.MyLogger_logger import LogHandler |
|||
from myutils.ConfigManager import myCongif |
|||
|
|||
class ViewManager: |
|||
|
|||
def __init__(self): |
|||
self.thcheck = False |
|||
self.lock = threading.Lock() |
|||
self.logger = LogHandler().get_logger("ViewManager") |
|||
|
|||
def check_rtsp_url(self,rtsp_url): #通过发送http请求判断rtsp流的在线状态 |
|||
http_url = rtsp_url.replace("rtsp://", "http://") |
|||
try: |
|||
response = requests.options(http_url, timeout=5) |
|||
if response.status_code == 200: |
|||
return True |
|||
except requests.RequestException: |
|||
pass |
|||
return False |
|||
|
|||
def check_all_urls(self): #? |
|||
strsql = "select ID,ulr from channel;" |
|||
datas = self.mDBM.do_select(strsql) |
|||
IDs = [row[0] for row in datas] |
|||
urls = [row[1] for row in datas] |
|||
#print(IDs,urls) |
|||
for i in range(len(urls)): |
|||
url = urls[i] |
|||
ID = IDs[i] |
|||
result = self.check_rtsp_url(url) |
|||
iret = 1 if result else 0 |
|||
strsql = f"update channel set status={iret} where ID={ID};" |
|||
self.logger.debug(strsql) |
|||
self.mDBM.do_sql(strsql) |
|||
|
|||
def start_check_rtsp(self, interval=myCongif.get_data('RTSP_Check_Time')): #通过线程循环检查添加的RTSP地址在线情况 |
|||
def run(): |
|||
self.mDBM = DBManager() |
|||
self.mDBM.connect() |
|||
while True: |
|||
self.check_all_urls() |
|||
time.sleep(interval) |
|||
|
|||
if self.thcheck == False: |
|||
thread = threading.Thread(target=run) |
|||
thread.daemon = True |
|||
thread.start() |
|||
self.thcheck = True |
|||
|
|||
|
|||
mVManager = ViewManager() |
|||
|
|||
if __name__ =='__main__': |
|||
mVManager.start_check_rtsp() |
|||
time.sleep(65) |
@ -1,13 +0,0 @@ |
|||
from util.ConfigManager import myCongif |
|||
from util.MyLogger_logger import LogHandler |
|||
|
|||
from web import create_app |
|||
|
|||
app = create_app() |
|||
|
|||
if __name__ == '__main__': |
|||
# logger = LogHandler().get_logger("main") |
|||
# logger.debug("debug") |
|||
# logger.info("info") |
|||
app.run(debug=True) |
|||
|
@ -0,0 +1,64 @@ |
|||
import torch |
|||
import cv2 |
|||
import numpy as np |
|||
from shapely.geometry import Point, Polygon |
|||
import os |
|||
|
|||
# # 自定义模型文件的路径 |
|||
# model_path = 'yolov5s.pt' # 假设模型文件名为 yolov5s.pt 并与执行文件在同一个目录 |
|||
# # 本地YOLOv5仓库路径 |
|||
# repo_path = '../base_model/yolov5' |
|||
# 自定义模型文件的路径 |
|||
print(f"Current working directory (yolov5.py): {os.getcwd()}") |
|||
model_path = 'D:/Project/FristProject/model/mode_test/yolov5s.pt' # 假设模型文件名为 yolov5s.pt 并与执行文件在同一个目录 |
|||
# 本地YOLOv5仓库路径 |
|||
repo_path = 'D:/Project/FristProject/model/base_model/yolov5' |
|||
|
|||
# 加载自定义模型 |
|||
model = torch.hub.load(repo_path, 'custom', path=model_path, source='local') |
|||
|
|||
# 定义监控区域(多边形顶点) |
|||
region_points = [(100, 100), (500, 100), (500, 400), (100, 400)] |
|||
polygon = Polygon(region_points) |
|||
|
|||
# 打开摄像头 |
|||
cap = cv2.VideoCapture(0) |
|||
|
|||
def is_point_in_polygon(point, polygon): |
|||
return polygon.contains(Point(point)) |
|||
|
|||
while True: |
|||
ret, frame = cap.read() |
|||
if not ret: |
|||
break |
|||
|
|||
# 进行推理 |
|||
results = model(frame) |
|||
detections = results.pandas().xyxy[0] |
|||
|
|||
# 绘制监控区域 |
|||
cv2.polylines(frame, [np.array(region_points, np.int32)], isClosed=True, color=(0, 255, 0), thickness=2) |
|||
|
|||
for _, row in detections.iterrows(): |
|||
if row['name'] == 'person': |
|||
# 获取人员检测框的中心点 |
|||
x_center = (row['xmin'] + row['xmax']) / 2 |
|||
y_center = (row['ymin'] + row['ymax']) / 2 |
|||
|
|||
if is_point_in_polygon((x_center, y_center), polygon): |
|||
# 触发报警 |
|||
cv2.putText(frame, 'Alert: Intrusion Detected', (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2) |
|||
print('Alert: Intrusion Detected') |
|||
|
|||
# 绘制检测框 |
|||
cv2.rectangle(frame, (int(row['xmin']), int(row['ymin'])), (int(row['xmax']), int(row['ymax'])), (255, 0, 0), 2) |
|||
cv2.circle(frame, (int(x_center), int(y_center)), 5, (0, 0, 255), -1) |
|||
|
|||
# 显示结果 |
|||
cv2.imshow('Frame', frame) |
|||
|
|||
if cv2.waitKey(1) & 0xFF == ord('q'): |
|||
break |
|||
|
|||
cap.release() |
|||
cv2.destroyAllWindows() |
@ -1,17 +1,48 @@ |
|||
from abc import abstractmethod |
|||
from abc import abstractmethod,ABC |
|||
from shapely.geometry import Point, Polygon |
|||
import numpy as np |
|||
import cv2 |
|||
|
|||
class ModelBase(object): |
|||
class ModelBase(ABC): |
|||
def __init__(self): |
|||
pass |
|||
self.name = None #基于name来查询,用户对模型的配置参数,代表着模型名称需要唯一 2024-6-18 -逻辑还需要完善和验证 |
|||
self.version = None |
|||
self.model_type = None # 模型类型 1-图像分类,2-目标检测(yolov5),3-分割模型,4-关键点 |
|||
self.do_map = { # 定义插件的入口函数 -- |
|||
# POCType.POC: self.do_verify, |
|||
# POCType.SNIFFER: self.do_sniffer, |
|||
# POCType.BRUTE: self.do_brute |
|||
} |
|||
|
|||
def __del__(self): |
|||
print("资源释放") |
|||
|
|||
def draw_polygon(self, img, polygon_points,color=(0, 255, 0)): |
|||
self.polygon = Polygon(polygon_points) |
|||
|
|||
points = np.array([self.polygon.exterior.coords], dtype=np.int32) |
|||
cv2.polylines(img, points, isClosed=True, color=color, thickness=2) |
|||
|
|||
def is_point_in_region(self, point): |
|||
'''判断点是否在区域内,需要先执行draw_polygon''' |
|||
if self.polygon: |
|||
return self.polygon.contains(Point(point)) |
|||
else: |
|||
return False |
|||
|
|||
def save_warn(self): |
|||
'''保存报警信息''' |
|||
pass |
|||
|
|||
def send_warn(self): |
|||
'''发送报警信息''' |
|||
pass |
|||
|
|||
''' |
|||
模型处理的标准接口 |
|||
1.输入标准化 |
|||
2.输出标准化 |
|||
''' |
|||
@abstractmethod |
|||
def doWork(self,image,mode,class_name,cfg,isdraw_box=False,bgr2rgb=True): |
|||
def verify(self,image,data): |
|||
''' |
|||
:param image: 需要验证的图片 |
|||
:param data: select t1.model_id,t1.check_area,t1.polygon ,t2.duration_time,t2.proportion,t2.model_path |
|||
:return: detections,bwarn,warntext |
|||
''' |
|||
pass |
@ -1,5 +1,42 @@ |
|||
from model.plugins.ModelBase import ModelBase |
|||
|
|||
from myutils.ConfigManager import myCongif |
|||
from shapely.geometry import Point, Polygon |
|||
import torch |
|||
import cv2 |
|||
import av |
|||
import numpy as np |
|||
class Model(ModelBase): |
|||
def doWork(self,image,mode,class_name,cfg,isdraw_box=False,bgr2rgb=True): |
|||
print("RYRQ_Model") |
|||
def __init__(self,path): |
|||
super().__init__() |
|||
self.name = "人员入侵" |
|||
self.version = "V1.0" |
|||
self.model_type = 2 |
|||
#实例化模型 |
|||
self.model = torch.hub.load(myCongif.get_data("yolov5_path"), 'custom', path=path, source='local') |
|||
|
|||
def verify(self,image,data): |
|||
results = self.model(image) # 进行模型检测 --- 需要统一接口 |
|||
detections = results.pandas().xyxy[0].to_dict(orient="records") |
|||
bwarn = False |
|||
warn_text = "" |
|||
#是否有检测区域,有先绘制检测区域 由于在该函数生成了polygon对象,所有需要在检测区域前调用。 |
|||
if data[1] == 1: |
|||
self.draw_polygon(image,data[2],(0, 255, 0)) |
|||
# 绘制检测结果 --- 也需要封装在类里, |
|||
for det in detections: |
|||
if det['name'] == 'person': #标签是人 |
|||
x1, y1, x2, y2 = int(det['xmin']), int(det['ymin']), int(det['xmax']), int(det['ymax']) |
|||
# 绘制目标识别的锚框 |
|||
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!" |
|||
return detections,bwarn,warn_text |
@ -0,0 +1,155 @@ |
|||
# 导入代码依赖 |
|||
import cv2 |
|||
import numpy as np |
|||
from myutils.ConfigManager import myCongif |
|||
import os |
|||
import torch |
|||
|
|||
import ipywidgets as widgets |
|||
from IPython.display import display |
|||
from skvideo.io import vreader, FFmpegWriter |
|||
import IPython.display |
|||
from ais_bench.infer.interface import InferSession |
|||
from det_utils import letterbox, scale_coords, nms |
|||
|
|||
def preprocess_image(image, cfg, bgr2rgb=True): |
|||
"""图片预处理""" |
|||
img, scale_ratio, pad_size = letterbox(image, new_shape=cfg['input_shape']) |
|||
if bgr2rgb: |
|||
img = img[:, :, ::-1] |
|||
img = img.transpose(2, 0, 1) # HWC2CHW |
|||
img = np.ascontiguousarray(img, dtype=np.float32) |
|||
return img, scale_ratio, pad_size |
|||
|
|||
def draw_bbox(bbox, img0, color, wt, names): |
|||
"""在图片上画预测框""" |
|||
det_result_str = '' |
|||
for idx, class_id in enumerate(bbox[:, 5]): |
|||
if float(bbox[idx][4] < float(0.05)): |
|||
continue |
|||
img0 = cv2.rectangle(img0, (int(bbox[idx][0]), int(bbox[idx][1])), (int(bbox[idx][2]), int(bbox[idx][3])), |
|||
color, wt) |
|||
img0 = cv2.putText(img0, str(idx) + ' ' + names[int(class_id)], (int(bbox[idx][0]), int(bbox[idx][1] + 16)), |
|||
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1) |
|||
img0 = cv2.putText(img0, '{:.4f}'.format(bbox[idx][4]), (int(bbox[idx][0]), int(bbox[idx][1] + 32)), |
|||
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1) |
|||
det_result_str += '{} {} {} {} {} {}\n'.format( |
|||
names[bbox[idx][5]], str(bbox[idx][4]), bbox[idx][0], bbox[idx][1], bbox[idx][2], bbox[idx][3]) |
|||
return img0 |
|||
|
|||
def get_labels_from_txt(path): |
|||
"""从txt文件获取图片标签""" |
|||
labels_dict = dict() |
|||
with open(path) as f: |
|||
for cat_id, label in enumerate(f.readlines()): |
|||
labels_dict[cat_id] = label.strip() |
|||
return labels_dict |
|||
|
|||
def draw_prediction(pred, image, labels): |
|||
"""在图片上画出预测框并进行可视化展示""" |
|||
imgbox = widgets.Image(format='jpg', height=720, width=1280) |
|||
img_dw = draw_bbox(pred, image, (0, 255, 0), 2, labels) |
|||
imgbox.value = cv2.imencode('.jpg', img_dw)[1].tobytes() |
|||
display(imgbox) |
|||
|
|||
def infer_image(img_path, model, class_names, cfg): |
|||
"""图片推理""" |
|||
# 图片载入 |
|||
image = cv2.imread(img_path) |
|||
# 数据预处理 |
|||
img, scale_ratio, pad_size = preprocess_image(image, cfg) |
|||
# 模型推理 |
|||
output = model.infer([img])[0] |
|||
|
|||
output = torch.tensor(output) |
|||
# 非极大值抑制后处理 |
|||
boxout = nms(output, conf_thres=cfg["conf_thres"], iou_thres=cfg["iou_thres"]) |
|||
pred_all = boxout[0].numpy() |
|||
# 预测坐标转换 |
|||
scale_coords(cfg['input_shape'], pred_all[:, :4], image.shape, ratio_pad=(scale_ratio, pad_size)) |
|||
# 图片预测结果可视化 |
|||
draw_prediction(pred_all, image, class_names) |
|||
|
|||
def infer_frame_with_vis(image, model, labels_dict, cfg, bgr2rgb=True): |
|||
# 数据预处理 |
|||
img, scale_ratio, pad_size = preprocess_image(image, cfg, bgr2rgb) |
|||
# 模型推理 |
|||
output = model.infer([img])[0] |
|||
|
|||
output = torch.tensor(output) |
|||
# 非极大值抑制后处理 |
|||
boxout = nms(output, conf_thres=cfg["conf_thres"], iou_thres=cfg["iou_thres"]) |
|||
pred_all = boxout[0].numpy() |
|||
# 预测坐标转换 |
|||
scale_coords(cfg['input_shape'], pred_all[:, :4], image.shape, ratio_pad=(scale_ratio, pad_size)) |
|||
# 图片预测结果可视化 |
|||
img_vis = draw_bbox(pred_all, image, (0, 255, 0), 2, labels_dict) |
|||
return img_vis |
|||
|
|||
def img2bytes(image): |
|||
"""将图片转换为字节码""" |
|||
return bytes(cv2.imencode('.jpg', image)[1]) |
|||
|
|||
def infer_video(video_path, model, labels_dict, cfg): |
|||
"""视频推理""" |
|||
image_widget = widgets.Image(format='jpeg', width=800, height=600) |
|||
display(image_widget) |
|||
|
|||
# 读入视频 |
|||
cap = cv2.VideoCapture(video_path) |
|||
while True: |
|||
ret, img_frame = cap.read() |
|||
if not ret: |
|||
break |
|||
# 对视频帧进行推理 |
|||
image_pred = infer_frame_with_vis(img_frame, model, labels_dict, cfg, bgr2rgb=True) |
|||
image_widget.value = img2bytes(image_pred) |
|||
|
|||
def infer_camera(model, labels_dict, cfg): |
|||
"""外设摄像头实时推理""" |
|||
def find_camera_index(): |
|||
max_index_to_check = 10 # Maximum index to check for camera |
|||
|
|||
for index in range(max_index_to_check): |
|||
cap = cv2.VideoCapture(index) |
|||
if cap.read()[0]: |
|||
cap.release() |
|||
return index |
|||
|
|||
# If no camera is found |
|||
raise ValueError("No camera found.") |
|||
|
|||
# 获取摄像头 |
|||
camera_index = find_camera_index() |
|||
cap = cv2.VideoCapture(camera_index) |
|||
# 初始化可视化对象 |
|||
image_widget = widgets.Image(format='jpeg', width=1280, height=720) |
|||
display(image_widget) |
|||
while True: |
|||
# 对摄像头每一帧进行推理和可视化 |
|||
_, img_frame = cap.read() |
|||
image_pred = infer_frame_with_vis(img_frame, model, labels_dict, cfg) |
|||
image_widget.value = img2bytes(image_pred) |
|||
|
|||
if __name__ == "__main__": |
|||
cfg = { |
|||
'conf_thres': 0.4, # 模型置信度阈值,阈值越低,得到的预测框越多 |
|||
'iou_thres': 0.5, # IOU阈值,高于这个阈值的重叠预测框会被过滤掉 |
|||
'input_shape': [640, 640], # 模型输入尺寸 |
|||
} |
|||
model_path = os.path.join(myCongif.get_data('weight_path'),'/yolov5-1/yolov5s.pt') |
|||
label_path = os.path.join(myCongif.get_data('weight_path'),'/yolov5-1/coco_names.txt') |
|||
# 初始化推理模型 |
|||
model = InferSession(0, model_path) |
|||
labels_dict = get_labels_from_txt(label_path) |
|||
|
|||
infer_mode = 'video' |
|||
|
|||
if infer_mode == 'image': |
|||
img_path = 'world_cup.jpg' |
|||
infer_image(img_path, model, labels_dict, cfg) |
|||
elif infer_mode == 'camera': |
|||
infer_camera(model, labels_dict, cfg) |
|||
elif infer_mode == 'video': |
|||
video_path = 'racing.mp4' |
|||
infer_video(video_path, model, labels_dict, cfg) |
@ -1,7 +1,7 @@ |
|||
from loguru import logger |
|||
import os |
|||
from functools import wraps |
|||
from util.ConfigManager import myCongif |
|||
from myutils.ConfigManager import myCongif |
|||
|
|||
''' |
|||
@logger.catch() 异常捕获注解 |
@ -0,0 +1,67 @@ |
|||
import ipaddress |
|||
import re |
|||
''' |
|||
正则管理类,用来实现对各类字符串进行合法性校验 |
|||
''' |
|||
class ReManager(): |
|||
def is_valid_ip(self,ip): |
|||
try: |
|||
ipaddress.ip_address(ip) |
|||
return True |
|||
except ValueError: |
|||
return False |
|||
|
|||
def is_valid_subnet_mask(self,subnet_mask): |
|||
# 将子网掩码字符串转换为整数列表 |
|||
bits = [int(b) for b in subnet_mask.split('.')] |
|||
if len(bits) != 4: |
|||
return False |
|||
|
|||
# 检查每个部分是否在0-255范围内 |
|||
if not all(0 <= b <= 255 for b in bits): |
|||
return False |
|||
|
|||
# 检查子网掩码是否连续为1后跟连续为0 |
|||
mask_int = (bits[0] << 24) + (bits[1] << 16) + (bits[2] << 8) + bits[3] |
|||
binary_mask = bin(mask_int)[2:].zfill(32) |
|||
ones_count = binary_mask.count('1') |
|||
if not all(bit == '1' for bit in binary_mask[:ones_count]) or not all( |
|||
bit == '0' for bit in binary_mask[ones_count:]): |
|||
return False |
|||
return True |
|||
|
|||
def is_valid_port(self,port): |
|||
try: |
|||
port = int(port) # 尝试将端口转换为整数 |
|||
if 1 <= port <= 65535: # 检查端口号是否在合法范围内 |
|||
return True |
|||
else: |
|||
return False |
|||
except ValueError: |
|||
# 如果端口不是整数,返回False |
|||
return False |
|||
|
|||
def is_valid_rtsp_url(self,url): |
|||
pattern = re.compile( |
|||
r'^(rtsp:\/\/)' # Start with rtsp:// |
|||
r'(?P<host>(?:[a-zA-Z0-9_\-\.]+)|(?:\[[a-fA-F0-9:]+\]))' # Hostname or IPv4/IPv6 address |
|||
r'(?::(?P<port>\d+))?' # Optional port number |
|||
r'(?P<path>\/[a-zA-Z0-9_\-\.\/]*)?$' # Optional path |
|||
) |
|||
match = pattern.match(url) |
|||
return match is not None |
|||
|
|||
mReM = ReManager() |
|||
|
|||
if __name__ == "__main__": |
|||
urls = [ |
|||
"rtsp://192.168.1.1:554/stream1", |
|||
"rtsp://example.com/stream", |
|||
"rtsp://[2001:db8::1]:554/stream", |
|||
"rtsp://localhost", |
|||
"http://example.com", |
|||
"ftp://example.com/stream" |
|||
] |
|||
|
|||
for url in urls: |
|||
print(f"{url}: {mReM.is_valid_rtsp_url(url)}") |
@ -0,0 +1,42 @@ |
|||
#!/usr/bin/env python |
|||
# -*- coding:utf-8 -*- |
|||
__author__ = 'ZL' |
|||
import importlib.util |
|||
import os |
|||
|
|||
def import_source(module_name,module_path): |
|||
module_spec = importlib.util.spec_from_file_location(module_name, module_path) #根据给定的模块名和路径创建一个模块规范对象 |
|||
module = importlib.util.module_from_spec(module_spec) #根据模块规范创建一个新模块对象 |
|||
module_spec.loader.exec_module(module) #执行模块的代码,将其导入到新创建的模块对象中 |
|||
return module |
|||
|
|||
#遍历指定目录的文件,并去除掉一部分文件 |
|||
def walk_py(path): |
|||
for dir_path, dir_names, filenames in os.walk(path): |
|||
if dir_path.endswith("__pycache__"): |
|||
continue |
|||
|
|||
for f in filenames: |
|||
if f.startswith('_') or f.endswith('_.py'): |
|||
continue |
|||
|
|||
split = f.split('.') |
|||
|
|||
if len(split) == 2 and split[1] == 'py': |
|||
abspath = os.path.abspath(os.path.join(dir_path, f)) |
|||
yield abspath, split[0] |
|||
|
|||
|
|||
def load_plugins(path): |
|||
plugins = [] |
|||
for file_path, name in walk_py(path): |
|||
try: |
|||
module = import_source(spec="repoc_plugins", path=file_path) |
|||
Plugin = getattr(module, 'Plugin') |
|||
plugin = Plugin() |
|||
setattr(plugin, '_plugin_name', name) |
|||
plugins.append(plugin) |
|||
except Exception as e: |
|||
print("load plugin error from {}".format(file_path)) |
|||
|
|||
return plugins |
@ -0,0 +1,17 @@ |
|||
from myutils.ConfigManager import myCongif |
|||
from myutils.MyLogger_logger import LogHandler |
|||
from core.ViewManager import mVManager |
|||
from web import create_app |
|||
import os |
|||
|
|||
|
|||
#print(f"Current working directory (run.py): {os.getcwd()}") |
|||
web = create_app() |
|||
|
|||
if __name__ == '__main__': |
|||
# logger = LogHandler().get_logger("main") |
|||
# logger.debug("debug") |
|||
# logger.info("info") |
|||
mVManager.start_check_rtsp() |
|||
web.run(debug=True,port=5001) |
|||
|
@ -0,0 +1,4 @@ |
|||
from quart import Blueprint |
|||
#定义模块 |
|||
api = Blueprint('api',__name__) |
|||
from . import user,system,viedo,channel,model |
@ -0,0 +1,290 @@ |
|||
import json |
|||
from quart import jsonify, request |
|||
from . import api |
|||
from web.common.utils import login_required |
|||
from core.DBManager import mDBM |
|||
from myutils.ReManager import mReM |
|||
|
|||
@api.route('/channel/tree',methods=['GET']) |
|||
@login_required |
|||
async def channel_tree(): #获取通道树 |
|||
strsql = ("select t2.area_name ,t1.ID ,t1.channel_name from channel t1 left join area t2 on t1.area_id = t2.id;") |
|||
data = mDBM.do_select(strsql) |
|||
channel_tree = [{"area_name": channel[0], "ID": channel[1],"channel_name":channel[2]} for channel in data] |
|||
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;") |
|||
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] |
|||
return jsonify(channel_list) |
|||
|
|||
@api.route('/channel/info',methods=['GET']) |
|||
@login_required |
|||
async def channel_info(): #获取通道信息 ---- list已获取详情 |
|||
return jsonify(1) |
|||
|
|||
@api.route('/channel/add',methods=['POST']) |
|||
@login_required |
|||
async def channel_add(): #新增通道 |
|||
area_id = (await request.form)['area_id'] |
|||
channel_name = (await request.form)['channel_name'] |
|||
url = (await request.form)['url'] |
|||
if mReM.is_valid_rtsp_url(url) is not True: |
|||
reStatus = 0 |
|||
reMsg = 'rtsp地址不合法' |
|||
return jsonify({'status': reStatus, 'msg': reMsg}) |
|||
strsql = f"select area_name from area where id = {area_id};" |
|||
ret = mDBM.do_select(strsql,1) |
|||
if ret: |
|||
strsql = (f"INSERT INTO channel (area_id,channel_name,ulr,'type',status) values " |
|||
f"({area_id},'{channel_name}','{url}',1,0);") |
|||
ret = mDBM.do_sql(strsql) |
|||
if ret == True: |
|||
reStatus = 1 |
|||
reMsg = '添加通道成功' |
|||
else: |
|||
reStatus = 0 |
|||
reMsg = '添加通道失败,请联系技术支持!' |
|||
else: |
|||
reStatus = 0 |
|||
reMsg = '错误的通道ID,请修改' |
|||
return jsonify({'status':reStatus,'msg':reMsg}) |
|||
|
|||
@api.route('/channel/change',methods=['POST']) |
|||
@login_required |
|||
async def channel_change(): #修改通道信息 |
|||
area_id = (await request.form)['area_id'] |
|||
channel_id = (await request.form)['channel_id'] |
|||
channel_name = (await request.form)['channel_name'] |
|||
url = (await request.form)['url'] |
|||
if mReM.is_valid_rtsp_url(url) is not True: |
|||
reStatus = 0 |
|||
reMsg = 'rtsp地址不合法' |
|||
return jsonify({'status': reStatus, 'msg': reMsg}) |
|||
strsql = f"select area_name from area where id = {area_id};" |
|||
ret = mDBM.do_select(strsql, 1) |
|||
if ret: |
|||
strsql = f"UPDATE channel SET area_id={area_id},channel_name='{channel_name}',ulr='{url}' where ID={channel_id};" |
|||
ret = mDBM.do_sql(strsql) |
|||
if ret == True: |
|||
reStatus = 1 |
|||
reMsg = '修改通道信息成' |
|||
else: |
|||
reStatus = 0 |
|||
reMsg = '修改通道信息失败,请联系技术支持!' |
|||
else: |
|||
reStatus = 0 |
|||
reMsg = '错误的通道ID,请修改' |
|||
return jsonify({'status': reStatus, 'msg': reMsg}) |
|||
|
|||
@api.route('/channel/del',methods=['POST']) |
|||
@login_required |
|||
async def channel_del(): #删除通道 |
|||
channel_id = (await request.form)['channel_id'] |
|||
#删除该通道和算法的关联信息:布防时间,算法模型数据----使用外键级联删除会方便很多,只要一个删除就可以 |
|||
ret = mDBM.delchannel(channel_id) |
|||
if ret == True: |
|||
reStatus = 1 |
|||
reMsg = '删除通道信息成' |
|||
else: |
|||
reStatus = 0 |
|||
reMsg = '删除通道信息失败,请联系技术支持!' |
|||
return jsonify({'status': reStatus, 'msg': reMsg}) |
|||
|
|||
@api.route('/channel/check',methods=['GET']) |
|||
@login_required |
|||
async def channel_check(): #检查通道视频的在线情况--可以获取全部 ID-0,全部,1*具体通道ID--10分钟会更新一次在线状态 |
|||
ID = request.args.get('ID') |
|||
if ID: |
|||
ID = int(ID) |
|||
if ID==0: |
|||
strsql = "select ID,status from channel;" |
|||
elif ID > 0: |
|||
strsql = f"select ID,status from channel where ID={ID};" |
|||
else: |
|||
reStatus = 0 |
|||
reMsg = "参数错误" |
|||
return jsonify({'status': reStatus, 'msg': reMsg}) |
|||
datas = mDBM.do_select(strsql) |
|||
reStatus = 1 |
|||
reMsg = [{'ID': data[0], 'status': data[1]} for data in datas] |
|||
return jsonify({'status': reStatus, 'msg': reMsg}) |
|||
else: |
|||
reStatus = 0 |
|||
reMsg = "参数错误" |
|||
return jsonify({'status': reStatus, 'msg': reMsg}) |
|||
|
|||
@api.route('/channel/area/list',methods=['GET']) |
|||
@login_required |
|||
async def channel_area_list(): #获取区域列表 |
|||
strsql = "select * from area;" |
|||
datas = mDBM.do_select(strsql) |
|||
reMsg = [{'id': data[0], 'area_name': data[1]} for data in datas] |
|||
return jsonify(reMsg) |
|||
|
|||
@api.route('/channel/area/change',methods=['POST']) |
|||
@login_required |
|||
async def channel_area_change(): #修改区域名称 |
|||
area_id = (await request.form)['id'] |
|||
area_name = (await request.form)['name'] |
|||
strsql = f"update area set area_name='{area_name}' where id = {area_id};" |
|||
ret = mDBM.do_sql(strsql) |
|||
if ret == True: |
|||
reStatus = 1 |
|||
reMsg = '修改通道名称成功' |
|||
else: |
|||
reStatus = 0 |
|||
reMsg = '修改通道名称失败,请联系技术支持!' |
|||
return jsonify({'status': reStatus, 'msg': reMsg}) |
|||
|
|||
@api.route('/channel/area/del',methods=['POST']) |
|||
@login_required |
|||
async def channel_area_del(): #删除区域 |
|||
area_id = (await request.form)['id'] |
|||
strsql = f"select * from channel where area_id={area_id};" |
|||
datas = mDBM.do_select(strsql) |
|||
if datas: |
|||
reStatus = 0 |
|||
reMsg = '该区域下配置了通道,请先删除通道后,再删除区域!' |
|||
else: |
|||
strsql = f"delete from area where id={area_id};" |
|||
ret = mDBM.do_sql(strsql) |
|||
if ret == True: |
|||
reStatus = 1 |
|||
reMsg = '删除区域成功' |
|||
else: |
|||
reStatus = 0 |
|||
reMsg = '删除区域失败,请联系技术支持!' |
|||
return jsonify({'status': reStatus, 'msg': reMsg}) |
|||
|
|||
@api.route('/channel/area/add',methods=['POST']) |
|||
@login_required |
|||
async def channel_area_add(): #添加区域 |
|||
area_name = (await request.form)['name'] |
|||
strsql = f"insert into area (area_name) values ('{area_name}');" |
|||
ret = mDBM.do_sql(strsql) |
|||
if ret == True: |
|||
reStatus = 1 |
|||
reMsg = '新增区域成功' |
|||
else: |
|||
reStatus = 0 |
|||
reMsg = '新增区域失败,请联系技术支持!' |
|||
return jsonify({'status': reStatus, 'msg': reMsg}) |
|||
|
|||
@api.route('/channel/model/linklist',methods=['GET']) |
|||
@login_required |
|||
async def channel_model_linklist(): #获取算法列表 --关联算法时展示 |
|||
ID = request.args.get('ID') #通道ID |
|||
strsql = (f"select t1.ID,t2.model_name from channel2model t1 left join model t2 " |
|||
f"on t1.model_id=t2.ID where channel_id={ID};") |
|||
datas = mDBM.do_select(strsql) |
|||
reMsg = {} |
|||
if datas: |
|||
reMsg = [{'ID': data[0], 'model_name': data[1]} for data in datas] |
|||
return jsonify(reMsg) |
|||
|
|||
@api.route('/channel/model/list',methods=['GET']) |
|||
@login_required |
|||
async def channel_model_list(): #获取算法列表 |
|||
strsql = "select ID,model_name,version from model;" |
|||
datas = mDBM.do_select(strsql) |
|||
reMsg = {} |
|||
if datas: |
|||
reMsg = [{'ID': data[0], 'model_name': data[1],'version':data[2]} for data in datas] |
|||
return jsonify(reMsg) |
|||
|
|||
@api.route('/channel/model/linkmodel',methods=['POST']) |
|||
@login_required |
|||
async def channel_model_linkmodel(): #获取算法列表 --关联算法时展示 |
|||
channel_id = (await request.form)['channel_id'] |
|||
model_list = json.loads((await request.form)['model_list']) |
|||
#? 需要对channel_id和model_list的是否在数据库要进行一个检查 |
|||
reStatus = 0 |
|||
reMsg = '提交的参数错误,请检查!' |
|||
strsql = f"select ID from channel where ID={channel_id};" |
|||
if mDBM.do_select(strsql,1): #通道号在数据库中 |
|||
for model_id in model_list: |
|||
strsql = f"select ID from model where ID = {model_id};" |
|||
if not mDBM.do_select(strsql,1): |
|||
return jsonify({'status': reStatus, 'msg': reMsg}) |
|||
ret =mDBM.updateC2M(channel_id,model_list) |
|||
if ret == True: |
|||
reStatus = 1 |
|||
reMsg = '修改关联算法成功' |
|||
else: |
|||
reStatus = 0 |
|||
reMsg = '修改关联算法失败,请联系技术支持!' |
|||
return jsonify({'status': reStatus, 'msg': reMsg}) |
|||
|
|||
@api.route('/channel/model/getarea',methods=['GET']) |
|||
@login_required |
|||
async def channel_model_getarea(): #获取算法区域的备注信息 |
|||
ID = request.args.get('ID') |
|||
strsql = f"select check_area,check_x,check_y,check_width,check_height from channel2model where ID={ID};" |
|||
data = mDBM.do_select(strsql,1) |
|||
if data: |
|||
reMsg = {'ID':ID,'check_area':data[0],'check_x':data[1],'check_y':data[2], |
|||
'check_width':data[3],'check_height':data[4]} |
|||
else: |
|||
reMsg = {} |
|||
return jsonify(reMsg) |
|||
|
|||
@api.route('/channel/model/changearea',methods=['POST']) |
|||
@login_required |
|||
async def channel_model_changearea(): #修改算法区域信息 |
|||
ID = (await request.form)['ID'] |
|||
check_area = (await request.form)['check_area'] |
|||
check_x = (await request.form)['check_x'] |
|||
check_y = (await request.form)['check_y'] |
|||
check_width = (await request.form)['check_width'] |
|||
check_height = (await request.form)['check_height'] |
|||
strsql = (f"update channel2model set check_area={check_area},check_x={check_x},check_y={check_y}" |
|||
f",check_width={check_width},check_height={check_height} where ID={ID};") |
|||
ret = mDBM.do_sql(strsql) |
|||
if ret == True: |
|||
reStatus = 1 |
|||
reMsg = '修改算法检测区域成功' |
|||
else: |
|||
reStatus = 0 |
|||
reMsg = '新修改算法检测区域失败,请联系技术支持!' |
|||
return jsonify({'status': reStatus, 'msg': reMsg}) |
|||
|
|||
@api.route('/channel/model/getschedule',methods=['GET']) |
|||
@login_required |
|||
async def channel_model_getschedule(): #获取算法的布防时间 |
|||
ID = request.args.get('ID') |
|||
strsql = f"select day,hour,status from schedule where channel2model_id={ID};" |
|||
datas = mDBM.do_select(strsql) |
|||
if datas: |
|||
reMsg = [{'day': data[0], 'hour': data[1],'status':data[2]} for data in datas] |
|||
else: |
|||
reMsg = {} |
|||
return jsonify(reMsg) |
|||
|
|||
@api.route('/channel/model/changeschedule',methods=['POST']) |
|||
@login_required |
|||
async def channel_model_changeschedule(): #修改算法的布防时间 |
|||
ID = (await request.form)['ID'] |
|||
schedule_data_str = (await request.form)['schedule_data'] |
|||
schedule_data = json.loads(schedule_data_str.replace("'", '"')) |
|||
for day,hours in schedule_data.items(): |
|||
for hour,status in enumerate(hours): |
|||
strsql = (f"insert into schedule (channel2model_id,day,hour,status) values ({ID},'{day}',{hour},{status})" |
|||
f" on conflict(channel2model_id,day,hour) do update set status=excluded.status;") |
|||
ret = mDBM.do_sql(strsql) |
|||
if ret == False: |
|||
reStatus = 0 |
|||
reMsg = '修改监测区域失败,请联系技术支持!' |
|||
return jsonify({'status': reStatus, 'msg': reMsg}) |
|||
reStatus = 1 |
|||
reMsg = '修改监测区域成功' |
|||
return jsonify({'status': reStatus, 'msg': reMsg}) |
|||
|
|||
|
|||
|
|||
|
@ -0,0 +1,113 @@ |
|||
import os |
|||
from quart import jsonify, request,session,flash,redirect |
|||
from . import api |
|||
from web.common.utils import login_required |
|||
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(): #获取算法列表 |
|||
strsql = "select ID,model_name,version,duration_time,proportion from model;" |
|||
datas = mDBM.do_select(strsql) |
|||
reMsg = [{"ID":data[0],"name":data[1],"version":data[2],"duration_time":data[3], |
|||
"proportion":data[4]} for data in datas] |
|||
return jsonify(reMsg) |
|||
|
|||
@api.route('/model/upload',methods=['POST']) |
|||
@login_required |
|||
async def model_upload(): #上传算法文件--需要进行文件校验 |
|||
form = await request.form |
|||
model_name = form['model_name'] |
|||
files = await request.files |
|||
if 'file' not in files: |
|||
flash('参数错误') |
|||
return redirect(request.url) |
|||
file = files['file'] |
|||
if file.filename == '': |
|||
flash('没有选择文件') |
|||
return redirect(request.url) |
|||
if file and allowed_file(file.filename): |
|||
filename = secure_filename(file.filename) |
|||
file_path = os.path.join(myCongif.get_data('UPLOAD_FOLDER'), filename) |
|||
await file.save(file_path) |
|||
if check_file(file_path,2): #itype--2 是算法模型升级 |
|||
strsql = (f"insert into upgrade (itype,filepath,model_name) values (2,'{file_path}','{model_name}')" |
|||
f" on conflict(itype,model_name) do update set filepath=excluded.filepath;") |
|||
ret = mDBM.do_sql(strsql) |
|||
# session['model'] = file_path |
|||
if ret: |
|||
strsql = f"select id from upgrade where itype=2 and model_name='{model_name}';" |
|||
data=mDBM.do_select(strsql,1) |
|||
reStatus = data[0] |
|||
reMsg = "升级包上传成功" |
|||
else: |
|||
reStatus = 0 |
|||
reMsg = "升级包上传失败" |
|||
return jsonify({'status': reStatus, 'msg': reMsg}) |
|||
else: |
|||
flash('升级包不合法,请重新上传') |
|||
# return redirect(url_for('main.get_html', html='系统管理.html')) |
|||
return redirect(request.url) |
|||
else: |
|||
flash('只允许上传zip文件') |
|||
return redirect(request.url) |
|||
|
|||
@api.route('/model/add',methods=['POST']) |
|||
@login_required |
|||
async def model_add(): #新增算法,需要先上传算法文件 |
|||
model_name = (await request.form)['model_name'] |
|||
upgrade_id = (await request.form)['upgrade_id'] #升级文件ID |
|||
###---需要根据升级包获取模型文件路径,算法版本号等信息,若根据upgrade_id获取不了对应的信息,则返回报错 |
|||
### |
|||
strsql = f"select ID from model where model_name='{model_name}';" |
|||
data = mDBM.do_select(strsql,1) |
|||
if data: |
|||
reStatus = 0 |
|||
reMsg = "算法名称重复,请修改!" |
|||
else: |
|||
strsql = f"insert into model (model_name) values ('{model_name}');" #还有参数未写全 |
|||
ret = mDBM.do_sql(strsql) |
|||
if ret: |
|||
reStatus = 1 |
|||
reMsg = "新增算法成功!" |
|||
else: |
|||
reStatus = 0 |
|||
reMsg = "新增算法失败,请联系技术支持!" |
|||
return jsonify({'status': reStatus, 'msg': reMsg}) |
|||
|
|||
@api.route('/model/upgrade',methods=['POST']) |
|||
@login_required |
|||
async def model_upgrade(): #升级算法,需要先上传算法文件 |
|||
return jsonify(1) |
|||
|
|||
@api.route('/model/config',methods=['GET']) |
|||
@login_required |
|||
async def model_config(): #获取算法的配置信息 --- list已经获取 |
|||
ID = request.args.get('ID') |
|||
strsql = f"select model_name,version,duration_time,proportion from model where ID={ID};" |
|||
data = mDBM.do_select(strsql,1) |
|||
reMsg = {"model_name":data[0],"version":data[1],"duration_time":data[2],"proportion":data[3]} |
|||
return jsonify(reMsg) |
|||
|
|||
@api.route('/model/changecnf',methods=['POST']) |
|||
@login_required |
|||
async def model_config_change(): #修改算法的配置信息 |
|||
ID = (await request.form)['ID'] |
|||
duration_time = (await request.form)['duration_time'] |
|||
proportion = float((await request.form)['proportion']) |
|||
if proportion>0 and proportion < 1: |
|||
strsql = f"update model set duration_time='{duration_time}',proportion='{proportion}' where ID={ID};" |
|||
ret = mDBM.do_sql(strsql) |
|||
if ret: |
|||
reStatus = 1 |
|||
reMsg = "修复算法配置成功" |
|||
else: |
|||
reStatus = 0 |
|||
reMsg = "修复算法配置失败" |
|||
else: |
|||
reStatus = 0 |
|||
reMsg = "占比需要为大于0,小于1的值" |
|||
return jsonify({'status': reStatus, 'msg': reMsg}) |
|||
|
@ -0,0 +1,108 @@ |
|||
import os |
|||
from quart import jsonify, request,flash,redirect,url_for,session |
|||
from . import api |
|||
from web.common.utils import login_required |
|||
from core.DBManager import mDBM |
|||
from core.Upload_file import allowed_file,check_file,update_system |
|||
from myutils.ConfigManager import myCongif |
|||
from myutils.ReManager import mReM |
|||
from werkzeug.utils import secure_filename |
|||
|
|||
@api.route('/system/info',methods=['GET']) |
|||
@login_required |
|||
async def system_info(): #获取系统信息 |
|||
strsql = "select * from device;" |
|||
data = mDBM.do_select(strsql,1) |
|||
strRet = "" |
|||
if data: |
|||
strRet = {"ID":data[0],"version":data[1],"dev_ip":data[2],"dev_mask":data[3],"dev_gateway":data[4], |
|||
"server_ip":data[5],"server_port":data[6]} |
|||
return jsonify(strRet) #它将Python对象转换为JSON格式的字符串,并将其作为HTTP响应的主体返回给客户端, |
|||
# 同时设置正确的Content-Type响应头,表明响应主体是JSON格式的数据。 |
|||
|
|||
@api.route('/system/upload',methods=['POST']) |
|||
@login_required |
|||
async def system_upload(): #上传系统升级文件 |
|||
form = await request.form |
|||
files = await request.files |
|||
if 'file' not in files: |
|||
flash('参数错误') |
|||
return redirect(request.url) |
|||
file = files['file'] |
|||
if file.filename == '': |
|||
flash('没有选择文件') |
|||
return redirect(request.url) |
|||
if file and allowed_file(file.filename): |
|||
filename = secure_filename(file.filename) |
|||
file_path = os.path.join(myCongif.get_data('UPLOAD_FOLDER'), filename) |
|||
await file.save(file_path) |
|||
if check_file(file_path,1): |
|||
session['file'] = file_path |
|||
flash('升级包上传成功') |
|||
else: |
|||
flash('升级包不合法,请重新上传') |
|||
#return redirect(url_for('main.get_html', html='系统管理.html')) |
|||
return redirect(request.url) |
|||
else: |
|||
flash('只允许上传zip文件') |
|||
return redirect(request.url) |
|||
|
|||
@api.route('/system/upgrade',methods=['POST']) |
|||
@login_required |
|||
async def system_upgrade(): #系统升级 |
|||
if 'file' not in session: |
|||
reStatus = 0 |
|||
reMsg = "请先上传升级包文件" |
|||
else: |
|||
filepath = session['file'] |
|||
if update_system(filepath): |
|||
reStatus = 1 |
|||
reMsg = "系统升级成功" |
|||
else: |
|||
reStatus = 0 |
|||
reMsg = "系统升级失败" |
|||
return jsonify({'status': reStatus, 'msg': reMsg}) |
|||
|
|||
@api.route('/system/netinfo',methods=['GET']) #获取系统信息 时已经获取,不再处理 |
|||
@login_required |
|||
async def system_net_info(): #获取网络信息 |
|||
return jsonify(1) |
|||
|
|||
@api.route('/system/changenet',methods=['POST']) |
|||
@login_required |
|||
async def system_net_change(): #修改网络信息 |
|||
ip = (await request.form)['ip'] |
|||
mask = (await request.form)['mask'] |
|||
gateway = (await request.form)['gateway'] |
|||
#对ip,mask.geteway的合法性进行判断 |
|||
if mReM.is_valid_ip(ip) and mReM.is_valid_ip(gateway) and mReM.is_valid_subnet_mask(mask): |
|||
strsql = f"update device set dev_ip='{ip}',dev_mask='{mask}',dev_gateway='{gateway}'" |
|||
if mDBM.do_sql(strsql): |
|||
reStatus = 1 |
|||
reMsg = '修改成功,请确认无误后,重启设备!' |
|||
else: |
|||
reStatus = 0 |
|||
reMsg = '修改失败,请联系技术支持!' |
|||
else: |
|||
reStatus = 0 |
|||
reMsg= '输入的参数不合法,请检查!' |
|||
return jsonify({'status':reStatus,'msg':reMsg}) |
|||
|
|||
@api.route('/system/changeserver',methods=['POST']) |
|||
@login_required |
|||
async def system_server_change(): #修改服务器信息 |
|||
ip = (await request.form)['ip'] |
|||
port = (await request.form)['port'] |
|||
# 对ip,mask.geteway的合法性进行判断 |
|||
if mReM.is_valid_ip(ip) and mReM.is_valid_port(port): |
|||
strsql = f"update device set service_ip='{ip}',service_port='{port}';" |
|||
if mDBM.do_sql(strsql): |
|||
reStatus = 1 |
|||
reMsg = '修改成功,请确认无误后,重启设备!' |
|||
else: |
|||
reStatus = 0 |
|||
reMsg = '修改失败,请联系技术支持!' |
|||
else: |
|||
reStatus = 0 |
|||
reMsg = '输入的参数不合法,请检查!' |
|||
return jsonify({'status': reStatus, 'msg': reMsg}) |
@ -0,0 +1,114 @@ |
|||
import os |
|||
from quart import Quart, render_template, request, session, redirect, url_for,jsonify,send_file |
|||
from quart_sqlalchemy import SQLAlchemy |
|||
from quart_session import Session |
|||
from web.common.utils import generate_captcha,login_required |
|||
from myutils.ConfigManager import myCongif |
|||
from core.DBManager import mDBM |
|||
from . import api |
|||
from web.common.errors import handle_error |
|||
|
|||
|
|||
@api.route('/user/code',methods=['GET']) |
|||
async def user_get_code(): #获取验证码 |
|||
captcha_text, buffer = generate_captcha() |
|||
print(captcha_text) |
|||
session['captcha'] = captcha_text # 记录验证码? |
|||
return await send_file(buffer, mimetype='image/png') |
|||
|
|||
|
|||
@api.route('/user/login',methods=['POST']) |
|||
async def user_login(): #用户登录 |
|||
username = (await request.form)['username'] |
|||
password = (await request.form)['password'] |
|||
captcha = (await request.form)['captcha'] |
|||
if captcha != session.get('captcha'): |
|||
#验证码验证过后,需要失效 |
|||
#? |
|||
return '验证码错误', 400 |
|||
#比对用户名和密码 |
|||
strsql = f"select password from user where username = '{username}'" |
|||
db_password = mDBM.do_select(strsql,1) |
|||
if db_password: |
|||
if db_password[0] == password: #后续需要对密码进行MD5加默 |
|||
print("登录成功") |
|||
session['user'] = username |
|||
return redirect(url_for('main.get_html', html='实时预览.html')) |
|||
return '用户名或密码错误', 400 |
|||
|
|||
@api.route('/user/userinfo',methods=['GET']) |
|||
@login_required |
|||
async def user_info(): #获取用户列表 |
|||
strsql = "select username,status,people,tellnum from user;"; |
|||
data = mDBM.do_select(strsql) |
|||
if data: |
|||
user_list = [{"username": user[0], "status": user[1], |
|||
"people":user[2],"tellnum":user[3]} for user in data] |
|||
return jsonify(user_list) |
|||
else: |
|||
return jsonify(0) |
|||
|
|||
|
|||
@api.route('/user/adduser',methods=['POST']) |
|||
@login_required |
|||
async def user_adduser(): #新增用户 |
|||
username = (await request.form)['username'] |
|||
people = (await request.form)['people'] |
|||
tellnum = (await request.form)['tellnum'] |
|||
strsql = f"select username from user where username = '{username}';" |
|||
data = mDBM.do_select(strsql) |
|||
if data: |
|||
reStatus = 0 |
|||
reMsg = '用户名重复,请重新输入!' |
|||
else: |
|||
strsql = (f"INSERT INTO user (username ,password ,status,people,tellnum ) VALUES " |
|||
f"('{username}','{myCongif.get_data("pw")}',1,'{people}','{tellnum}');") |
|||
ret = mDBM.do_sql(strsql) |
|||
if ret == True: |
|||
reStatus = 1 |
|||
reMsg = '添加用户成功' |
|||
else: |
|||
reStatus = 0 |
|||
reMsg = '添加用户异常,请联系管理员处理!' |
|||
return jsonify({'status':reStatus,'msg':reMsg}) |
|||
|
|||
@api.route('/user/passwd',methods=['POST']) |
|||
@login_required |
|||
async def user_change_passwd(): #重置密码 |
|||
username = (await request.form)['username'] |
|||
strsql = f"update user set password='{myCongif.get_data("pw")}' where username='{username}';" |
|||
ret = mDBM.do_sql(strsql) |
|||
if ret == True: |
|||
reStatus = 1 |
|||
reMsg = '重置密码成功' |
|||
else: |
|||
reStatus = 0 |
|||
reMsg = '重置密码失败,请联系管理员处理!' |
|||
return jsonify({'status':reStatus,'msg':reMsg}) |
|||
|
|||
@api.route('/user/changeuser',methods=['POST']) |
|||
@login_required |
|||
async def user_change_user_info(): #修改用户信息 |
|||
username = (await request.form)['username'] |
|||
people = (await request.form)['people'] |
|||
tellnum = (await request.form)['tellnum'] |
|||
strsql = f"update user set people='{people}',tellnum='{tellnum}' where username='{username}';" |
|||
ret = mDBM.do_sql(strsql) |
|||
if ret == True: |
|||
reStatus = 1 |
|||
reMsg = '修改用户信息成功' |
|||
else: |
|||
reStatus = 0 |
|||
reMsg = '修改失败,请联系管理员处理!' |
|||
return jsonify({'status': reStatus, 'msg': reMsg}) |
|||
|
|||
@api.route('/user/<int:user_id>', methods=['GET']) |
|||
async def get_user(user_id): |
|||
try: |
|||
user = user_id |
|||
if user: |
|||
return jsonify(user) |
|||
else: |
|||
return jsonify({'error': 'User not found'}), 404 |
|||
except Exception as e: |
|||
return handle_error(e) |
@ -0,0 +1,312 @@ |
|||
import cv2 |
|||
import asyncio |
|||
import av |
|||
import torch |
|||
import json |
|||
import time |
|||
import os |
|||
import sys |
|||
import numpy as np |
|||
from . import api |
|||
from quart import jsonify, request,render_template, Response,websocket |
|||
from aiortc import RTCPeerConnection, RTCSessionDescription, VideoStreamTrack |
|||
from aiortc.contrib.media import MediaBlackhole, MediaPlayer, MediaRecorder |
|||
from fractions import Fraction |
|||
from shapely.geometry import Point, Polygon |
|||
import threading |
|||
import logging |
|||
|
|||
# 配置日志 |
|||
logging.basicConfig(level=logging.INFO) |
|||
logger = logging.getLogger(__name__) |
|||
rtsp_url = 'rtsp://127.0.0.1/live1' |
|||
#-------------------基于WEBRTC实现拉流 |
|||
pcs = set() #创建一个空的集合,去重复且无序 |
|||
|
|||
''' |
|||
--------------基础信息--后续封装在函数里--------------- |
|||
''' |
|||
#print(f"Current working directory (video.py): {os.getcwd()}") |
|||
# 自定义模型文件的路径 |
|||
model_path = 'model/mode_test/yolov5s.pt' # 假设模型文件名为 yolov5s.pt 并与执行文件在同一个目录 |
|||
# 本地YOLOv5仓库路径 |
|||
repo_path = 'model/base_model/yolov5' |
|||
|
|||
# 加载自定义模型 |
|||
model = torch.hub.load(repo_path, 'custom', path=model_path, source='local') |
|||
# 定义监控区域(多边形顶点) |
|||
region_points = [(100, 100), (500, 100), (500, 400), (100, 400)] |
|||
polygon = Polygon(region_points) |
|||
|
|||
# 打开摄像头 |
|||
def get_camera(): |
|||
cap = cv2.VideoCapture(0) |
|||
if not cap.isOpened(): |
|||
raise IOError("Cannot open webcam") |
|||
return cap |
|||
|
|||
# cap = get_camera() |
|||
''' |
|||
---------------------传输-------------------------- |
|||
''' |
|||
class VideoTransformTrack(VideoStreamTrack): |
|||
kind = "video" |
|||
def __init__(self,strUrl,itype=1): #0-usb,1-RTSP,2-海康SDK |
|||
super().__init__() |
|||
self.cap = cv2.VideoCapture(strUrl) |
|||
if not self.cap.isOpened(): |
|||
raise Exception("Unable to open video source") |
|||
# Set desired resolution and frame rate |
|||
# self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) # Set width to 1280 |
|||
# self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) # Set height to 720 |
|||
# self.cap.set(cv2.CAP_PROP_FPS, 30) # Set frame rate to 30 FPS |
|||
self.frame_rate = int(self.cap.get(cv2.CAP_PROP_FPS)) or 15 # Default to 30 if unable to get FPS |
|||
self.time_base = Fraction(1, self.frame_rate) |
|||
|
|||
async def recv(self): |
|||
start_time = time.time() # 开始时间 |
|||
ret, frame = self.cap.read() |
|||
if not ret: |
|||
raise Exception("Unable to read frame") |
|||
# # Convert frame to RGB |
|||
# frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) |
|||
# |
|||
# # Convert to VideoFrame |
|||
# video_frame = av.VideoFrame.from_ndarray(frame_rgb, format="rgb24") |
|||
# # Set timestamp |
|||
# video_frame.pts = int(self.cap.get(cv2.CAP_PROP_POS_FRAMES)) |
|||
# video_frame.time_base = self.time_base |
|||
|
|||
img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) |
|||
#img = frame.to_ndarray(format="bgr24") |
|||
# 使用 Yolov5 进行目标检测 |
|||
results = model(img) |
|||
detections = results.pandas().xyxy[0].to_dict(orient="records") |
|||
# 绘制检测结果 |
|||
for det in detections: |
|||
if det['name'] == 'person': # 只检测人员 |
|||
x1, y1, x2, y2 = int(det['xmin']), int(det['ymin']), int(det['xmax']), int(det['ymax']) |
|||
cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) |
|||
cv2.putText(img, f"{det['name']} {det['confidence']:.2f}", (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, |
|||
(0, 255, 0), 2) |
|||
# 将检测结果图像转换为帧 |
|||
new_frame = av.VideoFrame.from_ndarray(img, format="rgb24") |
|||
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") |
|||
return new_frame |
|||
|
|||
@api.route('/offer', methods=['POST']) |
|||
async def offer(): |
|||
#接收客户端的连接请求 |
|||
params = await request.json |
|||
url_type = params["url_type"] |
|||
url = params["url"] |
|||
#提取客户端发来的SDP,生成服务器端的SDP |
|||
offer = RTCSessionDescription(sdp=params["sdp"], type=params["type"]) |
|||
# # 配置 STUN 服务器 |
|||
# ice_servers = [{"urls": ["stun:stun.voipbuster.com"]}] |
|||
# pc = RTCPeerConnection(configuration={"iceServers": ice_servers}) |
|||
pc = RTCPeerConnection() #实例化一个rtcp对象 |
|||
pcs.add(pc) #集合中添加一个对象,若已存在则不添加 |
|||
|
|||
@pc.on("datachannel") |
|||
def on_datachannel(channel): |
|||
@channel.on("message") |
|||
def on_message(message): |
|||
print(f'Message received: {message}') |
|||
# 处理接收到的数据 |
|||
if message == 'pause': |
|||
# 执行暂停操作 |
|||
pass |
|||
elif message == 'resume': |
|||
# 执行继续操作 |
|||
pass |
|||
#监听RTC连接状态 |
|||
@pc.on("iconnectionstatechange") #当ice连接状态发生变化时 |
|||
async def iconnectionstatechange(): |
|||
if pc.iceConnectionState == "failed": |
|||
await pc.close() |
|||
pcs.discard(pc) #移除对象 |
|||
|
|||
# 添加视频轨道 |
|||
video_track = VideoTransformTrack(url,url_type) |
|||
pc.addTrack(video_track) |
|||
|
|||
# @pc.on('track') --- stream.getTracks().forEach(track => pc.addTrack(track, stream)); 猜测是这里触发的添加了摄像头通道 |
|||
# def on_track(track): |
|||
# if track.kind == 'video': |
|||
# local_video = VideoTransformTrack(track) |
|||
# pc.addTrack(local_video) |
|||
|
|||
# 记录客户端 SDP |
|||
await pc.setRemoteDescription(offer) |
|||
# 生成本地 SDP |
|||
answer = await pc.createAnswer() |
|||
# 记录本地 SDP |
|||
await pc.setLocalDescription(answer) |
|||
|
|||
print("返回sdp") |
|||
return jsonify({ |
|||
"sdp": pc.localDescription.sdp, |
|||
"type": pc.localDescription.type |
|||
}) |
|||
|
|||
@api.route('/shutdown', methods=['POST']) |
|||
async def shutdown(): |
|||
coros = [pc.close() for pc in pcs] |
|||
await asyncio.gather(*coros) |
|||
pcs.clear() |
|||
return 'Server shutting down...' |
|||
|
|||
''' |
|||
----------------------实现方式二,网页显示分析画面--基础版 |
|||
''' |
|||
|
|||
def is_point_in_polygon(point, polygon): |
|||
return polygon.contains(Point(point)) |
|||
|
|||
frame_buffer = [] |
|||
def camera_thread(): |
|||
global cap, frame_buffer |
|||
while True: |
|||
try: |
|||
ret, frame = cap.read() |
|||
if not ret: |
|||
logger.warning("Camera disconnected. Reconnecting...") |
|||
cap.release() |
|||
cap = get_camera() |
|||
continue |
|||
frame = cv2.resize(frame, (640, 480)) # 降低视频分辨率 |
|||
frame_buffer.append(frame) |
|||
if len(frame_buffer) > 10: # 保持缓冲区大小不超过10帧 |
|||
frame_buffer.pop(0) |
|||
logger.debug("drop one frame!") |
|||
except Exception as e: |
|||
logger.error(f"Error in camera thread: {e}") |
|||
continue |
|||
|
|||
#threading.Thread(target=camera_thread, daemon=True).start() |
|||
|
|||
async def generate_frames(): |
|||
global frame_buffer |
|||
while True: |
|||
frame = None |
|||
buffer = None |
|||
try: |
|||
if frame_buffer: |
|||
frame = frame_buffer.pop(0) |
|||
|
|||
# 进行推理 |
|||
results = model(frame) |
|||
detections = results.pandas().xyxy[0] |
|||
|
|||
# 绘制监控区域 |
|||
cv2.polylines(frame, [np.array(region_points, np.int32)], isClosed=True, color=(0, 255, 0), thickness=2) |
|||
|
|||
for _, row in detections.iterrows(): |
|||
if row['name'] == 'person': |
|||
# 获取人员检测框的中心点 |
|||
x_center = (row['xmin'] + row['xmax']) / 2 |
|||
y_center = (row['ymin'] + row['ymax']) / 2 |
|||
|
|||
if is_point_in_polygon((x_center, y_center), polygon): |
|||
# 触发报警 |
|||
cv2.putText(frame, 'Alert: Intrusion Detected', (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, |
|||
(0, 0, 255), 2) |
|||
#logger.info('Alert: Intrusion Detected') |
|||
|
|||
# 绘制检测框 |
|||
cv2.rectangle(frame, (int(row['xmin']), int(row['ymin'])), (int(row['xmax']), int(row['ymax'])), |
|||
(255, 0, 0), 2) |
|||
cv2.circle(frame, (int(x_center), int(y_center)), 5, (0, 0, 255), -1) |
|||
|
|||
ret, buffer = cv2.imencode('.jpg', frame) |
|||
if not ret: |
|||
continue |
|||
frame_bytes = buffer.tobytes() |
|||
yield (b'--frame\r\n' |
|||
b'Content-Type: image/jpeg\r\n\r\n' + frame_bytes + b'\r\n') |
|||
await asyncio.sleep(0.2) |
|||
except Exception as e: |
|||
logger.error(f"Error in generate_frames: {e}") |
|||
finally: |
|||
if frame is not None: |
|||
del frame |
|||
if buffer is not None: |
|||
del buffer |
|||
|
|||
@api.route('/video_feed') |
|||
async def video_feed(): |
|||
#return jsonify(1) |
|||
return Response(generate_frames(), mimetype='multipart/x-mixed-replace; boundary=frame') |
|||
|
|||
''' |
|||
----------------------实现方式三,网页显示分析画面--WEBRTC |
|||
''' |
|||
|
|||
|
|||
class VideoTransformTrack_new(VideoStreamTrack): |
|||
def __init__(self, track): |
|||
super().__init__() |
|||
self.track = track |
|||
|
|||
async def recv(self): |
|||
frame = await self.track.recv() |
|||
img = frame.to_ndarray(format="bgr24") |
|||
|
|||
# 使用 Yolov5 进行目标检测 |
|||
results = model(img) |
|||
detections = results.pandas().xyxy[0].to_dict(orient="records") |
|||
|
|||
# 绘制检测结果 |
|||
for det in detections: |
|||
if det['name'] == 'person': # 只检测人员 |
|||
x1, y1, x2, y2 = int(det['xmin']), int(det['ymin']), int(det['xmax']), int(det['ymax']) |
|||
cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) |
|||
cv2.putText(img, f"{det['name']} {det['confidence']:.2f}", (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, |
|||
(0, 255, 0), 2) |
|||
|
|||
# 将检测结果图像转换为帧 |
|||
new_frame = frame.from_ndarray(img, format="bgr24") |
|||
return new_frame |
|||
|
|||
pcs = set() |
|||
|
|||
|
|||
@api.websocket('/ws') |
|||
async def ws(): |
|||
params = await websocket.receive_json() |
|||
offer = RTCSessionDescription(sdp=params['sdp'], type=params['type']) |
|||
|
|||
pc = RTCPeerConnection() |
|||
pcs.add(pc) |
|||
|
|||
@pc.on('iceconnectionstatechange') |
|||
async def on_iceconnectionstatechange(): |
|||
if pc.iceConnectionState == 'failed': |
|||
await pc.close() |
|||
pcs.discard(pc) |
|||
|
|||
@pc.on('track') |
|||
def on_track(track): |
|||
if track.kind == 'video': |
|||
local_video = VideoTransformTrack(track) |
|||
pc.addTrack(local_video) |
|||
|
|||
await pc.setRemoteDescription(offer) |
|||
answer = await pc.createAnswer() |
|||
await pc.setLocalDescription(answer) |
|||
|
|||
await websocket.send(json.dumps({ |
|||
'sdp': pc.localDescription.sdp, |
|||
'type': pc.localDescription.type |
|||
})) |
|||
|
|||
try: |
|||
while True: |
|||
await asyncio.sleep(1) |
|||
finally: |
|||
await pc.close() |
|||
pcs.discard(pc) |
@ -0,0 +1,4 @@ |
|||
from quart import jsonify |
|||
|
|||
def handle_error(e): |
|||
return jsonify({'error':str(e)}),500 |
@ -0,0 +1,10 @@ |
|||
from app import db |
|||
import datetime |
|||
|
|||
class Captcha(db.Model): |
|||
id = db.Column(db.Integer, primary_key=True) |
|||
captcha_text = db.Column(db.String(10), nullable=False) |
|||
timestamp = db.Column(db.DateTime, default=datetime.datetime.utcnow) |
|||
|
|||
def __init__(self, captcha_text): |
|||
self.captcha_text = captcha_text |
@ -0,0 +1,44 @@ |
|||
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 |
|||
|
|||
def generate_captcha(): |
|||
characters = string.ascii_uppercase + string.digits |
|||
captcha_text = ''.join(random.choices(characters, k=6)) |
|||
|
|||
font = ImageFont.truetype("arial.ttf", 36) |
|||
image = Image.new('RGB', (200, 60), color=(255, 255, 255)) |
|||
draw = ImageDraw.Draw(image) |
|||
|
|||
for i in range(6): |
|||
draw.text((10 + i * 30, 10), captcha_text[i], font=font, |
|||
fill=(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))) |
|||
# 模糊化处理 |
|||
image = image.filter(ImageFilter.BLUR) |
|||
|
|||
# 将图片保存到BytesIO流中 |
|||
buffer = io.BytesIO() |
|||
image.save(buffer, 'jpeg') |
|||
buffer.seek(0) |
|||
|
|||
# captcha_path = os.path.join('static', 'captcha', f'{captcha_text}.png') |
|||
# image.save(captcha_path) |
|||
# return captcha_text, f'static/captcha/{captcha_text}.png' |
|||
return captcha_text, buffer |
|||
|
|||
|
|||
def verify_captcha(user_input, actual_captcha): |
|||
return user_input == actual_captcha |
|||
|
|||
|
|||
def login_required(f): |
|||
@wraps(f) |
|||
async def decorated_function(*args, **kwargs): |
|||
if 'user' not in session: |
|||
return redirect(url_for('main.index',error='未登录,请重新登录')) |
|||
return await f(*args, **kwargs) |
|||
return decorated_function |
@ -1,5 +1,5 @@ |
|||
from quart import Blueprint |
|||
|
|||
main = Blueprint('main', __name__) |
|||
main = Blueprint('main', __name__,template_folder='templates') |
|||
|
|||
from . import routes |
|||
|
@ -1,10 +1,92 @@ |
|||
from quart import render_template |
|||
from . import main |
|||
import os |
|||
from web.main import main |
|||
from quart import render_template, send_from_directory,request |
|||
from quart import session, redirect, url_for,flash |
|||
from functools import wraps |
|||
from core.Upload_file import allowed_file |
|||
from myutils.ConfigManager import myCongif |
|||
from werkzeug.utils import secure_filename |
|||
|
|||
''' |
|||
页面路由 |
|||
''' |
|||
|
|||
def login_required(f): |
|||
@wraps(f) |
|||
async def decorated_function(*args, **kwargs): |
|||
if 'user' not in session: |
|||
return redirect(url_for('main.index',error='未登录,请重新登录')) |
|||
return await f(*args, **kwargs) |
|||
return decorated_function |
|||
|
|||
@main.route('/') |
|||
async def index(): |
|||
return await render_template('index.html') |
|||
# error = request.args.get('error') |
|||
return await render_template('实时预览.html') |
|||
# return await render_template('登录.html',error=error) |
|||
#return await render_template('index_webrtc.html') |
|||
|
|||
# @main.route('/', methods=['GET', 'POST']) |
|||
# async def upload_file(): |
|||
# if request.method == 'POST': |
|||
# form = await request.form |
|||
# files = await request.files |
|||
# if 'file' not in files: |
|||
# flash('No file part') |
|||
# return redirect(request.url) |
|||
# file = files['file'] |
|||
# if file.filename == '': |
|||
# flash('No selected file') |
|||
# return redirect(request.url) |
|||
# if file and allowed_file(file.filename): |
|||
# print("上传文件") |
|||
# filename = secure_filename(file.filename) |
|||
# file_path = os.path.join(myCongif.get_data('UPLOAD_FOLDER'), filename) |
|||
# await file.save(file_path) |
|||
# flash('File successfully uploaded') |
|||
# return redirect(url_for('main.upload_file')) |
|||
# else: |
|||
# flash('Allowed file types are zip') |
|||
# return redirect(request.url) |
|||
# return await render_template('upload.html') |
|||
|
|||
@main.route('/favicon.ico') |
|||
async def favicon(): |
|||
return await send_from_directory('web/main/static', 'favicon.ico') |
|||
|
|||
@main.route('/<html>') |
|||
@login_required |
|||
async def get_html(html): |
|||
return await render_template(html) |
|||
|
|||
|
|||
''' |
|||
各种配置文件路由 |
|||
''' |
|||
@main.route('/data/<file>') |
|||
async def data(file): |
|||
return await send_from_directory('web/main/static/data', file) |
|||
|
|||
@main.route('/files/<path:subdir>/<file>') |
|||
async def files(subdir,file): |
|||
return await send_from_directory(os.path.join('web/main/static/files', subdir), file) |
|||
|
|||
@main.route('/images/<path:subdir>/<file>') |
|||
async def images(subdir,file): |
|||
return await send_from_directory(os.path.join('web/main/static/images', subdir), file) |
|||
|
|||
@main.route('/resources/<file>') |
|||
async def resources(file): |
|||
return await send_from_directory('web/main/static/resources', file) |
|||
|
|||
@main.route('/resources/<path:subdir>/<file>') |
|||
async def resources_dir(subdir,file): |
|||
return await send_from_directory(os.path.join('web/main/static/resources', subdir), file) |
|||
|
|||
@main.route('/resources/css/<path:subdir>/<file>') |
|||
async def resources_css_dir(subdir,file): |
|||
return await send_from_directory(os.path.join('web/main/static/resources/css', subdir), file) |
|||
|
|||
@main.route('/about') |
|||
async def about(): |
|||
return await render_template('about.html') |
|||
@main.route('/resources/scripts/<path:subdir>/<file>') |
|||
async def resources_scripts_dir(subdir,file): |
|||
return await send_from_directory(os.path.join('web/main/static/resources/scripts', subdir), file) |
|||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 429 B After Width: | Height: | Size: 429 B |
Before Width: | Height: | Size: 278 B After Width: | Height: | Size: 278 B |
Before Width: | Height: | Size: 283 B After Width: | Height: | Size: 283 B |
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 113 KiB |
Before Width: | Height: | Size: 175 B After Width: | Height: | Size: 175 B |
Before Width: | Height: | Size: 176 B After Width: | Height: | Size: 176 B |
Before Width: | Height: | Size: 152 B After Width: | Height: | Size: 152 B |
Before Width: | Height: | Size: 257 B After Width: | Height: | Size: 257 B |
Before Width: | Height: | Size: 220 B After Width: | Height: | Size: 220 B |
Before Width: | Height: | Size: 214 B After Width: | Height: | Size: 214 B |
Before Width: | Height: | Size: 152 B After Width: | Height: | Size: 152 B |
Before Width: | Height: | Size: 257 B After Width: | Height: | Size: 257 B |
Before Width: | Height: | Size: 220 B After Width: | Height: | Size: 220 B |
Before Width: | Height: | Size: 214 B After Width: | Height: | Size: 214 B |
Before Width: | Height: | Size: 156 B After Width: | Height: | Size: 156 B |
Before Width: | Height: | Size: 260 B After Width: | Height: | Size: 260 B |
Before Width: | Height: | Size: 222 B After Width: | Height: | Size: 222 B |
Before Width: | Height: | Size: 217 B After Width: | Height: | Size: 217 B |
Before Width: | Height: | Size: 156 B After Width: | Height: | Size: 156 B |
Before Width: | Height: | Size: 260 B After Width: | Height: | Size: 260 B |
Before Width: | Height: | Size: 222 B After Width: | Height: | Size: 222 B |
Before Width: | Height: | Size: 217 B After Width: | Height: | Size: 217 B |
Before Width: | Height: | Size: 232 KiB After Width: | Height: | Size: 232 KiB |
Before Width: | Height: | Size: 153 B After Width: | Height: | Size: 153 B |
Before Width: | Height: | Size: 231 B After Width: | Height: | Size: 231 B |
Before Width: | Height: | Size: 240 B After Width: | Height: | Size: 240 B |
Before Width: | Height: | Size: 214 B After Width: | Height: | Size: 214 B |
Before Width: | Height: | Size: 214 B After Width: | Height: | Size: 214 B |
Before Width: | Height: | Size: 157 B After Width: | Height: | Size: 157 B |
Before Width: | Height: | Size: 236 B After Width: | Height: | Size: 236 B |
Before Width: | Height: | Size: 245 B After Width: | Height: | Size: 245 B |
Before Width: | Height: | Size: 217 B After Width: | Height: | Size: 217 B |
Before Width: | Height: | Size: 217 B After Width: | Height: | Size: 217 B |