video_monitor/app/models.py
2026-09-04 18:16:14 +08:00

429 lines
20 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from django.db import models
from app.utils.Database import g_dbLock
import json
class ThreadSafetyManager(models.Manager):
def get_queryset(self):
with g_dbLock:
ret = super(ThreadSafetyManager, self).get_queryset()
return ret
class StreamModel(models.Model):
"""视频流模型(摄像头管理)"""
objects = ThreadSafetyManager()
user_id = models.IntegerField(verbose_name='用户')
sort = models.IntegerField(verbose_name='排序')
code = models.CharField(max_length=50, verbose_name='编号')
app = models.CharField(max_length=50, verbose_name='流分组')
name = models.CharField(max_length=50, verbose_name='流名称')
pull_stream_url = models.CharField(max_length=300, verbose_name='视频流源地址')
pull_stream_type = models.IntegerField(verbose_name='视频流来源类型') # 0:未知,1:RTSP,2:RTMP,3:FLV,4:HLS,21:GB28181,31:被动RTSP,32:被动RTMP
pull_stream_transfer_mode = models.IntegerField(verbose_name='视频流传输模式') # 0:UDP,1:TCP被动,2:TCP主动
pull_stream_ip = models.CharField(max_length=50, verbose_name='拉流IP')
pull_stream_port = models.IntegerField(verbose_name='拉流端口')
pull_stream_username = models.CharField(max_length=512, verbose_name='拉流用户名')
pull_stream_password = models.CharField(max_length=512, verbose_name='拉流密码')
nickname = models.CharField(max_length=200, verbose_name='视频流昵称')
remark = models.CharField(max_length=200, verbose_name='备注')
forward_state = models.IntegerField(verbose_name='转发状态') # 0:未转发 1:转发中
is_audio = models.IntegerField(default=0, verbose_name='音频传输类型') # 0:静音 1:原始声音
snap_filepath = models.CharField(max_length=200, verbose_name='快照文件路径')
snap_time = models.DateTimeField(auto_now_add=True, verbose_name='快照时间')
camera_sum_num = models.IntegerField(default=0, verbose_name='通道总数')
camera_name = models.CharField(max_length=100, verbose_name='摄像头名称')
camera_manufacturer = models.CharField(max_length=100, verbose_name='摄像头厂商')
camera_owner = models.CharField(max_length=50, verbose_name='摄像头所属者')
camera_model = models.CharField(max_length=50, verbose_name='摄像头型号')
camera_device_id = models.CharField(max_length=50, verbose_name='GB28181设备ID') # gb28181注册的client_id
camera_parent_id = models.CharField(max_length=50, verbose_name='GB28181父设备ID')
camera_civilcode = models.CharField(max_length=50, verbose_name='行政区划码')
camera_last_keepalive_time = models.DateTimeField(auto_now_add=True, verbose_name='最近一次心跳时间')
camera_last_register_time = models.DateTimeField(auto_now_add=True, verbose_name='最近一次注册时间')
# 向上级联国标编号字段v1.0新增start
cascade_device_id = models.CharField(max_length=50, default='', verbose_name='向上级联国标编号') # 自定义向上级联的国标编号为空则使用camera_device_id
cascade_enable = models.IntegerField(default=0, verbose_name='是否启用向上级联') # 0:不启用 1:启用
# 向上级联国标编号字段 end
# 视频分析字段v1.0新增start
algorithm = models.ForeignKey('AlgorithmModel', on_delete=models.SET_NULL, null=True, blank=True,
related_name='streams', verbose_name='分析算法') # null=走默认算法
record_enable = models.IntegerField(default=0, verbose_name='启用24/7录像') # 0:否 1:是
# 视频分析字段 end
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
last_update_time = models.DateTimeField(auto_now_add=True, verbose_name='更新时间')
add_type = models.IntegerField(default=0, verbose_name='添加类型') # 0:手动添加 1:批量导入 10:接口添加 21:GB28181自动添加
state = models.IntegerField(default=0, verbose_name='状态')
def __repr__(self):
return self.nickname
def __str__(self):
return self.nickname
def delete(self, using=None, keep_parents=False):
with g_dbLock:
ret = super(StreamModel, self).delete(using, keep_parents)
return ret
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
with g_dbLock:
ret = super(StreamModel, self).save(force_insert, force_update, using, update_fields)
return ret
class Meta:
db_table = 'av_stream'
verbose_name = '视频流'
verbose_name_plural = '视频流'
class AlgorithmModel(models.Model):
"""算法模型 — 检测算法的元数据与运行时参数(每路摄像头可独立选择)"""
objects = ThreadSafetyManager()
ENGINE_YOLO_PYTORCH = 'yolo_pytorch'
ENGINE_ONNXRUNTIME = 'onnxruntime'
ENGINE_OPENVINO = 'openvino'
ENGINE_CHOICES = (
(ENGINE_YOLO_PYTORCH, 'Yolo-PyTorch'),
(ENGINE_ONNXRUNTIME, 'OnnxRuntime'),
(ENGINE_OPENVINO, 'OpenVINO'),
)
# 算法类型YOLO 检测系列 + ReID 特征系列
ALGO_TYPE_YOLO5 = 'yolo5'
ALGO_TYPE_YOLO8 = 'yolo8'
ALGO_TYPE_YOLO11 = 'yolo11'
ALGO_TYPE_YOLO26 = 'yolo26'
ALGO_TYPE_OSNET = 'osnet'
ALGO_TYPE_CHOICES = (
(ALGO_TYPE_YOLO5, 'YOLOv5'),
(ALGO_TYPE_YOLO8, 'YOLOv8'),
(ALGO_TYPE_YOLO11, 'YOLOv11'),
(ALGO_TYPE_YOLO26, 'YOLO26'),
(ALGO_TYPE_OSNET, 'OSNet ReID'),
)
# 任务类型
TASK_DETECT = 'detect'
TASK_SEGMENT = 'segment'
TASK_CLASSIFY = 'classify'
TASK_POSE = 'pose'
TASK_OBB = 'obb'
TASK_REID = 'reid'
TASK_CHOICES = (
(TASK_DETECT, 'Detect'),
(TASK_SEGMENT, 'Segment'),
(TASK_CLASSIFY, 'Classify'),
(TASK_POSE, 'Pose'),
(TASK_OBB, 'OBB'),
(TASK_REID, 'ReID'),
)
# 推理设备
DEVICE_CPU = 'cpu'
DEVICE_CUDA = 'cuda'
DEVICE_GPU = 'gpu'
DEVICE_CHOICES = (
(DEVICE_CPU, 'CPU'),
(DEVICE_CUDA, 'CUDA'),
(DEVICE_GPU, 'GPU'),
)
name = models.CharField(max_length=100, verbose_name='算法名称')
algorithm_type = models.CharField(max_length=30, default='yolo8', choices=ALGO_TYPE_CHOICES, verbose_name='算法类型')
task_type = models.CharField(max_length=20, default=TASK_DETECT, choices=TASK_CHOICES, verbose_name='任务类型')
inference_engine = models.CharField(max_length=20, default=ENGINE_YOLO_PYTORCH, choices=ENGINE_CHOICES, verbose_name='推理引擎')
device = models.CharField(max_length=20, default=DEVICE_CPU, choices=DEVICE_CHOICES, verbose_name='推理设备')
model_file = models.CharField(max_length=300, default='', verbose_name='模型文件相对路径') # 相对 uploadDir/weight/
model_file_size = models.IntegerField(default=0, verbose_name='模型文件大小(字节)')
input_width = models.IntegerField(default=640, verbose_name='输入宽度')
input_height = models.IntegerField(default=640, verbose_name='输入高度')
conf_threshold = models.FloatField(default=0.4, verbose_name='置信度阈值')
iou_threshold = models.FloatField(default=0.5, verbose_name='NMS IoU 阈值')
labels = models.TextField(default='[]', verbose_name='支持类别JSON数组') # ["person","car",...]
is_default = models.IntegerField(default=0, verbose_name='是否默认算法') # 1=全局兜底
state = models.IntegerField(default=1, verbose_name='状态') # 0=禁用 1=启用
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
last_update_time = models.DateTimeField(auto_now_add=True, verbose_name='更新时间')
def __repr__(self):
return self.name
def __str__(self):
return self.name
def delete(self, using=None, keep_parents=False):
with g_dbLock:
ret = super(AlgorithmModel, self).delete(using, keep_parents)
return ret
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
with g_dbLock:
ret = super(AlgorithmModel, self).save(force_insert, force_update, using, update_fields)
return ret
class Meta:
db_table = 'av_algorithm'
verbose_name = '小模型'
verbose_name_plural = '小模型'
class BizAlgorithmModel(models.Model):
"""业务算法 — 小模型/大模型推理 + 后处理业务逻辑(布控绑定此表)"""
objects = ThreadSafetyManager()
FLOW_SMALL = 1
FLOW_LLM = 2
FLOW_BOTH = 3
FLOW_DETECT_REID = 4
FLOW_CHOICES = (
(FLOW_SMALL, '小模型+后处理'),
(FLOW_LLM, '大模型+后处理'),
(FLOW_BOTH, '小模型+大模型+后处理'),
(FLOW_DETECT_REID, '检测+ReID+后处理'),
)
POST_AREA = 'AREA' # 区域入侵:目标中心在多边形内
POST_LINE_CROSS = 'LINE_CROSS' # 越线检测:轨迹跨过有向线段
POST_LINE_COUNT = 'LINE_COUNT' # 越线计数:正向/逆向分别累计,超阈值报警
POST_DIRECTION = 'DIRECTION' # 方向入侵:移动方向匹配设定方向
POST_DENSITY = 'DENSITY' # 密度报警:区域内目标数 >= 阈值
POST_DWELL = 'DWELL' # 滞留报警:在区域内停留 >= 阈值秒
POST_CHOICES = (
(POST_AREA, '区域入侵'),
(POST_LINE_CROSS, '越线检测'),
(POST_LINE_COUNT, '越线计数'),
(POST_DIRECTION, '方向入侵'),
(POST_DENSITY, '密度报警'),
(POST_DWELL, '滞留报警'),
)
name = models.CharField(max_length=100, verbose_name='算法名称')
flow_type = models.IntegerField(default=FLOW_SMALL, choices=FLOW_CHOICES, verbose_name='流程类型')
small_model = models.ForeignKey(
'AlgorithmModel', on_delete=models.SET_NULL, null=True, blank=True,
related_name='biz_algorithms', verbose_name='小模型',
)
detector_model = models.ForeignKey(
'AlgorithmModel', on_delete=models.SET_NULL, null=True, blank=True,
related_name='biz_algorithms_as_detector', verbose_name='检测小模型(YOLO)',
)
target_labels = models.TextField(default='[]', verbose_name='目标类别JSON') # ["person","car"]
llm = models.ForeignKey(
'LLMModel', on_delete=models.SET_NULL, null=True, blank=True,
related_name='biz_algorithms', verbose_name='大模型',
)
llm_prompt = models.TextField(default='', verbose_name='大模型提示词')
llm_validate = models.TextField(default='', verbose_name='提示词校验值') # 逗号分隔关键词
post_process = models.CharField(max_length=30, default=POST_AREA, choices=POST_CHOICES, verbose_name='后处理逻辑')
# DIRECTION 后处理参数:参考角度(0°=右,90°=下,180°=左,270°=上) 与容差
ref_angle = models.FloatField(default=90.0, verbose_name='方向参考角度')
angle_tolerance = models.FloatField(default=45.0, verbose_name='方向容差(度)')
forward_count_threshold = models.IntegerField(default=0, verbose_name='正向计数报警阈值') # 0=不报警
reverse_count_threshold = models.IntegerField(default=0, verbose_name='逆向计数报警阈值') # 0=不报警
state = models.IntegerField(default=1, verbose_name='状态') # 0=禁用 1=启用
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
last_update_time = models.DateTimeField(auto_now_add=True, verbose_name='更新时间')
def __repr__(self):
return self.name
def __str__(self):
return self.name
def delete(self, using=None, keep_parents=False):
with g_dbLock:
ret = super(BizAlgorithmModel, self).delete(using, keep_parents)
return ret
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
with g_dbLock:
ret = super(BizAlgorithmModel, self).save(force_insert, force_update, using, update_fields)
return ret
class Meta:
db_table = 'av_biz_algorithm'
verbose_name = '业务算法'
verbose_name_plural = '业务算法'
class ZoneModel(models.Model):
"""摄像头区域(多边形)— 跨摄像头追踪/告警规则的区域定义"""
objects = ThreadSafetyManager()
stream = models.ForeignKey(StreamModel, on_delete=models.CASCADE, verbose_name='所属摄像头')
name = models.CharField(max_length=100, verbose_name='区域名称')
coordinates = models.TextField(verbose_name='多边形坐标') # JSON: [[x1,y1],[x2,y2],...]
is_required = models.IntegerField(default=1, verbose_name='是否必需区域') # 1:目标必须在区域内才触发区域类后处理
loiter_threshold = models.IntegerField(default=0, verbose_name='滞留阈值(秒)') # 0=不检测滞留
detect_interval_sec = models.FloatField(default=1.0, verbose_name='检测间隔(秒)') # 每 N 秒
detect_frames = models.IntegerField(default=1, verbose_name='检测帧数') # 分析 M 帧,频率=M/N fps
alarm_repeat_sec = models.FloatField(default=30.0, verbose_name='重复报警间隔(秒)') # 0=同次停留不重复
color = models.CharField(max_length=20, default='#169F85', verbose_name='显示颜色')
# LINE_CROSS 后处理:警戒线段两端点(归一化坐标0~1)JSON: [x,y]
line_a = models.TextField(default='', verbose_name='警戒线端点A') # JSON: [x,y] 归一化
line_b = models.TextField(default='', verbose_name='警戒线端点B') # JSON: [x,y] 归一化
# DENSITY 后处理:密度报警阈值(区域内目标数)
density_threshold = models.IntegerField(default=0, verbose_name='密度阈值') # 0=不检测密度
algorithms = models.ManyToManyField('BizAlgorithmModel', blank=True, related_name='zones', verbose_name='分析算法')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
last_update_time = models.DateTimeField(auto_now_add=True, verbose_name='更新时间')
state = models.IntegerField(default=1, verbose_name='状态') # 1:启用 0:禁用
def __repr__(self):
return self.name
def __str__(self):
return self.name
def delete(self, using=None, keep_parents=False):
with g_dbLock:
ret = super(ZoneModel, self).delete(using, keep_parents)
return ret
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
with g_dbLock:
ret = super(ZoneModel, self).save(force_insert, force_update, using, update_fields)
return ret
class Meta:
db_table = 'av_zone'
verbose_name = '区域'
verbose_name_plural = '区域'
class AlarmModel(models.Model):
"""报警记录 — 布控分析触发的报警事件"""
objects = ThreadSafetyManager()
EVENT_TYPES = (
('entered_zone', '进入区域'),
('loiter', '滞留告警'),
)
stream = models.ForeignKey(StreamModel, null=True, on_delete=models.CASCADE, verbose_name='摄像头')
event_type = models.CharField(max_length=32, default='entered_zone', verbose_name='报警类型')
description = models.CharField(max_length=300, default='', verbose_name='描述')
timestamp = models.DateTimeField(verbose_name='发生时间')
metadata = models.TextField(default='{}', verbose_name='元数据JSON')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='入库时间')
def __repr__(self):
return self.event_type
def __str__(self):
return self.event_type
def delete(self, using=None, keep_parents=False):
with g_dbLock:
ret = super(AlarmModel, self).delete(using, keep_parents)
return ret
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
with g_dbLock:
ret = super(AlarmModel, self).save(force_insert, force_update, using, update_fields)
return ret
class Meta:
db_table = 'av_alarm'
verbose_name = '报警'
verbose_name_plural = '报警'
indexes = [
models.Index(fields=['-timestamp'], name='av_alarm_ts_idx'),
models.Index(fields=['stream', 'timestamp'], name='av_alarm_st_idx'),
]
class RecordingModel(models.Model):
"""24/7 录像分段索引"""
objects = ThreadSafetyManager()
stream = models.ForeignKey(StreamModel, on_delete=models.CASCADE, verbose_name='摄像头')
file_path = models.CharField(max_length=500, verbose_name='文件路径')
start_time = models.DateTimeField(verbose_name='开始时间')
end_time = models.DateTimeField(verbose_name='结束时间')
duration = models.FloatField(default=0, verbose_name='时长(秒)')
file_size = models.BigIntegerField(default=0, verbose_name='文件大小(字节)')
has_motion = models.IntegerField(default=0, verbose_name='含运动')
has_object = models.IntegerField(default=0, verbose_name='含目标')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='入库时间')
class Meta:
db_table = 'av_recording'
verbose_name = '录像分段'
verbose_name_plural = '录像分段'
indexes = [
models.Index(fields=['stream', 'start_time'], name='av_recording_st_idx'),
]
class LLMModel(models.Model):
"""大模型配置OpenAI 兼容 API"""
objects = ThreadSafetyManager()
user_id = models.IntegerField(verbose_name='用户')
sort = models.IntegerField(default=0, verbose_name='排序')
code = models.CharField(max_length=50, verbose_name='编号')
name = models.CharField(max_length=50, default='', verbose_name='名称')
model_name = models.CharField(max_length=200, verbose_name='模型名称')
api_url = models.CharField(max_length=500, verbose_name='API地址')
api_key = models.CharField(max_length=512, default='', verbose_name='API密钥')
timeout = models.IntegerField(default=30, verbose_name='超时时间(秒)')
inference_tool = models.CharField(max_length=100, default='OpenAI', verbose_name='推理工具')
remark = models.TextField(default='', verbose_name='备注')
state = models.IntegerField(default=1, verbose_name='状态') # 0=禁用 1=启用
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
last_update_time = models.DateTimeField(auto_now_add=True, verbose_name='更新时间')
def delete(self, using=None, keep_parents=False):
with g_dbLock:
ret = super(LLMModel, self).delete(using, keep_parents)
return ret
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
with g_dbLock:
ret = super(LLMModel, self).save(force_insert, force_update, using, update_fields)
return ret
class Meta:
db_table = 'av_llm'
verbose_name = '大模型'
verbose_name_plural = '大模型'
class LogModel(models.Model):
"""管理员操作日志"""
objects = ThreadSafetyManager()
user_id = models.IntegerField(verbose_name='用户ID')
log_type = models.IntegerField(verbose_name='日志类型') # 1:添加 2:编辑 3:删除 10:系统操作 100:系统重置
content = models.CharField(max_length=200, verbose_name='日志内容')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
state = models.IntegerField(verbose_name='状态') # 1:成功 0:失败
def __repr__(self):
return self.content
def __str__(self):
return self.content
def delete(self, using=None, keep_parents=False):
with g_dbLock:
ret = super(LogModel, self).delete(using, keep_parents)
return ret
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
with g_dbLock:
ret = super(LogModel, self).save(force_insert, force_update, using, update_fields)
return ret
class Meta:
db_table = 'av_log'
verbose_name = '管理员日志'
verbose_name_plural = '管理员日志'