深入探讨ncnn::Mat类——ncnn中的核心数据结构

news2025/1/27 19:29:55

在这里插入图片描述

最近在学习 ncnn 推理框架,下面整理了 ncnn::Mat 的使用方法。

ncnn作为一个高性能的神经网络推理框架,其核心数据结构ncnn::Mat在数据存储与处理上扮演了至关重要的角色。本文将从基础到高级,详细介绍ncnn::Mat类的各个方面,帮助开发者全面理解并高效利用这一强大的数据结构。

目录

  1. Mat类简介
  2. 基本属性和结构
  3. 构造函数详解
  4. 数据访问和操作
  5. 内存管理机制
  6. Vulkan支持
  7. SIMD优化支持
  8. 像素图像处理支持
  9. 边界扩充操作
  10. 高级数据转换
  11. 自定义内存分配器
  12. 跨平台编译优化
  13. 图像处理扩展功能
  14. Mat深拷贝与浅拷贝
  15. 数据遍历模式
  16. YUV图像处理
  17. 线性代数操作
  18. 异常处理与内存安全
  19. 高级内存管理特性
  20. 性能分析工具
  21. 特殊格式数据处理
  22. 辅助调试功能
  23. 总结

1. Mat类简介

ncnn::Mat是ncnn框架中用于存储和管理张量数据的核心数据结构。它支持最多4维的数据(NCDHW格式),能够高效处理神经网络中的特征图、权重以及其他相关数据。通过Mat类,ncnn实现了对数据的高效内存管理和快速访问,为深度学习推理提供了坚实的基础。

class NCNN_EXPORT Mat
{
public:
    // 指向实际数据的指针
    void* data;

    // 元素字节大小(如float32为4字节)
    size_t elemsize;

    // 数据打包数,用于SIMD优化
    int elempack;

    // 分配器
    Allocator* allocator;

    // 数据维度(1-4)
    int dims;

    // 宽、高、深度、通道数
    int w;
    int h;
    int d;
    int c;

    // 通道步长
    size_t cstep;
};

2. 基本属性和结构

Mat类包含多个成员变量,用于描述和管理数据的属性和结构:

  • data: 指向实际数据的内存地址。
  • elemsize: 每个元素的字节大小,例如float32为4字节。
  • elempack: 数据打包的数量,用于SIMD(单指令多数据)优化。
  • allocator: 用于内存分配的分配器对象。
  • dims: 数据的维度(1到4)。
  • w, h, d, c: 分别表示数据的宽度、高度、深度和通道数。
  • cstep: 通道步长,表示每个通道的数据跨度,便于内存对齐和快速访问。

示例

// 创建一个3通道的32x32图像Mat
Mat feat(32, 32, 3); // w=32, h=32, c=3
printf("Total size: %zu\n", feat.total()); // 输出总元素数:32*32*3

// 创建一个float32的向量
Mat vector(100, 4u); // w=100, elemsize=4 (float)

3. 构造函数详解

Mat类提供了多种构造函数,以适应不同的数据存储需求:

// 空Mat
Mat();

// 一维向量
Mat(int w, size_t elemsize = 4u, Allocator* allocator = 0);

// 二维矩阵/图像
Mat(int w, int h, size_t elemsize = 4u, Allocator* allocator = 0);

// 三维张量
Mat(int w, int h, int c, size_t elemsize = 4u, Allocator* allocator = 0);

// 四维张量
Mat(int w, int h, int d, int c, size_t elemsize = 4u, Allocator* allocator = 0);

// 拷贝构造
Mat(const Mat& m);

// 外部内存构造(不拷贝数据)
Mat(int w, void* data, size_t elemsize = 4u, Allocator* allocator = 0);

使用示例

// 创建3x224x224的RGB图像
Mat img(224, 224, 3);

// 创建batch=4的特征图
Mat feat(56, 56, 64, 4); // 4张56x56的64通道特征图

// 创建float16数据
Mat weight(64, 2u); // elemsize=2表示float16

// 使用外部内存构造Mat,不会发生数据拷贝
float external_data[1000];
Mat external_mat(100, external_data, 4u);

4. 数据访问和操作

Mat类提供了多种方式来访问和操作数据,包括使用下标操作符、行访问、通道访问以及范围访问等。

使用下标操作符

适用于一维数据的快速访问。

Mat v(10);
v[0] = 1.0f;
v[1] = 2.0f;
// ...

行访问

通过row()函数访问特定行的数据指针。

Mat m(100, 100);
float* row_ptr = m.row(50);
row_ptr[10] = 3.14f;

通道访问

通过channel()函数访问特定通道的数据。

Mat img(224, 224, 3); // RGB图像
Mat r_channel = img.channel(0); // 获取R通道
Mat g_channel = img.channel(1); // 获取G通道
Mat b_channel = img.channel(2); // 获取B通道

范围访问

