UDP视频测试

This commit is contained in:
2026-05-26 15:04:03 +08:00
parent 95931ae513
commit 53cd5a2b5f
7 changed files with 272 additions and 450 deletions
+149 -124
View File
@@ -2,157 +2,182 @@ import os
import cv2
import numpy as np
import time
import threading
import queue
import socket
from ais_bench.infer.interface import InferSession
# ==================== 硬编码配置区域 ====================
MODEL_PATH = "./om/yolo26n-balloon.om" # 你的 YOLO26 模型路径
CONF_THRESHOLD = 0.25 # 置信度过滤阈值 (YOLO26 推荐 0.25)
CAMERA_INDEX = 0 # 摄像头索引 (默认0,如果打不开可以尝试1或2)
# ==================== 配置区域 ====================
MODEL_PATH = "./om/yolo26n-balloon.om" # YOLO26 模型路径
INPUT_VIDEO = "./test_video.mp4" # 输入的测试视频(或 0 代表摄像头)
CONF_THRESHOLD = 0.25 # 置信度阈值
# -------------- 无线图传网络配置 --------------
GROUND_STATION_IP = "192.168.31.239" # 地面站的局域网 IP
UDP_PORT = 9999
JPEG_QUALITY = 75 # JPEG 压缩质量 (0-100)
# ==================================================
# 限制队列大小,发不完就直接丢帧,保证飞控算法实时性
tx_queue = queue.Queue(maxsize=2)
# ========================================================
def udp_stream_sender_thread():
""" 📡 后台图传专用线程 """
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_address = (GROUND_STATION_IP, UDP_PORT)
print(f"📡 图传后台线程已启动...")
class YOLO26CameraInfer:
while True:
try:
frame_to_send = tx_queue.get()
if frame_to_send is None:
break
# 压缩为 JPEG 字节流
result, img_encode = cv2.imencode('.jpg', frame_to_send, [cv2.IMWRITE_JPEG_QUALITY, JPEG_QUALITY])
if not result:
continue
data = img_encode.tobytes()
if len(data) > 65000: # 严格防丢包/报错限制
continue
sock.sendto(data, server_address)
except Exception as e:
print(f"❌ 图传线程异常: {e}")
time.sleep(0.1)
sock.close()
class YOLO26UAVInfer:
def __init__(self, model_path, device_id=0):
self.device_id = device_id
self.model_path = model_path
# 自动检查模型文件是否存在,防止硬编码路径写错导致程序崩溃
if not os.path.exists(model_path):
raise FileNotFoundError(f"找不到硬编码的模型文件: {model_path},请检查路径是否正确。")
# 创建 ais_bench 推理会话
raise FileNotFoundError(f"找不到模型文件: {model_path}")
self.session = InferSession(device_id, model_path)
self.input_width = 640
self.input_height = 640
def letterbox(self, img, new_shape=(640, 640), color=(114, 114, 114)):
shape = img.shape[:2]
shape = img.shape[:2] # 原始图的高宽 [height, width]
r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
new_unpad = (int(round(shape[1] * r)), int(round(shape[0] * r)))
dw = new_shape[1] - new_unpad[0]
dh = new_shape[0] - new_unpad[1]
left = dw // 2
right = dw - left
top = dh // 2
bottom = dh - top
dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1]
dw /= 2
dh /= 2
if shape[::-1] != new_unpad:
img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR)
top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color)
return img, r, (left, top)
return img, r, (dw, dh)
def preprocess_frame(self, img_bgr):
self.original_height, self.original_width = img_bgr.shape[:2]
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
img_resized, self.ratio, (self.pad_left, self.pad_top) = self.letterbox(
img_rgb, (self.input_width, self.input_height)
)
img_input = (img_resized.astype(np.float32) / 255.0).astype(np.float32)
img_input = np.ascontiguousarray(np.transpose(img_input, (2, 0, 1))[np.newaxis, :, :, :])
return img_input
def infer(self, img_input):
return self.session.infer([img_input])
def postprocess(self, infer_result, conf_threshold):
def postprocess(self, outputs, ratio, dwdh, orig_shape):
out = outputs[0]
detections = []
if isinstance(infer_result, (list, tuple)):
output = infer_result[0]
else:
output = infer_result
if output.ndim == 3:
output = output[0]
for det in output:
confidence = det[4]
if confidence < conf_threshold:
continue
x1_scale, y1_scale, x2_scale, y2_scale = det[0], det[1], det[2], det[3]
cls_id = int(det[5])
x1 = (x1_scale - self.pad_left) / self.ratio
y1 = (y1_scale - self.pad_top) / self.ratio
x2 = (x2_scale - self.pad_left) / self.ratio
y2 = (y2_scale - self.pad_top) / self.ratio
x1 = max(0, min(x1, self.original_width))
y1 = max(0, min(y1, self.original_height))
x2 = max(0, min(x2, self.original_width))
y2 = max(0, min(y2, self.original_height))
detections.append([x1, y1, x2, y2, confidence, cls_id])
for i in range(out.shape[1]):
box = out[0, i, :4]
conf = out[0, i, 4]
cls_id = int(out[0, i, 5])
if conf > CONF_THRESHOLD:
x1, y1, x2, y2 = box
# 严格基于原图比例还原坐标
x1 = (x1 - dwdh[0]) / ratio
y1 = (y1 - dwdh[1]) / ratio
x2 = (x2 - dwdh[0]) / ratio
y2 = (y2 - dwdh[1]) / ratio
x1 = max(0, min(x1, orig_shape[1]))
y1 = max(0, min(y1, orig_shape[0]))
x2 = max(0, min(x2, orig_shape[1]))
y2 = max(0, min(y2, orig_shape[0]))
detections.append([x1, y1, x2, y2, conf, cls_id])
return detections
def draw_boxes(image, detections):
def draw_boxes(img, detections):
for det in detections:
x1, y1, x2, y2, conf, cls_id = det
x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2)
cv2.rectangle(img, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2)
label = f"Target: {conf:.2f}"
cv2.putText(img, label, (int(x1), int(y1) - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
return img
cv2.rectangle(image, (x1, y1), (x2, y2), (0, 255, 0), 2)
label = f"Class: {cls_id} {conf:.2f}"
(label_w, label_h), _ = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)
cv2.rectangle(image, (x1, y1 - label_h - 5), (x1 + label_w, y1), (0, 255, 0), -1)
cv2.putText(image, label, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 1)
def main():
detector = YOLO26UAVInfer(MODEL_PATH)
cap = cv2.VideoCapture(INPUT_VIDEO)
return image
if not cap.isOpened():
print("❌ 无法打开视频源")
return
# 启动后台无线电图传线程
sender_thread = threading.Thread(target=udp_stream_sender_thread, daemon=True)
sender_thread.start()
print("🚀 NPU 精准推理主循环启动...")
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
# 【核心修正】完全保留读取到的原图大小,坚决不在这一步进行 cv2.resize!
orig_shape = frame.shape[:2]
img_center_x = orig_shape[1] / 2
img_center_y = orig_shape[0] / 2
# 1. 严格使用原图进行标准无畸变 letterbox 图像预处理
input_tensor, ratio, dwdh = detector.letterbox(frame)
input_tensor = input_tensor.astype(np.float32) / 255.0
input_tensor = np.transpose(input_tensor, (2, 0, 1))
input_tensor = np.expand_dims(input_tensor, axis=0)
input_tensor = np.ascontiguousarray(input_tensor)
t_start = time.time()
# 2. NPU 硬件推理
outputs = detector.session.infer([input_tensor])
# 3. 坐标反求(恢复到大图的分辨率,保证百分之百精准)
detections = detector.postprocess(outputs, ratio, dwdh, orig_shape)
t_end = time.time()
fps_pure = 1.0 / (t_end - t_start)
# 4. 寻找置信度最高的目标并计算 MAVLink 飞控误差
if len(detections) > 0:
best_det = max(detections, key=lambda x: x[4])
x1, y1, x2, y2, conf, cls_id = best_det
target_x = (x1 + x2) / 2
target_y = (y1 + y2) / 2
err_x = (target_x - img_center_x) / img_center_x
err_y = (target_y - img_center_y) / img_center_y
# 画中心对准线
cv2.line(frame, (int(img_center_x), int(img_center_y)), (int(target_x), int(target_y)), (255, 0, 0), 2)
cv2.putText(frame, f"MAVLink Out -> Err_X: {err_x:+.3f} Err_Y: {err_y:+.3f}", (20, 70),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 0), 2)
else:
cv2.putText(frame, "MAVLink Out -> Target LOST (HOVER)", (20, 70), cv2.FONT_HERSHEY_SIMPLEX, 0.6,
(0, 0, 255), 2)
# 5. 在原始分辨率大图上,渲染精准目标框
frame = draw_boxes(frame, detections)
cv2.putText(frame, f"NPU Pure FPS: {fps_pure:.1f}", (20, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
# 🚀【核心优化点】只有在进入发送队列的前一秒,才将画好完美框的图 resize 为轻量化分辨率
send_frame = cv2.resize(frame, (640, 480))
# 6. 非阻塞式尝试推入图传队列
try:
tx_queue.put_nowait(send_frame)
except queue.Full:
pass # 队列满则丢弃该帧,绝不积压延迟
cap.release()
tx_queue.put(None)
print("程序运行结束。")
if __name__ == "__main__":
print("正在初始化 YOLOv26 离线推理引擎...")
# 传入硬编码的模型路径
yolo_infer = YOLO26CameraInfer(MODEL_PATH)
# 打开硬编码指定的摄像头
cap = cv2.VideoCapture(CAMERA_INDEX)
if not cap.isOpened():
print(f"错误:无法打开摄像头索引 {CAMERA_INDEX},请检查物理连接。")
exit()
# 尝试设置合适的分辨率
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
print(f"初始化成功!当前模型:{MODEL_PATH} | 过滤阈值:{CONF_THRESHOLD}")
print("开始实时检测,在画面窗口按下 'q' 键可退出...")
fps_start_time = 0
while True:
ret, frame = cap.read()
if not ret:
continue
# 预处理与 NPU 推理
input_tensor = yolo_infer.preprocess_frame(frame)
raw_result = yolo_infer.infer(input_tensor)
# 后处理(使用硬编码的置信度)
detections = yolo_infer.postprocess(raw_result, conf_threshold=CONF_THRESHOLD)
# 绘制结果
frame = draw_boxes(frame, detections)
# FPS 实时计算与显示
fps_end_time = time.time()
time_diff = fps_end_time - fps_start_time
fps = 1 / time_diff if time_diff > 0 else 0
fps_start_time = fps_end_time
cv2.putText(frame, f"FPS: {fps:.1f}", (20, 40),
cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 0, 255), 2)
cv2.imshow("YOLOv26 Real-time Detection", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
print("检测已结束。")
main()