openvino入门:轻松调用预训练模型进行图像识别(你的第一个目标检测程序!)
OpenVINO™ (Open Visual Inference & Neural network Optimization) 是一个用于优化和部署深度学习模型的综合工具套件。
openvino入门:轻松调用预训练模型进行图像识别(你的第一个目标检测程序!)
大家好!你是否对深度学习模型的部署和加速感兴趣?想知道如何让你的AI应用在CPU或Intel集成显卡上也能跑的飞快吗?那么,Intel的OpenVINO"工具套件就是你的不二之选"!
今天,我们将开启OpenVINO学习之旅的第一步:学习如何使用OpenVINO C++ API来加载一个预训练好的目标检测模型(例如YOLO系列),并用它来识别图片或视频种的物体。这篇博客将侧重于同步推理的基础流程,一步步带你理解从模型加载到结果呈现的全过程。
什么是OpenVINO?
OpenVINO™ (Open Visual Inference & Neural network Optimization) 是一个用于优化和部署深度学习模型的综合工具套件。简单来说,它像一个“翻译官”和“加速器”:
翻译官:它可以读取不同深度学习框架(如TensorFlow,PyTorch,ONNX)训练出来的模型
加速器:它能针对Intel的各种硬件(CPU、集成显卡IGPU、专用视觉处理单元VPU等)进行深度优化,让模型跑的更快、更省电。
我们的目标:构建一个简单的目标检测程序
我们将编写一个C++程序,它可以:
1.加载一个预训练的目标检测模型(比如常见的ONNX格式)
2.读取一张图片
3.对图片进行预处理,使其符合模型的输入要求。
4.使用OpenVINO执行模型推理。
5.对模型的输出进行后处理,提取有用的检测结果(如物体位置、类别、置信度)
6.在原始图片上绘制检测框并显示
核心步骤概览(一步一个脚印):
1.环境准备:确保你已经安装了OpenVINO SDK和OpenCV。
2.初始化OpenVINO Core:这是与OpenVINO运行时交互的起点。
3.读取模型:将模型文件加载到内存中。
4.配置模型输入输出(重要!):告诉OpenVINO你的数据是什么样的,模型期望什么样的数据。
5.编译模型:OpenVINO根据你的目标硬件和配置优化模型。
6.创建推理请求:获取一个用于执行单次推理的对象。
7.准备输入数据:
- 用OpenCV读取图片
- 预处理:这是关键的一步,包括缩放图像到模型指定尺寸、调整颜色通道顺序、归一化像素值,并将其转换成模型能够理解的“Blob”格式。
8.执行推理(同步):将预处理好的数据送入模型,等待计算结果
9.处理输出数据:
- 解析:模型的原始输出通常是一堆数字(张量),需要按照模型的定义来解读它们。
- 置信度阈值过滤:只保留“比较确定”的检测结果。
- 非极大值抑制(NMS):消除对同一物体的重复检测框。
10.可视化结果:将检测框和标签画在图片上。
让我们可是编码!(同步推理详解)
首先,定义一些程序需要的常量和包含必要的头文件:
#include <iostream>
#include <vector>
#include <string>
#include <chrono> //用于计时
#include <opencv2/opencv.hpp> //OpenCV核心功能、图片读写、绘图
#include <opencv2/dnn.hpp> //OpenCV的DNN模块,我们将用它进行图像预处理(blobFromImage)和NMS
#include <openvino/openvino.hpp> //OpenVINO核心头文件
// 如果使用旧版OpenVINO (2023.2之前),可能需要 #include <inference_engine.hpp>
// --- 基本配置 ---
const std::vector<std::string> CLASS_NAMES = { //你的模型能够识别的类别名称列表
// 示例: "person", "car", "dog", "cat"... 根据你的模型修改
};
const std::string MODEL_PATH = "D:/path/to/your/model/best.onnx"; //修改为你的模型文件路径(.onnx或.xml(IR模型))
const std::string INPUT_IMAGE_PATH = "D:/path/to/your/image.jpg"; // 修改为你要识别的图片路径
// --- 模型相关参数(非常重要,必须与你的模型一致!) ---
const int MODEL_INPUT_WIDTH = 640; //模型期望的输入图像宽度(像素)
const int MODEL_INPUT_HEIGHT = 640; //模型期望的输入图像高度(像素)
// --- 后处理参数 ---
const float CONFIDENCE_THRESHOLD = 0.5f; //置信度阈值
const float NMS_THRESHOLD = 0.4f; //非极大值抑制(NMS)的IOU阈值
//绘图函数声明(具体实现在main函数后)
void draw_detections(cv::Mat& frame,
const std::vector<cv::Rect>& boxes,
const std::vector<float>& confidences,
const std::vector<int>& class_ids,
const std::vector<int>& indices,
const std::vector<std::string>& class_name);
int main(){
std::cout << "--- OpenVINO Synchronous Inference Demo ---" << std::endl;
//1. 初始化 OpenVINO Core
//ov::Core 是OpenVINO运行时的主要入口点,用于设备管理和模型操作。
ov::Core core;
std::cout << "Step 1: OpenVINO Core initialized." << std::endl;
//2.读取模型
//ov::Model 对象代表了从磁盘加载的神经网络结构
std::shared_ptr<ov::Model> model;
try{
// core.read_mode() 可以读取OpenVINO IR格式(.xml和.bin) 或ONNX格式(.onnx)
model = core.read_model(MODEL_PATH);
std::cout << "Step 2: Model loaded successfully from: " << MODEL_PATH << std::endl;
}catch(const std::exception& e){
std::cerr << "Model loading failed: " << e.what() << std::endl;
return -1;
}
// --- 理解模型输入:MODEL_INPUT_WIDTH 和 MODEL_INPUT_HEIGHT ---
//这两个值定义了神经网络在训练时接收的图片尺寸。
//例如,YOLOv11 可以使用640×640 或者 1280×1280 的输入
//我们输入的任何图像,在送入模型前都必须被缩放(或填充)到这个尺寸。
//如果尺寸不匹配,推理结果通常时无意义的。
//3.配置模型输入和输出(可选,可以不用这步,只是让你了解一下模型输入的预处理)
//这一步告诉OpenVINO我们打算如何向模型提供数据,以及期望如何接收数据。
//ov::preprocess::PrePostProcessor(PPP) 是一个强大的工具,用于定义预处理和后处理步骤。
ov::preprocess::PrePostProcessor ppp(model);
//配置模型的第一个输入(通常图像模型只有一个图像输入)
// ppp.input() 获取默认的第一个输入端口的配置器
ppp.input().tensor()
//.set_element_type(ov::element::u8):告诉OpenVINO,我们将提供原始的8位无符号整数像素数据(0-255)。
//如果你的预处理直接输出浮点数,这里可以是 ov::element::f32。
//u8通常与后续的.convert_element_type(ov::element::f32)和.scale()结合使用。
.set_element_type(ov::element::u8)
//.set_layout("NHWC"):指定输入张量数据的布局。
// N : Batch size (批处理大小,一次处理多少张图片)
// H : Height (高度)
// W : Width (宽度)
// C : Channels (颜色通道数,如RGB是3)
// OopenCV的cv::Mat默认是HWC布局。当我们创建一个批次(即使是单张图片),就变成了NHWC.
// 如果你的模型内部需要不同的布局(如NCHW),我们稍后会转换。
.set_layout("NHWC")
//.set_shape({1, MODEL_INPUT_HEIGHT, MODEL_INPUT_WIDTH, 3}):明确指定输入张量的形状。
// {1, ...}: Batch size 为 1,表示一次推理一张图片。
//MODEL_INPUT_HEIGHT, MODEL_INPUT_WIDTH: 之前定义的模型输入高宽。
//3: 3个颜色通道 (BGR 或 RGB)。
.set_shape({1, static_cast<unsigned long>(MODEL_INPUT_HEIGHT), static_cast<unsigned long>(MODEL_INPUT_WIDTH), 3});
//模型内部可能期望不同的数据类型或布局,PPP可以处理转换:
ppp.input().preprocess() //获取预处理步骤配置器
//.convert_layout("NCHW"):如果模型期望NCHW布局,在这里转换。
// NCHW = Batch, Channels, Height,Width。很多PyTorch模型使用此布局。
.convert_layout("NCHW")
// .convert_element_type(ov::element::f32):将u8像素值转换为32位浮点数。
//深度学习模型通常在内部使用浮点数进行计算.
.convert_element_type(ov::element::f32)
// .scale(255.0f): 对像素值进行归一化,通常是除以255,使像素值在0.0到1.0之间。
// 这是常见的预处理步骤。有些模型可能需要不同的归一化方式(如减均值除标准差)。
.scale(255.0f); // 等价于 value / 255.0f
//配置模型的第一个输出(目标检测模型可能有多个输出,这里假设最主要的那个)
// ppp.output() 或 ppp.output(output_port_index_or_name)
ppp.output(0).tensor() //获取第一个输出端口的配置器
// .set_element_type(ov::element::f32): 假设模型的输出是32位浮点数。
//这很常见,因为输出通常包含坐标和置信度等连续值。
.set_element_type(ov::element::f32);
//应用这些预处理/后处理配置到模型上
model = ppp.build();
std::cout << "Step 3: Model pre/post processing configured (Input: u8 NHWC -> f32 NCHW, Output: f32)." << std::endl;
// --- 理解数据类型f32和布局NHWC/NCHW ---
//f32(float32) :指的是32位单精度浮点数。这是神经网络中进行数值计算时常用的数据类型。
//u8(uint8):指的是8位无符号整数,范围0-255。图像像素通常以此格式存储。
//NHWC (Batch,Height,Width,CHannels):
// -N: 批次大小。比如你想同时推理4涨图片,N就是4。对于单张图片,N是1。
// -H: 图像高度。
// -W: 图像宽度。
// -C: 颜色通道。对于RGB或BGR图像,C是3。
// 这是TensorFlow和OpenCV图像中的常见内存布局
// NCHW (Batch, Channels, Height, Width):
// - C H W 是交错存储的。例如,所有R通道值在一起,然后所有G通道值,然后所有B通道值。
// 这是PyTorch和许多Caffe模型常用的布局。
//
// 为什么需要转换?模型在训练时是基于特定数据类型和布局的。推理时必须匹配。
// OpenVINO的PPP可以方便地处理这些转换。
//4.编译模型
// ov::CompiledModel 是针对特定设备(如CPU, GPU)优化和编译后的模型。
// 只有编译后的模型才能创建推理请求。
ov::CompiledModel compiled_model;
try{
//core.compile_model(model,"DEVICE_NAME",properties);
//"CPU":指定在CPU上执行。其他可选值有"CPU",“AUTO”,等。
//ov::hint::Performance_mode: 这是一个属性,用于指导编译过程。
// -ov::hint::PerformanceMode::LATENCY: 优化单次推理的延迟。
// 适合需要快速响应的场景,如实时交互应用。同步推理通常用这个。
// -ov::hint::PerformanceMode::THROUGHPUT: 优化单位时间内的处理量。
//适合需要处理大量数据(如视频流)的场景,常与异步推理结合。
compiled_model = core.compile_model(model,"CPU",ov::hint::performance_mode(ov::hint::PerformanceMode::LATENCY));
std::cout << "Step 4: Model compiled successfully for CPU (LATENCY mode)." << std::endl;
}catch(const std::exception& e){
std::cerr << "Model compilation failed: " << e.what() << std::endl;
return -1;
}
//--- 理解LATENCY vs THROUGHPUT 模式 ---
// - LATENCY(延迟优先)
// 目标是让单次推理尽可能快地完成。OpenVINO会尝试减少从输入到输出的端到端时间。
// 这可能意味着它不会并行优化所有可并行的操作,以避免并行带来的额外开销
// 适合场景:用户点击按钮后立即看到结束、交互式应用。
// - THROUGHPUT(吞吐量优先)
// 目标是在单位时间内处理尽可能多的推理请求。OpenVINO会更积极地使用并行处理、
//流聚合等技术来最大化利用硬件资源。单次请求的延迟可能会略有增加,
//但总体处理能力更强。
//适合场景:视频分析、批量图像处理、服务器端推理。常与异步API和多推理请求池配合。
//5.创建推理请求
//ov::InferRequest 对象用于执行实际的推理。
//对于同步模式,通常一个请求对象就足够了。
ov::InferRequest infer_request = compiled_mode.create_infer_request();
std::cout << "Step 5: Inference request created." << std::endl;
//6.准备输入数据
//使用OpenCV读取图像
cv::Mat frame = cv::imread(INPUT_IMAGE_PATH);
if (frame.empty()) {
std::cerr << "Error: Could not read image: " << INPUT_IMAGE_PATH << std::endl;
return -1;
}
cv::Mat original_frame_for_drawing = frame.clone(); //复制一份用于后续绘制结果
int original_width = frame.cols;
int original_height = frame.rows;
std::cout << "Step 6a: Image loaded. Original size: " << original_width << "x" << original_height << std::endl;
//预处理核心: 将cv::Mat转换为模型输入张量
//我们在第3步配置了PPP,它期望NHWC的u8输入。
//OPENVINO的'infer_request.set_input_tensor()' 可以直接接收一个构造好的'ov::Tensor'。
//这个 ‘ov::Tensor’ 可以共享‘cv::Mat’的数据内存,避免不必要的拷贝。
//首先,确保图像尺寸符合模型输入(如果需要,先用OpenCV缩放)
cv::Mat resized_frame;
if(frame.cols != MODEL_INPUT_WIDTH || frame.rows != MODEL_INPUT_HEIGHT){
cv::resize(frame, resized_frame, cv::Size(MODEL_INPUT_WIDTH, MODEL_INPUT_HEIGHT));
std::cout << "Step 6b: Image resized to model input size: " << MODEL_INPUT_WIDTH << "x" << MODEL_INPUT_HEIGHT << std::endl;
}else{
resized_frame = frame;
}
//创建一个OpenVINO Tensor,它将直接使用 resized_frame 的数据。
//注意:数据必须是连续的。cv::Mat通常是连续的,除非它是从一个更大的Mat中截取的ROI且未clone。
//{1,H,W,C} for NHWC
ov::Shape input_shape = {1, static_cast<unsigned long>(MODEL_INPUT_HEIGHT), static_cast<unsigned long>(MODEL_INPUT_WIDTH), 3};
// resized_frame.data 是指向像素数据的指针 (u8*)
ov::Tensor input_tensor(ov::element::u8, input_shape, resized_frame.data);
//将准备好的输入张量设置给推理请求的第一个输入端口
//compiled_model.input() 获取默认(第一个)输入端口
infer_request.set_input_tensor(input_tensor);
std::cout << "Step 6c: Input tensor prepared and set to infer_request." << std::endl;
// --- 关于批处理 (Batching) ---
//在上面的'input_shape'中,第一个维度 ‘1’代表批处理大小(N)
//这意味着我们一次只向模型发送一张图片进行推理。
//如果你想一次处理多张图片(例如4张),批处理大小N将是4.
//‘input_shape’ 会是 ‘{4,H,W,C}’,并且你需要将4张图片的数据连续地
//存放在 'input_tensor'指向的内存中。
//批处理可以提高吞吐量,因为它允许硬件更有效地并行计算。
//对于同步单图推理,批处理大小通常为1.
//7.执行推理(同步)
std::cout << "Step 7: Starting synchronous inference..." << std::endl;
auto start_time = std::chrono::high_resolution_clock::now();
infer_request.infer(); // 这是同步调用,程序会在此阻塞,直到推理完成。
auto end_time = std::chrono::high_resolution_clock::now();
std::chrono::duration<double, std::milli> infer_duration_ms = end_time - start_time;
std::cout << "Step 7: Inference completed in " << infer_duration_ms.count() << " ms." << std::endl;
//8.处理输出数据
std::cout << "Step 8: Processing output tensor..." << std::endl;
//获取模型的第一个输出张量(大多数目标检测模型的主要输出)
//如果模型有多个输出,可以用infer_request.get_output_tensor(output_port_index_or_name)
const ov::Tensor& output_tensor = infer_request.get_output_tensor();
ov::Shape output_shape = output_tensor.get_shape(); // 获取输出张量的形状
float* output_data = output_tensor.data<float>(); // 获取指向输出数据的指针 (假设是f32)
std::cout << "Output tensor shape: [ ";
for (size_t i = 0; i < output_shape.size(); ++i) {
std::cout << output_shape[i] << (i < output_shape.size() - 1 ? ", " : "");
}
std::cout << " ]" << std::endl;
// **解析YOLO输出 (这里的逻辑需要根据你的模型具体输出格式调整!)**
// 常见的YOLO输出格式之一 (如代码所示的ONNX模型):
// Shape: [batch_size, num_classes + 4, num_proposals]
// 或者 [batch_size, num_proposals, num_classes + 4]
// 其中 4 代表: cx, cy, w, h (中心点x, 中心点y, 宽度, 高度)
// 或 x1, y1, x2, y2 (左上角和右下角坐标)
// num_proposals 是模型生成的候选框数量。
//
// 代码中 `MODEL_PATH` 指向 `best.onnx`,其输出格式为:
// `output_shape[0]` = batch_size (应为1)
// `output_shape[1]` = 4 (cx, cy, w, h) + num_classes (类别分数)
// `output_shape[2]` = num_proposals (检测框的数量)
// 数据排列方式: output_data[box_info_idx * num_proposals + proposal_idx]
std::vector<cv::Rect> boxes; // 存储边界框
std::vector<float> confidences; // 存储每个框的置信度
std::vector<int> class_ids; // 存储每个框的类别ID
int num_output_elements_per_proposal = static_cast<int>(output_shape[1]); // e.g., 4 + 80 classes = 84
int num_model_classes = num_output_elements_per_proposal - 4; // 减去 cx, cy, w, h
int num_proposals = static_cast<int>(output_shape[2]);
if (CLASS_NAMES.size() != num_model_classes) {
std::cerr << "Warning: Number of CLASS_NAMES (" << CLASS_NAMES.size()
<< ") does not match number of classes in model output (" << num_model_classes
<< "). Please check your CLASS_NAMES list and model." << std::endl;
// 如果类别数不匹配,后续的class_ids可能会出错。
}
// 缩放因子:将模型输出的坐标(相对于模型输入尺寸)转换回原始图像尺寸
float scale_x = static_cast<float>(original_width) / MODEL_INPUT_WIDTH;
float scale_y = static_cast<float>(original_height) / MODEL_INPUT_HEIGHT;
for (int i = 0; i < num_proposals; ++i) {
// 对于每个候选框 (proposal i)
// output_data 的组织方式:
// [cx0,cx1,...,cx_N-1, cy0,cy1,...,cy_N-1, w0,...,h0,...,score_cls0_0,...,score_clsM_N-1]
// N = num_proposals, M = num_model_classes
float cx = output_data[0 * num_proposals + i]; // 中心点x
float cy = output_data[1 * num_proposals + i]; // 中心点y
float w = output_data[2 * num_proposals + i]; // 宽度
float h = output_data[3 * num_proposals + i]; // 高度
// 从第4个元素开始是类别分数
float max_class_score = 0.0f;
int class_id = -1;
for (int j = 0; j < num_model_classes; ++j) {
float score = output_data[(4 + j) * num_proposals + i];
if (score > max_class_score) {
max_class_score = score;
class_id = j;
}
}
// --- 理解置信度阈值 (CONFIDENCE_THRESHOLD) ---
// `max_class_score` 是模型对这个框属于 `class_id` 类别的“信心”程度。
// `CONFIDENCE_THRESHOLD` (例如0.5) 是一个门槛:
// 如果 max_class_score > CONFIDENCE_THRESHOLD,我们认为这个检测是有效的。
// 否则,我们忽略它(认为模型不够确定,可能是背景或噪声)。
// 调高此阈值会减少误报(假阳性),但可能漏掉一些真目标(假阴性)。
// 调低则相反。
if (max_class_score > CONFIDENCE_THRESHOLD) {
// 将归一化坐标转换为原始图像坐标
float x1 = (cx - w / 2.0f) * scale_x; // cx,cy是中心点,转换为左上角x
float y1 = (cy - h / 2.0f) * scale_y; // 左上角y
float box_width = w * scale_x;
float box_height = h * scale_y;
boxes.push_back(cv::Rect(static_cast<int>(x1), static_cast<int>(y1),
static_cast<int>(box_width), static_cast<int>(box_height)));
confidences.push_back(max_class_score);
class_ids.push_back(class_id);
}
}
std::cout << "Step 8a: Parsed " << boxes.size() << " detections above confidence threshold." << std::endl;
// **非极大值抑制 (NMS)**
std::vector<int> nms_indices; // 存储通过NMS的检测框在boxes向量中的索引
if (!boxes.empty()) {
// cv::dnn::NMSBoxes 参数:
// boxes: 边界框列表。
// confidences: 对应的置信度列表。
// CONFIDENCE_THRESHOLD: 这里的NMS函数内部可能还会用一次置信度过滤,但我们外部已经做过了。
// 通常这里的score_threshold可以设低一些或与外部一致。
// NMS_THRESHOLD: IOU (Intersection over Union) 阈值。
// nms_indices: 输出,被保留下来的框的索引。
cv::dnn::NMSBoxes(boxes, confidences, CONFIDENCE_THRESHOLD, NMS_THRESHOLD, nms_indices);
}
std::cout << "Step 8b: Performed NMS. Kept " << nms_indices.size() << " detections." << std::endl;
// --- 理解NMS阈值 (NMS_THRESHOLD) ---
// 非极大值抑制 (Non-Maximum Suppression) 用于解决一个常见问题:
// 模型可能对同一个物体检测出多个高度重叠的边界框。
// NMS的工作流程(简化版):
// 1. 选择置信度最高的检测框A。
// 2. 计算A与其他所有框的IOU (交并比)。IOU衡量两个框的重叠程度,值在0到1之间。
// IOU = (A和B的交集面积) / (A和B的并集面积)
// 3. 如果某个框B与A的IOU大于 `NMS_THRESHOLD` (例如0.4),则认为B是A的重复检测,抑制(删除)B。
// 4. 从剩下未被抑制的框中,重复步骤1-3,直到没有框剩下。
// `NMS_THRESHOLD` 越小,NMS越“严格”,会抑制掉更多框 (可能导致漏检)。
// 越大,NMS越“宽松”,可能保留一些重叠框。
// 9. 可视化结果
std::cout << "Step 9: Drawing detections on original image." << std::endl;
draw_detections(original_frame_for_drawing, boxes, confidences, class_ids, nms_indices, CLASS_NAMES);
cv::imshow("OpenVINO Detection - Synchronous Demo", original_frame_for_drawing);
std::cout << "Press any key to exit..." << std::endl;
cv::waitKey(0); // 等待用户按键
cv::destroyAllWindows();
std::cout << "--- Application finished ---" << std::endl;
return 0;
}
//绘制检测框函数,记得声明!
void draw_detections(cv::Mat& frame,
const std::vector<cv::Rect>& boxes,
const std::vector<float>& confidences,
const std::vector<int>& class_ids,
const std::vector<int>& indices, // 这些是NMS后保留的框在原始boxes/confidences/class_ids中的索引
const std::vector<std::string>& class_names) {
for (int idx : indices) { //只遍历NMS筛选后的索引
cv::Rect box = boxes[idx];
float score = confidences[idx];
int class_id_val = class_ids[idx]; // 使用不同的变量名以避免歧义
// 安全检查,确保 class_id_val 在 CLASS_NAMES 的有效范围内
if (class_id_val < 0 || class_id_val >= class_names.size()) {
std::cerr << "Warning: Invalid class_id " << class_id_val
<< " encountered in draw_detections. Max valid index is "
<< class_names.size() - 1 << "." << std::endl;
continue; // 跳过绘制这个无效的框
}
cv::rectangle(frame, box, cv::Scalar(0, 255, 0), 2); // 绿色框
std::string label = class_names[class_id_val] + ": " + cv::format("%.2f", score);
int baseLine;
cv::Size label_size = cv::getTextSize(label, cv::FONT_HERSHEY_SIMPLEX, 0.7, 1, &baseLine);
cv::rectangle(frame,
cv::Point(box.x, box.y - label_size.height - baseLine),
cv::Point(box.x + label_size.width, box.y),
cv::Scalar(0, 255, 0), // 绿色背景
cv::FILLED);
cv::putText(frame, label, cv::Point(box.x, box.y - baseLine),
cv::FONT_HERSHEY_SIMPLEX, 0.7, cv::Scalar(0, 0, 0), 2); // 黑色文字
}
}
关键点回顾与总结:
- 模型是核心:所有配置(输入尺寸、数据类型、布局、输出解析)都必须严格围绕你的模型来定义。仔细阅读模型文档或使用Netron等工具查看模型结构至关重要。
- 预处理不可或缺:原始图像数据几乎总是需要转换才能被模型正确理解。OpenVINO的PrePostProcessor (PPP) 使得这个过程更规范和高效。
- 数据流向:
- cv::Mat (原始图像, HWC, u8)
- cv::resize (如果需要,调整到模型输入尺寸, HWC, u8)
- ov::Tensor (共享cv::Mat数据, NHWC, u8, batch=1)
- PPP (在infer_request内部自动完成):
- u8 -> f32 (类型转换)
- /255.0f (归一化)
- NHWC -> NCHW (布局转换)
- 模型推理 (infer_request.infer())
- ov::Tensor (模型输出, 通常NCHW或特定格式, f32)
- 解析输出数据 (提取boxes, scores, class_ids)
- NMS
- 绘制到原始cv::Mat上
- 同步 vs 异步:我们这里使用的是同步推理 (infer_request.infer())。它简单直接,但对于高性能场景(如视频流处理)可能不是最优的。
下一步是什么?
恭喜你!你已经成功地使用OpenVINO完成了第一个目标检测程序。你理解了模型加载、预处理、同步推理和后处理的基本流程。
但是,如果我们要处理实时视频流,或者希望最大化硬件的吞吐量,这种一次处理一张、并且等待其完成的同步方式可能会遇到瓶颈。CPU在等待推理硬件完成时会空闲,无法充分利用资源。
在下一篇博客中,我们将学习如何利用OpenVINO的异步推理和推理请求池技术,来显著提升我们应用的识别效率和流畅度,让你的AI应用真正“飞”起来!敬请期待!
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐

所有评论(0)