参数分离完全版

This commit is contained in:
2025-08-05 16:55:45 +08:00
commit 25a2ded11d
16 changed files with 2013 additions and 0 deletions

226
frame_transfer.py Normal file
View File

@ -0,0 +1,226 @@
import base64
import requests as http_client
import queue
import time # 确保 time 被导入,如果之前被误删
import cv2
import traceback
import threading # 确保导入 threading
import av # 重新导入 av
import numpy as np
# from fastapi import requests
# 从 rfdetr_core 导入 RFDETRDetector 仅用于类型提示 (可选)
from rfdetr_core import RFDETRDetector
# 目标检测处理函数
# 函数签名已更改:现在接受一个 detector_instance 作为参数
def yolo_frame(rtc_q: queue.Queue, yolo_q: queue.Queue, stream_detector_instance: RFDETRDetector):
thread_name = threading.current_thread().name # 获取线程名称用于日志
print(f"处理线程 '{thread_name}' 已启动。")
error_message_displayed_once = False
no_detector_message_displayed_once = False # 用于只提示一次没有检测器
if stream_detector_instance is None:
print(f"错误 (线程 '{thread_name}'): 未提供有效的检测器实例给yolo_frame。线程将无法处理视频。")
# 此线程实际上无法做任何有用的工作,可以考虑直接退出或进入一个安全循环
# 为简单起见,我们允许它进入主循环,但它会在每次迭代时打印警告
while True:
frame = None
# current_category_counts = {} # 将在获取后转换
try:
# 恢复队列长度打印
print(f"线程 '{thread_name}' - 原始队列长度: {rtc_q.qsize()}, 检测队列长度: {yolo_q.qsize()}")
frame = rtc_q.get(timeout=0.1)
if frame is None:
print(f"处理线程 '{thread_name}' 接收到停止信号,正在退出...")
# 发送包含None frame和空计数的字典作为停止信号
yolo_q.put({"frame": None, "category_counts": {}})
break
category_counts_for_packet = {}
if stream_detector_instance:
no_detector_message_displayed_once = False # 检测器有效,重置提示
annotated_frame = stream_detector_instance.detect_and_draw_count(frame)
error_message_displayed_once = False
# 获取英文键的类别计数
english_counts = stream_detector_instance.category_counts.copy() if hasattr(stream_detector_instance, 'category_counts') else {}
# 转换为中文键的类别计数
if hasattr(stream_detector_instance, 'VISDRONE_CLASSES_CHINESE'):
chinese_map = stream_detector_instance.VISDRONE_CLASSES_CHINESE
for eng_key, count_val in english_counts.items():
# 使用 get 提供一个默认值,以防某个英文类别在中文映射表中确实没有
chi_key = chinese_map.get(eng_key, eng_key)
category_counts_for_packet[chi_key] = count_val
else:
# 如果没有中文映射表,则直接使用英文计数 (或记录警告)
category_counts_for_packet = english_counts
# logger.warning(f"线程 '{thread_name}': stream_detector_instance 没有 VISDRONE_CLASSES_CHINESE 属性,将使用英文类别计数。")
else:
# 如果没有有效的检测器实例传递进来
if not no_detector_message_displayed_once:
print(f"警告 (线程 '{thread_name}'): 无有效检测器实例。将在帧上绘制提示。")
no_detector_message_displayed_once = True
annotated_frame = frame.copy()
cv2.putText(annotated_frame,
"No detector instance provided for this stream",
(30, 50), cv2.FONT_HERSHEY_SIMPLEX,
1, (0, 0, 255), 2, cv2.LINE_AA)
category_counts_for_packet = {} # 无检测器,计数为空
# 将帧和类别计数一起放入队列
data_packet = {"frame": annotated_frame, "category_counts": category_counts_for_packet}
try:
yolo_q.put_nowait(data_packet)
except queue.Full:
# print(f"警告 (线程 '{thread_name}'): yolo_q 已满,丢弃帧。") # 避免刷屏
pass
except queue.Empty:
time.sleep(0.01)
continue
except Exception as e:
if not error_message_displayed_once:
print(f"线程 '{thread_name}' (yolo_frame) 处理时发生严重错误: {e}")
traceback.print_exc()
error_message_displayed_once = True
time.sleep(1)
if frame is not None:
try:
pass
except queue.Full:
pass
continue
print(f"处理线程 '{thread_name}' 已停止。")
def push_frame(yolo_q: queue.Queue, rtmp_url: str, gateway: str, frequency: int, push_url: str):
thread_name = threading.current_thread().name
print(f"推流线程 '{thread_name}' (RTMP: {rtmp_url}) 已启动。")
output_container = None
stream = None
first_frame_processed = False
last_push_time = 0 # 记录上次推送base64的时间
try:
while True:
frame_to_push = None
received_category_counts = {} # 初始化为空字典
try:
data_packet = yolo_q.get(timeout=0.1)
if data_packet:
frame_to_push = data_packet.get("frame")
received_category_counts = data_packet.get("category_counts", {})
else: # data_packet is None (不太可能除非队列明确放入None)
time.sleep(0.01)
continue
except queue.Empty:
time.sleep(0.01)
continue
if frame_to_push is None: # 这是通过 data_packet["frame"] is None 来判断的
print(f"推流线程 '{thread_name}' 接收到停止信号,正在清理并退出...")
break
if not first_frame_processed:
if frame_to_push is not None:
try:
height, width, _ = frame_to_push.shape
print(f"线程 '{thread_name}': 首帧尺寸 {width}x{height}正在初始化RTMP推流器到 {rtmp_url}")
output_container = av.open(rtmp_url, 'w', format='flv')
stream = output_container.add_stream('libx264', rate=25)
stream.pix_fmt = 'yuv420p'
stream.width = width
stream.height = height
stream.options = {'preset': 'ultrafast', 'tune': 'zerolatency', 'crf': '25'}
print(f"线程 '{thread_name}': RTMP推流器初始化成功。")
first_frame_processed = True
except Exception as e_init:
print(f"错误 (线程 '{thread_name}'): 初始化PyAV推流容器/流失败: {e_init}")
traceback.print_exc()
return
else:
continue
if not output_container or not stream:
print(f"错误 (线程 '{thread_name}'): 推流器未初始化,无法推流。可能是首帧处理失败。")
time.sleep(1)
continue
# 持续推流到RTMP
try:
video_frame = av.VideoFrame.from_ndarray(frame_to_push, format='bgr24')
for packet in stream.encode(video_frame):
output_container.mux(packet)
except Exception as e_push:
print(f"错误 (线程 '{thread_name}'): 推送帧到RTMP时发生错误: {e_push}")
time.sleep(0.5)
# 定时推送base64帧
current_time = time.time()
if current_time - last_push_time >= frequency:
# 将接收到的类别计数传递给 push_base64_frame
push_base64_frame(frame_to_push, gateway, push_url, thread_name, received_category_counts)
last_push_time = current_time
except Exception as e_outer:
print(f"推流线程 '{thread_name}' 发生严重外部错误: {e_outer}")
traceback.print_exc()
finally:
print(f"推流线程 '{thread_name}': 进入finally块准备关闭推流器。")
if stream and output_container:
try:
print(f"推流线程 '{thread_name}': 正在编码流的剩余部分...")
for packet in stream.encode(None):
output_container.mux(packet)
print(f"推流线程 '{thread_name}': 编码剩余部分完成。")
except Exception as e_flush:
print(f"错误 (线程 '{thread_name}'): 关闭推流流时发生编码/刷新错误: {e_flush}")
traceback.print_exc()
if output_container:
try:
print(f"推流线程 '{thread_name}': 正在关闭推流容器...")
output_container.close()
print(f"推流线程 '{thread_name}': 推流容器已关闭。")
except Exception as e_close:
print(f"错误 (线程 '{thread_name}'): 关闭推流容器时发生错误: {e_close}")
traceback.print_exc()
print(f"推流线程 '{thread_name}' 已停止并完成清理。")
def push_base64_frame(frame: np.ndarray, gateway: str, push_url: str, thread_name: str, category_counts: dict):
"""将帧转换为base64并推送到指定URL"""
try:
# 转换为JPEG格式
_, buffer = cv2.imencode('.jpg', frame)
# 转换为base64字符串
frame_base64 = base64.b64encode(buffer).decode('utf-8')
# 构建JSON数据
data = {
"gateway": gateway,
"frame_base64": frame_base64,
"category_counts": category_counts # 使用传入的 category_counts
}
print(f"DEBUG push_base64_frame: Pushing data: {data.get('category_counts')}") # 调试打印,检查发送的数据
# 发送POST请求
response = http_client.post(push_url, json=data, timeout=5)
# 检查响应
if response.status_code == 200:
print(f"线程 '{thread_name}': base64帧已成功推送到 {push_url}")
else:
print(f"错误 (线程 '{thread_name}'): 推送base64帧失败状态码: {response.status_code}")
except Exception as e:
print(f"错误 (线程 '{thread_name}'): 处理或推送base64帧时发生错误: {e}")