当前位置: 首页 > 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.lryc.cn/news/620714.html

相关文章:

  • 入门基础人工智能理论
  • 计算机视觉(opencv)实战二——图像边界扩展cv2.copyMakeBorder()
  • 论,物联网日志系统架构如何设计?
  • AI增强SEO关键词表现
  • Postman 平替 技术解析:架构优势与实战指南
  • 考研408《计算机组成原理》复习笔记,第五章(2)——CPU指令执行过程
  • 使用 Docker 部署 PostgreSQL
  • 考研408《计算机组成原理》复习笔记,第四章(3)——指令集、汇编语言
  • Java设计模式之《策略模式》
  • Effective C++ 条款41:理解隐式接口和编译期多态
  • 应用系统连达梦数据库报“服务器模式不匹配”的根源与修复方案
  • 使用colmap自制3DGaussian_Splatting数据集
  • BotCash:GPT-5发布观察 工程优化的进步,还是技术突破的瓶颈?
  • GoLand 项目从 0 到 1:第八天 ——GORM 命名策略陷阱与 Go 项目启动慢问题攻坚
  • 通过机器学习框架实现Android手写识别输入功能
  • Spring Boot 3中JWT密钥安全存储方案
  • Python训练营打卡Day32-神经网络的训练
  • 【Golang】Golang内存泄漏问题排查(二)
  • OpenCv(三)——图像平滑处理
  • 8. 函数简介
  • OpenCV中对图像进行平滑处理的4种方式
  • HarmonyOS AI辅助编程工具(CodeGenie)智慧调优
  • 力扣(LeetCode) ——225 用队列实现栈(C语言)
  • 信息vs知识:人类学习与AI规则提取
  • 异步编程的 8 种实现方式:疑难点与注意事项解析
  • 《疯狂Java讲义(第3版)》学习笔记ch4
  • 安全加固4(K8S最小化微服务安全)
  • C++ 中的元控制流与概念化类型擦除
  • Elasticsearch 中如何配置 RBAC 权限-实现安全的访问控制
  • 论郑和下西洋元素融入课件编辑器的意义与影响​