opencv——Mat数据格式及其遍历

本文介绍了OpenCV中的Mat数据格式,重点讨论了Mat对象的内存管理,包括引用计数机制,以及浅拷贝和深拷贝的区别。通过示例展示了clone()和copyTo()函数的应用。此外,还解释了Mat对象的类型标识,如CV_8UC3等,并探讨了高效遍历Mat的方法,包括isContinous()函数的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        在opencv早期的版本中,图像通过一个叫做IplImage的结构(structure)存储在内存中。由于C语言对程序员高度的信任,因此它需要手动地对内存进行管理,比如内存的分配和回收,这在大型程序设计中是比较麻烦的。幸运地是,C++可以很好地帮助程序员管理内存,因此opencv2.0后就引入了C++接口。但是C++也有缺点,比如说目前大部分的嵌入式系统只支持C语言,在这些平台上开发opencv程序的话用C++就不是很好。

    cv::Mat是一个C++类,包含两部分:1)Matrix header,包括矩阵的size、存储方式、矩阵的存储地址等信息;2)指向Marix的指针ji。由于图像处理算法通常都是计算密集型算法,出于程序速度上的考虑,opencv的设计应尽可能地避免拷贝大图像,为了解决这个问题,opencv使用了引用计数机制(reference counter system)【python也使用了这个机制,参考之前的博客】。简单来说,灭个Mat对象都有自己的header,在进行copy运算时,只有headers和指向矩阵的指针会被拷贝,而矩阵本身不会被拷贝,举个栗子:

int main() {
        cv::Mat srcImg(2, 2, CV_8UC3, cv::Scalar(0, 0, 255));
	cv::Mat dstImg(srcImg);
	cv::Mat C = srcImg;
	std::cout << srcImg << std::endl;
	std::cout << dstImg << std::endl;
	dstImg.ptr<uchar>(0)[0] = 255;
	dstImg.ptr<uchar>(0)[1] = 255;
	dstImg.ptr<uchar>(0)[2] = 0;
	std::cout << "After modified dstImg:" << std::endl;
	std::cout << srcImg << std::endl;
	std::cout << dstImg << std::endl;
	std::cout << C << std::endl;
        return 0;
}

        上面的三个Mat对象srcImg,dstImg,C最终都只想同一个数据矩阵,虽然它们的headers是不同的。对它们其中的任意一个进行修改都会影响另外两个对象。上面程序的运行结果如图:

        当然,如果想拷贝矩阵本身也是有办法的,opencv提供了两个方法:clone()和copyTo():

Mat F = A.clone();
Mat G;
A.copyTo(G);

        最后总结一下:

1)opencv函数中输出图像的内存是自动分配的;

2)赋值运算和拷贝构造函数只是拷贝了header,我们可以把这种拷贝理解为一种浅拷贝;

3)如果想进行深拷贝,即拷贝矩阵本身的数据,可以采用clone()或copyTo()函数。

        对1和2的理解可以很重要,这可以解释下面这个程序:

int main() {
	cv::String path = "E:/Data Sets/ORIGINAL/data_road_fisheye/training/image/";
	std::vector<cv::String> filenames;
	cv::glob(path, filenames);
	cv::Mat srcImg(2, 2, CV_8UC3, cv::Scalar(0, 0, 255));
	cv::Mat dstImg(srcImg);
	cv::Mat C = srcImg;
	std::cout << srcImg << std::endl;
	std::cout << dstImg << std::endl;
	std::cout << C << std::endl;
	dstImg.ptr<uchar>(0)[0] = 255;
	dstImg.ptr<uchar>(0)[1] = 255;
	dstImg.ptr<uchar>(0)[2] = 0;
	std::cout << "After modified dstImg:" << std::endl;
	std::cout << srcImg << std::endl;
	std::cout << dstImg << std::endl;
	std::cout << C << std::endl;
	cv::cvtColor(C, C, CV_RGB2GRAY);
	std::cout << "After call cv::cvtColor(C,C,CV_RGB2GRAY)" << std::endl;
	std::cout << srcImg << std::endl;
	std::cout << dstImg << std::endl;
	std::cout << C << std::endl;
	return 0;
}

        其运行结果为:

        关于如何创建一个Mat对象,最好的办法就是看mat.hpp,因为实在有太多了...,这里在介绍一下opencv里面的一下data