使用range()channel_range()row_range()函数进行子区域的数据访问。

// 一维向量前10个元素
Mat subset = img.range(0, 10);

// 提取前2个通道
Mat first_two_channels = img.channel_range(0, 2);

// 提取25-74行
Mat central_rows = img.row_range(25, 50);

5. 内存管理机制

Mat类采用引用计数(reference counting)机制管理内存,确保内存的高效利用和避免内存泄漏。

引用计数相关成员

// 引用计数指针
int* refcount;

// 增加引用计数
void addref();

// 释放内存,减少引用计数
void release();

引用计数机制示例

Mat a(224, 224, 3);
Mat b = a; // b和a共享同一数据,引用计数增加
// 当b和a都被销毁或释放时,数据才会被真正释放

拷贝赋值操作

赋值运算符重载实现了浅拷贝,增加引用计数而不复制数据。

Mat& Mat::operator=(const Mat& m)
{
    if (this == &m)
        return *this;

    if (m.refcount)
        NCNN_XADD(m.refcount, 1); // 引用计数+1

    release(); // 释放当前数据

    data = m.data;
    refcount = m.refcount;
    elemsize = m.elemsize;
    elempack = m.elempack;
    allocator = m.allocator;

    dims = m.dims;
    w = m.w;
    h = m.h;
    d = m.d;
    c = m.c;
    cstep = m.cstep;

    return *this;
}

深拷贝操作

通过clone()clone_from()函数实现数据的完整复制,创建独立的数据副本。

Mat a(224, 224, 3);
Mat b = a.clone(); // b拥有a的数据副本,不共享

6. Vulkan支持

为了提升性能,ncnn提供了基于Vulkan的VkMatVkImageMat类,支持GPU加速的数据操作。

VkMat类概述

VkMat类与Mat类似,但数据存储在GPU的缓冲区中,适用于在Vulkan环境下的高效计算。

class NCNN_EXPORT VkMat
{
public:
    VkBufferMemory* data; // GPU缓冲区
    size_t elemsize;
    int elempack;
    VkAllocator* allocator;
    int dims;
    int w, h, d, c;
    size_t cstep;

    // 数据访问
    Mat mapped() const;
    void* mapped_ptr() const;
};

VkImageMat类概述

VkImageMat类用于存储图像格式的数据,适用于需要纹理处理的场景。

class NCNN_EXPORT VkImageMat
{
public:
    VkImageMemory* data; // GPU图像内存
    size_t elemsize;
    int elempack;
    VkAllocator* allocator;
    int dims;
    int w, h, d, c;

    // 数据访问
    Mat mapped() const;
    void* mapped_ptr() const;
};

使用示例

// 创建一个GPU缓冲区中的Mat
VkAllocator vkalloc;
VkMat vk_mat;
vk_mat.create(224, 224, 3, sizeof(float), &vkalloc);

// 映射到CPU内存进行访问
Mat cpu_mat = vk_mat.mapped();
cpu_mat.fill(1.0f); // 填充数据

7. SIMD优化支持

为了充分利用现代CPU的并行计算能力,Mat类通过elempack支持SIMD(单指令多数据)数据打包优化,支持多种指令集如ARM NEON、x86 SSE/AVX、MIPS MSA和RISC-V向量扩展。

SIMD优化成员函数

#if __ARM_NEON
    void fill(float32x4_t _v);
    void fill(uint16x4_t _v);
    // ...
#endif

#if __SSE2__
    void fill(__m128 _v);
    void fill(__m256 _v, int i = 0);
    // ...
#endif

#if __mips_msa
    void fill(v4f32 _v);
#endif

#if __riscv_vector
    void fill(vfloat32m1_t _v);
    void fill(vuint16m1_t _v);
    // ...
#endif

使用示例

Mat m(8, 8, 64);
#if __ARM_NEON
float32x4_t _v = vdupq_n_f32(1.f);
m.fill(_v); // NEON 4路并行填充
#endif

8. 像素图像处理支持

Mat类提供了丰富的图像处理功能,包括图像格式转换、调整图像大小、ROI裁剪等。

常用枚举 - PixelType

enum PixelType
{
    PIXEL_RGB = 1,
    PIXEL_BGR = 2,
    PIXEL_GRAY = 3,
    PIXEL_RGBA = 4,
    PIXEL_BGRA = 5,
    // 其他转换类型...
};

构造与转换函数

// 从像素数据构造Mat
static Mat from_pixels(const unsigned char* pixels, int type, int w, int h);

// 调整图像大小
static Mat from_pixels_resize(const unsigned char* pixels, int type, int w, int h, 
                             int target_w, int target_h);

// ROI裁剪
static Mat from_pixels_roi(const unsigned char* pixels, int type, int w, int h,
                           int roix, int roiy, int roiw, int roih);

// 转换Mat到像素数据
void to_pixels(unsigned char* pixels, int type) const;

