Files
opi-yolo/base_test/infer.py
T
2026-06-02 11:48:25 +08:00

198 lines
7.6 KiB
Python
Raw 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.
import os
import cv2
import numpy as np
import argparse
from ais_bench.infer.interface import InferSession
class YOLO26Infer:
def __init__(self, model_path, device_id=0):
"""
初始化推理器
:param model_path: .om 模型的本地路径
:param device_id: NPU 设备ID
"""
self.device_id = device_id
self.model_path = model_path
# 创建 ais_bench 推理会话
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]
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
# 缩放图片
if shape[::-1] != new_unpad:
img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR)
# 添加填充(可能左右或上下不对称,但总尺寸正好是 new_shape)
img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color)
return img, r, (left, top) # 返回填充的左上角偏移量
def preprocess(self, image_path):
"""
预处理图片,使其符合模型输入要求
"""
# 使用OpenCV读取图片 (BGR格式)
img_bgr = cv2.imread(image_path)
if img_bgr is None:
raise FileNotFoundError(f"图片未找到: {image_path}")
# 获取原始图片尺寸,用于后处理时的坐标转换
self.original_height, self.original_width = img_bgr.shape[:2]
# 将图片从BGR转换为RGB
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
# 调整图片尺寸并保持宽高比,进行Letterbox填充
img_resized, self.ratio, (self.pad_left, self.pad_top) = self.letterbox(
img_rgb, (self.input_width, self.input_height)
)
# 归一化:将像素值从 [0, 255] 转为 [0, 1]
img_input = (img_resized.astype(np.float32) / 255.0).astype(np.float32)
# 调整维度:HWC -> NCHW,并转换为连续数组
img_input = np.ascontiguousarray(np.transpose(img_input, (2, 0, 1))[np.newaxis, :, :, :])
return img_input
def infer(self, img_input):
"""
执行推理
"""
# 使用会话的 infer 方法进行推理
# infer 方法接收一个列表,包含输入张量,返回一个列表,包含输出张量
result = self.session.infer([img_input])
print(f"Input tensor shape: {img_input.shape}")
print(f"Input tensor dtype: {img_input.dtype}")
print(f"Input tensor size: {img_input.nbytes}")
return result
def postprocess(self, infer_result, conf_threshold=0.5):
"""
对模型输出进行后处理
:param infer_result: 模型原始输出,一个列表,通常第一个元素是输出张量
:param conf_threshold: 置信度阈值
:return: 包含检测结果的列表,每个元素为 [x1, y1, x2, y2, confidence, class_id]
"""
detections = []
# 获取输出张量,假设输出列表的第一个元素是输出数组
if isinstance(infer_result, (list, tuple)):
output = infer_result[0]
else:
output = infer_result
# ========== 调试信息 ==========
# print(f"[DEBUG] output shape: {output.shape}")
# print(f"[DEBUG] output dtype: {output.dtype}")
# print(f"[DEBUG] output min: {output.min()}, max: {output.max()}")
# # 打印前5个检测框(如果形状是 [N,6] 或 [1,N,6])
# if output.ndim == 3:
# flat_output = output[0]
# else:
# flat_output = output
# print(f"[DEBUG] first 5 rows:\n{flat_output[:5]}")
# 假设输出维度为 [batch, num_detections, 6]
# 移除batch维度,并只取第一个batch的结果
if output.ndim == 3:
output = output[0]
# 遍历所有检测框
for det in output:
confidence = det[4]
if confidence < conf_threshold:
continue
# 获取原始输出坐标 (x1, y1, x2, y2) 和 类别ID
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])
return detections
def draw_boxes(image, detections, class_names=None):
"""
在图片上绘制检测框和标签
:param image: 原始图片数组 (BGR格式)
:param detections: 后处理得到的结果列表
:param class_names: 类别名称列表,默认使用类别ID
"""
img_copy = image.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"Class: {cls_id}" if class_names is None else class_names[cls_id]
label += f" {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__":
parser = argparse.ArgumentParser(description="YOLOv26 目标检测推理")
parser.add_argument("--model", type=str, required=True, help="YOLOv26 OM模型文件路径")
parser.add_argument("--image", type=str, required=True, help="输入图片路径")
parser.add_argument("--conf", type=float, default=0.5, help="置信度阈值")
args = parser.parse_args()
# 初始化推理器
yolo_infer = YOLO26Infer(args.model)
# 图片预处理
print("预处理图片...")
input_tensor = yolo_infer.preprocess(args.image)
# 执行推理
print("执行推理...")
raw_result = yolo_infer.infer(input_tensor)
# 后处理
print("后处理...")
detections = yolo_infer.postprocess(raw_result, conf_threshold=args.conf)
# 打印结果
print(f"检测到 {len(detections)} 个目标:")
for det in detections:
print(f"类别ID: {int(det[5])}, 置信度: {det[4]:.4f}, 边界框: ({det[0]:.2f}, {det[1]:.2f}, {det[2]:.2f}, {det[3]:.2f})")
# 可选:绘制结果并保存
original_img = cv2.imread(args.image)
result_img = draw_boxes(original_img, detections)
output_path = "output_" + os.path.basename(args.image)
cv2.imwrite(output_path, result_img)
print(f"结果图片已保存至: {output_path}")