摘要
本文详细阐述了如何将 YOLOv8 姿态估计模型转换为 OpenVINO IR 格式,并利用 NNCF 工具进行量化8位量化优化,以提升推理推理性能。文中系统介绍了模型转换、推理验证、精度评估及量化优化的完整流程,并通过实验对比了原始模型与量化模型的性能差异,为计算机视觉领域的姿态部署提供了可行的技术方案。
前提条件
在开始之前,需安装必要的依赖包以确保后续实验的顺利进行:
%pip install -q "openvino>=2023.1.0" "nncf>=2.5.0" "protobuf==3.20.*" "torch>=2.1" "torchvision>=0.16" "ultralytics==8.0.159" "onnx" --extra-index-url https://download.pytorch.org/whl/cpu
导入所需的工具函数,包括从 GitHub 仓库获取辅助脚本:
from pathlib import Path
# 从 openvino_notebooks 仓库获取 notebook utils 脚本
import urllib.request
urllib.request.urlretrieve(
url='https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/main/notebooks/utils/notebook_utils.py',
filename='notebook_utils.py'
)
from notebook_utils import download_file, VideoPlayer
结果可视化工具函数
为了直观展示模型推理结果,定义以下用于绘制边界框和关键点的工具函数:
from typing import Tuple, Dict
import cv2
import numpy as np
import random
from PIL import Image
from ultralytics.utils.plotting import colors
def plot_one_box(box:np.ndarray, img:np.ndarray, color:Tuple[int, int, int] = None, keypoints:np.ndarray = None, label:str = None, line_thickness:int = 5):
"""
辅助函数,用于在图像上绘制单个边界框及关键点
参数:
box (np.ndarray):边界框坐标,格式为 [x1, y1, x2, y2]
img (np.ndarray):输入图像
color (Tuple[int, int, int],可选):用于绘制边界框的 BGR 格式颜色,未指定则随机选择
keypoints (np.ndarray,可选):关键点格式为 [x1, y1, s],其中 x1, y1 为坐标,s 为置信度分数
label (str,可选):边界框标签字符串
line_thickness (int,可选):边界框绘制线条的厚度,默认值为5
"""
# 计算线宽和字体厚度
tl = line_thickness or round(0.002 * (img.shape[0] + img.shape[1]) / 2) + 1
color = color or [random.randint(0, 255) for _ in range(3)]
c1, c2 = (int(box[0]), int(box[1])), (int(box[2]), int(box[3]))
cv2.rectangle(img, c1, c2, color, thickness=tl, lineType=cv2.LINE_AA)
# 绘制标签
if label:
tf = max(tl - 1, 1)
t_size = cv2.getTextSize(label, 0, fontScale=tl / 3, thickness=tf)[0]
c2 = c1[0] + t_size[0], c1[1] - t_size[1] - 3
cv2.rectangle(img, c1, c2, color, -1, cv2.LINE_AA)
cv2.putText(img, label, (c1[0], c1[1] - 2), 0, tl / 3, [225, 255, 255], thickness=tf, lineType=cv2.LINE_AA)
# 绘制关键点和骨架
if keypoints is not None:
# 关键点颜色配置
kpt_color = colors.pose_palette[[16, 16, 16, 16, 16, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9]]
# 人体骨架连接关系
skeleton = [[16, 14], [14, 12], [17, 15], [15, 13], [12, 13], [6, 12], [7, 13], [6, 7], [6, 8],
[7, 9], [8, 10], [9, 11], [2, 3], [1, 2], [1, 3], [2, 4], [3, 5], [4, 6], [5, 7]]
limb_color = colors.pose_palette[[9, 9, 9, 9, 7, 7, 7, 0, 0, 0, 0, 0, 16, 16, 16, 16, 16, 16, 16]]
shape = img.shape[:2]
# 绘制关键点
for i, k in enumerate(keypoints):
color_k = [int(x) for x in kpt_color[i]]
x_coord, y_coord = k[0], k[1]
if x_coord % shape[1] != 0 and y_coord % shape[0] != 0:
if len(k) == 3 and k[2] < 0.5:
continue
cv2.circle(img, (int(x_coord), int(y_coord)), 5, color_k, -1, lineType=cv2.LINE_AA)
# 绘制骨架连接
ndim = keypoints.shape[-1]
for i, sk in enumerate(skeleton):
pos1 = (int(keypoints[(sk[0] - 1), 0]), int(keypoints[(sk[0] - 1), 1]))
pos2 = (int(keypoints[(sk[1] - 1), 0]), int(keypoints[(sk[1] - 1), 1]))
if ndim == 3:
conf1 = keypoints[(sk[0] - 1), 2]
conf2 = keypoints[(sk[1] - 1), 2]
if conf1 < 0.5 or conf2 < 0.5:
continue
if pos1[0] % shape[1] == 0 or pos1[1] % shape[0] == 0 or pos1[0] < 0 or pos1[1] < 0:
continue
if pos2[0] % shape[1] == 0 or pos2[1] % shape[0] == 0 or pos2[0] < 0 or pos2[1] < 0:
continue
cv2.line(img, pos1, pos2, [int(x) for x in limb_color[i]], thickness=2, lineType=cv2.LINE_AA)
return img
def draw_results(results:Dict, source_image:np.ndarray, label_map:Dict):
"""
辅助函数,用于在图像上绘制所有检测结果
参数:
results (Dict):检测结果,包含边界框和关键点信息
source_image (np.ndarray):用于绘制的输入图像
label_map (Dict[int, str]):标签 ID 到类别名称的映射
"""
boxes = results["box"]
keypoints = results.get("kpt")
h, w = source_image.shape[:2]
for idx, (*xyxy, conf, lbl) in enumerate(boxes):
if conf < 0.4: # 过滤低置信度结果
continue
label = f'{label_map[0]} {conf:.2f}'
kp = keypoints[idx] if keypoints is not None else None
source_image = plot_one_box(xyxy, source_image, keypoints=kp, label=label, color=colors(int(lbl)), line_thickness=1)
return source_image
测试数据准备
下载测试图像用于后续模型推理验证:
# 下载测试样本
IMAGE_PATH = Path('./data/intel_rnb.jpg')
download_file(
url='https://storage.openvinotoolkit.org/repositories/openvino_notebooks/data/data/image/intel_rnb.jpg',
filename=IMAGE_PATH.name,
directory=IMAGE_PATH.parent
)
'data/intel_rnb.jpg' 已存在。
PosixPath('/home/ea/work/openvino_notebooks/notebooks/230-yolov8-optimization/data/intel_rnb.jpg')
模型实例化与原始推理
加载预训练模型
YOLOv8 提供了便捷的模型加载接口,支持从本地路径或模型中心加载预训练模型。加载模型后,可直接对图像进行推理并可视化结果:
models_dir = Path('./models')
models_dir.mkdir(exist_ok=True) # 创建模型保存目录
from ultralytics import YOLO
POSE_MODEL_NAME = "yolov8n-pose"
# 加载 YOLOv8 姿态估计模型
pose_model = YOLO(models_dir / f'{POSE_MODEL_NAME}.pt')
label_map = pose_model.model.names # 获取类别标签映射
# 对测试图像进行推理
res = pose_model(IMAGE_PATH)
Image.fromarray(res[0].plot()[:, :, ::-1]) # 转换颜色通道并显示结果
image 1/1 /home/ea/work/openvino_notebooks/notebooks/230-yolov8-optimization/data/intel_rnb.jpg: 480x640 1 person, 52.6ms
Speed: 2.1ms preprocess, 52.6ms inference, 1.3ms postprocess per image at shape (1, 3, 480, 640)

