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

OpenCV C++ 核心:Mat 与像素操作全解析

在 OpenCV 的 C++ 开发中,Mat是承载图像数据的核心数据结构 —— 它不仅解决了传统IplImage手动内存管理的痛点,还支持多通道、多数据类型的灵活配置,是所有图像处理操作的 “基石”。本章将围绕Mat的创建、像素访问、算术 / 位运算及通道操作,通过实操代码与原理剖析,帮助开发者夯实底层基础。

一、Mat 创建方法与基础操作

Mat的创建需结合业务场景选择合适方式,不同创建方法对应不同内存分配逻辑;基础操作则聚焦Mat的核心属性(尺寸、类型、通道数)获取与修改,是后续操作的前提。

1.1 常用创建方法(附代码示例)

Mat的创建本质是 “定义图像尺寸、数据类型与通道数”,以下为 6 种高频创建方式,覆盖绝大多数开发场景:

(1)默认构造(空矩阵,需后续初始化)

适用于先声明Mat对象,后续通过其他操作(如imreadresize)赋值的场景:

#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;int main() {// 默认构造:创建空Mat,无实际数据Mat img;// 后续通过imread赋值(此时才分配内存)img = imread("test.jpg");if (img.empty()) {cout << "图像加载失败!" << endl;return -1;}imshow("默认构造+imread", img);waitKey(0);destroyAllWindows();return 0;
}
(2)指定尺寸、类型与初始值

最常用的创建方式,需明确rows(行数 / 高度)、cols(列数 / 宽度)、type(数据类型 + 通道数),可选初始值:

// 关键:理解Mat类型定义(以CV_8UC3为例)
// CV_[位深][数据类型][通道数]:8U=8位无符号uchar,C3=3通道(如RGB)
Mat img1(480, 640, CV_8UC3, Scalar(255, 0, 0));  // 480x640蓝色图像(BGR顺序!)
Mat img2(Size(640, 480), CV_8UC1, Scalar(128));  // 640x480灰度图(像素值128,灰色)// 显示结果
imshow("3通道蓝色图像", img1);
imshow("单通道灰度图", img2);
waitKey(0);
destroyAllWindows();

坑点提醒:Mat(rows, cols)Mat(Size(cols, rows))参数顺序不同,前者是 “高度 × 宽度”,后者是 “宽度 × 高度”,需严格匹配,否则图像会拉伸变形。

(3)从已有数据创建(数组 / 指针)

适用于将外部数组(如 C 数组)转换为Mat,需确保数据类型与Mat类型一致:

