此芯P1 NPU部署深度估计depth_anything_v2模型(python和C++)
·
前言
在智能感知的世界里,深度估计如同为机器赋予了“洞察万物远近”的立体视觉。从自动驾驶的避障导航到移动端的AR互动,从工业质检的精密测量到消费电子的影像虚化,理解三维场景的需求无处不在。
近年来,Depth Anything V2 作为单目深度估计领域的新星,以其卓越的泛化能力和精准的细节表现,吸引了大量开发者的目光。此芯P1处理器,凭借其高能效的专用神经网络处理单元,专为边缘AI计算场景设计,为复杂的视觉模型提供强劲且低功耗的推理动力。
本篇博客将手把手带领您完成从模型准备、模型量化,到最终在此芯P1平台上分别通过Python和C++进行高效部署的全过程。
模型准备
onnx模型
跑通github里面的run.py,并导出onnx模型
模型量化
build.cfg
[Common]
mode = build
[Parser]
model_type = onnx
model_name = depth_anything_v2
detection_postprocess =
model_domain = image_segmentation
input_model = depth_anything_v2.onnx
input = image
input_shape = [1, 3, 518, 518]
output = depth
output_dir = ./out
[Optimizer]
output_dir = ./out
calibration_data = cal.npy
calibration_batch_size = 1
metric_batch_size = 1
dataset = NumpyDataset
quantize_method_for_weight = per_channel_symmetric_restricted_range
quantize_method_for_activation = per_tensor_asymmetric
save_statistic_info = True
[GBuilder]
outputs = depth_anything_v2.cix
target = X2_1204MP3
tiling = fps
模型部署
基于python部署
import numpy as np
import cv2
from NOE_Engine import EngineInfer
def preprocess(image_path : str, mean : list = [0.485, 0.456, 0.406], std : list = [0.229, 0.224, 0.225], target_size : tuple = (520, 520), flag : bool = True, rgb : bool = True):
mean = np.array(mean).astype(np.float32)
std = np.array(std).astype(np.float32)
image = cv2.imread(image_path)
image = image[:, :, ::-1] # BGR2RGB
image_resized = cv2.resize(image, target_size)
image_normalized = image_resized.astype(np.float32) / 255.0
image_standardized = (image_normalized - mean) / std
image_transposed = image_standardized.transpose(2, 0, 1)
input_tensor = np.expand_dims(image_transposed, axis=0)
return input_tensor
if __name__ == "__main__":
model = EngineInfer('depth_anything_v2.cix')
image_path = 'test_data/dog.jpg'
image = cv2.imread(image_path)
h, w = image.shape[:2]
input = preprocess(image_path,[0.485, 0.456, 0.406],[0.229, 0.224, 0.225],(518,518))
print('input',input.min(),input.max(),input.mean())
depth = model.forward([input])[0]
depth = depth.reshape(1,518, 518)
print(depth.max(),depth.min())
depth = (depth - depth.min()) / (depth.max() - depth.min()) * 255.0
depth = depth.transpose(1, 2, 0).astype("uint8")
depth = cv2.resize(depth, (w, h), interpolation=cv2.INTER_CUBIC)
depth_color = cv2.applyColorMap(depth, cv2.COLORMAP_INFERNO)
cv2.imwrite('res.jpg', depth_color)
model.clean()
基于C++部署
#include <opencv2/opencv.hpp>
#include <vector>
#include <string>
#include <memory>
#include <algorithm>
#include "cix_noe_standard_api.h"
enum np_dtype_t {
NP_INT8,
NP_UINT8,
NP_INT16,
NP_UINT16,
NP_FLOAT32
};
using namespace std;
class EngineInfer {
private:
noe_context_t context_;
uint64_t graph_id_;
uint64_t job_id_;
std::string model_path_;
job_config_npu_t npu_config;
job_config_t create_job_cfg = {0};
std::vector<np_dtype_t> input_type_;
std::vector<float> input_dtype_min_;
std::vector<float> input_dtype_max_;
std::vector<tensor_desc_t> in_tensor_desc_;
std::vector<np_dtype_t> output_type_;
std::vector<float> output_dtype_min_;
std::vector<float> output_dtype_max_;
std::vector<tensor_desc_t> out_tensor_desc_;
std::vector<cv::Mat> output_mats_;
public:
EngineInfer(const std::string& model_path) : model_path_(model_path) {
_init_context();
_load_graph();
_create_job();
_setup_tensor_descriptors();
}
~EngineInfer() {
clean();
}
std::vector<cv::Mat> forward(const std::vector<cv::Mat>& input_mats) {
if (input_mats.size() != in_tensor_desc_.size()) {
throw std::runtime_error("Input count mismatch");
}
output_mats_.clear();
for (size_t i = 0; i < input_mats.size(); ++i) {
cv::Mat processed_input = preprocess_input(input_mats[i], i);
int len = in_tensor_desc_[i].size;
char* buffer = new char[len];
memcpy(buffer, processed_input.data, len);
noe_status_t ret = noe_load_tensor(context_, job_id_, i, buffer);
if (ret != NOE_STATUS_SUCCESS) {
delete[] buffer;
throw std::runtime_error("npu: noe_load_tensor failed for input " + std::to_string(i));
}
delete[] buffer;
}
noe_status_t ret = noe_job_infer_sync(context_, job_id_, -1);
if (ret != NOE_STATUS_SUCCESS) {
throw std::runtime_error("npu: noe_job_infer_sync failed");
}
for (uint32_t j = 0; j < out_tensor_desc_.size(); ++j) {
size_t tensor_size = out_tensor_desc_[j].size;
char* out_buffer = new char[tensor_size];
noe_status_t ret = noe_get_tensor(context_, job_id_, NOE_TENSOR_TYPE_OUTPUT, j, out_buffer);
if (ret != NOE_STATUS_SUCCESS) {
delete[] out_buffer;
throw std::runtime_error("npu: noe_get_tensor failed for output " + std::to_string(j));
}
cv::Mat output_mat = bytes_to_mat(out_buffer, out_tensor_desc_[j], output_type_[j]);
cv::Mat deprocessed_output = deprocess_output(output_mat, j);
output_mats_.push_back(deprocessed_output);
delete[] out_buffer;
}
return output_mats_;
}
std::vector<cv::Mat> forward(const cv::Mat& input_mat) {
std::vector<cv::Mat> inputs = {input_mat};
return forward(inputs);
}
void clean() {
if (job_id_ != 0) {
noe_status_t ret = noe_clean_job(context_, job_id_);
if (ret == NOE_STATUS_SUCCESS) {
std::cout << "npu: noe_clean_job success" << std::endl;
} else {
std::cout << "npu: noe_clean_job fail" << std::endl;
}
job_id_ = 0;
}
if (graph_id_ != 0) {
noe_status_t ret = noe_unload_graph(context_, graph_id_);
if (ret == NOE_STATUS_SUCCESS) {
std::cout << "npu: noe_unload_graph success" << std::endl;
} else {
std::cout << "npu: noe_unload_graph fail" << std::endl;
}
graph_id_ = 0;
}
if (context_ != nullptr) {
noe_status_t ret = noe_deinit_context(context_);
if (ret == NOE_STATUS_SUCCESS) {
std::cout << "npu: noe_deinit_context success" << std::endl;
} else {
std::cout << "npu: noe_deinit_context fail" << std::endl;
}
context_ = nullptr;
}
}
private:
void _init_context() {
noe_status_t ret = noe_init_context(&context_, NOE_DEVICE_AIPU);
if (ret != NOE_STATUS_SUCCESS) {
throw std::runtime_error("npu: noe_init_context fail");
}
std::cout << "init context success" << std::endl;
}
void _load_graph() {
uint64_t graph_id = 0;
noe_status_t ret = noe_load_graph(context_, model_path_.c_str(), &graph_id);
if (ret != NOE_STATUS_SUCCESS) {
throw std::runtime_error("npu: noe_load_graph failed");
}
graph_id_ = graph_id;
std::cout << "load graph success" << std::endl;
}
void _create_job() {
uint64_t job_id = 0;
noe_status_t ret = noe_create_job(context_, graph_id_, &job_id, &create_job_cfg);
if (ret != NOE_STATUS_SUCCESS) {
throw std::runtime_error("npu: noe_create_job failed");
}
job_id_ = job_id;
std::cout << "create job success" << std::endl;
}
void _setup_tensor_descriptors() {
uint32_t input_count = _get_tensor_count(NOE_TENSOR_TYPE_INPUT);
in_tensor_desc_.resize(input_count);
input_type_.resize(input_count);
input_dtype_min_.resize(input_count);
input_dtype_max_.resize(input_count);
for (uint32_t i = 0; i < input_count; ++i) {
noe_status_t ret = noe_get_tensor_descriptor(context_, graph_id_, NOE_TENSOR_TYPE_INPUT, i, &in_tensor_desc_[i]);
if (ret != NOE_STATUS_SUCCESS) {
throw std::runtime_error("npu: noe_get_tensor_descriptor failed for input " + std::to_string(i));
}
_setup_dtype_info(in_tensor_desc_[i].data_type, input_type_[i], input_dtype_min_[i], input_dtype_max_[i]);
}
uint32_t output_count = _get_tensor_count(NOE_TENSOR_TYPE_OUTPUT);
out_tensor_desc_.resize(output_count);
output_type_.resize(output_count);
output_dtype_min_.resize(output_count);
output_dtype_max_.resize(output_count);
for (uint32_t i = 0; i < output_count; ++i) {
noe_status_t ret = noe_get_tensor_descriptor(context_, graph_id_, NOE_TENSOR_TYPE_OUTPUT, i, &out_tensor_desc_[i]);
if (ret != NOE_STATUS_SUCCESS) {
throw std::runtime_error("npu: noe_get_tensor_descriptor failed for output " + std::to_string(i));
}
_setup_dtype_info(out_tensor_desc_[i].data_type, output_type_[i], output_dtype_min_[i], output_dtype_max_[i]);
}
}
uint32_t _get_tensor_count(tensor_type_t tensor_type) {
uint32_t count = 0;
noe_status_t ret = noe_get_tensor_count(context_, graph_id_, tensor_type, &count);
if (ret != NOE_STATUS_SUCCESS) {
throw std::runtime_error("npu: noe_get_tensor_count failed for type " + std::to_string(tensor_type));
}
return count;
}
void _setup_dtype_info(noe_data_type_t dtype, np_dtype_t& type, float& min_val, float& max_val) {
type = NP_UINT8;
min_val = 0.0f;
max_val = 255.0f;
switch (dtype) {
case NOE_DATA_TYPE_S8:
type = NP_INT8;
min_val = -128.0f;
max_val = 127.0f;
break;
case NOE_DATA_TYPE_U8:
type = NP_UINT8;
min_val = 0.0f;
max_val = 255.0f;
break;
default:
throw std::runtime_error("Unsupported data type");
}
}
cv::Mat preprocess_input(const cv::Mat& input, size_t index) {
if (index >= in_tensor_desc_.size()) {
throw std::runtime_error("Input index out of range");
}
const tensor_desc_t desc = in_tensor_desc_[index];
cv::Mat resized = input;
cv::Mat float_mat;
resized.convertTo(float_mat, CV_32F);
cv::Mat processed;
float_mat.convertTo(processed, CV_32F, desc.scale, -desc.zero_point);
cv::Mat clipped;
cv::max(processed, input_dtype_min_[index], clipped);
cv::min(clipped, input_dtype_max_[index], clipped);
// printMatStatistics(clipped, "input矩阵");
switch (input_type_[index]) {
case NP_INT8: {
cv::Mat int8_mat;
clipped.convertTo(int8_mat, CV_8S, 1.0, 0.5f);
return int8_mat;
}
case NP_UINT8: {
cv::Mat uint8_mat;
clipped.convertTo(uint8_mat, CV_8U, 1.0, 0.5f);
return uint8_mat;
}
case NP_INT16: {
cv::Mat int16_mat;
clipped.convertTo(int16_mat, CV_16S);
return int16_mat;
}
case NP_UINT16: {
cv::Mat uint16_mat;
clipped.convertTo(uint16_mat, CV_16U);
return uint16_mat;
}
case NP_FLOAT32: {
return processed;
}
default:
throw std::runtime_error("Unsupported input data type");
}
}
cv::Mat bytes_to_mat(char* bytes, const tensor_desc_t& desc, np_dtype_t dtype) {
int cv_type = 0;
switch (dtype) {
case NP_INT8:
cv_type = CV_8S;
break;
case NP_UINT8:
cv_type = CV_8U;
break;
case NP_INT16:
cv_type = CV_16S;
break;
case NP_UINT16:
cv_type = CV_16U;
break;
case NP_FLOAT32:
cv_type = CV_32F;
break;
default:
throw std::runtime_error("Unsupported output data type");
}
cv::Mat mat(1, desc.size, cv_type);
std::memcpy(mat.data, bytes, desc.size);
return mat;
}
cv::Mat deprocess_output(const cv::Mat& output, size_t index) {
if (index >= out_tensor_desc_.size()) {
throw std::runtime_error("Output index out of range");
}
const tensor_desc_t& desc = out_tensor_desc_[index];
cv::Mat float_mat;
output.convertTo(float_mat, CV_32F);
cv::Mat deprocessed = (float_mat + desc.zero_point) / desc.scale;
return deprocessed;
}
};
// 图像预处理函数
cv::Mat preprocess_image_deeplabv3(const std::string& image_path,
const std::vector<float>& mean = {0.485f, 0.456f, 0.406f},
const std::vector<float>& std = {0.229f, 0.224f, 0.225f},
const cv::Size& target_size = cv::Size(520, 520)) {
cv::Mat image = cv::imread(image_path);
if (image.empty()) {
std::cerr << "Error: Image not found!" << std::endl;
return image;
}
cv::Mat blob;
cv::cvtColor(image, image, cv::COLOR_BGR2RGB);
cv::resize(image, image, target_size);
image.convertTo(blob, CV_32F, 1.0/255.0);
cv::subtract(blob, cv::Scalar(0.485f, 0.456f, 0.406f), blob);
cv::divide(blob, cv::Scalar(0.229f, 0.224f, 0.225f), blob);
cv::Mat timg = cv::dnn::blobFromImage(blob);
std::cout << timg.size[0] << "x" << timg.size[1] << "x" << timg.size[2] << "x" << timg.size[3] << std::endl;
return timg;
}
void safeMinMaxLoc(const cv::Mat& mat, double& minVal, double& maxVal) {
// 确保是2维矩阵
cv::Mat flat_mat;
if (mat.dims > 2) {
flat_mat = mat.reshape(1, mat.total());
} else {
flat_mat = mat;
}
cv::minMaxLoc(flat_mat, &minVal, &maxVal);
}
int main() {
try {
std::string model_path = "depth_anything_v2.cix";
std::string image_path = "test_data/dog.jpg";
// 读取原始图像获取尺寸
cv::Mat original_image = cv::imread(image_path);
if (original_image.empty()) {
std::cerr << "无法读取图像: " << image_path << std::endl;
return -1;
}
int h = original_image.rows;
int w = original_image.cols;
std::cout << "原始图像尺寸: " << w << " x " << h << std::endl;
// 初始化模型
EngineInfer model(model_path);
// 预处理
std::cout << "开始预处理..." << std::endl;
cv::Mat input = preprocess_image_deeplabv3(image_path,
{0.485f, 0.456f, 0.406f},
{0.229f, 0.224f, 0.225f},
cv::Size(518, 518));
std::cout << "输入张量形状: " << input.size[0] << " x " << input.size[1] << " x " << input.size[2]<< " x " << input.size[3] << std::endl;
// 推理
printMatStatistics(input);
std::cout << "开始推理..." << std::endl;
std::vector<cv::Mat> outputs = model.forward({input});
cv::Mat depth = outputs[0];
std::cout << "原始输出形状: ";
for (int i = 0; i < depth.dims; ++i) {
std::cout << depth.size[i] << " ";
}
std::cout << std::endl;
depth = depth.reshape(1, {518, 518}); // 重塑为518x518的2D矩阵
std::cout << "重塑后深度图形状: " << depth.rows << " x " << depth.cols << std::endl;
double min_val, max_val;
safeMinMaxLoc(depth, min_val, max_val);
cv::Mat normalized_depth;
if (max_val > min_val) {
depth.convertTo(normalized_depth, CV_32F);
normalized_depth = (normalized_depth - min_val) / (max_val - min_val) * 255.0;
} else {
normalized_depth = cv::Mat::zeros(depth.size(), CV_32F);
}
cv::Mat depth_uint8;
normalized_depth.convertTo(depth_uint8, CV_8U);
if (w <= 0 || h <= 0) {
std::cerr << "Error: target width and height must be positive integers." << std::endl;
}
cv::Mat depth_resized;
cv::resize(depth_uint8, depth_resized, cv::Size(w, h), 0, 0, cv::INTER_CUBIC);
cv::Mat depth_color;
cv::applyColorMap(depth_resized, depth_color, cv::COLORMAP_INFERNO);
cv::imwrite("res.jpg", depth_color);
std::cout << "深度图已保存到 res.jpg" << std::endl;
model.clean();
} catch (const std::exception& e) {
std::cerr << "错误: " << e.what() << std::endl;
return -1;
}
return 0;
}
makefile
# # Makefile
TARGET = depth
SOURCES = main.cpp
OBJECTS = $(SOURCES:.cpp=.o)
CXX = g++
CXXFLAGS = -I/usr/share/cix/include/npu $(shell pkg-config --cflags opencv4) -std=c++17 -Wall -Wextra
LDFLAGS = -L/usr/share/cix/lib -lnoe $(shell pkg-config --libs opencv4)
all: $(TARGET)
$(TARGET): $(OBJECTS)
$(CXX) $(OBJECTS) $(LDFLAGS) -o $(TARGET)
%.o: %.cpp
$(CXX) $(CXXFLAGS) -c $< -o $@
clean:
rm -f $(OBJECTS) $(TARGET)
运行结果
python运行结果

c++运行结果

通过结果可以看出C++和python的推理结果一致。
更多推荐
所有评论(0)