// 调整大小并转换
void to_pixels_resize(unsigned char* pixels, int type, int target_w, int target_h) const;

使用示例

// 从RGB像素数据创建Mat
unsigned char rgb_data[224 * 224 * 3];
Mat img = Mat::from_pixels(rgb_data, PIXEL_RGB, 224, 224);

// 调整图像大小
Mat resized_img = Mat::from_pixels_resize(rgb_data, PIXEL_RGB, 224, 224, 112, 112);

// ROI裁剪
Mat roi_img = Mat::from_pixels_roi(rgb_data, PIXEL_RGB, 224, 224, 50, 50, 100, 100);

// 转换Mat到BGR像素数据
unsigned char bgr_data[112 * 112 * 3];
resized_img.to_pixels(bgr_data, PIXEL_BGR);

9. 边界扩充操作

在图像处理和卷积操作中,经常需要对图像进行边界扩充。Mat类提供了多种边界填充模式,确保数据处理的完整性和准确性。

边界填充模式枚举

enum BorderType
{
    BORDER_CONSTANT = 0,    // 常数填充
    BORDER_REPLICATE = 1,   // 边缘复制
    BORDER_REFLECT = 2,     // 边缘镜像
    BORDER_TRANSPARENT = -233  // 透明填充
};

边界扩充函数

// 二维图像边界扩充
void copy_make_border(const Mat& src, Mat& dst, 
                     int top, int bottom, int left, int right,
                     int type, float v, const Option& opt = Option());

// 三维张量边界扩充
void copy_make_border_3d(const Mat& src, Mat& dst, 
                        int top, int bottom, int left, int right, 
                        int front, int behind,
                        int type, float v, const Option& opt = Option());

// 边界裁剪函数
void copy_cut_border(const Mat& src, Mat& dst, 
                    int top, int bottom, int left, int right, 
                    const Option& opt = Option());

使用示例

Mat src_img(224, 224, 3);
Mat dst_img;

// 使用常数填充,上下各10像素,左右各20像素,填充值为0
copy_make_border(src_img, dst_img, 10, 10, 20, 20, BORDER_CONSTANT, 0.f);

// 使用边缘复制填充
copy_make_border(src_img, dst_img, 5, 5, 5, 5, BORDER_REPLICATE, 0.f);

// 对三维张量进行边界扩充
Mat src_feat(56, 56, 64);
Mat dst_feat;
copy_make_border_3d(src_feat, dst_feat, 2, 2, 2, 2, 1, 1, BORDER_REFLECT, 0.f);

10. 高级数据转换

Mat类支持多种数据类型的转换,包括不同浮点精度的转换、量化与反量化等,以满足不同计算需求和硬件优化。

数据类型转换函数

// float32 与 float16 互转
void cast_float32_to_float16(const Mat& src, Mat& dst);
void cast_float16_to_float32(const Mat& src, Mat& dst);

// float32 与 bfloat16 互转
unsigned short float32_to_bfloat16(float value);
float bfloat16_to_float32(unsigned short value);
void cast_float32_to_bfloat16(const Mat& src, Mat& dst);
void cast_bfloat16_to_float32(const Mat& src, Mat& dst);

// 量化与反量化
void quantize_to_int8(const Mat& src, Mat& dst, const Mat& scale_data, const Option& opt = Option());
void dequantize_from_int32(const Mat& src, Mat& dst, 
                          const Mat& scale_data, const Mat& bias_data, const Option& opt = Option());
void requantize_from_int32_to_int8(const Mat& src, Mat& dst, 
                                  const Mat& scale_in_data, const Mat& scale_out_data, 
                                  const Mat& bias_data, int activation_type, 
                                  const Mat& activation_params, const Option& opt = Option());

使用示例

// float32 转 float16
Mat float32_mat(100, 224, 224, 3);
Mat float16_mat;
cast_float32_to_float16(float32_mat, float16_mat);

// 量化为int8
Mat quantized_mat;
Mat scale_data = Mat::from_pixels(...); // 假设已经定义
quantize_to_int8(float32_mat, quantized_mat, scale_data);

// 反量化为float32
Mat dequantized_mat;
Mat bias_data = Mat::from_pixels(...); // 假设已经定义
dequantize_from_int32(quantized_mat, dequantized_mat, scale_data, bias_data);

11. 自定义内存分配器

Mat类支持使用自定义内存分配器,以满足特定的内存管理需求或优化策略。

定义自定义分配器

class CustomAllocator : public Allocator 
{
public:
    virtual void* fastMalloc(size_t size) override
    {
        // 自定义内存分配逻辑
        return malloc(size);
    }
    
    virtual void fastFree(void* ptr) override
    {
        // 自定义内存释放逻辑
        free(ptr);
    }
};

使用自定义分配器

CustomAllocator myalloc;
Mat m(224, 224, 3, &myalloc);

