@ -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 #是否记录日志 |
file_log_level: INFO #是否记录日志 |
||||
show_log_level: DEBUG #日志记录级别 |
show_log_level: DEBUG #日志记录级别 |
||||
log_dir: logs |
log_dir: logs |
||||
#远程数据库 |
|
||||
|
#远程数据库 --- 香橙派用不了 |
||||
|
DBType: 1 #0--mysql,1--sqlite |
||||
mysql: |
mysql: |
||||
host: 192.168.2.54 |
host: 192.168.3.45 |
||||
port: 3306 |
port: 3306 |
||||
user: root |
user: root |
||||
pass: crnn@cz*** |
pass: ZFkj_123456 |
||||
db: sfind |
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 threading |
||||
import socket |
|
||||
import socketserver |
import socketserver |
||||
from DataStruct import * |
from DataStruct import * |
||||
from util.MyLogger_logging import MyLogger |
from myutils.MyLogger_logging import MyLogger |
||||
from DBManager import DBManager |
from core.DBManager import DBManager |
||||
import time |
import time |
||||
|
|
||||
m_BRun = False #线程运行标识 |
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): |
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): |
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 |
pass |
||||
|
|
||||
|
def send_warn(self): |
||||
|
'''发送报警信息''' |
||||
|
pass |
||||
|
|
||||
|
@abstractmethod |
||||
|
def verify(self,image,data): |
||||
''' |
''' |
||||
模型处理的标准接口 |
:param image: 需要验证的图片 |
||||
1.输入标准化 |
:param data: select t1.model_id,t1.check_area,t1.polygon ,t2.duration_time,t2.proportion,t2.model_path |
||||
2.输出标准化 |
:return: detections,bwarn,warntext |
||||
''' |
''' |
||||
@abstractmethod |
|
||||
def doWork(self,image,mode,class_name,cfg,isdraw_box=False,bgr2rgb=True): |
|
||||
pass |
pass |
@ -1,5 +1,42 @@ |
|||||
from model.plugins.ModelBase import ModelBase |
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): |
class Model(ModelBase): |
||||
def doWork(self,image,mode,class_name,cfg,isdraw_box=False,bgr2rgb=True): |
def __init__(self,path): |
||||
print("RYRQ_Model") |
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 |
from loguru import logger |
||||
import os |
import os |
||||
from functools import wraps |
from functools import wraps |
||||
from util.ConfigManager import myCongif |
from myutils.ConfigManager import myCongif |
||||
|
|
||||
''' |
''' |
||||
@logger.catch() 异常捕获注解 |
@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 |
from quart import Blueprint |
||||
|
|
||||
main = Blueprint('main', __name__) |
main = Blueprint('main', __name__,template_folder='templates') |
||||
|
|
||||
from . import routes |
from . import routes |
||||
|
@ -1,10 +1,92 @@ |
|||||
from quart import render_template |
import os |
||||
from . import main |
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('/') |
@main.route('/') |
||||
async def index(): |
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') |
@main.route('/resources/scripts/<path:subdir>/<file>') |
||||
async def about(): |
async def resources_scripts_dir(subdir,file): |
||||
return await render_template('about.html') |
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 |