模型转换为 OpenVINO IR 格式
OpenVINO IR(Intermediate Representation)是一种针对深度学习模型的中间表示格式,能够优化模型在 Intel 硬件上的推理性能。YOLOv8 提供了直接导出为 OpenVINO IR 格式的 API:
# 定义转换后的模型路径
pose_model_path = models_dir / f"{POSE_MODEL_NAME}_openvino_model/{POSE_MODEL_NAME}.xml"
# 如果模型未转换,则执行转换
if not pose_model_path.exists():
pose_model.export(format="openvino", dynamic=True, half=False)
OpenVINO 模型推理验证
为了确保转换后的模型能够正常工作,需要构建完整的推理管道,包括图像预处理、模型推理和结果后处理三个关键步骤。
图像预处理
YOLOv8 模型对输入图像有特定要求,需要进行尺寸调整、通道转换和归一化等预处理操作:
from typing import Tuple
import torch
import numpy as np
def letterbox(img: np.ndarray, new_shape:Tuple[int, int] = (640, 640), color:Tuple[int, int, int] = (114, 114, 114), auto:bool = False, scale_fill:bool = False, scaleup:bool = False, stride:int = 32):
"""
为检测任务调整图像大小并填充,保持原始宽高比
参数:
img (np.ndarray):输入图像
new_shape (Tuple(int, int)):目标图像大小,格式为 [高度,宽度]
color (Tuple(int, int, int)):填充区域的颜色
auto (bool):是否使用动态输入大小,仅填充以满足步长约束
scale_fill (bool):是否拉伸图像以填充新形状
scaleup (bool):是否允许放大图像
stride (int):输入填充步长
返回:
img (np.ndarray):预处理后的图像
ratio (Tuple(float, float)):高度和宽度的缩放比例
padding_size (Tuple(int, int)):高度和宽度的填充大小
"""
shape = img.shape[:2] # 当前形状 [高度,宽度]
if isinstance(new_shape, int):
new_shape = (new_shape, new_shape)
# 计算缩放比例
r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
if not scaleup: # 仅缩小,不放大以保持精度
r = min(r, 1.0)
# 计算填充大小
ratio = r, 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]
if auto: # 最小矩形填充
dw, dh = np.mod(dw, stride), np.mod(dh, stride)
elif scale_fill: # 拉伸填充
dw, dh = 0.0, 0.0
new_unpad = (new_shape[1], new_shape[0])
ratio = new_shape[1] / shape[1], new_shape[0] / shape[0]
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, ratio, (dw, dh)
def preprocess_image(img0: np.ndarray):
"""
根据 YOLOv8 输入要求预处理图像
参数:
img0 (np.ndarray):原始图像
返回:
img (np.ndarray):预处理后的图像,格式为 CHW
"""
# 调整大小
img = letterbox(img0)[0]
# 转换通道布局 HWC -> CHW
img = img.transpose(2, 0, 1)
img = np.ascontiguousarray(img)
return img
def image_to_tensor(image:np.ndarray):
"""
将预处理后的图像转换为模型输入张量
参数:
image (np.ndarray):预处理后的图像
返回:
input_tensor (np.ndarray):输入张量,格式为 NCHW,值范围 [0, 1]
"""
input_tensor = image.astype(np.float32) # 转换为 float32
input_tensor /= 255.0 # 归一化到 [0.0, 1.0]
# 添加批量维度
if input_tensor.ndim == 3:
input_tensor = np.expand_dims(input_tensor, 0)
return input_tensor
结果后处理
模型输出需要经过非最大抑制(NMS)等后处理步骤,以过滤冗余框并将坐标映射回原始图像尺寸:
from ultralytics.utils import ops
def postprocess(
pred_boxes:np.ndarray,
input_hw:Tuple[int, int],
orig_img:np.ndarray,
min_conf_threshold:float = 0.25,
nms_iou_threshold:float = 0.45,
agnostic_nms:bool = False,
max_detections:int = 80,
):
"""
YOLOv8 模型后处理函数
参数:
pred_boxes (np.ndarray):模型输出的预测框
input_hw (np.ndarray):预处理后的图像尺寸
orig_image (np.ndarray):原始图像
min_conf_threshold (float):置信度阈值
nms_iou_threshold (float):NMS 的 IoU 阈值
agnostic_nms (bool):是否应用类别无关的 NMS
max_detections (int):NMS 后保留的最大检测数量
返回:
pred (List[Dict[str, np.ndarray]]):包含检测框和关键点的字典列表
"""
nms_kwargs = {"agnostic": agnostic_nms, "max_det": max_detections}
# 应用非最大抑制
preds = ops.non_max_suppression(
torch.from_numpy(pred_boxes),
min_conf_threshold,
nms_iou_threshold,
nc=1,
**nms_kwargs
)
results = []
kpt_shape = [17, 3] # 17个关键点,每个包含x,y,score
for i, pred in enumerate(preds):
shape = orig_img[i].shape if isinstance(orig_img, list) else orig_img.shape
# 将框坐标缩放回原始图像尺寸
pred[:, :4] = ops.scale_boxes(input_hw, pred[:, :4], shape).round()
# 处理关键点
pred_kpts = pred[:, 6:].view(len(pred), *kpt_shape) if len(pred) else pred[:, 6:]
pred_kpts = ops.scale_coords(input_hw, pred_kpts, shape)
results.append({"box": pred[:, :6].numpy(), 'kpt': pred_kpts.numpy()})
return results
推理设备选择
OpenVINO 支持多种硬件设备进行推理,可通过以下方式选择合适的设备:
import ipywidgets as widgets
import openvino as ov
core = ov.Core()
# 创建设备选择下拉菜单
device = widgets.Dropdown(
options=core.available_devices + ["AUTO"],
value='AUTO',
description='Device:',
disabled=False,
)
device
Dropdown(description='Device:', index=2, options=('CPU', 'GPU', 'AUTO'), value='AUTO')
单图像推理测试
构建完整的推理函数并测试转换后的 OpenVINO 模型:
core = ov.Core()
# 读取 OpenVINO 模型
pose_ov_model = core.read_model(pose_model_path)
# 对于非 CPU 设备,固定输入形状以优化性能
if device.value != "CPU":
pose_ov_model.reshape({0: [1, 3, 640, 640]})
# 编译模型到指定设备
pose_compiled_model = core.compile_model(pose_ov_model, device.value)
def detect(image:np.ndarray, model:ov.Model):
"""
OpenVINO YOLOv8 模型推理函数
参数:
image (np.ndarray):输入图像
model (Model):OpenVINO 编译模型
返回:
detections (np.ndarray):包含检测框和关键点的字典列表
"""
preprocessed_image = preprocess_image(image)
input_tensor = image_to_tensor(preprocessed_image)
result = model(input_tensor)
boxes = result[model.output(0)]
input_hw = input_tensor.shape[2:]
detections = postprocess(pred_boxes=boxes, input_hw=input_hw, orig_img=image)
return detections
# 加载测试图像并进行推理
input_image = np.array(Image.open(IMAGE_PATH))
detections = detect(input_image, pose_compiled_model)[0]
image_with_boxes = draw_results(detections, input_image, label_map)
Image.fromarray(image_with_boxes)

