3通道图的数据在opencv的mat是如何存放的

Source

在 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::rowcv::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<>() 方法、指针访问或迭代器来操作像素数据。了解这些细节有助于高效地处理图像数据。