// 使用自定义分配器创建Mat
Mat a(100, 100, 3, &myalloc);

12. 跨平台编译优化

Mat类通过条件编译支持多种指令集和平台优化,确保在不同硬件环境下都能高效运行。

条件编译支持

#if __ARM_NEON
    // ARM NEON优化代码
#endif

#if __SSE2__
    // x86 SSE2优化代码
#endif

#if __mips_msa
    // MIPS MSA优化代码
#endif

#if __riscv_vector
    // RISC-V向量扩展优化代码
#endif

导出符号控制

通过宏定义控制导出符号,确保跨平台兼容性。

// 导出符号控制宏
#define NCNN_EXPORT 

13. 图像处理扩展功能

Mat类不仅提供基本的图像处理功能,还支持高级的旋转和仿射变换操作,满足复杂的图像处理需求。

图像旋转支持

支持8种不同的旋转方式,基于EXIF的方向信息进行图像旋转。

// 图像旋转类型枚举
enum RotateType {
    ROTATE_0 = 1,
    ROTATE_90 = 6,
    ROTATE_180 = 3,
    ROTATE_270 = 8,
    // 其他旋转类型可扩展
};

// 旋转操作函数
void kanna_rotate_c3(const unsigned char* src, int srcw, int srch,
                    unsigned char* dst, int w, int h, int type);

图像仿射变换支持

支持通过旋转角度、缩放因子和偏移量计算仿射变换矩阵,并应用于图像数据。

// 获取旋转仿射变换矩阵
void get_rotation_matrix(float angle, float scale, float dx, float dy, float* tm);

// 应用仿射变换
void warpaffine_bilinear_c3(const unsigned char* src, int srcw, int srch,
                           unsigned char* dst, int w, int h, const float* tm, 
                           int type = 0, unsigned int v = 0);

使用示例

// 图像旋转
const unsigned char* src_pixels = ...; // 原始图像数据
unsigned char* rotated_pixels = new unsigned char[new_width * new_height * 3];
kanna_rotate_c3(src_pixels, original_width, original_height, 
               rotated_pixels, new_width, new_height, ROTATE_90);

// 图像仿射变换
float tm[6];
get_rotation_matrix(45.0f, 1.0f, 0.0f, 0.0f, tm);
warpaffine_bilinear_c3(src_pixels, original_width, original_height, 
                       transformed_pixels, transformed_width, transformed_height, tm);

14. Mat深拷贝与浅拷贝

Mat类支持深拷贝和浅拷贝两种数据复制方式,分别用于不同的使用场景。

浅拷贝机制

默认情况下,Mat通过浅拷贝构造函数和赋值运算符实现数据的共享,仅复制指针,引用计数增加。

Mat a(224, 224, 3);
Mat b = a; // b和a共享同一数据,引用计数增加
Mat c(a);  // 同样是浅拷贝

深拷贝操作

通过clone()clone_from()函数实现数据的完整复制,创建独立的数据副本。

Mat a(224, 224, 3);
Mat b = a.clone(); // b拥有a的数据副本,不共享
Mat c;
c.clone_from(a);    // 将a的数据深拷贝到c

使用示例

// 浅拷贝
Mat a(100, 100, 3);
Mat b = a;
// 修改b的数据也会影响a
b[0] = 1.0f;

// 深拷贝
Mat c = a.clone();
c[0] = 2.0f;
// a的数据不受c的修改影响

15. 数据遍历模式

根据不同的应用需求,Mat类支持多种数据遍历模式,包括通道优先和行优先两种常见方式。

通道优先遍历

先遍历通道,再遍历每个通道内的行和列。

Mat m(w, h, c);
for (int q = 0; q < c; q++) // 通道循环最外层
{
    const float* ptr = m.channel(q);
    for (int i = 0; i < h; i++)
    {
        for (int j = 0; j < w; j++)
        {
            float val = ptr[i * w + j];
            // 处理数据
        }
    }
}

行优先遍历

先遍历行,再遍历每行内的通道和列。

for (int i = 0; i < h; i++)
{
    for (int q = 0; q < c; q++)
    {
        const float* ptr = m.channel(q).row(i);
        for (int j = 0; j < w; j++)
        {
            float val = ptr[j];
            // 处理数据
        }
    }
}

使用示例

Mat img(640, 480, 3); // RGB图像

// 通道优先遍历
for (int c = 0; c < img.c; c++)
{
    Mat channel = img.channel(c);
    for (int y = 0; y < img.h; y++)
    {
        float* row = channel.row(y);
        for (int x = 0; x < img.w; x++)
        {
            row[x] = 255.0f; // 设置像素值
        }
    }
}

// 行优先遍历
for (int y = 0; y < img.h; y++)
{
    for (int c = 0; c < img.c; c++)
    {
        float* row = img.channel(c).row(y);
        for (int x = 0; x < img.w; x++)
        {
            row[x] = 128.0f; // 设置像素值
        }
    }
}

