视频测试更新

This commit is contained in:
2026-05-26 09:38:43 +08:00
parent 2f7ac3bf60
commit 0aa8be481f
3 changed files with 196 additions and 66 deletions
+42
View File
@@ -0,0 +1,42 @@
import cv2
import os
video_path = "./output_result.mp4" # 替换成你上传的视频文件名
if not os.path.exists(video_path):
print(f"错误:找不到文件 {video_path}")
exit()
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
print("错误:无法打开视频文件!")
exit()
# 获取视频自带的物理帧率 (FPS)
fps = cap.get(cv2.CAP_PROP_FPS)
# 获取视频的总帧数
total_frames = cap.get(cv2.CAP_PROP_FRAME_COUNT)
# 获取视频的分辨率宽度和高度
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
# 计算视频总时长(秒)
duration = total_frames / fps if fps > 0 else 0
print("==========================================")
print(f"视频文件: {os.path.basename(video_path)}")
print(f"视频分辨率: {width} x {height}")
print(f"视频原生帧率 (FPS): {fps:.2f} 帧/秒 <--- [核心原因看这里]")
print(f"视频总帧数: {int(total_frames)} 帧")
print(f"视频总时长: {duration:.2f} 秒")
print("==========================================")
if fps <= 25.5:
print("💡 结论:不出所料,你的视频本身就被录制/压制成了 25 帧!")
print("OpenCV 的 cap.read() 会卡在 25fps 的播放速度上等下一帧,导致 NPU 闲置。")
print("你想测试 NPU 的真实上限,需要用前面提到的『纯随机数矩阵压测脚本』。")
cap.release()
+153 -65
View File
@@ -2,15 +2,53 @@ import os
import cv2 import cv2
import numpy as np import numpy as np
import time import time
import threading
import queue
import socket
from ais_bench.infer.interface import InferSession from ais_bench.infer.interface import InferSession
# ==================== 模拟测试硬编码配置 ==================== # ==================== 配置区域 ====================
MODEL_PATH = "./yolo26_8t.om" # 你的 YOLO26 模型路径 MODEL_PATH = "./om/yolo26n-balloon.om" # YOLO26 模型路径
VIDEO_PATH = "./drone_test.mp4" # 用于模拟相机的测试视频文件 INPUT_VIDEO = "./test_video.mp4" # 输入的测试视频(首飞时可改成 0 代表机载 USB 相机)
CONF_THRESHOLD = 0.25 # 置信度阈值 CONF_THRESHOLD = 0.25 # 置信度阈值
# -------------- 无线图传网络配置 --------------
GROUND_STATION_IP = "192.168.31.239" # 地面站的局域网 IP
UDP_PORT = 9999
# ==================================================
# 初始化一个图传专用的轻量级队列,最大长度为2。如果后台发得慢导致队列满了,主推理环会触发丢帧,绝对不卡死。
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"📡 图传后台线程已启动,正在持续向地面站 [{GROUND_STATION_IP}:{UDP_PORT}] 推流...")
while True:
try:
# 阻塞等待主线程丢进来的实时画框图像
frame = tx_queue.get()
# 1. 预处理:将图片缩放到 640x480(极大降低空中无线带宽压力和延迟)
small_frame = cv2.resize(frame, (640, 480))
# 2. 图像进行轻量级 JPEG 压缩(质量设为 60,画面清晰且计算速度极快)
ret, jpeg_img = cv2.imencode('.jpg', small_frame, [int(cv2.IMWRITE_JPEG_QUALITY), 60])
if ret:
data = np.array(jpeg_img).tobytes()
# 3. 确保单个 UDP 数据包不超过缓冲区上限(~65KB)
if len(data) < 65000:
sock.sendto(data, server_address)
tx_queue.task_done()
except Exception:
# 即使无线网络抖动或地面站未开启,后台线程也静默承受,绝不连累无人机主系统
pass
# ============================================================
class YOLO26UAVInfer: class YOLO26UAVInfer:
def __init__(self, model_path, device_id=0): def __init__(self, model_path, device_id=0):
@@ -25,95 +63,145 @@ class YOLO26UAVInfer:
r = min(new_shape[0] / shape[0], new_shape[1] / shape[1]) r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
new_unpad = (int(round(shape[1] * r)), int(round(shape[0] * r))) new_unpad = (int(round(shape[1] * r)), int(round(shape[0] * r)))
dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1]
left, top = dw // 2, dh // 2 dw /= 2
right, bottom = dw - left, dh - top dh /= 2
if shape[::-1] != new_unpad: if shape[::-1] != new_unpad:
img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR) 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) 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): def preprocess(self, img):
return self.session.infer([img_input]) img_canvas, ratio, (dw, dh) = self.letterbox(img, (self.input_width, self.input_height))
img_in = img_canvas[:, :, ::-1].transpose(2, 0, 1)
img_in = np.ascontiguousarray(img_in).astype(np.float32) / 255.0
img_in = np.expand_dims(img_in, axis=0)
return img_in, ratio, (dw, dh)
def postprocess(self, infer_result, conf_threshold):
detections = [] def infer(self, input_tensor):
output = infer_result[0] if isinstance(infer_result, (list, tuple)) else infer_result outputs = self.session.infer([input_tensor])
if output.ndim == 3: output = output[0] return outputs
for det in output:
confidence = det[4]
if confidence < conf_threshold: continue def postprocess(self, outputs, ratio, pad, orig_shape, conf_thres=0.25):
x1_scale, y1_scale, x2_scale, y2_scale = det[0], det[1], det[2], det[3] det_out = outputs[0][0]
cls_id = int(det[5]) valid_detections = []
x1 = max(0, min((x1_scale - self.pad_left) / self.ratio, self.original_width)) for i in range(det_out.shape[0]):
y1 = max(0, min((y1_scale - self.pad_top) / self.ratio, self.original_height)) row = det_out[i]
x2 = max(0, min((x2_scale - self.pad_left) / self.ratio, self.original_width)) conf = row[4]
y2 = max(0, min((y2_scale - self.pad_top) / self.ratio, self.original_height)) if conf < conf_thres:
detections.append([x1, y1, x2, y2, confidence, cls_id]) continue
return detections cls_id = int(row[5])
cx, cy, w, h = row[0], row[1], row[2], row[3]
x1 = cx - w / 2
y1 = cy - h / 2
x2 = cx + w / 2
y2 = cy + h / 2
dw, dh = pad
x1 = (x1 - dw) / ratio
y1 = (y1 - dh) / ratio
x2 = (x2 - dw) / ratio
y2 = (y2 - dh) / ratio
x1 = max(0, min(orig_shape[1], x1))
y1 = max(0, min(orig_shape[0], y1))
x2 = max(0, min(orig_shape[1], x2))
y2 = max(0, min(orig_shape[0], y2))
valid_detections.append([x1, y1, x2, y2, conf, cls_id])
return valid_detections
def draw_boxes(img, detections):
img_copy = img.copy()
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_copy, (x1, y1), (x2, y2), (0, 255, 0), 2)
label = f"Target: {conf:.2f}"
(label_w, label_h), _ = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)
cv2.rectangle(img_copy, (x1, y1 - label_h - 5), (x1 + label_w, y1), (0, 255, 0), -1)
cv2.putText(img_copy, label, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 1)
return img_copy
if __name__ == "__main__": if __name__ == "__main__":
print("====== 启动香橙派无人机检测算法模拟测试 ======") # 0. 提前拉起后台异步无线图传线程
stream_thread = threading.Thread(target=udp_stream_sender_thread, daemon=True)
stream_thread.start()
# 初始化推理会话
yolo_infer = YOLO26UAVInfer(MODEL_PATH) yolo_infer = YOLO26UAVInfer(MODEL_PATH)
# 用视频文件模拟机载相机输入 # 视频输入源
cap = cv2.VideoCapture(VIDEO_PATH) cap = cv2.VideoCapture(INPUT_VIDEO)
if not cap.isOpened(): if not cap.isOpened():
print(f"错误:无法打开模拟视频文件 {VIDEO_PATH}") print(f"❌ 无法打开输入视频: {INPUT_VIDEO}")
exit() exit()
frame_w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) print("🚀 NPU 推理主循环已启动!")
frame_h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
img_center_x, img_center_y = frame_w / 2, frame_h / 2
print(f"成功加载视频,分辨率: {frame_w}x{frame_h} | 正在进行 NPU 循环推理...")
fps_start_time = time.time()
frame_count = 0
while True: while True:
t_start = time.time()
ret, frame = cap.read() ret, frame = cap.read()
if not ret: if not ret:
print("视频播放结束或无法读取。") print("视频播放结束或未捕获到帧。")
break break
frame_count += 1 orig_shape = frame.shape[:2]
img_center_x = orig_shape[1] / 2
img_center_y = orig_shape[0] / 2
# 1. 预处理与 NPU 推理 # 1. 预处理
input_tensor = yolo_infer.preprocess_frame(frame) input_tensor, ratio, pad = yolo_infer.preprocess(frame)
raw_result = yolo_infer.infer(input_tensor)
detections = yolo_infer.postprocess(raw_result, conf_threshold=CONF_THRESHOLD)
# 2. 模拟数据下发接口(核心优化点) # 2. 执行昇腾 NPU 硬件推理
outputs = yolo_infer.infer(input_tensor)
# 3. 后处理
detections = yolo_infer.postprocess(outputs, ratio, pad, orig_shape, conf_thres=CONF_THRESHOLD)
# 4. 执行你的核心控制逻辑(寻找最显著的目标并计算偏差)
if len(detections) > 0: if len(detections) > 0:
best_det = max(detections, key=lambda x: x[4]) # 追踪置信度最高的目标 best_det = max(detections, key=lambda x: x[4])
x1, y1, x2, y2, conf, cls_id = best_det x1, y1, x2, y2, conf, cls_id = best_det
target_x, target_y = (x1 + x2) / 2, (y1 + y2) / 2 target_x = (x1 + x2) / 2
target_y = (y1 + y2) / 2
# 计算标准化像素偏差(-1.0 到 1.0)
err_x = (target_x - img_center_x) / img_center_x err_x = (target_x - img_center_x) / img_center_x
err_y = (target_y - img_center_y) / img_center_y err_y = (target_y - img_center_y) / img_center_y
# 模拟发送给飞控 # 画一条从画面中心指向目标中心的蓝色对准线,直观展示控制走向
print( cv2.line(frame, (int(img_center_x), int(img_center_y)),
f"[Frame {frame_count:04d}] 检测到目标(类别:{cls_id}/置信度:{conf:.2f}) -> 模拟下发飞控控制量 err_x: {err_x:+.4f}, err_y: {err_y:+.4f}") (int(target_x), int(target_y)), (255, 0, 0), 2)
else:
print(f"[Frame {frame_count:04d}] 未检测到目标 -> 告知飞控保持悬停对准...")
# 3. 计算端到端真实 FPS(包含预处理、推理和后处理) # 把计算好的、原本要发给飞控的误差值实时写在视频上
if frame_count % 30 == 0: cv2.putText(frame, f"MAVLink Out -> Err_X: {err_x:+.3f} Err_Y: {err_y:+.3f}",
end_time = time.time() (20, 70), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 0), 2)
avg_fps = 30 / (end_time - fps_start_time) else:
print(f"----------------> 当前香橙派端到端平均处理速度: {avg_fps:.1f} FPS") cv2.putText(frame, "MAVLink Out -> Target LOST (HOVER)",
fps_start_time = time.time() (20, 70), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
# 5. 渲染基础边界框
frame = draw_boxes(frame, detections)
# 6. 计算单帧总耗时和纯计算 FPS
t_end = time.time()
fps = 1.0 / (t_end - t_start)
cv2.putText(frame, f"NPU Process-FPS: {fps:.1f}", (20, 40),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
# ==================== 异步丢帧推流 ====================
try:
# block=False 意味着如果无线网络太慢队列满了,程序不会原地死等,而是抛出异常并直接走下一次循环
tx_queue.put(frame, block=False)
except queue.Full:
pass # 主动丢帧
# ===================================================================
# 打印调试信息到终端
print(f"当前帧推理完成 | 纯计算主环帧率: {fps:.1f} FPS")
cap.release() cap.release()
print("香橙派端任务运行完毕。")
BIN
View File
Binary file not shown.