跳转至

矩阵核心 — 元数据、构造与基础操作

矩阵元数据

矩阵结构

Mat类使用行主序存储布局,支持填充和step。stride在该类中是step的向后兼容别名。这种设计实现了高效的内存访问模式,并与DSP库兼容。

核心维度

  • int row : 矩阵的行数。

  • int col : 矩阵的列数。

  • int element : 元素总数 = 行数 × 列数。

内存布局

  • int step / int stride : 每行总元素数(逻辑列 + 填充)。在本类中二者等价,stridestep别名。

  • int pad : 两行之间的填充元素数。填充用于内存对齐和DSP优化。

  • int memory : 数据缓冲区大小 = 行数 × step(以float元素为单位)。

数据指针

  • float *data : 指向包含矩阵元素的数据缓冲区的指针。元素按行主序存储:位置(i, j)的元素位于 data[i * step + j]

  • float *temp : 指向临时数据缓冲区的指针(如果已分配)。某些操作内部使用。

内存管理标志

  • bool ext_buff : 标志矩阵是否使用外部缓冲区。当为true时,析构函数不会释放内存(调用者负责管理)。

  • bool sub_matrix : 标志矩阵是否为另一个矩阵的子集/视图。当为true时,矩阵与父矩阵共享数据。

内存布局示例

对于3×4矩阵,步幅=4(无填充):

[a b c d]   行 0: data[0*4+0] 到 data[0*4+3]
[e f g h]   行 1: data[1*4+0] 到 data[1*4+3]
[i j k l]   行 2: data[2*4+0] 到 data[2*4+3]

对于3×4矩阵,步幅=6(填充=2):

[a b c d _ _]   行 0: data[0*6+0] 到 data[0*6+3],填充在 data[0*6+4,5]
[e f g h _ _]   行 1: data[1*6+0] 到 data[1*6+3],填充在 data[1*6+4,5]
[i j k l _ _]   行 2: data[2*6+0] 到 data[2*6+3],填充在 data[2*6+4,5]

ROI 结构

感兴趣区域

ROI(感兴趣区域)结构表示矩阵的矩形子区域。它与view_roi()copy_roi()函数一起使用,以高效地提取或引用子矩阵。

ROI 元数据

  • int pos_x : 起始列索引(左上角的x坐标)。

  • int pos_y : 起始行索引(左上角的y坐标)。

  • int width : ROI的宽度(列数)。

  • int height : ROI的高度(行数)。

ROI 构造函数

Mat::ROI::ROI(int pos_x = 0, int pos_y = 0, int width = 0, int height = 0);

描述:

构造一个 ROI 对象,默认值为 (0, 0, 0, 0)。

参数:

  • int pos_x: 起始列索引

  • int pos_y: 起始行索引

  • int width: ROI 的宽度(列数)

  • int height: ROI 的高度(行数)

ROI 重置函数

void Mat::ROI::resize_roi(int pos_x, int pos_y, int width, int height);

描述:

重置 ROI 的位置和大小。

参数:

  • int pos_x: 起始列索引

  • int pos_y: 起始行索引

  • int width: ROI 的宽度(列数)

  • int height: ROI 的高度(行数)

返回值:

void

ROI 面积函数

int Mat::ROI::area_roi(void) const;

描述:

计算 ROI 的面积。

参数:

void

返回值:

整数类型 ROI 的面积

打印函数

调试工具

这些函数对于调试和理解矩阵状态至关重要。使用它们来验证矩阵维度、内存布局和数据值。

打印矩阵信息

void Mat::print_info() const;

描述:

打印全面的矩阵信息,包括:

  • 维度:行数、列数、元素数

  • 内存布局:填充数、步幅、内存大小

  • 指针:数据缓冲区地址、临时缓冲区地址

  • 标志:外部缓冲区使用情况、子矩阵状态

  • 警告:维度不匹配、无效状态

参数:

void

返回值:

void