16. YUV图像处理

在移动设备和视频处理应用中,YUV格式广泛应用。Mat类提供了高效的YUV格式转换函数,支持快速将YUV图像转换为RGB格式。

YUV转换函数

// YUV420sp(NV21)转RGB,快速近似版本
void yuv420sp2rgb(const unsigned char* yuv420sp, int w, int h, unsigned char* rgb);

// YUV420sp(NV12)转RGB,快速近似版本
void yuv420sp2rgb_nv12(const unsigned char* yuv420sp, int w, int h, unsigned char* rgb);

// YUV420sp(NV21)转RGB并半尺寸缩放,更快的近似版本
void yuv420sp2rgb_half(const unsigned char* yuv420sp, int w, int h, unsigned char* rgb);

使用示例

unsigned char yuv_data[640 * 480 * 3 / 2]; // YUV420sp数据
unsigned char rgb_data[640 * 480 * 3];      // RGB数据

// YUV420sp(NV21)转RGB
yuv420sp2rgb(yuv_data, 640, 480, rgb_data);

// YUV420sp(NV12)转RGB
yuv420sp2rgb_nv12(yuv_data, 640, 480, rgb_data);

// YUV420sp(NV21)转RGB并缩放到320x240
unsigned char resized_rgb_data[320 * 240 * 3];
yuv420sp2rgb_half(yuv_data, 640, 480, resized_rgb_data);

17. 线性代数操作

Mat类支持多种线性代数操作,如矩阵变形、切片等,方便进行矩阵运算和数据处理。

矩阵变形

通过reshape()函数,可以改变Mat的维度而不复制数据。

// 1D向量转2D矩阵
Mat v(100);
Mat m = v.reshape(10, 10); // 10x10矩阵

// 2D矩阵转3D张量
Mat m2(10, 10);
Mat t = m2.reshape(10, 10, 3); // 10x10x3张量

矩阵切片

使用range()channel_range()row_range()函数进行子区域的数据访问。

// 切片示例
Mat img(100, 100, 3);
Mat top_left = img.range(0, 50).row_range(0, 50); // 前50行前50列
Mat first_two_channels = img.channel_range(0, 2); // 前两个通道

使用示例

// 矩阵变形
Mat vector(100);
Mat matrix = vector.reshape(10, 10);

// 矩阵切片
Mat img(100, 100, 3);
Mat middle_section = img.range(25, 50).row_range(25, 50).channel_range(1, 1); // 中间区域的G通道

18. 异常处理与内存安全

为了确保数据操作的可靠性和内存的安全性,Mat类提供了多种机制进行异常处理和边界检查。

空指针检查

在访问数据前,始终检查Mat是否为空,防止空指针访问导致的程序崩溃。

bool Mat::empty() const 
{
    return data == 0 || total() == 0;
}

// 安全的数据访问示例
void process_mat(const Mat& m) 
{
    if (m.empty()) {
        // 处理空Mat情况
        return;
    }
    // 安全处理数据
    float* ptr = (float*)m.data;
    // ...
}

维度边界检查

在访问特定通道或行时,必须确保索引在有效范围内,防止越界访问。

Mat m(224, 224, 3);

// 访问通道时进行范围检查
int c = 2;
if (c >= 0 && c < m.c) {
    Mat channel = m.channel(c);
}

// 访问行时进行范围检查
int y = 50;
if (y >= 0 && y < m.h) {
    float* row_ptr = m.row(y);
}

使用示例

Mat img(224, 224, 3);

// 安全访问第4个通道(索引为3)
int channel_index = 3;
if (channel_index >= 0 && channel_index < img.c) {
    Mat channel = img.channel(channel_index);
    // 操作channel
} else {
    // 处理无效通道索引
}

19. 高级内存管理特性

Mat类通过内存池复用和自动内存对齐等高级内存管理特性,提升内存利用率和访问效率。

内存池复用

通过自定义分配器,实现内存池复用,减少频繁的内存分配与释放,提高性能。

class PoolAllocator : public Allocator 
{
    std::vector<std::pair<size_t, void*>> freed_memory;
public:
    virtual void* fastMalloc(size_t size) override 
    {
        // 从freed_memory中查找合适的内存块
        for (auto it = freed_memory.begin(); it != freed_memory.end(); ++it) {
            if (it->first >= size) {
                void* ptr = it->second;
                freed_memory.erase(it);
                return ptr;
            }
        }
        // 如果没有合适的,才新建
        return malloc(size);
    }
    
    virtual void fastFree(void* ptr) override 
    {
        // 将内存块放入内存池
        size_t size = /* 获取ptr对应的大小 */;
        freed_memory.emplace_back(size, ptr);
    }
};

内存对齐优化

通过alignSize()函数,实现对齐后的内存分配,提升数据访问效率。

