【端侧部署系列】EfficientVit_seg部署至全志开发板
简介
EfficientVit开源项目所在的github仓库地址:https://github.com/mit-han-lab/efficientvit
官方基于Cityscapes(城市街景),和ADE20K(室内外场景)两种数据集分别进行了模型训练,并提供了模型权重,本文以Cityscapes图像分割为例进行测试。
Cityscapes数据集https://www.cityscapes-dataset.com/
EfficientViT针对不同的性能要求,提供了多个版本的efficientvit,L(大模型)和B(轻量)。
本文使用EfficientViT-B1测试,官方权重下载链接如下:
| Model | Resolution | Cityscapes mIoU | Params | MACs | Jetson Nano (bs1) | Jetson Orin (bs1) | Checkpoint |
|---|---|---|---|---|---|---|---|
| EfficientViT-B0 | 1024x2048 | 75.653 | 0.7M | 4.4G | 275ms | 9.9ms | link |
| EfficientViT-B1 | 1024x2048 | 80.547 | 4.8M | 25G | 819ms | 24.3ms | link |
| EfficientViT-B2 | 1024x2048 | 82.073 | 15M | 74G | 1676ms | 46.5ms | link |
| EfficientViT-B3 | 1024x2048 | 83.016 | 40M | 179G | 3192ms | 81.8ms | link |
pt模型推理
在开源项目efficientvit-master文件中,将下载的权重放置assets/checkpoints/efficientvit_seg/中,执行下列代码进行测试,若没有cuda,删除demo_efficientvit_seg_model.py中的.cuda()操作,禁止调用cuda。
python applications/efficientvit_seg/demo_efficientvit_seg_model.py --image_path assets/fig/city.png --dataset cityscapes --crop_size 1024 --model efficientvit-seg-b1-cityscapes --gpu 1
输入图像:
分割结果
pt模型转onnx模型
python assets/onnx_export.py --export_path assets/export_models/efficientvit_seg_b1_cityscapes_r1024x2048.onnx --task seg --model efficientvit-seg-b1-cityscapes --resolution 1024 2048 --bs 1
使用Netron打开onnx文件,模型输入(1, 3, 1024, 2048) RGB 图,输出:(1, 19, 128, 256) 19 个通道的特征图(logits)
onnx模型测试
导出onnx模型后,加上预处理和后处理操作,对onnx进行推理,不再依赖torch框架(注:在官方模型的训练中,该模型的输入图像是直接resize,没有保持高宽比,此处保持一致)
import cv2
import numpy as np
import onnxruntime as ort
# 配置
IMAGE_PATH = "assets/fig/city.png"
ONNX_PATH = "assets/export_models/efficientvit_seg_b1_cityscapes_r1024x2048.onnx"
OUTPUT_PATH = "output_onnx.png"
INPUT_H = 1024
INPUT_W = 2048
MEAN = np.array([0.485, 0.456, 0.406], dtype=np.float32)
STD = np.array([0.229, 0.224, 0.225], dtype=np.float32)
CITY_COLORS = [
[128, 64, 128], [244, 35, 232], [70, 70, 70],
[102, 102, 156], [190, 153, 153], [153, 153, 153],
[250, 170, 30], [220, 220, 0], [107, 142, 35],
[152, 251, 152], [70, 130, 180], [220, 20, 60],
[255, 0, 0], [0, 0, 142], [0, 0, 70],
[0, 60, 100], [0, 80, 100], [0, 0, 230], [119, 11, 32]
]
def preprocess(image_path):
img = cv2.imread(image_path)
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
ori_h, ori_w = img_rgb.shape[:2]
img_rgb = cv2.resize(img_rgb, (INPUT_W, INPUT_H), interpolation=cv2.INTER_AREA)
img_tensor = img_rgb.astype(np.float32) / 255.0
img_tensor = (img_tensor - MEAN) / STD
img_tensor = img_tensor.transpose(2, 0, 1)
img_tensor = np.expand_dims(img_tensor, axis=0)
return img_tensor, ori_h, ori_w, img
def postprocess(output_onnx, ori_h, ori_w, ori_img):
logit = output_onnx[0]
logit = logit.transpose(1, 2, 0)
logit = cv2.resize(logit, (INPUT_W, INPUT_H), interpolation=cv2.INTER_LINEAR)
logit = logit.transpose(2, 0, 1)
pred_mask = np.argmax(logit, axis=0)
pred_mask = cv2.resize(pred_mask, (ori_w, ori_h), interpolation=cv2.INTER_NEAREST)
color_mask = np.zeros((ori_h, ori_w, 3), dtype=np.uint8)
for idx, c in enumerate(CITY_COLORS):
color_mask[pred_mask == idx] = c
color_mask = cv2.cvtColor(color_mask, cv2.COLOR_RGB2BGR)
final = cv2.addWeighted(ori_img, 0.5, color_mask, 0.5, 0)
return final, pred_mask
if __name__ == "__main__":
input_tensor, h, w, ori_img = preprocess(IMAGE_PATH)
print("输入形状:", input_tensor.shape, " 精度:", input_tensor.dtype)
session = ort.InferenceSession(ONNX_PATH, providers=["CPUExecutionProvider"])
output = session.run(None, {session.get_inputs()[0].name: input_tensor})
# 查看输出
out = output[0]
print("输出全局 min = %.6f" % out.min())
print("输出全局 max = %.6f" % out.max())
# 打印第一个像素所有通道
print("第一个像素 (x=0,y=0) 19个通道值:")
pixel = out[0, :, 0, 0]
for i, v in enumerate(pixel):
print(f"类别 {i:2d}: {v:8.3f}")
print("=" * 60)
result, pred_mask = postprocess(out, h, w, ori_img)
print("类别分布:", np.bincount(pred_mask.flatten()))
cv2.imwrite(OUTPUT_PATH, result)
推理结果出现异常:
打印onnx推理过程的输出:
输出第一个像素: [nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan]
发现onnx输出都是nan,但导出的模型onnx确实是有权重参数,在导出onnx过程打印了如下内容
Obtain model graph for `torch.export.export(..., strict=False)
---->定位问题:PyTorch 2.x 新导出器不兼容 EfficientViT!,有些操作没有正常导出,导致ONNX输出 NAN 。
降级torch版本为2.0.1后,重新导出onnx,推理正常:
输入形状: (1, 3, 1024, 2048) 精度: float32 输出第一个像素,19个通道值: [-5.024589 -7.6734905 -0.9376892 -5.3960695 -3.7622366 -2.0407202 -1.7906388 -1.7736986 4.07744 -3.7523015 1.0932604 -4.949296 -6.097727 -2.632298 -2.8067648 -5.3893995 -4.2956705 -5.9038606 -6.673056 ] 类别分布:0:625076 1:306627 2:420187 3:24546 4:25459 5:16729 7:5656 8:372128 10:56722 11:35837 13:208185
交叉编译项目构建
基于model_zoo中的examples项目,构建efficientvit_seg,需要修改的内容如下:
cd ./convert_model/
修改config_yml.py文件的量化数据集(选取部分cityscapes数据集的训练集),预处理参数。
其中,模型训练的预处理参数为
'mean': [0.485, 0.456, 0.406], 'std': [0.229, 0.224, 0.225]转换后 mean=[0.485, 0.456, 0.406] * 255,scale=1/(std * 255)
# "database"
DATASET = ['../../dataset/cityscapes_10/dataset.txt']
DATASET_TYPE = ["TEXT"]
# mean, scale ##根据模型训练中的图像预处理参数
MEAN = [124, 116, 104]
SCALE = [0.0171, 0.0175, 0.0174]
# reverse_channel: True bgr, False rgb
REVERSE_CHANNEL = False
# add_preproc_node, True or False
ADD_PREPROC_NODE = True
# "preproc_type"
PREPROC_TYPE = ["IMAGE_RGB"]
# add_postproc_node, quant output -> float32 output
ADD_POSTPROC_NODE = True
修改model_config.h文件的相关参数配置,定义模型输入尺寸,掩码的色彩
#ifndef _MODEL_CONFIG_H_
#define _MODEL_CONFIG_H_
#include <iostream>
#include <vector>
#include <opencv2/opencv.hpp>
#define Resize_Height 1024
#define Resize_Width 2048
const cv::Vec3b CITY_COLORS[] = {
{128, 64,128}, // 0
{244, 35,232}, // 1
{ 70, 70, 70}, // 2
{102,102,156}, // 3
{190,153,153}, // 4
{153,153,153}, // 5
{250,170, 30}, // 6
{220,220, 0}, // 7
{107,142, 35}, // 8
{152,251,152}, // 9
{ 70,130,180}, // 10
{220, 20, 60}, // 11
{255, 0, 0}, // 12
{ 0, 0,142}, // 13
{ 0, 0, 70}, // 14
{ 0, 60,100}, // 15
{ 0, 80,100}, // 16
{ 0, 0,230}, // 17
{119, 11, 32}, // 18
};
const int NUM_CITY_COLORS = 19;
#endif
模型导入、量化、导出等步骤:(efficientvit_seg_b1_cityscapes_r1024x2048.onnx已重命名为efficientvit_seg.onnx)
# using xxx_env.sh to create softlink
./convert_model_env.sh
# 导入
# pegasus_import.sh <model_name>
./pegasus_import.sh efficientvit_seg
# 量化
# pegasus_quantize.sh <model_name> <quantize_type> <calibration_set_size>
./pegasus_quantize.sh efficientvit_seg int16 10
# 仿真(可选)
# pegasus_inference.sh <model_name> <quantize_type>
./pegasus_inference.sh efficientvit_seg int16
# 导出nb模型
# pegasus_export_ovx_nbg.sh <model_name> <quantize_type> <platform>
./pegasus_export_ovx_nbg.sh efficientvit_seg int16 t736
# 导出的模型文件存放在../model目录
# 例如 ../model/efficientvit_seg_int16_t736.nb
模型输出后处理
模型后处理efficientvit_seg_post.cpp
/*
* Company: AW
* Author: zhanghuans
* Date: 2026/5/25
*/
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <map>
#include <opencv2/opencv.hpp>
#include <sys/time.h>
#include "model_config.h"
// 处理 NPU 输出 → 生成 pred_mask
static cv::Mat get_pred_mask(const cv::Mat& bgr, float** output)
{
const int C = NUM_CITY_COLORS;
const int H_OUT = 128;
const int W_OUT = 256;
const int INPUT_H = Resize_Height;
const int INPUT_W = Resize_Width;
const float* npu_out = output[0];
cv::Mat logit_hwc(H_OUT, W_OUT, CV_32FC(C));
const int HW = H_OUT * W_OUT;
for (int c = 0; c < C; ++c) {
float* ptr_channel = logit_hwc.ptr<float>(0) + c;
for (int i = 0; i < HW; ++i) {
ptr_channel[i * C] = npu_out[c * HW + i];
}
}
// 上采样)
cv::Mat resized_logit;
cv::resize(logit_hwc, resized_logit, cv::Size(INPUT_W, INPUT_H), 0, 0, cv::INTER_LINEAR);
struct timeval tv1, tv2;
gettimeofday(&tv1, NULL);
// argmax
cv::Mat pred_mask(INPUT_H, INPUT_W, CV_8UC1);
const float* src = (const float*)resized_logit.data;
uint8_t* dst = pred_mask.data;
const int INPUT_HW = INPUT_H * INPUT_W;
for (int i = 0; i < INPUT_HW; ++i) {
int max_id = 0;
float max_score = src[0];
for (int c = 1; c < C; ++c) {
if (src[c] > max_score) {
max_score = src[c];
max_id = c;
}
}
dst[i] = max_id;
src += C;
}
gettimeofday(&tv2, NULL);
double total_time_ms = (tv2.tv_sec - tv1.tv_sec) * 1000.0 + (tv2.tv_usec - tv1.tv_usec) / 1000.0;
fprintf(stderr, "argmax time: %.2f ms\n", total_time_ms);
return pred_mask;
}
// 处理 pred_mask + 上色、融合、保存
int efficientvit_seg_postprocess(const char *imagepath, float **output)
{
cv::Mat m = cv::imread(imagepath, 1);
if (m.empty()) {
fprintf(stderr, "cv::imread %s failed\n", imagepath);
return -1;
}
struct timeval tv1, tv2;
gettimeofday(&tv1, NULL);
// 获取 pred_mask
cv::Mat pred_mask = get_pred_mask(m, output);
gettimeofday(&tv2, NULL);
double total_time_ms = (tv2.tv_sec - tv1.tv_sec) * 1000.0 + (tv2.tv_usec - tv1.tv_usec) / 1000.0;
fprintf(stderr, "get pred_mask time: %.2f ms\n", total_time_ms);
/* 调试代码,类别分布
int cls_count[19] = {0};
for (int y = 0; y < pred_mask.rows; y++) {
for (int x = 0; x < pred_mask.cols; x++) {
int c = pred_mask.at<uint8_t>(y, x);
if (c >= 0 && c < 19) cls_count[c]++;
}
}
printf("类别分布:");
for (int i = 0; i < 19; i++) {
if (cls_count[i] > 0) printf("%d:%d ", i, cls_count[i]);
}
printf("\n==================================================\n");
*/
// 还原原图大小
cv::Mat final_mask;
cv::resize(pred_mask, final_mask, m.size(), 0, 0, cv::INTER_NEAREST);
// 上色
cv::Mat color_mask(m.size(), CV_8UC3);
for (int y = 0; y < m.rows; y++) {
for (int x = 0; x < m.cols; x++) {
int idx = final_mask.at<uint8_t>(y, x);
if (idx >= 0 && idx < 19)
color_mask.at<cv::Vec3b>(y, x) = CITY_COLORS_BGR[idx];
else
color_mask.at<cv::Vec3b>(y, x) = {0,0,0};
}
}
// 融合
cv::addWeighted(m, 0.5, color_mask, 0.5, 0, color_mask);
cv::imwrite("output_segmentation.png", color_mask);
fprintf(stderr, "segmentation finished\n");
return 0;
}
板端demo
含demo编译及运行说明。
解压opencv压缩包
# 进入目录
cd ../../../3rdparty/opencv/
# 解压,选择对应平台
# armhf, eg: V85x, R853
unzip opencv-3.4.16-gnueabihf-linux.zip
# linux aarch64, eg: T527/MR527/MR536/T536/A733/T736
unzip opencv-4.9.0-aarch64-linux-sunxi-glibc.zip
# android aarch64, eg: T527/A733/T736
unzip opencv-4.9.0-android.zip
准备交叉编译工具链
Linux
# 进入目录
cd ../../0-toolchains/
# 解压
# armhf, V85x, R853
unzip arm-openwrt-linux-muslgnueabi.zip
chmod 777 -R ./arm-openwrt-linux-muslgnueabi
# aarch64, MR527, T527, MR536, T536, A733, T736
tar xvf gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu.tar.xz
# aarch64 for debian11, T527, A733, T736
tar vxf gcc-arm-10.2-2020.11-x86_64-aarch64-none-linux-gnu.tar.xz
build && run
Linux
在Linux系统下测试。编译用法如下:
# 途径一:在centernet目录编译 cd ../examples//efficientvit_seg/ ./../build_linux.sh -t <platform> [-s <system>] # 途径二:在examples目录,再选择centernet目录编译 cd ../examples ./build_linux.sh -t <platform> -p /efficientvit_seg [-s <system>]
以下说明以T736平台为例;
cd ../examples
./build_linux.sh -t t736 -p efficientvit_seg
push 可执行文件、模型文件、输入图片到板端目录;
adb push install/efficientvit_seg_demo_linux_t736 /mnt/UDISK/
运行;
adb shell
cd /mnt/UDISK//efficientvit_seg_demo_linux_t736
# 可选
export LD_LIBRARY_PATH=./lib
# 运行可执行文件
# ./centernet_demo_t736 -h 查看执行示例说明
chmod +x ./efficientvit_seg_demo_t736
./efficientvit_seg_demo_t736 -nb model//efficientvit_seg_int16_t736.nb -i model/city.png
exit
adb pull /mnt/UDISK//efficientvit_seg_demo_linux_t736/output_segmentation.png D:/zhanghuans/install/output
运行后,打印输出,保存分割结果。
input 0 dim 3 2048 1024 1, data_format=2, quant_format=0, name=input.1_264_out0, none-quant output 0 dim 256 128 19 1, data_format=0, name=uid_1_out_0/out0_0_out0, none-quant nbg name=model//efficientvit_seg_int16_t736.nb, size: 10030512. create network 0: 23366 us. prepare network: 22461 us. buffer ptr: 0xb129300, buffer size: 6291456 feed input cost: 89516 us. network: 0, loop count: 1 run time for this network 0: 566510 us.
更多推荐


所有评论(0)