使用建议:

  • 调试: 对于验证矩阵状态和检测内存问题至关重要。

  • 内存分析: 显示实际内存使用情况与逻辑大小的对比,帮助识别内存效率问题。

  • 子矩阵检测: 清楚地指示矩阵是否为视图,这会影响内存管理。

打印矩阵元素

   void Mat::print_matrix(bool show_padding) const;

描述:

以格式化表格形式打印矩阵元素。可选择显示由视觉分隔符分隔的填充元素。

参数:

  • bool show_padding : 如果为true,显示填充值并用分隔符|分隔。如果为false,仅显示实际矩阵元素。

返回值:

void

使用建议:

  • 格式化: 元素以固定宽度(12个字符)格式化以保持对齐。

  • 填充可视化: show_padding选项有助于理解内存布局和验证填充值。

  • 大矩阵: 对于非常大的矩阵,考虑使用view_roi()打印特定区域。

构造与析构函数

内存管理

构造函数自动处理内存分配。析构函数仅在内存是内部分配的情况下安全释放内存(不包括外部缓冲区或视图)。构造后始终检查data指针以确保分配成功。

内存分配

void Mat::alloc_mem();

描述:

根据计算的内存需求为矩阵分配内存的内部函数。设置ext_buff = false并分配row * step个float元素。

参数:

void

返回值:

void

使用建议:

  • 自动调用: 由构造函数自动调用。很少需要手动调用。

  • 内存计算: 分配row * step个元素,可能包括填充。

  • 错误处理: 如果分配失败,data保持为nullptr。构造后始终检查data

默认构造函数

Mat::Mat();

描述:

默认构造函数创建一个1×1的零矩阵。这对于初始化和作为错误情况的返回值很有用。

数学原理:

在某些上下文中创建矩阵运算的恒等元素,尽管通常您会希望指定维度。

参数:

void

返回值:

Mat - 一个元素为0的1×1矩阵。

构造函数 - Mat(int rows, int cols)

Mat::Mat(int rows, int cols);

描述:

构造函数创建具有指定维度的矩阵。所有元素初始化为零。这是最常用的构造函数。

参数:

  • int rows : 行数(必须 > 0)。

  • int cols : 列数(必须 > 0)。

返回值:

Mat - 一个rows×cols的矩阵,所有元素初始化为0。

使用建议:

  • 零初始化: 所有元素使用memset设置为零,确保干净的状态。

  • 内存布局: 创建连续的内存布局,无填充(stride = cols)。

  • 错误处理: 如果内存分配失败,data将为nullptr。始终验证分配成功。

构造函数 - Mat(int rows, int cols, int step)

Mat::Mat(int rows, int cols, int step);

描述:

构造函数创建具有指定维度、列数和步幅的矩阵。当需要填充以进行内存对齐或DSP优化时很有用。

参数:

  • int rows : 行数。

  • int cols : 列数。

  • int step : 每行总元素数(必须 ≥ cols)。填充 = step - cols。

返回值:

Mat - 一个具有步幅的rows×cols矩阵,所有元素初始化为0。

使用建议:

  • DSP优化: 某些DSP库需要对齐的内存。使用步幅确保适当的对齐。

  • 内存效率: 填充允许在对齐边界上进行高效的向量化操作。

  • 兼容性: 实现与使用步幅内存布局的外部库的兼容性。

构造函数 - Mat(float *data, int rows, int cols)

Mat::Mat(float *data, int rows, int cols);

描述:

构造函数在外部数据缓冲区上创建矩阵视图。矩阵不拥有内存;调用者负责管理它。对于与现有数据数组接口很有用。

参数:

  • float *data : 指向外部数据缓冲区的指针(在矩阵生命周期内必须保持有效)。

  • int rows : 行数。

  • int cols : 列数。

返回值:

Mat - 一个ext_buff = true的矩阵视图。