// 示例:将3x3的uchar数组转为单通道Mat
uchar data[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
Mat img(3, 3, CV_8UC1, data);  // 3行3列,单通道,数据指向data数组// 打印Mat内容(验证是否正确加载)
cout << "从数组创建的Mat:" << endl << img << endl;
(4)拷贝构造与克隆(浅拷贝 vs 深拷贝)

Mat的拷贝默认是浅拷贝(仅复制头部,数据区共享),修改拷贝后的对象会影响原对象;若需完全独立的副本,需用clone()copyTo()实现深拷贝

Mat img = imread("test.jpg");
Mat img_shallow = img;  // 浅拷贝:共享数据区
Mat img_deep = img.clone();  // 深拷贝:独立数据区// 修改浅拷贝对象的像素(原对象也会变)
img_shallow.at<Vec3b>(0, 0) = Vec3b(0, 0, 255);  // 左上角像素设为红色// 显示对比:原图像左上角已变红,深拷贝图像不变
imshow("原图像(受浅拷贝影响)", img);
imshow("深拷贝图像(独立)", img_deep);
waitKey(0);
destroyAllWindows();

1.2 Mat 基础操作(属性获取与修改)

通过Mat的成员函数或属性,可快速获取图像核心信息,常用于预处理判断:

Mat img = imread("test.jpg");
// 1. 获取尺寸与通道
int rows = img.rows;          // 高度(行数)
int cols = img.cols;          // 宽度(列数)
Size size = img.size();       // 尺寸(Size(cols, rows))
int channels = img.channels();// 通道数(1=灰度,3=彩色,4=带Alpha通道)// 2. 获取数据类型与深度
int type = img.type();        // 完整类型(如CV_8UC3)
int depth = img.depth();      // 数据深度(如CV_8U对应0,CV_32F对应5)// 3. 修改尺寸(resize)
Mat img_resized;
resize(img, img_resized, Size(320, 240));  // 缩放到320x240// 4. 打印信息
cout << "图像尺寸:" << rows << "x" << cols << endl;
cout << "通道数:" << channels << endl;
cout << "数据类型:" << type << "(CV_8UC3对应16)" << endl;

二、像素访问遍历(3 种常用方式)

像素遍历是图像处理的核心操作(如灰度化、阈值分割),不同遍历方式的效率差异较大,需根据场景选择。以下按 “效率从高到低” 排序讲解,均以CV_8UC3(8 位 3 通道彩色图) 为例。

2.1 指针遍历(效率最高,推荐大图像)

通过ptr<>()获取每行的像素指针,逐行遍历,避免重复计算行偏移,效率最优,适合实时处理或大尺寸图像:

Mat img = imread("test.jpg");
if (img.empty() || img.type() != CV_8UC3) {cout << "图像类型需为8位3通道!" << endl;return -1;
}// 指针遍历:逐行逐列访问像素
for (int i = 0; i < img.rows; ++i) {// 获取第i行的像素指针(Vec3b=3个uchar组成的向量,对应BGR)Vec3b* row_ptr = img.ptr<Vec3b>(i);for (int j = 0; j < img.cols; ++j) {// 访问第i行第j列的像素(BGR顺序)Vec3b& pixel = row_ptr[j];// 示例:反色处理(255-原像素值)pixel[0] = 255 - pixel[0];  // B通道pixel[1] = 255 - pixel[1];  // G通道pixel[2] = 255 - pixel[2];  // R通道}
}imshow("指针遍历-反色图像", img);
waitKey(0);
destroyAllWindows();

2.2 迭代器遍历(最安全,无越界风险)

通过MatIterator_迭代器遍历,无需手动计算索引,自动处理边界,适合对安全性要求高的场景(如不确定图像尺寸时),效率略低于指针:

Mat img = imread("test.jpg");
MatIterator_<Vec3b> it = img.begin<Vec3b>();  // 起始迭代器
MatIterator_<Vec3b> it_end = img.end<Vec3b>();// 结束迭代器// 迭代器遍历:逐像素访问
for (; it != it_end; ++it) {// 示例:降低亮度(每个通道值减50,避免溢出用saturate_cast)(*it)[0] = saturate_cast<uchar>((*it)[0] - 50);  // B通道(*it)[1] = saturate_cast<uchar>((*it)[1] - 50);  // G通道(*it)[2] = saturate_cast<uchar>((*it)[2] - 50);  // R通道
}imshow("迭代器遍历-低亮度图像", img);
waitKey(0);
destroyAllWindows();

关键:saturate_cast<uchar>()用于处理像素值溢出,若直接减 50 导致值为负,会自动截断为 0;若超过 255,截断为 255,避免出现异常颜色。

2.3 at 方法遍历(最简单,效率最低)

通过at<>()方法直接按坐标访问像素,语法简洁,适合小图像或调试场景,效率最低(每次调用需检查边界):

Mat img = imread("test.jpg");
// at方法遍历:i=行(y轴),j=列(x轴)
for (int i = 0; i < img.rows; ++i) {for (int j = 0; j < img.cols; ++j) {// 示例:转为灰度(加权平均公式:Y=0.299R+0.587G+0.114B)Vec3b pixel = img.at<Vec3b>(i, j);uchar gray = saturate_cast<uchar>(0.299 * pixel[2] + 0.587 * pixel[1] + 0.114 * pixel[0]);// 将彩色像素设为灰度值(B=G=R=gray)img.at<Vec3b>(i, j) = Vec3b(gray, gray, gray);}
}imshow("at方法遍历-灰度图像", img);
waitKey(0);
destroyAllWindows();

坑点提醒:at<Type>(i,j)Type必须与Mat类型匹配,如 CV_8UC3 对应Vec3b,CV_32FC1 对应float,若类型不匹配会直接崩溃。

三、图像算术运算(加减乘除与加权融合)

图像算术运算基于像素逐元素操作,常用于图像叠加、亮度调整、曝光融合等场景,OpenCV 提供封装函数,自动处理溢出与边界。

3.1 基础算术运算(add/subtract/multiply/divide)

核心规则:逐像素对应通道运算,默认采用 “饱和运算”(溢出时截断到 0-255),支持单通道与多通道图像(需尺寸、类型一致)。

// 加载两张尺寸、类型相同的图像(示例:前景图与背景图)
Mat img1 = imread("foreground.jpg");
Mat img2 = imread("background.jpg");
Mat dst_add, dst_sub, dst_mul, dst_div;// 1. 加法(img1 + img2,像素值叠加,溢出取255)
add(img1, img2, dst_add);  // 等价于 dst_add = img1 + img2(OpenCV重载运算符)// 2. 减法(img1 - img2,像素值相减,不足取0)
subtract(img1, img2, dst_sub);  // 等价于 dst_sub = img1 - img2// 3. 乘法(像素值相乘,结果需归一化,否则易溢出)
// 注意:CV_8U图像相乘后值可能远超255,需先转CV_32F再归一化
Mat img1_f, img2_f;
img1.convertTo(img1_f, CV_32F);
img2.convertTo(img2_f, CV_32F);
multiply(img1_f, img2_f, dst_mul);  // 32F类型,值范围0~255²
dst_mul = dst_mul / 255;  // 归一化到0~255
dst_mul.convertTo(dst_mul, CV_8U);  // 转回8U类型// 4. 除法(像素值相除,需避免除零,同样建议先转32F)
divide(img1_f, img2_f + 1e-5, dst_div);  // +1e-5防止除零
dst_div = dst_div * 255;  // 缩放回0~255
dst_div.convertTo(dst_div, CV_8U);// 显示结果
imshow("加法", dst_add);
imshow("减法", dst_sub);
imshow("乘法(归一化)", dst_mul);
imshow("除法(防除零)", dst_div);
waitKey(0);
destroyAllWindows();

3.2 加权融合(addWeighted,常用图像叠加)

addWeighted实现 “加权平均” 运算:dst = src1*alpha + src2*beta + gamma,常用于图像叠加(如加水印、前景背景融合),支持调整透明度。

Mat img = imread("background.jpg");
Mat logo = imread("logo.png");  // 假设logo尺寸小于img,且带Alpha通道(可选)// 1. 提取logo感兴趣区域(ROI:Region of Interest)
Rect roi(100, 100, logo.cols, logo.rows);  // 左上角(100,100),尺寸与logo一致
Mat img_roi = img(roi);  // img的ROI区域(浅拷贝,修改会影响原图像)// 2. 加权融合:logo透明度0.5,背景透明度0.5,gamma=0(亮度偏移)
double alpha = 0.5;  // logo权重
double beta = 1 - alpha;  // 背景权重
addWeighted(logo, alpha, img_roi, beta, 0, img_roi);// 显示结果:logo叠加在img的(100,100)位置,半透明
imshow("加权融合-加水印", img);
waitKey(0);
destroyAllWindows();

四、图像位运算(与 / 或 / 非 / 异或,核心用于遮罩)

位运算基于二进制位操作,在图像分割、遮罩生成、区域提取中应用广泛,尤其适合 “非黑即白” 的二值图像操作。OpenCV 提供bitwise_andbitwise_orbitwise_notbitwise_xor四个函数。

4.1 位运算基础(以二值图像为例)

// 创建两张二值图像(0=黑,255=白)
Mat img1 = Mat::zeros(400, 400, CV_8UC1);
Mat img2 = Mat::zeros(400, 400, CV_8UC1);
rectangle(img1, Rect(50, 50, 150, 150), Scalar(255), -1);  // img1画白色矩形
circle(img2, Point(200, 200), 100, Scalar(255), -1);       // img2画白色圆形Mat dst_and, dst_or, dst_not, dst_xor;// 1. 位与(bitwise_and):两图均为白(255)时结果为白,否则黑(提取交集)
bitwise_and(img1, img2, dst_and);// 2. 位或(bitwise_or):任一图为白时结果为白(提取并集)
bitwise_or(img1, img2, dst_or);// 3. 位非(bitwise_not):反转像素值(黑变白,白变黑,取反)
bitwise_not(img1, dst_not);// 4. 位异或(bitwise_xor):两图像素值不同时为白,相同为黑(提取异集)
bitwise_xor(img1, img2, dst_xor);// 显示结果
imshow("img1(矩形)", img1);
imshow("img2(圆形)", img2);
imshow("位与(交集)", dst_and);
imshow("位或(并集)", dst_or);
imshow("位非(img1反转)", dst_not);
imshow("位异或(异集)", dst_xor);
waitKey(0);
destroyAllWindows();

4.2 位运算实战:图像抠图(提取特定区域)

结合位运算与掩膜(mask),可实现 “抠出前景并叠加到新背景” 的经典需求,步骤如下:

  1. 加载前景图(带 Alpha 通道或手动创建掩膜);
  2. 创建前景掩膜(二值图,前景为白,背景为黑);
  3. 用位与提取前景,位与非清除背景对应区域;
  4. 前景与背景叠加。
// 1. 加载图像(前景图带Alpha通道,背景图任意)
Mat foreground = imread("foreground_alpha.png", IMREAD_UNCHANGED);  // 读取Alpha通道
Mat background = imread("background.jpg");
resize(background, background, foreground.size());  // 背景尺寸与前景一致// 2. 拆分前景图的RGB通道与Alpha通道(Alpha通道作为掩膜)
vector<Mat> fg_channels;
split(foreground, fg_channels);  // fg_channels[0]=B, 1=G, 2=R, 3=Alpha
Mat fg_rgb = Mat(foreground.size(), CV_8UC3);
vector<Mat> fg_rgb_channels = {fg_channels[0], fg_channels[1], fg_channels[2]};
merge(fg_rgb_channels, fg_rgb);  // 前景RGB图像
Mat mask = fg_channels[3];        // Alpha通道作为掩膜(255=前景,0=背景)// 3. 位运算抠图
Mat background_roi;
bitwise_not(mask, mask);  // 掩膜反转(0=前景,255=背景)
bitwise_and(background, background, background_roi, mask);  // 清除背景的前景区域
bitwise_not(mask, mask);  // 恢复原掩膜
Mat dst = background_roi + fg_rgb;  // 前景与背景叠加// 显示结果:前景抠出并叠加到新背景
imshow("抠图结果", dst);
waitKey(0);
destroyAllWindows();

五、通道与数据类型操作(拆分 / 合并 / 类型转换)

通道与数据类型是Mat的核心属性,不同场景需灵活调整(如单通道灰度图用于边缘检测,3 通道彩色图用于显示,32 位浮点图用于算法计算)。

5.1 通道操作(split/merge/mixChannels)

(1)通道拆分(split)与合并(merge)

将多通道图像拆分为单通道图像,修改后再合并,常用于调整特定通道(如增强 R 通道亮度):

Mat img = imread("test.jpg");  // CV_8UC3(BGR)
vector<Mat> channels;// 1. 拆分通道
split(img, channels);  // channels[0]=B, 1=G, 2=R// 2. 修改单个通道(示例:增强R通道亮度)
channels[2] = channels[2] + 50;  // R通道值加50
channels[2] = saturate_cast<uchar>(channels[2]);  // 处理溢出// 3. 合并通道
Mat img_enhanced;
merge(channels, img_enhanced);// 显示对比:增强R通道后图像更红
imshow("原图像", img);
imshow("R通道增强", img_enhanced);
waitKey(0);
destroyAllWindows();
(2)通道重排(mixChannels)

更灵活的通道操作,可实现 “跨图像通道复制”,如将 BGR 图像转为 RGB(OpenCV 默认 BGR,其他库如 OpenCV-Python 默认 RGB):

Mat img_bgr = imread("test.jpg");
Mat img_rgb(img_bgr.size(), CV_8UC3);// 通道重排规则:BGR→RGB
// fromTo数组:{原通道索引, 目标通道索引, ...}
int fromTo[] = {0, 2, 1, 1, 2, 0};  // B→R, G→G, R→B
mixChannels(&img_bgr, 1, &img_rgb, 1, fromTo, 3);  // 1个输入,1个输出,3组通道映射imshow("BGR(OpenCV默认)", img_bgr);
imshow("RGB(重排后)", img_rgb);
waitKey(0);
destroyAllWindows();

5.2 数据类型转换(convertTo)

convertTo用于实现Mat数据类型的转换,常需指定 “缩放因子” 与 “偏移量”,核心场景:

  • 8 位无符号(CV_8U)→32 位浮点(CV_32F):用于后续算法计算(如滤波、特征检测,避免整数截断误差);
  • 32 位浮点→8 位无符号:计算完成后转回显示格式。
Mat img_8u = imread("test.jpg");  // CV_8U(0~255)
Mat img_32f, img_8u_back;// 1. CV_8U → CV_32F(归一化到0~1,避免后续计算溢出)
img_8u.convertTo(img_32f, CV_32F, 1.0/255.0);  // 缩放因子=1/255,偏移量=0
cout << "CV_32F图像像素范围:" << img_32f.at<float>(0,0) << "~" << img_32f.at<float>(img_32f.rows-1, img_32f.cols-1) << endl;// 2. CV_32F → CV_8U(缩放回0~255)
img_32f.convertTo(img_8u_back, CV_8U, 255.0);  // 缩放因子=255,偏移量=0// 显示:转换后图像无明显差异(因仅改变数据类型,未修改像素值)
imshow("原CV_8U图像", img_8u);
imshow("转换回CV_8U的图像", img_8u_back);
waitKey(0);
destroyAllWindows();

关键:转换时需合理设置缩放因子,如 CV_8U→CV_32F 除以 255,确保浮点值在 0~1 之间,避免后续算术运算(如矩阵乘法)出现数值溢出。

六、总结

Mat是 OpenCV C++ 开发的 “基石”,本章围绕Mat的核心操作展开,关键要点总结如下:

  1. Mat 创建:区分浅拷贝(默认)与深拷贝(clone()),明确CV_8UC3等类型的含义,避免尺寸参数顺序错误;
  2. 像素遍历:大图像用指针,安全场景用迭代器,调试用at方法,需匹配Mat数据类型(如Vec3b对应 3 通道 8U);
  3. 算术运算:基础运算用add/subtract,叠加用addWeighted,注意 8U 图像溢出问题,建议先转 32F;
  4. 位运算:核心用于掩膜与区域提取,bitwise_and取交集,bitwise_or取并集,实战中常结合 ROI;
  5. 通道与类型split/merge处理通道,convertTo转换类型,BGR 与 RGB 的重排需用mixChannels

掌握这些操作后,可轻松应对后续的图像滤波、边缘检测、目标识别等进阶场景 —— 所有复杂算法的底层,都是对Mat像素的精准操作。建议结合实际需求编写代码,重点关注 “效率” 与 “数据类型匹配”,避免常见坑点(如浅拷贝修改原图像、像素值溢出)。

http://www.xdnf.cn/news/20235.html

相关文章:

  • CAN通信入门
  • 关于rust的所有权以及借用borrowing
  • 汽车 信息娱乐系统 概览
  • 【前端教程】JavaScript 实现图片鼠标悬停切换效果与==和=的区别
  • 寻找AI——初识3D建模AI
  • 中囯移动电视盒子(魔百和)B860AV2.1-A2和CM311-5-zg刷机手记
  • MacOS 通过Homebrew 安装nvm
  • 深度学习中的Zero-shot(零次学习)
  • 【Python基础】 18 Rust 与 Python print 函数完整对比笔记
  • 通过Gen AI SDK调用gemini 2.5 pro,单独上传pdf文件 | ai agent 开发笔记 2025.9.2 Day 2
  • 确保 SQL Server 备份安全有效的最佳实践
  • 【面试场景题】spring应用启动时出现内存溢出怎么排查
  • Nginx 高性能调优指南:从配置到原理
  • 用 Cursor AI 快速开发你的第一个编程小程序
  • Sentinel和Cluster,到底该怎么选?
  • 2025高教社数学建模国赛A题 - 烟幕干扰弹的投放策略(完整参考论文)
  • 【Tailwind, Daisyui】响应式表格 responsive table
  • 一文教您学会Ubuntu安装Pycharm
  • 管家婆分销ERP A/V系列导出提示加载数据过大的处理方式
  • 【Python基础】 17 Rust 与 Python 运算符对比学习笔记
  • k8s除了主server服务器可正常使用kubectl命令,其他节点不能使用原因,以及如何在其他k8s节点正常使用kubectl命令??
  • 人工智能机器学习——聚类
  • 2025 汽车租赁大会:九识智能以“租赁+运力”革新城市智能配送
  • 指定端口-SSH连接的目标(告别 22 端口暴力破解)
  • 结构体简介
  • window cmd 命令行中指定代理
  • 对于单链表相关经典算法题:203. 移除链表元素的解析
  • 数据结构:栈和队列力扣算法题
  • 空域属不属于自然资源?(GPT5)
  • Redis-事务与管道