// 计算对齐后的大小
static size_t alignSize(size_t sz, int n) 
{
    return (sz + n - 1) & -n;
}

// 使用示例
size_t aligned_width = alignSize(width, 16);  // 16字节对齐
Mat m(aligned_width, height, channels);

使用示例

PoolAllocator pool_alloc;
Mat m(224, 224, 3, &pool_alloc);

20. 性能分析工具

为了优化性能,开发者可以通过内存使用统计和操作耗时统计工具,评估和提升Mat类的使用效率。

内存使用统计

通过跟踪内存分配和释放,实现对内存使用情况的统计和监控。

class TrackedAllocator : public Allocator 
{
    size_t total_allocated = 0;
    size_t peak_allocated = 0;
    std::unordered_map<void*, size_t> size_map;
public:
    virtual void* fastMalloc(size_t size) override 
    {
        void* ptr = malloc(size);
        total_allocated += size;
        peak_allocated = std::max(peak_allocated, total_allocated);
        size_map[ptr] = size;
        return ptr;
    }
    
    virtual void fastFree(void* ptr) override 
    {
        if (size_map.find(ptr) != size_map.end()) {
            total_allocated -= size_map[ptr];
            size_map.erase(ptr);
        }
        free(ptr);
    }

    size_t get_total_allocated() const { return total_allocated; }
    size_t get_peak_allocated() const { return peak_allocated; }
};

操作耗时统计

通过封装分配和释放函数,记录各类操作的耗时,帮助识别性能瓶颈。

class BenchmarkAllocator : public Allocator 
{
    double total_alloc_time = 0.0;
    double total_free_time = 0.0;
    
    double get_current_time() const 
    {
        return std::chrono::duration<double>(std::chrono::steady_clock::now().time_since_epoch()).count();
    }

public:
    virtual void* fastMalloc(size_t size) override 
    {
        double start = get_current_time();
        void* ptr = malloc(size);
        double end = get_current_time();
        total_alloc_time += (end - start);
        return ptr;
    }
    
    virtual void fastFree(void* ptr) override 
    {
        double start = get_current_time();
        free(ptr);
        double end = get_current_time();
        total_free_time += (end - start);
    }

    double get_total_alloc_time() const { return total_alloc_time; }
    double get_total_free_time() const { return total_free_time; }
};

使用示例

BenchmarkAllocator bench_alloc;
Mat m(224, 224, 3, &bench_alloc);

// 执行若干操作...

printf("Total allocation time: %f seconds\n", bench_alloc.get_total_alloc_time());
printf("Total free time: %f seconds\n", bench_alloc.get_total_free_time());

21. 特殊格式数据处理

Mat类支持处理特殊格式的数据,如稀疏矩阵和压缩数据,进一步提升数据处理的灵活性和效率。

稀疏矩阵支持

使用结构体存储稀疏矩阵的非零元素,并提供转换函数。

struct SparseMatEntry 
{
    int i, j;    // 位置索引
    float value; // 非零值
};

// 将稀疏数据转换为Mat
Mat sparse_to_dense(const std::vector<SparseMatEntry>& entries, 
                   int w, int h) 
{
    Mat m(w, h);
    m.fill(0.f); // 填充0
    
    for (const auto& entry : entries) {
        float* ptr = m.row(entry.i);
        ptr[entry.j] = entry.value;
    }
    return m;
}

压缩数据处理

支持游程编码(Run-Length Encoding, RLE)等压缩数据格式的解码,节省存储空间。

// 游程编码数据结构
struct RLEEntry 
{
    float value; // 值
    int count;    // 连续次数
};

// 解码游程编码数据到Mat
Mat rle_decode(const std::vector<RLEEntry>& rle_data, 
               int w, int h) 
{
    Mat m(w, h);
    float* ptr = (float*)m.data;
    
    for (const auto& run : rle_data) {
        std::fill(ptr, ptr + run.count, run.value);
        ptr += run.count;
    }
    return m;
}

使用示例

// 稀疏矩阵转换
std::vector<SparseMatEntry> sparse_entries = {
    {0, 0, 1.0f},
    {0, 1, 2.0f},
    {1, 0, 3.0f},
    // 其他非零元素...
};
Mat dense_mat = sparse_to_dense(sparse_entries, 100, 100);

// 游程编码解码
std::vector<RLEEntry> rle_data = {
    {0.0f, 50},
    {1.0f, 100},
    {2.0f, 50},
    // 其他游程...
};
Mat decoded_mat = rle_decode(rle_data, 200, 200);

22. 辅助调试功能

Mat类提供了多种辅助调试功能,帮助开发者快速获取数据的形状信息和内存使用情况,便于调试和优化。

形状信息获取

// 获取元素位数
int elembits() const;

// 获取形状信息Mat
Mat shape() const;

// 判断是否为空
bool empty() const;

内存分析

// 计算总元素数
size_t total() const;

// 获取通道步长
size_t cstep;