使用建议:

  • 零拷贝: 不发生内存拷贝;矩阵直接引用外部数据。

  • 生命周期管理: 外部缓冲区在矩阵存在期间必须保持有效。析构函数不会释放此内存。

  • 数据布局: 假设行主序布局,无填充(stride = cols)。

  • 使用场景:

  • 包装C数组

  • 与其他库接口

  • 避免不必要的拷贝

构造函数 - Mat(float *data, int rows, int cols, int step)

Mat::Mat(float *data, int rows, int cols, int step);

描述:

构造函数在具有指定step的外部数据缓冲区上创建矩阵视图。支持带填充的行主序内存布局以实现DSP兼容性。

参数:

  • float *data : 指向外部数据缓冲区的指针(在矩阵生命周期内必须保持有效)。

  • int rows : 行数。

  • int cols : 列数。

  • int step : 每行总元素数(必须 ≥ cols)。

返回值:

Mat - 一个ext_buff = true并具有指定step的矩阵视图。

使用建议:

  • 步幅布局: 对于使用步幅内存布局的DSP库至关重要。

  • 内存安全: 与上一个构造函数相同的生命周期要求 - 外部缓冲区必须保持有效。

  • 填充支持: 可以处理行间有填充的缓冲区。

拷贝构造函数 - Mat(const Mat &src)

Mat::Mat(const Mat &src);

描述:

拷贝构造函数从源矩阵创建新矩阵。使用智能拷贝策略:常规矩阵进行深拷贝,子矩阵视图进行浅拷贝。

拷贝策略:

  • 常规矩阵: 深拷贝 - 分配新内存并拷贝所有数据

  • 子矩阵视图: 浅拷贝 - 与源共享数据(创建另一个视图)

参数:

  • const Mat &src : 源矩阵。

返回值:

Mat - 根据源类型具有拷贝或共享数据的新矩阵。

使用建议:

  • 自动选择: 根据源矩阵类型自动选择深拷贝或浅拷贝。

  • 内存效率: 子矩阵视图进行浅拷贝以避免不必要的内存分配。

  • 独立性: 深拷贝是独立的;修改不会影响源。

析构函数

Mat::~Mat();

描述:

析构函数安全地释放分配的内存。仅释放内部分配的内存(ext_buff = false)。外部缓冲区和视图不会被释放。

内存管理:

  • 如果ext_buff = false,释放data缓冲区

  • 如果已分配,释放temp缓冲区

  • 对外部缓冲区或视图不执行任何操作

参数:

void

返回值:

void

构造函数和析构函数规则

  • 构造函数函数必须与类名相同且无返回类型
  • C++允许通过更改参数数量/顺序进行函数重载
  • 当对象超出作用域时,析构函数会自动调用
  • 构造后始终检查data != nullptr以验证分配成功

元素访问

矩阵索引

Mat类使用运算符重载提供直观的矩阵元素访问。operator()允许使用A(i, j)这样的自然语法,而不是A.data[i * step + j]。实现自动处理步幅和填充。

访问矩阵元素(非常量)

inline float &operator()(int row, int col);

描述:

以读写方式访问矩阵元素。返回元素的引用,允许读取和修改。

数学原理:

位置(row, col)的元素访问为data[row * step + col],其中step考虑了填充。

参数

  • int row : 行索引(基于0,必须在范围[0, row-1]内)。

  • int col : 列索引(基于0,必须在范围[0, col-1]内)。

返回值:

float& - 矩阵元素的引用,允许修改。

使用建议:

  • 边界检查: 为了性能,不进行自动边界检查。确保索引有效。

  • 步幅处理: 自动考虑步幅,因此可以正确处理带填充的矩阵。

  • 性能: 内联函数,开销最小,适用于紧密循环。

  • 示例: A(2, 3) = 5.0f; 将第2行第3列的元素设置为5.0。

访问矩阵元素(常量)

inline const float &operator()(int row, int col) const;

描述:

以只读方式访问矩阵元素。返回常量引用,防止修改。当矩阵为const时使用。

