视线估计嵌入式部署实战:TensorRT与QNN优化

引言:从模型到产品

模型精度 ≠ 产品性能

维度 模型阶段 产品阶段
精度 MAE 4-5° MAE 5-6°(量化损失)
速度 50-100ms <30ms(实时性要求)
平台 NVIDIA GPU 嵌入式NPU
功耗 不限 <5W(车规级)

本文详解如何将视线估计模型部署到嵌入式平台。


一、Euro NCAP实时性要求

1.1 性能指标

指标 要求
推理延迟 <30ms/帧
检测延迟 <2秒(分心报警)
功耗 <5W(车载)
精度损失 <1°(量化)

1.2 计算量分析

GazeCapsNet

  • 参数量:11.7M
  • FLOPs:2.3G
  • 输入:224×224×3

理论性能

平台 算力 理论延迟
Snapdragon 8255 15 TOPS ~15ms
Jetson Orin Nano 40 TOPS ~8ms
NVIDIA GPU (RTX 3080) 30 TFLOPS ~5ms

二、TensorRT加速

2.1 TensorRT优化原理

优化技术

  1. 层融合:合并Conv+BN+ReLU
  2. 精度校准:FP16/INT8量化
  3. 内核自动调优:选择最优CUDA kernel
  4. 动态张量内存:减少内存占用

2.2 导出ONNX

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import torch
import torch.onnx

# 加载模型
model = GazeCapsNet.load_from_checkpoint('checkpoint.pth')
model.eval()

# 导出ONNX
dummy_input = torch.randn(1, 3, 224, 224)

torch.onnx.export(
model,
dummy_input,
'gazecapsnet.onnx',
opset_version=11,
input_names=['image'],
output_names=['gaze'],
dynamic_axes={
'image': {0: 'batch_size'},
'gaze': {0: 'batch_size'}
}
)

print("✅ ONNX模型导出成功")

2.3 TensorRT转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import tensorrt as trt
import pycuda.driver as cuda
import pycuda.autoinit

# 创建TensorRT Builder
logger = trt.Logger(trt.Logger.WARNING)
builder = trt.Builder(logger)

# 创建网络
network = builder.create_network(1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH))

# 解析ONNX
parser = trt.OnnxParser(network, logger)
with open('gazecapsnet.onnx', 'rb') as f:
parser.parse(f.read())

# 配置Builder
config = builder.create_builder_config()

# FP16模式
config.set_flag(trt.BuilderFlag.FP16)

# 设置最大工作空间
config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, 1 << 30) # 1GB

# 构建引擎
engine = builder.build_serialized_network(network, config)

# 保存引擎
with open('gazecapsnet_fp16.engine', 'wb') as f:
f.write(engine)

print("✅ TensorRT引擎构建成功")

2.4 INT8量化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# INT8校准数据集
class CalibrationDataset:
def __init__(self, images):
self.images = images
self.index = 0

def __len__(self):
return len(self.images)

def __getitem__(self, idx):
return self.images[idx]

def next_batch(self, batch_size):
if self.index >= len(self.images):
return None

batch = self.images[self.index:self.index+batch_size]
self.index += batch_size
return batch

# INT8校准
config.set_flag(trt.BuilderFlag.INT8)

# 设置校准器
calibration_data = CalibrationDataset(calibration_images)
calibrator = trt.IInt8EntropyCalibrator2(calibration_data)
config.int8_calibrator = calibrator

# 构建INT8引擎
engine_int8 = builder.build_serialized_network(network, config)

2.5 性能对比

Jetson Orin Nano

精度 延迟 精度损失
FP32 35ms
FP16 18ms 0.1°
INT8 10ms 0.5°

结论:FP16最佳性价比。


三、QNN部署(Qualcomm)

3.1 QNN简介

Qualcomm AI Engine Direct (QNN)

  • Snapdragon平台的NPU加速框架
  • 支持INT8/INT16量化
  • 支持动态量化

3.2 模型转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 安装QNN SDK
pip install qnn