使用示例

Mat img(224, 224, 3);

// 获取形状信息
Mat shape_info = img.shape();
printf("Shape total elements: %zu\n", shape_info.total());

// 检查是否为空
if (img.empty()) {
    printf("Mat is empty.\n");
} else {
    printf("Mat is not empty.\n");
}

// 输出通道步长
printf("Channel step: %zu\n", img.cstep);

23. 总结

ncnn::Mat作为ncnn框架的基础数据结构,具备以下显著特点:

  1. 多维数据支持:支持1到4维的数据存储,涵盖向量、矩阵、张量等多种数据形式。
  2. 高效内存管理:通过引用计数和自定义分配器,确保内存的高效利用和安全性。
  3. SIMD优化:支持多种指令集的SIMD优化,提升数据处理的并行性能。
  4. 丰富的数据操作接口:提供多种访问和操作方式,适应不同的数据处理需求。
  5. 跨平台兼容性:通过条件编译和导出符号控制,确保在不同硬件和平台上高效运行。
  6. 图像处理扩展:支持高级的图像旋转、仿射变换等操作,满足复杂的图像处理需求。
  7. 调试与分析工具:内置多种调试功能和性能分析工具,帮助开发者优化和调试代码。

合理使用ncnn::Mat类,可以显著提升深度学习推理的效率和性能,为开发高效的人工智能应用打下坚实的基础。


无论是在数据管理、内存优化还是高级数据处理上,Mat类都展现出了强大的功能和灵活性。希望这篇博客能帮助您更好地理解和利用ncnn::Mat,在您的深度学习项目中发挥更大的作用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2283128.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

npm:升级自身时报错:EBADENGINE

具体报错信息如下&#xff1a; 1.原因分析 npm和当前的node版本不兼容。 // 当前实际版本: Actual: {"npm":"10.2.4","node":"v20.11.0"}可以通过官网文档查看与自己 node 版本 兼容的是哪一版本的npm&#xff0c;相对应进行更新即可…

ipad和macbook同步zotero文献附件失败的解决办法

背景&#xff1a;我所有的文献及其附件pdf都是在台式机&#xff08;windows系统&#xff09;&#xff0c;想要把这些文献同步到云上&#xff0c;然后再从云上同步到平板和其他笔记本电脑比如macbook。文献同步虽已成功&#xff0c;但文献附件都无法打开。 平板报错如下&#xf…

【嵌入式】总结——Linux驱动开发(三)

鸽了半年&#xff0c;几乎全忘了&#xff0c;幸亏前面还有两篇总结。出于快速体验嵌入式linux的目的&#xff0c;本篇与前两篇一样&#xff0c;重点在于使用、快速体验&#xff0c;uboot、linux、根文件系统不作深入理解&#xff0c;能用就行。 重新梳理一下脉络&#xff0c;本…

15_业务系统基类

创建脚本 SystemRoot.cs 因为 业务系统基类的子类 会涉及资源加载服务层ResSvc.cs 和 音乐播放服务层AudioSvc.cs 所以在业务系统基类 提取引用资源加载服务层ResSvc.cs 和 音乐播放服务层AudioSvc.cs 并调用单例初始化 using UnityEngine; // 功能 : 业务系统基类 public c…

C语言-构造数据类型

1、构造数据类型 结构体、共用体、枚举。 2、结构体 1、结构体的定义 结构体是一个自定义的复合数据类型&#xff0c;它允许将不同类型的数据组合在一起。 struct 结构体名 {数据类型1 成员变量1;数据类型2 成员变量2;数据类型3 成员变量3;数据类型4 成员变量4; } 2、结构体变…

文档解析:PDF里的复杂表格、少线表格如何还原?

PDF中的复杂表格或少线表格还原通常需要借助专业的工具或在线服务&#xff0c;以下是一些可行的方法&#xff1a; 方法一&#xff1a;使用在线PDF转换工具 方法二&#xff1a;使用桌面PDF编辑软件 方法三&#xff1a;通过OCR技术提取表格 方法四&#xff1a;手动重建表格 …

【统计的思想】假设检验(二)

假设检验是根据人为设定的显著水平&#xff0c;对被测对象的总体质量特性进行统计推断的方法。 如果我们通过假设检验否定了零假设&#xff0c;只是说明在设定的显著水平下&#xff0c;零假设成立的概率比较小&#xff0c;并不是说零假设就肯定不成立。如果零假设事实上是成立…

汽车定速巡航

配备定速巡航功能的车型&#xff0c;一般在方向盘附近设有4~6个按键&#xff08;可能共用键位&#xff09;。 要设置定速巡航&#xff0c;不仅需要方向盘上的按键&#xff0c;还要油门配合。 设置的一般流程&#xff1a; 开关&#xff1a;类似步枪上的“保险”&#xff0c;按…

MacOS安装Docker battery-historian

