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

相关文章:

  • docker compose部署mysql
  • 面试题之项目中灰度发布是怎么做的
  • 深入了解linux系统—— 线程概念
  • ZigBee入门与提高(3)—— ZigBee协议初识
  • Visual Studio2019/2022离线安装完整教程(含闪退解决方法)
  • Windows bypassUAC 提权技法详解(一)
  • 基于FPGA的8PSK+卷积编码Viterbi译码通信系统,包含帧同步,信道,误码统计,可设置SNR
  • Python之Django使用技巧(附视频教程)
  • HTML <link rel=“preload“>:提前加载关键资源的性能优化利器
  • 企业智脑正在构建企业第二大脑,四大场景引擎驱动数字化转型新范式
  • C++入门自学Day11-- List类型的自实现
  • 手写MyBatis第16弹:泛型魔法应用:MyBatis如何破解List的运行时类型
  • 一种适用于 3D 低剂量和少视角心脏单光子发射计算机断层成像(SPECT)的可泛化扩散框架|文献速递-深度学习人工智能医疗图像
  • OpenCV 高斯模糊降噪
  • Spring Boot + Redis + 布隆过滤器防止缓存穿透
  • 带root权限_贝尔RG020ET-CA融合终端S905L处理器当贝纯净版刷机教程
  • 分布式系统架构设计模式:从微服务到云原生
  • pycharm远程连接服务器跑实验详细操作
  • Go语言实战案例:简易图像验证码生成
  • Java 设计模式-组合模式
  • Vscode的wsl环境开发ESP32S3的一些问题总结
  • 在 Windows 系统中解决 Git 推送时出现的 Permission denied (publickey) 错误,请按照以下详细步骤操作:
  • 宋红康 JVM 笔记 Day01|JVM介绍
  • [工具]vscode 使用AI 优化代码
  • 使用EvalScope对GPT-OSS-20B进行推理性能压测实战
  • 【完整源码+数据集+部署教程】肾脏病变实例分割系统源码和数据集:改进yolo11-CARAFE
  • 自动化运维实验(二)---自动识别设备,并导出配置
  • AM32电调学习-使用Keil编译uboot
  • 搭建局域网yum源仓库全流程
  • 华为实验 链路聚合