参数

  • int row : 行索引(基于0)。

  • int col : 列索引(基于0)。

返回值:

const float& - 矩阵元素的常量引用(只读)。

使用建议:

  • 常量正确性: 实现正确的const-correct代码。在const成员函数中使用此版本。

  • 安全性: 防止意外修改const矩阵。

运算符重载

这些函数重载了()运算符,实现了自然的矩阵索引语法:

Mat A(3, 4);
A(1, 2) = 3.14f;        // 写访问
float val = A(1, 2);    // 读访问
const Mat& B = A;
float val2 = B(1, 2);   // 只读访问(使用const版本)

数据操作

复制其他矩阵到当前矩阵

tiny_error_t copy_paste(const Mat &src, int row_pos, int col_pos);

描述:

将源矩阵的元素复制到当前矩阵的指定位置。

参数:

  • const Mat &src: 源矩阵对象

  • int row_pos: 目标矩阵的起始行索引

  • int col_pos: 目标矩阵的起始列索引

返回值:

错误代码

复制矩阵头部

tiny_error_t copy_head(const Mat &src);

描述:

将源矩阵的头部信息复制到当前矩阵。

参数:

  • const Mat &src: 源矩阵对象

返回值:

错误代码

获取子矩阵视图

Mat view_roi(int start_row, int start_col, int roi_rows, int roi_cols) const;

描述:

获取当前矩阵的子矩阵视图。

参数:

  • int start_row: 起始行索引

  • int start_col: 起始列索引

  • int roi_rows: 子矩阵的行数

  • int roi_cols: 子矩阵的列数

返回值:

子矩阵对象

获取子矩阵视图 - 使用 ROI 结构

Mat view_roi(const Mat::ROI &roi) const;

描述:

获取当前矩阵的子矩阵视图,使用 ROI 结构。

参数:

  • const Mat::ROI &roi: ROI 结构对象

返回值:

子矩阵对象

警告

与 ESP-DSP 不同,view_roi 不允许设置步长,因为它会根据列数和填充数自动计算步长。该函数还会拒绝非法请求,即超出范围的请求。

获取子矩阵副本

Mat copy_roi(int start_row, int start_col, int height, int width);

描述:

获取当前矩阵的子矩阵副本。

参数:

  • int start_row: 起始行索引

  • int start_col: 起始列索引

  • int height: 子矩阵的行数

  • int width: 子矩阵的列数

返回值:

子矩阵对象

获取子矩阵副本 - 使用 ROI 结构

Mat copy_roi(const Mat::ROI &roi);

描述:

获取当前矩阵的子矩阵副本,使用 ROI 结构。

参数:

  • const Mat::ROI &roi: ROI 结构对象

返回值:

子矩阵对象

获取矩阵块

Mat block(int start_row, int start_col, int block_rows, int block_cols);

描述:

获取当前矩阵的块。

参数:

  • int start_row: 起始行索引

  • int start_col: 起始列索引

  • int block_rows: 块的行数

  • int block_cols: 块的列数

返回值:

块对象

viewroi | copyroi | block 之间的区别

  • view_roi : 从该矩阵浅拷贝子矩阵 (ROI)。

  • copy_roi : 从该矩阵深拷贝子矩阵 (ROI)。复制内存拷贝,速度更快。

  • block : 从该矩阵深拷贝块。逐个元素拷贝,速度更慢。

交换行

void Mat::swap_rows(int row1, int row2);

描述:

交换当前矩阵的两行。

参数:

  • int row1: 第一行索引

  • int row2: 第二行索引

返回值:

void

交换列

void Mat::swap_cols(int col1, int col2);

描述:

交换当前矩阵的两列。

参数:

  • int col1: 第一列索引

  • int col2: 第二列索引

返回值:

void

清除矩阵

void Mat::clear(void);

描述:

通过将所有元素设置为零来清除矩阵。

参数:

void

返回值:

void