实验结果表明,转换后的 OpenVINO 模型能够产生与原始模型一致的推理结果,验证了模型转换的正确性。
模型精度评估
为了科学衡量模型性能,需要在标准数据集上进行精度评估。YOLOv8 预训练模型基于 COCO 数据集训练,因此使用该数据集的验证集进行评估。
验证数据集准备
下载 COCO 验证集及对应的标签文件:
from zipfile import ZipFile
# 数据集和配置文件 URL
DATA_URL = "http://images.cocodataset.org/zips/val2017.zip"
LABELS_URL = "https://github.com/ultralytics/yolov5/releases/download/v1.0/coco2017labels-segments.zip"
CFG_URL = "https://raw.githubusercontent.com/ultralytics/ultralytics/8ebe94d1e928687feaa1fee6d5668987df5e43be/ultralytics/datasets/coco-pose.yaml"
OUT_DIR = Path('./datasets')
# 定义文件路径
DATA_PATH = OUT_DIR / "val2017.zip"
LABELS_PATH = OUT_DIR / "coco2017labels-segments.zip"
CFG_PATH = OUT_DIR / "coco-pose.yaml"
# 下载文件
download_file(DATA_URL, DATA_PATH.name, DATA_PATH.parent)
download_file(LABELS_URL, LABELS_PATH.name, LABELS_PATH.parent)
download_file(CFG_URL, CFG_PATH.name, CFG_PATH.parent)
# 解压文件(如果尚未解压)
if not (OUT_DIR / "coco/labels").exists():
with ZipFile(LABELS_PATH , "r") as zip_ref:
zip_ref.extractall(OUT_DIR)
with ZipFile(DATA_PATH , "r") as zip_ref:
zip_ref.extractall(OUT_DIR / 'coco/images')
'datasets/val2017.zip' 已存在。
'datasets/coco2017labels-segments.zip' 已存在。
datasets/coco-pose.yaml: 0%| | 0.00/781 [00:00<?, ?B/s]
精度评估函数
定义用于模型精度评估的函数,计算精确率(Precision)、召回率(Recall)和平均精度(mAP)等指标:
from tqdm.notebook import tqdm
from ultralytics.utils.metrics import ConfusionMatrix
def test(model:ov.Model, core:ov.Core, data_loader:torch.utils.data.DataLoader, validator, num_samples:int = None):
"""
OpenVINO YOLOv8 模型精度验证函数
参数:
model (Model):OpenVINO 模型
data_loader (torch.utils.data.DataLoader):数据集加载器
validator:验证器类实例
num_samples (int,可选):验证样本数量,None 表示使用全部样本
返回:
stats: (Dict[str, float]) - 包含聚合精度指标的字典
"""
# 初始化验证器状态
validator.seen = 0
validator.jdict = []
validator.stats = []
validator.batch_i = 1
validator.confusion_matrix = ConfusionMatrix(nc=validator.nc)
# 调整模型输入形状并编译
model.reshape({0: [1, 3, -1, -1]})
compiled_model = core.compile_model(model)
# 迭代数据集进行验证
for batch_i, batch in enumerate(tqdm(data_loader, total=num_samples)):
if num_samples is not None and batch_i == num_samples:
break
batch = validator.preprocess(batch)
results = compiled_model(batch["img"])
preds = torch.from_numpy(results[compiled_model.output(0)])
preds = validator.postprocess(preds)
validator.update_metrics(preds, batch)
# 获取并返回统计结果
stats = validator.get_stats()
return stats
def print_stats(stats:np.ndarray, total_images:int, total_objects:int):
"""
打印精度统计信息的辅助函数
参数:
stats: (Dict[str, float]) - 精度指标字典
total_images (int) - 评估图像数量
total_objects (int) - 总对象数量
"""
print("Boxes:")
mp, mr, map50, mean_ap = stats['metrics/precision(B)'], stats['metrics/recall(B)'], stats['metrics/mAP50(B)'], stats['metrics/mAP50-95(B)']
# 打印表头
s = ('%20s' + '%12s' * 6) % ('Class', 'Images', 'Labels', 'Precision', 'Recall', 'mAP@.5', 'mAP@.5:.95')
print(s)
# 打印结果
pf = '%20s' + '%12i' * 2 + '%12.3g' * 4
print(pf % ('all', total_images, total_objects, mp, mr, map50, mean_ap))
# 如果存在关键点指标,则打印
if 'metrics/precision(M)' in stats:
s_mp, s_mr, s_map50, s_mean_ap = stats['metrics/precision(M)'], stats['metrics/recall(M)'], stats['metrics/mAP50(M)'], stats['metrics/mAP50-95(M)']
s = ('%20s' + '%12s' * 6) % ('Class', 'Images', 'Labels', 'Precision', 'Recall', 'mAP@.5', 'mAP@.5:.95')
print(s)
pf = '%20s' + '%12i' * 2 + '%12.3g' * 4
print(pf % ('all', total_images, total_objects, s_mp, s_mr, s_map50, s_mean_ap))
配置验证器和数据加载器
使用 YOLOv8 提供的验证器类进行精度评估,配置相关参数并创建数据加载器:
from ultralytics.utils import DEFAULT_CFG
from ultralytics.cfg import get_cfg
from ultralytics.data.utils import check_det_dataset
# 配置验证参数
args = get_cfg(cfg=DEFAULT_CFG)
args.data = 'coco8-pose.yaml'
args.model = 'yolov8n-pose.pt'
from ultralytics.models.yolo.pose import PoseValidator
# 创建姿态估计验证器实例
pose_validator = PoseValidator(args=args)
# 检查数据集并创建数据加载器
pose_validator.data = check_det_dataset(args.data)
pose_data_loader = pose_validator.get_dataloader("datasets/coco8-pose", 1)
val: Scanning datasets/coco8-pose/labels/train.cache... 8 images, 0 backgrounds, 0 corrupt: 100%|██████████| 8/8 [00:00<?, ?it/s]
from ultralytics.utils.metrics import OKS_SIGMA
# 配置验证器参数
pose_validator.is_coco = True
pose_validator.names = pose_model.model.names
pose_validator.metrics.names = pose_validator.names
pose_validator.nc = pose_model.model.model[-1].nc
pose_validator.sigma = OKS_SIGMA # 关键点评估的 OKS sigma 参数
执行精度评估
为了减少评估时间,可以指定评估样本数量。在实际应用中,建议使用完整数据集进行评估以获得准确结果:
NUM_TEST_SAMPLES = 300 # 评估样本数量,None 表示使用全部样本
# 对 OpenVINO 模型进行精度评估
fp_pose_stats = test(pose_ov_model, core, pose_data_loader, pose_validator, num_samples=NUM_TEST_SAMPLES)
0%| | 0/300 [00:00<?, ?it/s]
# 打印评估结果
print_stats(fp_pose_stats, pose_validator.seen, pose_validator.nt_per_class.sum())
Boxes:
Class Images Labels Precision Recall mAP@.5 mAP@.5:.95
all 8 21 1 0.9 0.955 0.736
评估指标说明:
- Precision(精确率):模型预测为正的样本中实际为正的比例,衡量模型识别相关对象的精确程度。
- Recall(召回率):实际为正的样本中被模型正确预测的比例,衡量模型检测所有真实对象的能力。
- mAP@.5:在 IoU 阈值为 0.5 时计算的平均精度均值,反映模型在中等重叠要求下的性能。
- mAP@.5:.95:在 IoU 阈值从 0.5 到 0.95(步长 0.05)范围内计算的平均精度均值,综合反映模型在不同重叠要求下的性能。
使用 NNCF 进行模型量化优化
模型量化是一种有效的模型压缩和加速技术,能够在保持精度的同时显著提升推理性能。NNCF(Neural Network Compression Framework)提供了便捷的模型量化工具,支持 Post-training 量化模式(无需重新训练)。
量化数据集准备
量化过程需要少量校准数据来确定量化参数,这里使用验证数据集的子集作为量化校准集:
import nncf # noqa: F811
from typing import Dict
def transform_fn(data_item:Dict):
"""
量化转换函数,从数据加载器项中提取并预处理输入数据
参数:
data_item: Dict 数据加载器产生的数据项
返回:
input_tensor: 用于量化的输入数据
"""
input_tensor = pose_validator.preprocess(data_item)['img'].numpy()
return input_tensor
# 创建量化数据集
quantization_dataset = nncf.Dataset(pose_data_loader, transform_fn)
INFO:nncf:NNCF 初始化成功。检测到支持的框架:torch, tensorflow, onnx, openvino
模型量化
使用 NNCF 对 OpenVINO 模型进行 8 位量化。由于 YOLOv8 模型包含非 ReLU 激活函数,采用混合量化策略(权重对称量化,激活非对称量化)以获得更好的精度:
# 定义量化过程中需要忽略的操作范围
ignored_scope = nncf.IgnoredScope(
types=["Multiply", "Subtract", "Sigmoid"], # 按类型忽略的操作
names=[
"/model.22/dfl/conv/Conv", # 后处理子图中的操作
"/model.22/Add",
"/model.22/Add_1",
"/model.22/Add_2",
"/model.22/Add_3",
"/model.22/Add_4",
"/model.22/Add_5",
"/model.22/Add_6",
"/model.22/Add_7",
"/model.22/Add_8",
"/model.22/Add_9",
"/model.22/Add_10"
]
)
# 对姿态估计模型进行量化
quantized_pose_model = nncf.quantize(
pose_ov_model,
quantization_dataset,
preset=nncf.QuantizationPreset.MIXED,
ignored_scope=ignored_scope
)
INFO:nncf:12 ignored nodes was found by name in the NNCFGraph
INFO:nncf:12 ignored nodes was found by types in the NNCFGraph
INFO:nncf:Not adding activation input quantizer for operation: 134 /model.22/Mul_6
145 /model.22/Add_12
INFO:nncf:Not adding activation input quantizer for operation: 135 /model.22/Sigmoid_1
INFO:nncf:Not adding activation input quantizer for operation: 156 /model.22/Mul_7
INFO:nncf:Not adding activation input quantizer for operation: 144 /model.22/Sigmoid
INFO:nncf:Not adding activation input quantizer for operation: 174 /model.22/dfl/conv/Conv
INFO:nncf:Not adding activation input quantizer for operation:
196 /model.22/Sub
INFO:nncf:Not adding activation input quantizer for operation: 197 /model.22/Add_10
INFO:nncf:Not adding activation input quantizer for operation: 212 /model.22/Sub_1
INFO:nncf:Not adding activation input quantizer for operation: 239 /model.22/Mul_5
Statistics collection: 3%|███▉ | 8/300 [00:01<00:38, 7.55it/s]
Applying Fast Bias correction: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 72/72 [00:03<00:00, 19.73it/s]
保存量化模型
将量化后的模型保存为 OpenVINO IR 格式,以便后续部署使用:
from openvino.runtime import serialize
# 定义量化模型保存路径
int8_model_pose_path = models_dir / f'{POSE_MODEL_NAME}_openvino_int8_model/{POSE_MODEL_NAME}.xml'
print(f"量化关键点检测模型将保存到 {int8_model_pose_path}")
serialize(quantized_pose_model, str(int8_model_pose_path))
量化关键点检测模型将保存到 models/yolov8n-pose_openvino_int8_model/yolov8n-pose.xml
量化模型推理验证
验证量化模型的推理功能是否正常,确保量化过程没有破坏模型的基本功能:
device
Dropdown(description='Device:', index=2, options=('CPU', 'GPU', 'AUTO'), value='AUTO')
# 配置量化模型并进行推理
if device.value != "CPU":
quantized_pose_model.reshape({0: [1, 3, 640, 640]})
quantized_pose_compiled_model = core.compile_model(quantized_pose_model, device.value)
# 加载测试图像并执行推理
input_image = np.array(Image.open(IMAGE_PATH))
detections = detect(input_image, quantized_pose_compiled_model)[0]
image_with_boxes = draw_results(detections, input_image, label_map)
Image.fromarray(image_with_boxes)

