上传文件至 main
This commit is contained in:
commit
e864f3aab2
112
main/CameraConfigure.py
Normal file
112
main/CameraConfigure.py
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import requests
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
def load_config(config_path="stattioncfg.json"):
|
||||||
|
"""
|
||||||
|
从JSON配置文件加载配置信息
|
||||||
|
:param config_path: 配置文件路径(默认当前目录config.json)
|
||||||
|
:return: 配置字典(成功时),None(失败时)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with open(config_path, "r", encoding="utf-8") as f:
|
||||||
|
config = json.load(f)
|
||||||
|
|
||||||
|
# 验证必要字段
|
||||||
|
if "station_name" not in config:
|
||||||
|
print("配置错误:缺少 station_name 字段")
|
||||||
|
return None
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"配置文件 {config_path} 未找到")
|
||||||
|
return None
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
print("配置文件解析失败,请检查JSON格式")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_camera_rtsp_config(station_name):
|
||||||
|
"""
|
||||||
|
获取指定变电站的摄像头RTSP配置信息
|
||||||
|
:param station_name: 变电站名称(字符串)
|
||||||
|
:return: 包含摄像头配置的列表(成功时),None(失败时)
|
||||||
|
"""
|
||||||
|
# 接口地址
|
||||||
|
url = "http://192.168.110.229:38091/api/services/isas/VideoElectronicFence/GetVideoCameraRTSPConfigure"
|
||||||
|
|
||||||
|
# 请求参数
|
||||||
|
params = {
|
||||||
|
"stationName": station_name
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 发送GET请求
|
||||||
|
response = requests.get(url, params=params, timeout=10)
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
print(f"请求失败,HTTP状态码:{response.status_code}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
json_data = response.json()
|
||||||
|
|
||||||
|
if not json_data.get("success", False):
|
||||||
|
print(f"接口返回错误:{json_data.get('result', {}).get('message', '未知错误')}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
result = json_data.get("result", {})
|
||||||
|
if not (result.get("flag", False) and "resultData" in result):
|
||||||
|
print("未找到有效的摄像头配置数据")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 提取并处理摄像头配置
|
||||||
|
cameras = result["resultData"]
|
||||||
|
|
||||||
|
for cam in cameras:
|
||||||
|
# 转换ROI格式
|
||||||
|
roi = cam.get('roi', '0,0,0,0')
|
||||||
|
if isinstance(roi, str):
|
||||||
|
cam['roi'] = tuple(map(int, roi.split(',')))
|
||||||
|
elif isinstance(roi, list):
|
||||||
|
cam['roi'] = tuple(map(int, roi))
|
||||||
|
else:
|
||||||
|
cam['roi'] = (0, 0, 0, 0)
|
||||||
|
|
||||||
|
# 补全RTSP参数
|
||||||
|
# rtsp_url = cam['url']
|
||||||
|
# if 'transportmode' not in rtsp_url:
|
||||||
|
# connector = '&' if '?' in rtsp_url else '?'
|
||||||
|
# cam['url'] = f"{rtsp_url}{connector}"
|
||||||
|
|
||||||
|
print(f"成功获取 {result['totalCount']} 个摄像头配置")
|
||||||
|
return cameras
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"请求发生异常:{str(e)}")
|
||||||
|
return None
|
||||||
|
except ValueError:
|
||||||
|
print("响应解析失败,无效的JSON格式")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# 更新后的使用示例
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# 从配置文件加载设置
|
||||||
|
config = load_config()
|
||||||
|
if not config:
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
station = config["station_name"]
|
||||||
|
print(f"正在查询变电站:{station}")
|
||||||
|
|
||||||
|
cameras = get_camera_rtsp_config(station)
|
||||||
|
|
||||||
|
if cameras:
|
||||||
|
print("\n摄像头列表:")
|
||||||
|
for idx, camera in enumerate(cameras, 1):
|
||||||
|
print(f"\n摄像头 {idx}:")
|
||||||
|
print(f"名称:{camera['name']}")
|
||||||
|
print(f"ID:{camera['id']}")
|
||||||
|
print(f"RTSP地址:{camera['url']}")
|
||||||
|
print(f"ROI区域:{camera['roi']}")
|
159
main/UploadAlarmMsg.py
Normal file
159
main/UploadAlarmMsg.py
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
import requests
|
||||||
|
from typing import Optional, Dict, Any
|
||||||
|
import logging
|
||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
import time
|
||||||
|
|
||||||
|
# 配置日志系统
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||||
|
handlers=[
|
||||||
|
logging.FileHandler('alarm_upload.log', encoding='utf-8'),
|
||||||
|
logging.StreamHandler()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# 审计日志独立配置(记录原始请求响应数据)
|
||||||
|
audit_logger = logging.getLogger('audit')
|
||||||
|
audit_logger.setLevel(logging.INFO)
|
||||||
|
audit_handler = logging.FileHandler('alarm_audit.log', encoding='utf-8')
|
||||||
|
audit_handler.setFormatter(logging.Formatter('%(message)s'))
|
||||||
|
audit_logger.addHandler(audit_handler)
|
||||||
|
|
||||||
|
|
||||||
|
def upload_alarm(
|
||||||
|
camera_id: str,
|
||||||
|
detection_status: Dict[str, Any],
|
||||||
|
content: str,
|
||||||
|
timeout: int = 10
|
||||||
|
) -> Optional[Dict]:
|
||||||
|
"""
|
||||||
|
增强版安全报警信息上传函数
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
camera_id: 摄像头唯一标识ID
|
||||||
|
detection_status: 检测状态字典,需包含:
|
||||||
|
{
|
||||||
|
"has_head": bool,
|
||||||
|
"has_helmet": bool,
|
||||||
|
"has_safevest": bool,
|
||||||
|
"timestamp": str # 格式:YYYY-MM-DD HH:MM:SS
|
||||||
|
}
|
||||||
|
content: 报警内容描述
|
||||||
|
timeout: 请求超时时间(秒)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
服务器响应字典(成功时),None(失败时)
|
||||||
|
"""
|
||||||
|
# 参数验证
|
||||||
|
required_keys = ['has_head', 'has_helmet', 'has_safevest', 'timestamp']
|
||||||
|
if not all(key in detection_status for key in required_keys):
|
||||||
|
logging.error(f"参数错误:缺失必要检测状态字段,需要包含:{required_keys}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
if not camera_id or not content:
|
||||||
|
logging.error("参数错误:camera_id 和 content 不能为空")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 构造请求参数
|
||||||
|
base_url = "http://192.168.110.229:38090/api/services/isas/VideoElectronicFence/UploadAlarmMsg"
|
||||||
|
params = {
|
||||||
|
"id": camera_id,
|
||||||
|
"content": f"[{detection_status['timestamp']}] {content}",
|
||||||
|
"details": json.dumps({
|
||||||
|
"camera_id": camera_id,
|
||||||
|
"detection_status": detection_status,
|
||||||
|
# "system_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
}, ensure_ascii=False)
|
||||||
|
}
|
||||||
|
|
||||||
|
# 审计日志数据
|
||||||
|
audit_data = {
|
||||||
|
"camera_id": camera_id,
|
||||||
|
# "request_time": time.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
"request_params": params,
|
||||||
|
"response_status": None,
|
||||||
|
"response_content": None,
|
||||||
|
"exception": None
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 发送GET请求
|
||||||
|
response = requests.get(
|
||||||
|
base_url,
|
||||||
|
params=params,
|
||||||
|
timeout=timeout
|
||||||
|
)
|
||||||
|
audit_data["response_status"] = response.status_code
|
||||||
|
|
||||||
|
# HTTP状态码检查
|
||||||
|
if response.status_code != 200:
|
||||||
|
logging.error(f"HTTP错误 | 摄像头: {camera_id} | 状态码: {response.status_code}")
|
||||||
|
audit_data["exception"] = f"HTTP错误:{response.status_code}"
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 解析JSON响应
|
||||||
|
try:
|
||||||
|
json_data = response.json()
|
||||||
|
audit_data["response_content"] = json_data
|
||||||
|
except ValueError:
|
||||||
|
logging.error(f"响应解析失败 | 摄像头: {camera_id}")
|
||||||
|
audit_data["exception"] = "无效的JSON响应"
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 检查业务逻辑成功状态
|
||||||
|
if json_data.get("success", False):
|
||||||
|
logging.info(f"报警上传成功 | 摄像头: {camera_id} | 内容: {content}")
|
||||||
|
return json_data
|
||||||
|
else:
|
||||||
|
error_msg = json_data.get("result", {}).get("message", "未知错误")
|
||||||
|
logging.error(f"业务逻辑错误 | 摄像头: {camera_id} | 错误: {error_msg}")
|
||||||
|
audit_data["exception"] = error_msg
|
||||||
|
return None
|
||||||
|
|
||||||
|
except requests.exceptions.Timeout:
|
||||||
|
error_msg = f"请求超时 | 摄像头: {camera_id} | 超时时间: {timeout}s"
|
||||||
|
logging.error(error_msg)
|
||||||
|
audit_data["exception"] = error_msg
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
error_msg = f"网络请求异常 | 摄像头: {camera_id} | 错误: {str(e)}"
|
||||||
|
logging.error(error_msg)
|
||||||
|
audit_data["exception"] = str(e)
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"未知错误 | 摄像头: {camera_id} | 错误: {str(e)}"
|
||||||
|
logging.error(error_msg)
|
||||||
|
audit_data["exception"] = str(e)
|
||||||
|
finally:
|
||||||
|
# 记录审计日志
|
||||||
|
audit_logger.info(json.dumps(audit_data, ensure_ascii=False))
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# 测试用例
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# 模拟检测状态
|
||||||
|
test_status = {
|
||||||
|
"has_head": True,
|
||||||
|
"has_helmet": False,
|
||||||
|
"has_safevest": True,
|
||||||
|
# "timestamp": "2023-08-20 15:30:00"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 测试正常上传
|
||||||
|
result = upload_alarm(
|
||||||
|
camera_id="1236",
|
||||||
|
detection_status=test_status,
|
||||||
|
content="安全警报:未佩戴安全帽"
|
||||||
|
)
|
||||||
|
print("上传结果:", result)
|
||||||
|
|
||||||
|
# 测试错误参数
|
||||||
|
result = upload_alarm(
|
||||||
|
camera_id="",
|
||||||
|
detection_status=test_status,
|
||||||
|
content=""
|
||||||
|
)
|
||||||
|
print("错误测试结果:", result)
|
45
main/roi_config.json
Normal file
45
main/roi_config.json
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"cameraCfgs": [
|
||||||
|
{
|
||||||
|
"box": [
|
||||||
|
87,
|
||||||
|
23,
|
||||||
|
1826,
|
||||||
|
1038
|
||||||
|
],
|
||||||
|
"channel": 3,
|
||||||
|
"types": [
|
||||||
|
"person",
|
||||||
|
"animal",
|
||||||
|
"smoke",
|
||||||
|
"helmet",
|
||||||
|
"safevest",
|
||||||
|
"cellphone",
|
||||||
|
"fire"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"box": [
|
||||||
|
34,
|
||||||
|
27,
|
||||||
|
1866,
|
||||||
|
1040
|
||||||
|
],
|
||||||
|
"channel": 9,
|
||||||
|
"types": [
|
||||||
|
"animal",
|
||||||
|
"smoke",
|
||||||
|
"helmet",
|
||||||
|
"safevest",
|
||||||
|
"cellphone",
|
||||||
|
"fire"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nvr": {
|
||||||
|
"ip": "192.168.81.33",
|
||||||
|
"password": "yunda123",
|
||||||
|
"port": 8000,
|
||||||
|
"username": "admin"
|
||||||
|
}
|
||||||
|
}
|
4
main/stattioncfg.json
Normal file
4
main/stattioncfg.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"station_name": "变电所",
|
||||||
|
"comment": "其他配置项可以继续添加..."
|
||||||
|
}
|
592
main/test_main.py
Normal file
592
main/test_main.py
Normal file
@ -0,0 +1,592 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import tkinter
|
||||||
|
from tkinter import *
|
||||||
|
from HCNetSDK import *
|
||||||
|
from PlayCtrl import *
|
||||||
|
from datetime import datetime
|
||||||
|
import json
|
||||||
|
from yolo_detector import YOLODetector
|
||||||
|
import cv2
|
||||||
|
import numpy as np
|
||||||
|
from PIL import Image, ImageTk
|
||||||
|
import redis
|
||||||
|
from PIL import ImageFont, ImageDraw, Image
|
||||||
|
import base64
|
||||||
|
from threading import Thread
|
||||||
|
from queue import Queue
|
||||||
|
import queue
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
from yolo_processor import process_frame_with_yolo
|
||||||
|
|
||||||
|
|
||||||
|
class DISPLAY_INFO(Structure):
|
||||||
|
_fields_ = [
|
||||||
|
("pBuf", POINTER(c_ubyte)),
|
||||||
|
("nSize", c_ulong),
|
||||||
|
("nWidth", c_long),
|
||||||
|
("nHeight", c_long),
|
||||||
|
("nStamp", c_ulong),
|
||||||
|
("nType", c_ulong),
|
||||||
|
("nReserved", c_ulong),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# 读取配置文件
|
||||||
|
def load_nvr_config(config_path='roi_config.json'):
|
||||||
|
with open(config_path, 'r') as f:
|
||||||
|
config = json.load(f)
|
||||||
|
return config['nvr'], config['cameraCfgs']
|
||||||
|
|
||||||
|
|
||||||
|
# 获取NVR配置和摄像头配置
|
||||||
|
nvr_config, camera_configs = load_nvr_config()
|
||||||
|
|
||||||
|
# 登录的设备信息
|
||||||
|
DEV_IP = create_string_buffer(nvr_config['ip'].encode())
|
||||||
|
DEV_PORT = nvr_config['port']
|
||||||
|
DEV_USER_NAME = create_string_buffer(nvr_config['username'].encode())
|
||||||
|
DEV_PASSWORD = create_string_buffer(nvr_config['password'].encode())
|
||||||
|
YOLO_FPS = 2 # 每秒处理的帧数
|
||||||
|
|
||||||
|
WINDOWS_FLAG = True
|
||||||
|
win = None # 预览窗口
|
||||||
|
funcRealDataCallBack_V30 = None # 实时预览回调函数,需要定义为全局的
|
||||||
|
|
||||||
|
PlayCtrl_Port = c_long(-1) # 播放句柄
|
||||||
|
Playctrldll = None # 播放库
|
||||||
|
FuncDecCB = None # 播放库解码回调函数,需要定义为全局的
|
||||||
|
max_channels = 9 # 最大支持9个通道
|
||||||
|
windows = [None] * max_channels # 预览窗口数组
|
||||||
|
canvases = [None] * max_channels # 画布数组
|
||||||
|
PlayCtrl_Ports = [c_long(-1)] * max_channels # 播放句柄数组
|
||||||
|
funcRealDataCallBack_V30_array = [None] * max_channels # 预览回调函数数组
|
||||||
|
funcDecCB_array = [None] * max_channels # 预览回调函数数组
|
||||||
|
frame_queues = [Queue(maxsize=2) for _ in range(max_channels)]
|
||||||
|
|
||||||
|
# 线程控制参数
|
||||||
|
WORKER_THREADS = 4 # 根据CPU核心数调整(建议物理核心数×2)
|
||||||
|
EXIT_SIGNAL = "EXIT"
|
||||||
|
consumers = [] # 消费者线程池
|
||||||
|
# 初始化队列
|
||||||
|
frame_queue = queue.Queue(maxsize=200) # 原始数据队列
|
||||||
|
result_queue = queue.Queue() # GUI更新队列
|
||||||
|
|
||||||
|
processing_threads = [None] * max_channels
|
||||||
|
enable_yolo = True
|
||||||
|
last_process_times = [datetime.now()] * max_channels # 每个通道上次处理的时间
|
||||||
|
redis_client = redis.Redis(host='192.168.110.229', port=36379, db=0, password="yunda123")
|
||||||
|
|
||||||
|
|
||||||
|
# 获取当前系统环境
|
||||||
|
def GetPlatform():
|
||||||
|
sysstr = platform.system()
|
||||||
|
print('' + sysstr)
|
||||||
|
if sysstr != "Windows":
|
||||||
|
global WINDOWS_FLAG
|
||||||
|
WINDOWS_FLAG = False
|
||||||
|
|
||||||
|
|
||||||
|
# 设置SDK初始化依赖库路径
|
||||||
|
def SetSDKInitCfg():
|
||||||
|
# 设置HCNetSDKCom组件库和SSL库加载路径
|
||||||
|
# print(os.getcwd())
|
||||||
|
if WINDOWS_FLAG:
|
||||||
|
strPath = os.getcwd().encode('gbk')
|
||||||
|
sdk_ComPath = NET_DVR_LOCAL_SDK_PATH()
|
||||||
|
sdk_ComPath.sPath = strPath
|
||||||
|
Objdll.NET_DVR_SetSDKInitCfg(2, byref(sdk_ComPath))
|
||||||
|
Objdll.NET_DVR_SetSDKInitCfg(3, create_string_buffer(strPath + b'\\libcrypto-1_1-x64.dll'))
|
||||||
|
Objdll.NET_DVR_SetSDKInitCfg(4, create_string_buffer(strPath + b'\\libssl-1_1-x64.dll'))
|
||||||
|
else:
|
||||||
|
strPath = os.getcwd().encode('utf-8')
|
||||||
|
sdk_ComPath = NET_DVR_LOCAL_SDK_PATH()
|
||||||
|
sdk_ComPath.sPath = strPath
|
||||||
|
Objdll.NET_DVR_SetSDKInitCfg(2, byref(sdk_ComPath))
|
||||||
|
Objdll.NET_DVR_SetSDKInitCfg(3, create_string_buffer(strPath + b'/libcrypto.so.1.1'))
|
||||||
|
Objdll.NET_DVR_SetSDKInitCfg(4, create_string_buffer(strPath + b'/libssl.so.1.1'))
|
||||||
|
|
||||||
|
|
||||||
|
def LoginDev(Objdll):
|
||||||
|
# 登录注册设备
|
||||||
|
device_info = NET_DVR_DEVICEINFO_V30()
|
||||||
|
lUserId = Objdll.NET_DVR_Login_V30(DEV_IP, DEV_PORT, DEV_USER_NAME, DEV_PASSWORD, byref(device_info))
|
||||||
|
return (lUserId, device_info)
|
||||||
|
|
||||||
|
|
||||||
|
def start_processing_thread(channel_index):
|
||||||
|
def run():
|
||||||
|
while True:
|
||||||
|
frame_data = frame_queues[channel_index].get()
|
||||||
|
print("out")
|
||||||
|
if frame_data is None: # 终止信号
|
||||||
|
break
|
||||||
|
# 在此处执行YOLO处理和Redis发布
|
||||||
|
# process_frame_with_yolo(frame_data, channel_index)
|
||||||
|
process_frame_with_yolo(frame_data, channel_index, camera_configs, yolo_detector, redis_client)
|
||||||
|
|
||||||
|
thread = Thread(target=run, daemon=True)
|
||||||
|
thread.start()
|
||||||
|
return thread
|
||||||
|
|
||||||
|
|
||||||
|
def on_closing():
|
||||||
|
for i in range(num_channels):
|
||||||
|
frame_queues[i].put(None) # 发送终止信号
|
||||||
|
processing_threads[i].join()
|
||||||
|
root.destroy()
|
||||||
|
|
||||||
|
|
||||||
|
def DecCBFun(nPort, pBuf, nSize, pFrameInfo, nUser, nReserved2):
|
||||||
|
# 解码回调函数
|
||||||
|
channel_index = nUser - 1
|
||||||
|
# print(f"回调函数被调用 - 通道: {channel_index + 1}, 数据类型: {dwDataType}, 数据大小: {dwBufSize}")
|
||||||
|
|
||||||
|
if channel_index < 0 or channel_index >= max_channels:
|
||||||
|
print(f"无效的通道索引: {channel_index}")
|
||||||
|
return
|
||||||
|
|
||||||
|
port = PlayCtrl_Ports[channel_index]
|
||||||
|
canvas = canvases[channel_index]
|
||||||
|
if pFrameInfo.contents.nType == 3:
|
||||||
|
# 获取当前时间
|
||||||
|
now = datetime.now()
|
||||||
|
# 格式化为 HH:mm:ss:fff 格式
|
||||||
|
formatted_time = now.strftime("%H:%M:%S:%f")[:-3]
|
||||||
|
|
||||||
|
sFileName = ('../../pic/test_stamp[%s].jpg' % pFrameInfo.contents.nStamp)
|
||||||
|
nWidth = pFrameInfo.contents.nWidth
|
||||||
|
nHeight = pFrameInfo.contents.nHeight
|
||||||
|
nType = pFrameInfo.contents.nType
|
||||||
|
dwFrameNum = pFrameInfo.contents.dwFrameNum
|
||||||
|
nStamp = pFrameInfo.contents.nStamp
|
||||||
|
#print(nWidth, nHeight, nType, dwFrameNum, nStamp, sFileName)
|
||||||
|
|
||||||
|
if yolo_detector is not None:
|
||||||
|
try:
|
||||||
|
if nWidth > 0 and nHeight > 0:
|
||||||
|
try:
|
||||||
|
# 提取YUV数据并转换为RGB
|
||||||
|
yuv_data = np.frombuffer(pBuf[:nSize], dtype=np.uint8)
|
||||||
|
height = pFrameInfo.contents.nHeight
|
||||||
|
width = pFrameInfo.contents.nWidth
|
||||||
|
# YUV420转RGB
|
||||||
|
yuv_frame = yuv_data.reshape((height * 3 // 2, width))
|
||||||
|
# rgb_frame = cv2.cvtColor(yuv_frame, cv2.COLOR_YUV2RGB_I420)
|
||||||
|
try:
|
||||||
|
# 仅传递原始数据到处理队列
|
||||||
|
if not frame_queue.full():
|
||||||
|
frame_queue.put((channel_index, yuv_frame), block=False)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"回调异常: {str(e)}")
|
||||||
|
# cv2.imshow("frame", frame)
|
||||||
|
# cv2.waitKey(0)
|
||||||
|
# cv2.destroyAllWindows()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"YUV转RGB失败: {str(e)}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"YOLO处理异常: {str(e)}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
|
||||||
|
def save_detections(detections, frame, output_dir="."):
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
output_image = os.path.join(output_dir, f"detection_{timestamp}.jpg")
|
||||||
|
output_json = os.path.join(output_dir, f"detection_{timestamp}.json")
|
||||||
|
# 保存可视化图片
|
||||||
|
cv2.imwrite(output_image, frame)
|
||||||
|
|
||||||
|
# 保存结构化数据
|
||||||
|
with open(output_json, 'w') as f:
|
||||||
|
json.dump(detections, f, indent=2)
|
||||||
|
# 在全局变量部分修改
|
||||||
|
# 删除原有的win1, win2, cv1, cv2等变量
|
||||||
|
# 替换为通道数组
|
||||||
|
# 修改RealDataCallBack_V30函数,支持多通道
|
||||||
|
def RealDataCallBack_V30(lPlayHandle, dwDataType, pBuffer, dwBufSize, pUser):
|
||||||
|
try:
|
||||||
|
channel_index = pUser - 1
|
||||||
|
# print(f"回调函数被调用 - 通道: {channel_index + 1}, 数据类型: {dwDataType}, 数据大小: {dwBufSize}")
|
||||||
|
|
||||||
|
if channel_index < 0 or channel_index >= max_channels:
|
||||||
|
print(f"无效的通道索引: {channel_index}")
|
||||||
|
return
|
||||||
|
|
||||||
|
port = PlayCtrl_Ports[channel_index]
|
||||||
|
canvas = canvases[channel_index]
|
||||||
|
|
||||||
|
if dwDataType == NET_DVR_SYSHEAD:
|
||||||
|
print(f"通道 {channel_index + 1} 收到系统头数据")
|
||||||
|
Playctrldll.PlayM4_SetStreamOpenMode(port, 0)
|
||||||
|
if Playctrldll.PlayM4_OpenStream(port, pBuffer, dwBufSize, 1024 * 1024):
|
||||||
|
canvas_id = canvas.winfo_id()
|
||||||
|
print(f"通道 {pUser} 尝试使用画布ID: {canvas_id}")
|
||||||
|
|
||||||
|
# 确保画布仍然有效
|
||||||
|
if not canvas.winfo_exists():
|
||||||
|
print(f"通道 {channel_index + 1} 的画布已经不存在")
|
||||||
|
return
|
||||||
|
# 设置解码回调,可以返回解码后YUV视频数据
|
||||||
|
funcDecCB_array[channel_index] = DECCBFUNWIN(DecCBFun)
|
||||||
|
Playctrldll.PlayM4_SetDecCallBackExMend(port, funcDecCB_array[channel_index], None, 0, pUser)
|
||||||
|
|
||||||
|
Playctrldll.PlayM4_Stop(port)
|
||||||
|
|
||||||
|
if Playctrldll.PlayM4_Play(port, canvas_id):
|
||||||
|
print(f'通道 {pUser} 播放库播放成功,使用画布ID: {canvas_id}')
|
||||||
|
Playctrldll.PlayM4_SetDisplayBuf(port, 15)
|
||||||
|
Playctrldll.PlayM4_SetOverlayMode(port, 0, 0)
|
||||||
|
# 禁用自动显示
|
||||||
|
Playctrldll.PlayM4_SetDisplayCallBack(port, None)
|
||||||
|
else:
|
||||||
|
err = Playctrldll.PlayM4_GetLastError(port)
|
||||||
|
print(f'通道 {pUser} 播放库播放失败,错误码: {err}')
|
||||||
|
else:
|
||||||
|
err = Playctrldll.PlayM4_GetLastError(port)
|
||||||
|
print(f'通道 {pUser} 播放库打开流失败,错误码: {err}')
|
||||||
|
|
||||||
|
elif dwDataType == NET_DVR_STREAMDATA:
|
||||||
|
try:
|
||||||
|
# 检查端口是否有效
|
||||||
|
if port.value < 0:
|
||||||
|
print(f"通道 {channel_index + 1} 的播放端口无效")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 直接输入数据,但不显示
|
||||||
|
if not Playctrldll.PlayM4_InputData(port, pBuffer, dwBufSize):
|
||||||
|
err = Playctrldll.PlayM4_GetLastError(port)
|
||||||
|
# print(f"通道 {channel_index + 1} 输入数据失败,错误码: {err}")
|
||||||
|
# return
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"通道 {channel_index + 1} 数据处理异常: {str(e)}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"回调函数发生未知异常: {str(e)}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
|
||||||
|
# 添加一个计算布局的函数
|
||||||
|
def calculate_layout(num_channels):
|
||||||
|
"""根据通道数计算最佳布局"""
|
||||||
|
if num_channels <= 1:
|
||||||
|
return 1, 1
|
||||||
|
elif num_channels <= 4:
|
||||||
|
return 2, 2
|
||||||
|
elif num_channels <= 6:
|
||||||
|
return 2, 3
|
||||||
|
else: # 7-9通道
|
||||||
|
return 3, 3
|
||||||
|
|
||||||
|
|
||||||
|
def OpenPreview(Objdll, lUserId, callbackFun, channel_index):
|
||||||
|
'''
|
||||||
|
打开预览
|
||||||
|
'''
|
||||||
|
preview_info = NET_DVR_PREVIEWINFO()
|
||||||
|
preview_info.hPlayWnd = 0
|
||||||
|
preview_info.lChannel = camera_configs[channel_index]['channel'] # 根据索引获取对应通道号
|
||||||
|
preview_info.dwStreamType = 0
|
||||||
|
preview_info.dwLinkMode = 0
|
||||||
|
preview_info.bBlocked = 1
|
||||||
|
|
||||||
|
lRealPlayHandle = Objdll.NET_DVR_RealPlay_V40(lUserId, byref(preview_info), callbackFun, channel_index + 1)
|
||||||
|
return lRealPlayHandle
|
||||||
|
|
||||||
|
|
||||||
|
def InputData(fileMp4, Playctrldll):
|
||||||
|
while True:
|
||||||
|
pFileData = fileMp4.read(4096)
|
||||||
|
if pFileData is None:
|
||||||
|
break
|
||||||
|
|
||||||
|
if not Playctrldll.PlayM4_InputData(PlayCtrl_Port, pFileData, len(pFileData)):
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------ 线程管理函数 ------------------
|
||||||
|
def start_consumers():
|
||||||
|
"""启动消费者线程池"""
|
||||||
|
global consumers
|
||||||
|
# 清理残留线程
|
||||||
|
consumers = [t for t in consumers if t.is_alive()]
|
||||||
|
|
||||||
|
# 创建新线程
|
||||||
|
for _ in range(WORKER_THREADS):
|
||||||
|
t = threading.Thread(
|
||||||
|
target=consumer_worker,
|
||||||
|
daemon=True # 主退出时自动结束
|
||||||
|
)
|
||||||
|
t.start()
|
||||||
|
consumers.append(t)
|
||||||
|
print(f"已启动 {len(consumers)} 个消费者线程")
|
||||||
|
|
||||||
|
|
||||||
|
def stop_consumers():
|
||||||
|
"""安全停止所有消费者线程"""
|
||||||
|
for _ in range(WORKER_THREADS):
|
||||||
|
frame_queue.put((EXIT_SIGNAL, None)) # 发送退出信号
|
||||||
|
|
||||||
|
# 等待线程结束
|
||||||
|
for t in consumers:
|
||||||
|
t.join(timeout=5)
|
||||||
|
if t.is_alive():
|
||||||
|
print(f"警告: 线程 {t.ident} 未正常退出")
|
||||||
|
print("所有消费者线程已停止")
|
||||||
|
|
||||||
|
|
||||||
|
# 修改消费者工作线程
|
||||||
|
def consumer_worker():
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
channel, yuv_array = frame_queue.get(timeout=5)
|
||||||
|
if channel is EXIT_SIGNAL:
|
||||||
|
break
|
||||||
|
# YUV转RGB(实际分辨率从配置读取)
|
||||||
|
config = camera_configs[channel]
|
||||||
|
rgb_frame = cv2.cvtColor(yuv_array, cv2.COLOR_YUV2RGB_I420)
|
||||||
|
# # 执行推理
|
||||||
|
# process_frame_with_yolo(rgb_frame, channel)
|
||||||
|
process_frame_with_yolo(rgb_frame, channel, camera_configs, yolo_detector, redis_client)
|
||||||
|
# 准备GUI更新数据
|
||||||
|
resized_frame = cv2.resize(rgb_frame, (320, 240))
|
||||||
|
img = cv2.cvtColor(resized_frame, cv2.COLOR_RGB2BGR)
|
||||||
|
except queue.Empty:
|
||||||
|
continue
|
||||||
|
|
||||||
|
|
||||||
|
# 增强型GUI更新函数
|
||||||
|
def update_gui():
|
||||||
|
while not result_queue.empty():
|
||||||
|
try:
|
||||||
|
channel, img_tk, status = result_queue.get_nowait()
|
||||||
|
|
||||||
|
# 安全更新GUI组件
|
||||||
|
canvases[channel].configure(image=img_tk)
|
||||||
|
canvases[channel].image = img_tk # 保持引用
|
||||||
|
canvases[channel].itemconfigure("status", text=status)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"GUI更新异常: {str(e)}")
|
||||||
|
|
||||||
|
root.after(50, update_gui)
|
||||||
|
|
||||||
|
|
||||||
|
# 在界面初始化时预加载画布ID
|
||||||
|
def create_canvas_grid():
|
||||||
|
global canvas_ids
|
||||||
|
canvas_ids = {}
|
||||||
|
|
||||||
|
for i in range(num_channels):
|
||||||
|
canvases[i].update()
|
||||||
|
canvas_ids[i] = canvases[i].winfo_id() # 预获取窗口ID
|
||||||
|
print(f"通道 {i} 画布ID: {canvas_ids[i]}")
|
||||||
|
|
||||||
|
|
||||||
|
# 在设备初始化时设置播放参数
|
||||||
|
def set_play_params(port, channel):
|
||||||
|
# 使用预存储的画布ID
|
||||||
|
Playctrldll.PlayM4_SetDisplayWindow(
|
||||||
|
port,
|
||||||
|
canvas_ids[channel], # 使用预获取的ID
|
||||||
|
0, 0,
|
||||||
|
single_width, single_height
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# 修改SDK初始化部分
|
||||||
|
def Initcustom():
|
||||||
|
# 预加载所有画布ID
|
||||||
|
create_canvas_grid()
|
||||||
|
|
||||||
|
# 启动消费者线程
|
||||||
|
start_consumers()
|
||||||
|
|
||||||
|
# 启动GUI更新循环
|
||||||
|
root.after(0, update_gui)
|
||||||
|
|
||||||
|
# 启动主循环
|
||||||
|
root.mainloop()
|
||||||
|
# 程序退出时清理
|
||||||
|
stop_consumers()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
try:
|
||||||
|
print("程序启动...")
|
||||||
|
|
||||||
|
# YOLO初始化
|
||||||
|
print("正在初始化YOLO检测器...")
|
||||||
|
try:
|
||||||
|
yolo_detector = YOLODetector() if enable_yolo else None
|
||||||
|
print("YOLO检测器初始化成功")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"YOLO检测器初始化失败: {str(e)}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
# 创建窗口
|
||||||
|
print("正在创建主窗口...")
|
||||||
|
root = tkinter.Tk()
|
||||||
|
root.title("多通道预览")
|
||||||
|
|
||||||
|
# 加载配置
|
||||||
|
print("正在加载配置文件...")
|
||||||
|
num_channels = min(len(camera_configs), max_channels)
|
||||||
|
print(f"检测到 {len(camera_configs)} 个摄像头配置,将显示 {num_channels} 个通道")
|
||||||
|
|
||||||
|
# 计算布局
|
||||||
|
rows, cols = calculate_layout(num_channels)
|
||||||
|
|
||||||
|
# 设置单个视频窗口的大小
|
||||||
|
single_width, single_height = 320, 240
|
||||||
|
|
||||||
|
# 设置主窗口大小
|
||||||
|
window_width = cols * single_width
|
||||||
|
window_height = rows * single_height
|
||||||
|
root.geometry(f"{window_width}x{window_height}+100+100")
|
||||||
|
|
||||||
|
# 创建帧来容纳所有画布
|
||||||
|
frame = tkinter.Frame(root, width=window_width, height=window_height)
|
||||||
|
frame.pack()
|
||||||
|
|
||||||
|
# 创建画布网格
|
||||||
|
for i in range(num_channels):
|
||||||
|
row = i // cols
|
||||||
|
col = i % cols
|
||||||
|
|
||||||
|
# 创建每个通道的画布,并确保它们有唯一的标识
|
||||||
|
# 使用Frame作为容器,可能有助于解决显示问题
|
||||||
|
frame_container = tkinter.Frame(frame, width=single_width, height=single_height)
|
||||||
|
frame_container.grid(row=row, column=col, padx=2, pady=2)
|
||||||
|
frame_container.grid_propagate(False) # 防止frame自动调整大小
|
||||||
|
|
||||||
|
canvases[i] = tkinter.Canvas(frame_container, bg='black',
|
||||||
|
width=single_width,
|
||||||
|
height=single_height,
|
||||||
|
name=f"canvas_{i}")
|
||||||
|
canvases[i].pack(fill=tkinter.BOTH, expand=True)
|
||||||
|
|
||||||
|
# 添加通道标签
|
||||||
|
canvases[i].create_text(10, 10, text=f"通道 {i + 1}", fill="white", anchor="nw")
|
||||||
|
|
||||||
|
# 确保画布已经更新并有有效的窗口ID
|
||||||
|
canvases[i].update()
|
||||||
|
|
||||||
|
# 获取系统平台
|
||||||
|
print("\n系统环境检测:")
|
||||||
|
GetPlatform()
|
||||||
|
print(f"当前系统平台标志: {'Windows' if WINDOWS_FLAG else 'Linux'}")
|
||||||
|
|
||||||
|
# 加载库
|
||||||
|
print("\n正在加载SDK库...")
|
||||||
|
try:
|
||||||
|
if WINDOWS_FLAG:
|
||||||
|
print("当前工作目录:", os.getcwd())
|
||||||
|
print("尝试切换到SDK目录: ./lib/win")
|
||||||
|
os.chdir(r'./lib/win')
|
||||||
|
print("加载 HCNetSDK.dll...")
|
||||||
|
Objdll = ctypes.CDLL(r'./HCNetSDK.dll')
|
||||||
|
print("加载 PlayCtrl.dll...")
|
||||||
|
Playctrldll = ctypes.CDLL(r'./PlayCtrl.dll')
|
||||||
|
else:
|
||||||
|
# Linux加载代码保持不变...
|
||||||
|
pass
|
||||||
|
print("SDK库加载成功")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"SDK库加载失败: {str(e)}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
print("\n正在初始化SDK...")
|
||||||
|
SetSDKInitCfg()
|
||||||
|
|
||||||
|
# 初始化DLL
|
||||||
|
if Objdll.NET_DVR_Init() == 0:
|
||||||
|
print(f"SDK初始化失败,错误码: {Objdll.NET_DVR_GetLastError()}")
|
||||||
|
raise Exception("SDK初始化失败")
|
||||||
|
print("SDK初始化成功")
|
||||||
|
|
||||||
|
# 登录设备
|
||||||
|
print(f"\n正在登录设备 {DEV_IP.value.decode()}:{DEV_PORT}...")
|
||||||
|
(lUserId, device_info) = LoginDev(Objdll)
|
||||||
|
if lUserId < 0:
|
||||||
|
err = Objdll.NET_DVR_GetLastError()
|
||||||
|
print(f'设备登录失败,错误码: {err}')
|
||||||
|
Objdll.NET_DVR_Cleanup()
|
||||||
|
raise Exception("设备登录失败")
|
||||||
|
print(f"设备登录成功,用户ID: {lUserId}")
|
||||||
|
|
||||||
|
# 预览通道初始化
|
||||||
|
print("\n开始初始化预览通道...")
|
||||||
|
lRealPlayHandles = []
|
||||||
|
|
||||||
|
for i in range(num_channels):
|
||||||
|
PlayCtrl_Ports[i] = c_long(-1)
|
||||||
|
if not Playctrldll.PlayM4_GetPort(byref(PlayCtrl_Ports[i])):
|
||||||
|
error_code = Playctrldll.PlayM4_GetLastError(PlayCtrl_Ports[i])
|
||||||
|
print(f'通道 {i + 1} 获取播放库句柄失败,错误码: {error_code}')
|
||||||
|
PlayCtrl_Ports[i] = c_long(-1) # 明确标记为无效
|
||||||
|
continue
|
||||||
|
print(f'通道 {i + 1} 获取播放库端口成功: {PlayCtrl_Ports[i].value}')
|
||||||
|
|
||||||
|
# 创建回调函数
|
||||||
|
print(f'正在为通道 {i + 1} 创建回调函数...')
|
||||||
|
funcRealDataCallBack_V30_array[i] = REALDATACALLBACK(RealDataCallBack_V30)
|
||||||
|
|
||||||
|
# 开启预览
|
||||||
|
print(f'正在开启通道 {i + 1} 的预览...')
|
||||||
|
lRealPlayHandle = OpenPreview(Objdll, lUserId, funcRealDataCallBack_V30_array[i], i)
|
||||||
|
lRealPlayHandles.append(lRealPlayHandle)
|
||||||
|
|
||||||
|
if lRealPlayHandle < 0:
|
||||||
|
print(f'通道 {i + 1} 预览失败,错误码: {Objdll.NET_DVR_GetLastError()}')
|
||||||
|
else:
|
||||||
|
print(f'通道 {i + 1} 预览成功,句柄: {lRealPlayHandle}')
|
||||||
|
|
||||||
|
Initcustom()
|
||||||
|
root.mainloop()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\n程序发生异常: {str(e)}")
|
||||||
|
print("错误详细信息:")
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
finally:
|
||||||
|
print("\n开始清理资源...")
|
||||||
|
# 关闭预览
|
||||||
|
if 'lRealPlayHandles' in locals():
|
||||||
|
for handle in lRealPlayHandles:
|
||||||
|
if handle >= 0:
|
||||||
|
print(f"正在关闭预览句柄: {handle}")
|
||||||
|
Objdll.NET_DVR_StopRealPlay(handle)
|
||||||
|
|
||||||
|
# 释放播放库资源
|
||||||
|
if 'PlayCtrl_Ports' in locals():
|
||||||
|
for i in range(num_channels):
|
||||||
|
if PlayCtrl_Ports[i].value > -1:
|
||||||
|
print(f"正在释放通道 {i + 1} 的播放库资源")
|
||||||
|
Playctrldll.PlayM4_Stop(PlayCtrl_Ports[i])
|
||||||
|
Playctrldll.PlayM4_CloseStream(PlayCtrl_Ports[i])
|
||||||
|
Playctrldll.PlayM4_FreePort(PlayCtrl_Ports[i])
|
||||||
|
|
||||||
|
# 登出设备
|
||||||
|
if 'lUserId' in locals() and lUserId >= 0:
|
||||||
|
print("正在登出设备...")
|
||||||
|
Objdll.NET_DVR_Logout(lUserId)
|
||||||
|
|
||||||
|
# 清理SDK资源
|
||||||
|
if 'Objdll' in locals():
|
||||||
|
print("正在清理SDK资源...")
|
||||||
|
Objdll.NET_DVR_Cleanup()
|
||||||
|
|
||||||
|
print("程序退出")
|
||||||
|
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user