当前位置: 首页 > news >正文

【OpenCV】Mat详解

在OpenCV中,cv::Mat是用于存储图像、矩阵等多维数据的核心数据结构,替代了早期的IplImage(需手动管理内存),其设计的核心目标是自动内存管理高效数据操作。下面详细介绍其组成原理及使用方法。

一、cv::Mat的组成原理

cv::Mat的结构由两部分组成:矩阵头(Matrix Header)数据指针(Data Pointer),二者分离的设计使其既能高效传递,又能避免冗余内存占用。

1. 矩阵头(Matrix Header)

矩阵头是一个轻量级结构体,存储了数据的元信息,不直接存储像素数据。核心成员包括:

  • rows:行数(图像的高度,单位为像素)。
  • cols:列数(图像的宽度,单位为像素)。
  • size():返回cv::Size(cols, rows),便捷表示尺寸。
  • type():数据类型,由位深度通道数组成(如CV_8UC3表示8位无符号整数,3通道)。
    • 位深度:如8U(8位无符号)、16S(16位有符号)、32F(32位浮点)等。
    • 通道数:单通道(灰度图)、3通道(RGB/BGR)、4通道(带Alpha通道)等。
  • channels():返回通道数(由type()推导,如CV_8UC3的通道数为3)。
  • step:行步长(每行数据的总字节数,包括像素数据和可能的填充字节,用于快速定位某一行的起始地址)。
  • refcount:引用计数器(用于内存自动释放,记录当前有多少个Mat对象共享同一块数据)。
2. 数据指针(Data Pointer)

数据指针(uchar* data)指向存储实际像素数据的内存区域。数据在内存中的排列方式由通道数决定:

  • 单通道(灰度图):按行存储,每行像素依次排列(如[p0, p1, p2, ..., p_cols-1])。
  • 多通道:每个像素的通道数据连续存储(如3通道图像的一个像素为[B, G, R],按B0, G0, R0, B1, G1, R1, ...排列,OpenCV默认通道顺序为BGR而非RGB)。
3. 内存管理机制:引用计数

cv::Mat通过引用计数实现高效内存管理:

  • 当复制Mat对象(如Mat B = A)时,仅复制矩阵头,数据指针指向同一块内存,引用计数refcount加1。
  • Mat对象销毁时,引用计数减1;当refcount为0时,自动释放数据内存,避免内存泄漏。
  • 若需深拷贝(独立数据),需使用clone()copyTo()方法(如Mat C = A.clone())。

二、cv::Mat的使用方法

1. 创建cv::Mat对象

常用创建方式包括:

(1)通过构造函数创建

指定行数、列数、数据类型:

// 创建3行2列的8位无符号单通道矩阵(初始值随机)
cv::Mat mat1(3, 2, CV_8UC1);// 创建3行2列的32位浮点3通道矩阵(初始值随机)
cv::Mat mat2(3, 2, CV_32FC3);// 用cv::Size指定尺寸(宽x高)
cv::Mat mat3(cv::Size(200, 100), CV_8UC3); // 宽200,高100(rows=100, cols=200)
(2)创建并初始化特殊矩阵

使用zeros()ones()eye()(单位矩阵):

// 创建3x3的8位无符号单通道零矩阵
cv::Mat zeros_mat = cv::Mat::zeros(3, 3, CV_8UC1);// 创建2x4的32位浮点3通道全1矩阵
cv::Mat ones_mat = cv::Mat::ones(2, 4, CV_32FC3);// 创建5x5的64位浮点单通道单位矩阵
cv::Mat eye_mat = cv::Mat::eye(5, 5, CV_64FC1);
(3)从已有数据创建

将外部数组数据包装为Mat(不复制数据,仅共享内存):

float data[] = {1.0f, 2.0f, 3.0f, 4.0f};
// 创建2行2列的32位浮点单通道矩阵,数据指向data数组
cv::Mat mat_from_data(2, 2, CV_32FC1, data);
(4)从图像文件读取

使用cv::imread读取图像,返回Mat对象:

// 读取彩色图像(默认3通道BGR)
cv::Mat img_color = cv::imread("image.jpg");// 读取灰度图(单通道)
cv::Mat img_gray = cv::imread("image.jpg", cv::IMREAD_GRAYSCALE);
2. 访问cv::Mat的属性

通过成员函数或成员变量获取元信息:

cv::Mat img = cv::imread("image.jpg");
int rows = img.rows;         // 图像高度(行数)
int cols = img.cols;         // 图像宽度(列数)
cv::Size size = img.size();  // 尺寸(cols, rows)
int channels = img.channels(); // 通道数(如3)
int type = img.type();       // 数据类型(如CV_8UC3)
size_t step = img.step;      // 行步长(每行字节数)
3. 访问像素数据

根据场景选择不同方法(效率和便捷性权衡):

(1)at<T>()方法(便捷,适合单像素访问)

需指定数据类型T(与type()匹配),语法:mat.at<T>(row, col)(单通道)或mat.at<T>(row, col)[channel](多通道)。

cv::Mat img = cv::imread("image.jpg"); // CV_8UC3类型// 访问(10, 20)处的像素(行10,列20)
cv::Vec3b pixel = img.at<cv::Vec3b>(10, 20); // Vec3b对应8UC3(3个uchar)
uchar blue = pixel[0];   // B通道
uchar green = pixel[1]; // G通道
uchar red = pixel[2];    // R通道// 修改像素值
img.at<cv::Vec3b>(10, 20) = cv::Vec3b(255, 0, 0); // 改为蓝色
  • 常用类型对应:CV_8UC1ucharCV_32FC1floatCV_8UC3cv::Vec3bCV_32FC3cv::Vec3f
