前言

在智能感知的世界里,深度估计如同为机器赋予了“洞察万物远近”的立体视觉。从自动驾驶的避障导航到移动端的AR互动,从工业质检的精密测量到消费电子的影像虚化,理解三维场景的需求无处不在。

近年来,Depth Anything V2 作为单目深度估计领域的新星,以其卓越的泛化能力和精准的细节表现,吸引了大量开发者的目光。此芯P1处理器,凭借其高能效的专用神经网络处理单元,专为边缘AI计算场景设计,为复杂的视觉模型提供强劲且低功耗的推理动力。

本篇博客将手把手带领您完成从模型准备、模型量化,到最终在此芯P1平台上分别通过Python和C++进行高效部署的全过程

模型准备

onnx模型

参考链接GitHub - DepthAnything/Depth-Anything-V2: [NeurIPS 2024] Depth Anything V2. A More Capable Foundation Model for Monocular Depth Estimation

跑通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的推理结果一致。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