# 转换ONNX到QNN
qnn-onnx-converter \
--input_model gazecapsnet.onnx \
--output_path gazecapsnet.cpp \
--input_list input_list.txt

# 编译QNN模型
qnn-model-lib-generator \
-c gazecapsnet.cpp \
-b gazecapsnet.bin \
-o ./output

3.3 动态量化

1
2
3
4
5
6
7
8
9
10
11
import torch.quantization as quant

# 动态量化(仅权重)
model_quantized = quant.quantize_dynamic(
model,
{nn.Linear, nn.Conv2d},
dtype=torch.qint8
)

# 保存量化模型
torch.save(model_quantized.state_dict(), 'gazecapsnet_dynamic_quant.pth')

3.4 静态量化(需校准)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 准备量化
model.qconfig = quant.get_default_qconfig('qnnpack')
quant.prepare(model, inplace=True)

# 校准(使用100张图片)
with torch.no_grad():
for image in calibration_images:
model(image)

# 转换为INT8
quant.convert(model, inplace=True)

# 验证精度
test_accuracy(model)

3.5 Snapdragon 8255性能

精度 延迟 功耗 精度损失
FP32 28ms 4W
FP16 18ms 3W 0.1°
INT8 10ms 2.5W 0.5°

四、部署实战:Snapdragon 8255

4.1 系统架构

1
2
3
4
5
6
7
8
9
10
11
12
13
┌─────────────────────────────────┐
│ Snapdragon 8255 SoC │
│ ├── CPU (Kryo 670) │
│ │ - 人脸检测 │
│ │ - 数据预处理 │
│ │ │
│ ├── GPU (Adreno 670) │
│ │ - 后处理 │
│ │ │
│ └── NPU (Hexagon DSP) │
│ - GazeCapsNet推理 │
│ - INT8加速 │
└─────────────────────────────────┘

4.2 完整Pipeline

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// C++部署代码(Android)
#include "QnnModel.h"
#include "QnnContext.h"

class GazeEstimator {
public:
GazeEstimator(const std::string& model_path) {
// 加载QNN模型
context_ = QnnContext_create();
model_ = QnnModel_load(model_path);
}

std::pair<float, float> estimate(const cv::Mat& image) {
// 1. 预处理
cv::Mat preprocessed = preprocess(image);

// 2. 推理
auto input_tensor = create_tensor(preprocessed);
auto output_tensor = QnnModel_execute(model_, input_tensor);

// 3. 后处理
float pitch = output_tensor[0];
float yaw = output_tensor[1];

return {pitch, yaw};
}

private:
cv::Mat preprocess(const cv::Mat& image) {
// 缩放到224×224
cv::Mat resized;
cv::resize(image, resized, cv::Size(224, 224));

// 归一化
resized.convertTo(resized, CV_32F);
resized /= 255.0;

return resized;
}

QnnContext_t context_;
QnnModel_t model_;
};

// 使用示例
GazeEstimator estimator("/data/models/gazecapsnet.bin");
auto [pitch, yaw] = estimator.estimate(camera_frame);

4.3 性能优化技巧

技巧1:输入缓存

1
2
3
4
5
6
7
8
9
10
11
// 使用持久化缓冲区
std::vector<uint8_t> input_buffer(224 * 224 * 3);

// 避免每次分配
void process_frame(const cv::Mat& frame) {
// 直接写入缓存
preprocess_to_buffer(frame, input_buffer.data());

// 推理
run_inference(input_buffer.data());
}

技巧2:多线程流水线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 双缓冲
std::queue<Frame> frame_queue;
std::mutex queue_mutex;

// 采集线程
void capture_thread() {
while (running) {
Frame frame = camera.read();
std::lock_guard<std::mutex> lock(queue_mutex);
frame_queue.push(frame);
}
}

// 推理线程
void inference_thread() {
while (running) {
Frame frame;
{
std::lock_guard<std::mutex> lock(queue_mutex);
if (!frame_queue.empty()) {
frame = frame_queue.front();
frame_queue.pop();
}
}

if (!frame.empty()) {
auto gaze = estimator.estimate(frame);
// 处理结果...
}
}
}