(2)行指针ptr<T>()(高效,适合遍历行)

获取某一行的起始指针,通过指针遍历像素(效率高于at<T>()):

cv::Mat img = cv::imread("image.jpg"); // 8UC3
for (int i = 0; i < img.rows; ++i) {// 获取第i行的指针(uchar*,因8UC3每个像素3字节)uchar* row_ptr = img.ptr<uchar>(i);for (int j = 0; j < img.cols; ++j) {// 计算当前像素的起始位置(每行j列的像素:j*通道数)int pos = j * 3;uchar b = row_ptr[pos];     // Buchar g = row_ptr[pos + 1]; // Guchar r = row_ptr[pos + 2]; // R// 修改为灰度(简单平均)row_ptr[pos] = row_ptr[pos + 1] = row_ptr[pos + 2] = (b + g + r) / 3;}
}
(3)迭代器(安全,适合复杂遍历)

使用cv::MatIterator_<T>遍历,自动处理边界:

cv::Mat img = cv::imread("image.jpg");
// 3通道迭代器
cv::MatIterator_<cv::Vec3b> it = img.begin<cv::Vec3b>();
cv::MatIterator_<cv::Vec3b> it_end = img.end<cv::Vec3b>();
for (; it != it_end; ++it) {// 每个迭代器指向一个像素(Vec3b)(*it)[0] = 0; // 将B通道置0
}
4. 常用操作
  • 通道分离与合并:用split()merge()处理多通道图像:

    cv::Mat img = cv::imread("image.jpg"); // BGR
    std::vector<cv::Mat> channels;
    cv::split(img, channels); // 分离为3个单通道(B, G, R)
    channels[2] = cv::Mat::zeros(img.size(), CV_8UC1); // 将R通道置0
    cv::Mat img_no_red;
    cv::merge(channels, img_no_red); // 合并回3通道
    
  • ROI(感兴趣区域):提取子矩阵(共享原数据,需深拷贝时用clone()):

    cv::Mat img = cv::imread("image.jpg");
    // 提取从(10, 20)开始,宽100、高50的区域(行范围[10,10+50),列范围[20,20+100))
    cv::Mat roi = img(cv::Rect(20, 10, 100, 50)); // Rect(x, y, width, height)
    roi.setTo(cv::Scalar(0, 255, 0)); // 直接修改ROI,原图像也会变化
    
  • 保存图像:用cv::imwrite

    cv::Mat img = cv::imread("image.jpg");
    cv::imwrite("output.jpg", img); // 保存为JPG
    

三、注意事项

  1. 数据类型匹配:访问像素时,at<T>()ptr<T>()T必须与Mat::type()匹配(如CV_8UC3对应cv::Vec3b),否则会导致内存访问错误。
  2. 引用计数与深拷贝:默认复制为浅拷贝(共享数据),需独立数据时用clone()copyTo()
  3. 通道顺序:OpenCV默认图像通道为BGR(而非RGB),处理时需注意转换(可通过cv::cvtColor(img, img_rgb, cv::COLOR_BGR2RGB)转换)。

通过理解cv::Mat的组成和使用方法,可高效处理图像数据,避免内存问题并优化操作性能。

http://www.dtcms.com/a/331194.html

相关文章:

  • 论,物联网日志系统架构如何设计?
  • AI增强SEO关键词表现
  • Postman 平替 技术解析:架构优势与实战指南
  • 审批流程系统设计与实现:状态驱动、灵活扩展的企业级解决方案
  • Java研学-RabbitMQ(八)
  • Rabbitmq+STS+discovery_k8s +localpv部署排坑详解
  • 队列的使用以及泛型思考[二叉树的层序遍历]
  • 【P27 4-8】OpenCV Python——Mat类、深拷贝(clone、copyTo、copy)、浅拷贝,原理讲解与示例代码
  • Horse3D游戏引擎研发笔记(五):在QtOpenGL环境下,仿three.js的BufferGeometry管理VAO和EBO绘制四边形
  • 算法训练营day51 图论② 岛屿数量深搜、广搜、最大面积
  • 图论(5)最小生成树算法
  • Claude Code 国内直接使用,原生支持 Windows 免WSL安装教程
  • Day56--图论--108. 冗余的边(卡码网),109. 冗余的边II(卡码网)
  • Day58--图论--117. 软件构建(卡码网),47. 参加科学大会(卡码网)
  • MySQL窗口函数与PyMySQL以及SQL注入
  • MySQLl中OFFSET 的使用方法
  • 中国AI生态加速迭代,AI硬件引领人机互动新范式
  • LeetCode 分类刷题:2302. 统计得分小于 K 的子数组数目
  • Gradle(四)Maven 项目迁移 Gradle 项目实践
  • 文件服务器:samba
  • Java 并发新范式:用 Structured Concurrency 优雅收拾多线程烂摊子
  • 编排之神-Kubernetes微服务专题--ingress-nginx及金丝雀Canary的演练
  • 电动自行车:中国式制霸
  • 支付域——账户系统设计
  • 2025年Java大厂面试场景题全解析:高频考点与实战攻略
  • 优德普SAP一体化平台有哪些功能?
  • 力扣(盛最多水的容器)
  • Java基础 8.14
  • 力扣-5.最长回文子串
  • MySQL的索引(索引的创建和设计原则):