在 OpenCV 中,3 通道图像(如常见的 RGB 或 BGR 图像)的数据存储在 cv::Mat
对象中,其存储方式是基于行优先的连续内存布局。这意味着图像的像素数据是按行顺序存储的,并且每个像素包含 3 个通道的值。以下是详细的解释:
1. 内存布局
对于一个 3 通道图像,cv::Mat
的内存布局如下:
-
行优先存储:图像的每一行数据是连续存储的。
-
通道顺序:在 OpenCV 中,默认的通道顺序是 BGR(蓝、绿、红),而不是 RGB。
-
像素格式:每个像素由 3 个字节组成,分别表示蓝色、绿色和红色通道的值。
假设有一个图像 img
,其大小为 rows×cols,类型为 CV_8UC3
(表示每个通道是 8 位无符号整数,共有 3 个通道)。那么:
-
每个像素占用 3 个字节。
-
每行的字节数为 cols×3。
-
整个图像的内存大小为 rows×cols×3 字节。
2. 数据访问
在 OpenCV 中,可以通过多种方式访问 3 通道图像的像素数据:
2.1 使用 at<>()
方法
cv::Mat::at<>()
是一种安全的像素访问方法,适用于单个像素的读写操作。对于 3 通道图像,可以使用 cv::Vec3b
类型来访问像素值:
cv::Mat img = cv::imread("image.jpg");
if (img.empty()) {
std::cerr << "无法加载图像!" << std::endl;
return -1;
}
// 访问第 (y, x) 位置的像素
int y = 10, x = 20;
cv::Vec3b pixel = img.at<cv::Vec3b>(y, x);
// 获取每个通道的值
uchar blue = pixel[0]; // 蓝色通道
uchar green = pixel[1]; // 绿色通道
uchar red = pixel[2]; // 红色通道
// 修改像素值
img.at<cv::Vec3b>(y, x) = cv::Vec3b(255, 0, 0); // 设置为蓝色
2.2 使用指针访问
对于性能敏感的应用,可以直接通过指针访问像素数据。这种方式更高效,但需要手动计算偏移量:
cv::Mat img = cv::imread("image.jpg");
if (img.empty()) {
std::cerr << "无法加载图像!" << std::endl;
return -1;
}
// 获取图像的指针
uchar* data = img.data;
// 访问第 (y, x) 位置的像素
int y = 10, x = 20;
int cols = img.cols;
int channels = img.channels();
// 计算偏移量
int offset = y * cols * channels + x * channels;
// 获取每个通道的值
uchar blue = data[offset];
uchar green = data[offset + 1];
uchar red = data[offset + 2];
// 修改像素值
data[offset] = 255; // 蓝色通道
data[offset + 1] = 0; // 绿色通道
data[offset + 2] = 0; // 红色通道
2.3 使用迭代器
OpenCV 提供了迭代器,可以方便地遍历图像的像素:
cv::Mat img = cv::imread("image.jpg");
if (img.empty()) {
std::cerr << "无法加载图像!" << std::endl;
return -1;
}
cv::MatIterator_<cv::Vec3b> it = img.begin<cv::Vec3b>(), it_end = img.end<cv::Vec3b>();
for (; it != it_end; ++it) {
cv::Vec3b& pixel = *it;
pixel[0] = 255; // 设置蓝色通道为 255
pixel[1] = 0; // 设置绿色通道为 0
pixel[2] = 0; // 设置红色通道为 0
}
3. 内存对齐
在某些情况下,cv::Mat
的每一行可能会有额外的填充字节,以确保内存对齐。这可以通过 cv::Mat::step
属性来获取每行的实际字节数:
int step = img.step[0]; // 每行的实际字节数
对于连续存储的图像(大多数情况下),step
等于 cols * channels * sizeof(uchar)
。如果图像不是连续存储的(例如,通过 cv::Mat::row
或 cv::Mat::col
提取的子图像),step
可能会大于预期。
4. 示例:遍历图像
以下是一个完整的示例,展示如何遍历 3 通道图像并修改像素值:
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
cv::Mat img = cv::imread("image.jpg");
if (img.empty()) {
std::cerr << "无法加载图像!" << std::endl;
return -1;
}
// 遍历图像的每一行
for (int y = 0; y < img.rows; ++y) {
// 获取当前行的指针
uchar* row = img.ptr<uchar>(y);
// 遍历当前行的每一列
for (int x = 0; x < img.cols; ++x) {
// 计算偏移量
int offset = x * 3;
// 获取每个通道的值
uchar blue = row[offset];
uchar green = row[offset + 1];
uchar red = row[offset + 2];
// 修改像素值
row[offset] = 255; // 蓝色通道
row[offset + 1] = 0; // 绿色通道
row[offset + 2] = 0; // 红色通道
}
}
// 显示结果
cv::imshow("Processed Image", img);
cv::waitKey(0);
return 0;
}
5. 总结
在 OpenCV 中,3 通道图像的数据存储在 cv::Mat
对象中,采用行优先的连续内存布局,每个像素包含 3 个通道的值(默认为 BGR 顺序)。可以通过 at<>()
方法、指针访问或迭代器来操作像素数据。了解这些细节有助于高效地处理图像数据。