五、部署实战:Jetson Orin

5.1 TensorRT Python API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import tensorrt as trt
import pycuda.driver as cuda
import pycuda.autoinit
import numpy as np

class TensorRTInference:
def __init__(self, engine_path):
# 加载引擎
with open(engine_path, 'rb') as f:
runtime = trt.Runtime(trt.Logger())
self.engine = runtime.deserialize_cuda_engine(f.read())

self.context = self.engine.create_execution_context()

# 分配GPU内存
self.inputs, self.outputs, self.bindings = [], [], []
for i in range(self.engine.num_bindings):
binding = self.engine[i]
size = trt.volume(binding.shape) * np.dtype(np.float32).itemsize

# 分配GPU内存
device_memory = cuda.mem_alloc(size)

if binding.is_input:
self.inputs.append(device_memory)
else:
self.outputs.append(device_memory)

self.bindings.append(int(device_memory))

def infer(self, image):
# 预处理
input_data = self.preprocess(image)

# 拷贝到GPU
cuda.memcpy_htod(self.inputs[0], input_data)

# 推理
self.context.execute_v2(self.bindings)

# 拷贝回CPU
output_data = np.empty(2, dtype=np.float32)
cuda.memcpy_dtoh(output_data, self.outputs[0])

return output_data # [pitch, yaw]

def preprocess(self, image):
# 实现预处理
pass

# 使用示例
inference = TensorRTInference('gazecapsnet_fp16.engine')
gaze = inference.inference(frame)

5.2 DeepStream集成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# DeepStream Pipeline
import gi
gi.require_version('Gst', '1.0')
from gi.repository import Gst

# 创建Pipeline
pipeline_str = """
nvarguscamerasrc !
nvvidconv !
video/x-raw,format=RGBA,width=224,height=224 !
nvvidconv !
video/x-raw(memory:NVMM) !
nvinfer config=model-path,engine-path !
nvvidconv !
video/x-raw,format=RGBA !
fakesink
"""

pipeline = Gst.parse_launch(pipeline_str)
pipeline.set_state(Gst.State.PLAYING)

六、精度优化

6.1 量化感知训练

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import torch.quantization as quant

# 模型准备
model.qconfig = quant.get_default_qat_qconfig('qnnpack')
quant.prepare_qat(model, inplace=True)

# 训练(会学习量化参数)
for epoch in range(epochs):
for batch in dataloader:
output = model(batch['image'])
loss = criterion(output, batch['gaze'])
loss.backward()
optimizer.step()

# 转换为量化模型
quant.convert(model, inplace=True)

6.2 精度对比

方法 ETH-XGaze MAE 精度损失
FP32 5.10°
FP16 5.15° 0.05°
INT8(后量化) 5.60° 0.5°
INT8(QAT) 5.20° 0.1°

结论:量化感知训练可显著降低精度损失。


七、总结

7.1 平台选型

平台 适用场景 推荐精度
Snapdragon 8255/8295 车载量产 INT8 + QAT
Jetson Orin 原型开发 FP16
NVIDIA GPU 云端推理 FP16

7.2 优化清单

优化项 效果
FP16量化 延迟↓50%,精度损失<0.1°
INT8量化 延迟↓70%,精度损失~0.5°
QAT训练 精度损失<0.2°
TensorRT 延迟↓60%
QNN 延迟↓65%(Snapdragon)

7.3 Euro NCAP合规检查

  • 推理延迟 <30ms
  • 精度损失 <1°
  • 功耗 <5W
  • 连续运行24小时稳定

参考文献

  1. NVIDIA. “TensorRT Developer Guide.” 2025.
  2. Qualcomm. “QNN SDK Documentation.” 2025.
  3. Euro NCAP. “Driver Monitoring Test Protocol.” 2026.

本文是IMS视线估计系列文章之一,上一篇:数据集深度解析


视线估计嵌入式部署实战:TensorRT与QNN优化
https://dapalm.com/2026/03/13/2026-03-13-视线估计嵌入式部署实战-TensorRT与QNN优化/
作者
Mars
发布于
2026年3月13日
许可协议