文章目录 需求安装battery-historian实测配置国内源相关文章 需求 分析Android电池耗电情况、唤醒、doze状态等都要用battery-historian&#xff0c; 在 MacOS 上安装 battery-historian&#xff0c;可以使用 Docker 进行安装runcare/battery-historian:latest。装完不需要做任…

总线、UART、IIC、SPI

一图流 总线 概念 连接多个部件的信息传输线&#xff0c;是各部件共享的传输介质 类型 片内总线&#xff1a;连接处理器内核和外设的总线&#xff0c;在芯片内部 片外总线&#xff1a;连接芯片和其他芯片或者模块的总线 总线的通信 总线通信的方式 串行通信 数据按位顺序传…

大型齿轮箱健康监测与智能维护系列套件:测试台+故障诊断算法工具箱+齿轮箱智能维护系统平台+案例分析

大型齿轮箱健康监测与智能维护系列套件&#xff1a;测试台故障诊断算法工具箱齿轮箱智能维护系统平台案例分析 大型齿轮箱健康监测与智能维护系列套件&#xff1a;测试台定制、数据测试服务、算法工具箱与算法模型的定制研制服务&#xff0c;以及各类设备故障诊断与健康预诊系…

Yearning开源MySQL SQL审核平台

一款MYSQL SQL语句/查询审计工具&#xff0c;为DBA与开发人员使用. 本地部署&#xff0c;注重隐私&#xff0c;简单高效的MYSQL审计平台。 它可以通过流程审批&#xff0c;实现真实线上环境sql的审核和执行&#xff0c;还可以回滚执行&#xff0c;能够确保线上SQL更新的可靠性…

【MySQL — 数据库增删改查操作】深入解析MySQL的create insert 操作

数据库CRUD操作 1 CRUD简介 CURD是对数据库中的记录进行基本的增删改查操作: 2. Create 新增 语法 INSERT [INTO] table_name[(column [&#xff0c;column] ...)] VALUES(value_list)[&#xff0c;(value_list)] ... # value 后面的列的个数和类型&#xff0c;要和表结构匹配…

推箱子游戏

java小游戏2 一游戏介绍 二图像准备 墙、箱子、人、箱子目的地&#xff0c;人左边、人右边、人上边、人下边 三结构准备 地图是什么&#xff0c;我们把地图想象成一个网格&#xff0c;每个格子就是工人每次移动的步长&#xff0c;也是箱子移动的距离&#xff0c;设置一个二维数…

【Uniapp-Vue3】动态设置页面导航条的样式

1. 动态修改导航条标题 uni.setNavigationBarTitle({ title:"标题名称" }) 点击修改以后顶部导航栏的标题会从“主页”变为“动态标题” 2. 动态修改导航条颜色 uni.setNavigationBarColor({ backgroundColor:"颜色" }) 3. 动态添加导航加载动画 // 添加加…

pytest执行报错:found no collectors

今天在尝试使用pytest运行用例的时候出现报错&#xff1a;found no collectors&#xff1b;从两个方向进行排查&#xff0c;一是看文件名和函数名是不是符合规范&#xff0c;命名要是"test_*"格式&#xff1b;二是是否存在修改文件名的情况&#xff0c;如果修改过文件…

QT6 + CMAKE编译OPENCV3.9

参考文档 [1] https://blog.csdn.net/rjkf_css/article/details/135676077 前提条件 配置好相关运行环境&#xff1a;QT6、OPENCV3.9的sources文件 OPENCV下载网页&#xff1a;https://opencv.org/releases/ QT6下载教程&#xff1a;https://blog.csdn.net/caoshangpa/article…

pycharm踩坑(1)

由于我重装系统&#xff0c;导致我的pycharm需要进行重装&#xff0c;因此我觉得需要记录一下&#xff0c;pycharm的正确使用方法 汉化 汉化很重要&#xff0c;除非你从小就双语教学&#xff0c;不然你看着那些英文就是会消耗大量的精力 我使用的pycharm版本是pycharm-commun…

24_游戏启动逻辑梳理总结

首先这个项目从游戏根入口GameRoot.cs的初始化开始 分为 服务层初始化Svc.cs 与 业务系统层初始化Sys.cs 而服务层 分为 资源加载服务层ResSvc.cs 与 音乐播放服务层AudioSvc.cs 而在 资源加载服务层ResSvc.cs中 初始化了 名字的 配置文件 而音乐播放服务层AudioSvc.cs 暂时没…

Datawhale组队学习笔记task2——leetcode面试题

文章目录 写在前面Day5题目1.0112.路径总和解答2.0113路径总和II解答3.0101.对称二叉树解答 Day6题目1.0124.二叉树中的最大路径和解答2.0199.二叉树的右视图解答3.0226.翻转二叉树解答 Day7题目1.0105.从前序与中序遍历序列构造二叉树解答2.0098.验证二叉搜索树解答3.0110.平衡…