可视化结果表明,量化模型能够正确检测人体并估计关键点,与原始模型的输出保持一致。
模型性能对比
使用 OpenVINO 提供的 Benchmark Tool 对原始 FP32 模型和量化 INT8 模型的推理性能进行对比测试:
device
Dropdown(description='Device:', index=2, options=('CPU', 'GPU', 'AUTO'), value='AUTO')
FP32 模型性能测试
# 推理 FP32 模型(OpenVINO IR)
!benchmark_app -m $pose_model_path -d $device.value -api async -shape "[1,3,640,640]"
[Step 1/11] 解析和验证输入参数
[ INFO ] 解析输入参数
[Step 2/11] 加载 OpenVINO 运行时
[ WARNING ] 未知设备 AUTO 的默认持续时间 120 秒
[ INFO ] OpenVINO:
[ INFO ] 版本 ................................. 2023.2.0-12690-0ee0b4d9561
[ INFO ]
[ INFO ] 设备信息:
[ INFO ] AUTO
[ INFO ] 版本 ................................. 2023.2.0-12690-0ee0b4d9561
[ INFO ]
[ INFO ]
[Step 3/11] 设置设备配置
[ WARNING ] 性能提示未在命令行中明确指定。设备(AUTO)性能提示将设置为 PerformanceMode.THROUGHPUT。
[Step 4/11] 读取模型文件
[ INFO ] 加载模型文件
[ INFO ] 读取模型耗时 17.85 毫秒
[ INFO ] 原始模型 I/O 参数:
[ INFO ] 模型输入:
[ INFO ] images (node: images) : f32 / [...] / [?,3,?,?]
[ INFO ] 模型输出:
[ INFO ] output0 (node: output0) : f32 / [...] / [?,56,?]
[Step 5/11] 调整模型大小以匹配图像大小和给定批量
[ INFO ] 模型批量大小:1
[ INFO ] 调整模型形状:'images': [1,3,640,640]
[ INFO ] 调整模型形状耗时 11.94 毫秒
[Step 6/11] 配置模型输入
[ INFO ] 模型输入:
[ INFO ] images (node: images) : u8 / [N,C,H,W] / [1,3,640,640]
[ INFO ] 模型输出:
[ INFO ] output0 (node: output0) : f32 / [...] / [1,56,8400]
[Step 7/11] 将模型加载到设备
[ INFO ] 编译模型耗时 410.27 毫秒
[Step 8/11] 查询最佳运行时参数
[ INFO ] 模型:
[ INFO ] NETWORK_NAME: torch_jit
[ INFO ] EXECUTION_DEVICES: ['CPU']
[ INFO ] PERFORMANCE_HINT: PerformanceMode.THROUGHPUT
[ INFO ] OPTIMAL_NUMBER_OF_INFER_REQUESTS: 12
[ INFO ] MULTI_DEVICE_PRIORITIES: CPU
[ INFO ] CPU:
[ INFO ] AFFINITY: Affinity.CORE
[ INFO ] CPU_DENORMALS_OPTIMIZATION: False
[ INFO ] CPU_SPARSE_WEIGHTS_DECOMPRESSION_RATE: 1.0
[ INFO ] ENABLE_CPU_PINNING: True
[ INFO ] ENABLE_HYPER_THREADING: True
[ INFO ] EXECUTION_DEVICES: ['CPU']
[ INFO ] EXECUTION_MODE_HINT: ExecutionMode.PERFORMANCE
[ INFO ] INFERENCE_NUM_THREADS: 36
[ INFO ] INFERENCE_PRECISION_HINT: <Type: 'float32'>
[ INFO ] NETWORK_NAME: torch_jit
[ INFO ] NUM_STREAMS: 12
[ INFO ] OPTIMAL_NUMBER_OF_INFER_REQUESTS: 12
[ INFO ] PERFORMANCE_HINT: PerformanceMode.THROUGHPUT
[ INFO ] PERFORMANCE_HINT_NUM_REQUESTS: 0
[ INFO ] PERF_COUNT: False
[ INFO ] SCHEDULING_CORE_TYPE: SchedulingCoreType.ANY_CORE
[ INFO ] MODEL_PRIORITY: Priority.MEDIUM
[ INFO ] LOADED_FROM_CACHE: False
[Step 9/11] 创建推理请求并准备输入张量
[ WARNING ] 未为输入 'images' 提供输入文件!此输入将用随机值填充!
[ INFO ] 用随机值填充输入 'images'
[Step 10/11] 测量性能(开始异步推理,12 个推理请求,限制:120000 毫秒持续时间)
[ INFO ] 仅在推理模式下进行基准测试(输入填充不包含在测量循环中)。
[ INFO ] 首次推理耗时 33.91 毫秒
[Step 11/11] 转储统计报告
[ INFO ] 执行设备:['CPU']
[ INFO ] 计数: 18420 次迭代
[ INFO ] 持续时间: 120067.97 毫秒
[ INFO ] 延迟:
[ INFO ] 中位数: 74.24 毫秒
[ INFO ] 平均值: 78.05 毫秒
[ INFO ] 最小值: 39.74 毫秒
[ INFO ] 最大值: 165.06 毫秒
[ INFO ] 吞吐量: 153.41 FPS
INT8 模型性能测试
# 推理 INT8 模型(OpenVINO IR)
!benchmark_app -m $int8_model_pose_path -d $device.value -api async -shape "[1,3,640,640]" -t 15
[Step 1/11] 解析和验证输入参数
[ INFO ] 解析输入参数
[Step 2/11] 加载 OpenVINO 运行时
[ INFO ] OpenVINO:
[ INFO ] 版本 ................................. 2023.2.0-12690-0ee0b4d9561
[ INFO ]
[ INFO ] 设备信息:
[ INFO ] AUTO
[ INFO ] 版本 ................................. 2023.2.0-12690-0ee0b4d9561
[ INFO ]
[ INFO ]
[Step 3/11] 设置设备配置
[ WARNING ] 性能提示未在命令行中明确指定。设备(AUTO)性能提示将设置为 PerformanceMode.THROUGHPUT。
[Step 4/11] 读取模型文件
[ INFO ] 加载模型文件
[ INFO ] 读取模型耗时 29.51 毫秒
[ INFO ] 原始模型 I/O 参数:
[ INFO ] 模型输入:
[ INFO ] images (node: images) : f32 / [...] / [1,3,?,?]
[ INFO ] 模型输出:
[ INFO ] output0 (node: output0) : f32 / [...] / [1,56,21..]
[Step 5/11] 调整模型大小以匹配图像大小和给定批量
[ INFO ] 模型批量大小:1
[ INFO ] 调整模型形状:'images': [1,3,640,640]
[ INFO ] 调整模型形状耗时 16.46 毫秒
[Step 6/11] 配置模型输入
[ INFO ] 模型输入:
[ INFO ] images (node: images) : u8 / [N,C,H,W] / [1,3,640,640]
[ INFO ] 模型输出:
[ INFO ] output0 (node: output0) : f32 / [...] / [1,56,8400]
[Step 7/11] 将模型加载到设备
[ INFO ] 编译模型耗时 732.13 毫秒
[Step 8/11] 查询最佳运行时参数
[ INFO ] 模型:
[ INFO ] NETWORK_NAME: torch_jit
[ INFO ] EXECUTION_DEVICES: ['CPU']
[ INFO ] PERFORMANCE_HINT: PerformanceMode.THROUGHPUT
[ INFO ] OPTIMAL_NUMBER_OF_INFER_REQUESTS: 18
[ INFO ] MULTI_DEVICE_PRIORITIES: CPU
[ INFO ] CPU:
[ INFO ] AFFINITY: Affinity.CORE
[ INFO ] CPU_DENORMALS_OPTIMIZATION: False
[ INFO ] CPU_SPARSE_WEIGHTS_DECOMPRESSION_RATE: 1.0
[ INFO ] ENABLE_CPU_PINNING: True
[ INFO ] ENABLE_HYPER_THREADING: True
[ INFO ] EXECUTION_DEVICES: ['CPU']
[ INFO ] EXECUTION_MODE_HINT: ExecutionMode.PERFORMANCE
[ INFO ] INFERENCE_NUM_THREADS: 36
[ INFO ] INFERENCE_PRECISION_HINT: <Type: 'float32'>
[ INFO ] NETWORK_NAME: torch_jit
[ INFO ] NUM_STREAMS: 18
[ INFO ] OPTIMAL_NUMBER_OF_INFER_REQUESTS: 18
[ INFO ] PERFORMANCE_HINT: PerformanceMode.THROUGHPUT
[ INFO ] PERFORMANCE_HINT_NUM_REQUESTS: 0
[ INFO ] PERF_COUNT: False
[ INFO ] SCHEDULING_CORE_TYPE: SchedulingCoreType.ANY_CORE
[ INFO ] MODEL_PRIORITY: Priority.MEDIUM
[ INFO ] LOADED_FROM_CACHE: False
[Step 9/11] 创建推理请求并准备输入张量
[ WARNING ] 未为输入 'images' 提供输入文件!此输入将用随机值填充!
[ INFO ] 用随机值填充输入 'images'
[Step 10/11] 测量性能(开始异步推理,18 个推理请求,限制:15000 毫秒持续时间)
[ INFO ] 仅在推理模式下进行基准测试(输入填充不包含在测量循环中)。
[ INFO ] 首次推理耗时 26.46 毫秒
[Step 11/11] 转储统计报告
[ INFO ] 执行设备:['CPU']
[ INFO ] 计数: 6426 次迭代
[ INFO ] 持续时间: 15072.05 毫秒
[ INFO ] 延迟:
[ INFO ] 中位数: 40.12 毫秒
[ INFO ] 平均值: 42.00 毫秒
[ INFO ] 最小值: 27.49 毫秒
结论
本研究实现了 YOLOv8 姿态估计模型到 OpenVINO IR 格式的转换,并利用 NNCF 工具进行了 8 位量化优化。实验结果表明:
- 转换后的 OpenVINO 模型能够保持与原始模型相同的推理精度和可视化效果。
- 量化后的 INT8 模型在几乎不损失精度的前提下,显著提升了推理性能,吞吐量从 153.41 FPS 提升至约 426.36 FPS(根据 15 秒内 6426 次迭代计算),延迟中位数从 74.24 毫秒降低至 40.12 毫秒。
- 模型量化同时减少了模型存储空间(约 4 倍压缩),更适合部署在资源受限的边缘设备上。
本方案为 YOLOv8 姿态估计模型的高效部署提供了可行的技术路径,特别适用于需要实时性能的应用场景,如视频监控、运动分析等领域。
3956

被折叠的 条评论
为什么被折叠?