type,比如说CV_8UC3,CV_32FC3,CV_32F是什么意思:

CV_[the number of bits per item][signed or unsigned][Type prefix]C[The channel number]

        最后是一个大头部分:介绍如何遍历cv::Mat。

Q1:图像在Mat中是如何存储的呢?

A1:

通常我们有足够多的内存,使得上面这个矩阵可以一行接着一行地连续存储,具体是不是呢,可以用isContinous()函数来判断。因此最高效的遍历方法还是采用指针(还有迭代器方法):

Mat& ScanImageAndReduceC(Mat& I, const uchar* const table)
{
    // accept only char type matrices
    CV_Assert(I.depth() != sizeof(uchar));     

    int channels = I.channels();

    int nRows = I.rows * channels; 
    int nCols = I.cols;

    if (I.isContinuous())
    {
        nCols *= nRows;
        nRows = 1;         
    }

    int i,j;
    uchar* p; 
    for( i = 0; i < nRows; ++i)
    {
        p = I.ptr<uchar>(i);
        for ( j = 0; j < nCols; ++j)
        {
            p[j] = table[p[j]];             
        }
    }
    return I; 
}
MatObject.ptr<type>(rowIndex)[columIndex] = a number             
### 关于OpenCV C++ 中 `Mat` 类的 `at` 方法 在 OpenCV 的 C++ 接口中,`Mat` 是一种用于存储图像或矩阵数据的核心类。为了访问 `Mat` 对象中的单个元素,可以使用其成员函数 `at<T>()`[^1]。此方法允许通过指定索引来读取或修改特定位置上的像素值。 #### 使用说明 - **模板参数**:`T` 表示目标类型的指针类型,例如对于灰度图像是 `uchar` 或者彩色图像是 `Vec3b`。 - **输入参数**:接受两个整数作为行号和列号来定位具体的位置。 以下是具体的语法形式: ```cpp template<typename _Tp> const _Tp& Mat::at(int y, int x) const; template<typename _Tp> _Tp& Mat::at(int y, int x); ``` 其中 `_Tp` 应当匹配实际的数据类型以及通道数量。如果尝试用错误的类型调用,则会抛出异常或者返回未定义的结果。 #### 示例代码 下面展示如何利用 `at` 函数操作不同种类的图片数据结构: ##### 灰度图像例子 假设我们有一张8位深度的灰度图像(即每像素占用一个字节),那么可以通过如下方式获取某个点的颜色强度: ```cpp cv::Mat grayImage = cv::imread("path_to_image", cv::IMREAD_GRAYSCALE); if (!grayImage.empty()) { uchar pixelValue = grayImage.at<uchar>(rowIndex, colIndex); // 获取(rowIndex,colIndex)处的亮度值 } else { std::cout << "Error loading image!" << std::endl; } ``` ##### 彩色BGR图像例子 对于标准三通道 BGR 图像来说,通常采用 `cv::Vec3b` 来表示每一个像素包含三个组成部分——蓝色、绿色和红色分量。 ```cpp cv::Mat colorImage = cv::imread("path_to_color_image"); if(!colorImage.empty()){ cv::Vec3b &pixel = colorImage.at<cv::Vec3b>(y,x); unsigned char blue = pixel[0]; unsigned char green = pixel[1]; unsigned char red = pixel[2]; } else{ cout<<"Could not open or find the image"<< endl ; } ``` 注意,在上述实例里,必须确保所给定坐标 `(y,x)` 落入有效范围之内;否则程序可能会崩溃或是行为不可预测。 #### 性能考量 尽管直接地址寻址提供了极大的灵活性,但在循环遍历整个数组时应谨慎考虑效率问题。因为每次单独调用都会引入额外开销。因此建议尽可能批量处理大块连续内存区域以减少不必要的性能损失。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值