cv::mat的基本操作

本文详细解释了OpenCV中Mat数据结构的基本原理,包括如何创建、存储、显示和保存图像,并讨论了Mat对象的引用计数机制、复制方法以及不同类型的内存分配。此外,还介绍了Mat的显示、输入输出以及关键参数设置。

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

1.Mat基础
在计算机内存中,数字图像是已矩阵的形式保存的。OpenCV2中,数据结构Mat是保存图像像素信息的矩阵,它主要包含两部分:矩阵头和一个指向像素数据的矩阵指针。
矩阵头主要包含,矩阵尺寸、存储方法、存储地址和引用次数等。
矩阵头的大小是一个常数,不会随着图像的大小而改变,但是保存图像像素数据的矩阵则会随着图像的大小而改变,通常数据量会很大,比矩阵头大几个数量级。这样,在图像复制和传递过程中,主要的开销是由存放图像像素的矩阵而引起的。因此,OpenCV使用了引用次数,当进行图像复制和传递时,不再复制整个Mat数据,而只是复制矩阵头和指向像素矩阵的指针。例如:
1
2
3
cv::Mat a ;//创建矩阵头
a = cv::imread("f:\\psb.jpg");//读入图像
cv::Mat b = a ;//复制 
上面的a,b有各自的矩阵头,但是其矩阵指针指向同一个矩阵,也就是其中任何一个改变了矩阵数据都会影响另外一个。
那么,多个Mat共用一个矩阵数据,最后谁来释放矩阵数据呢?
这就是引用计数的作用,当Mat对象每被复制一次时,就会将引用计数加1,而每销毁一个Mat对象(共用同一个矩阵数据)时引用计数会被减1,当引用计数为0时,矩阵数据会被清理。
 
上图是Mat对象a,b共用一个矩阵,故其引用计数refcount为2.
但是有些时候仍然会需要复制矩阵数据本身(不只是矩阵头和矩阵指针),这时候可以使用clone 和copyTo方法。
cv::Mat c = a.clone();
cv::Mat d ;
a.copyTo(d);
上面代码中的c,d各自拥有自己的矩阵,改变自己的矩阵数据不会相互影响。
在使用Mat中,需要记住:
  1. OpenCV中的内存分配是自动完成的(不是特别指定的话)
  2. 使用OpenCV的C++ 接口时不需要考虑内存释放问题
  3. Mat的赋值运算和拷贝构造函数只会拷贝矩阵头,仍然共同同一个矩阵
  4. 如果要复制矩阵数据,可以使用clone和copyTo函数
2.Mat存储方法
Mat中矩阵的每个元素可以使用不同的数据类型,最小的数据类型是char,占用一个字节或者8位,可以是有符号的(0到255)或者是无符号的(-127到127)。在RGB颜色空间中,使用三个char类型可以表示1600万中颜色,但在图像处理的过程中有可能会使用到float或者double来表示图像的像素。
Mat的创建
构造函数
cv::Mat img(2,2,CV_8UC3,cv::Scalar(0,0,255));
上述代码创建了一个2行2列的矩阵,矩阵元素使用8位无符号char类型保存,具有3通道,每个像素的初始值是(0,0,255)
构造函数的前两个参数指定了矩阵的行和列
第三个参数指定矩阵元素的数据类型以及通道数,其指定规则如下:
CV_[The number of bits per item][Signed or Unsigned][TypePrefix]C[The channel number]
四部分分别指定:元素的大小,是有符号还是无符号,数据类型以及通道数
最后一个参数,Scalar是short型的vector,提供矩阵的初始化。
Create方法
该方法不能为矩阵设置初始值,只是在改变尺寸时为矩阵数据重新分配内存。使用方法:
img.create(4,4,CV_8UC(2));
创建了一个4行4列有2个通道的矩阵
MATLAB形式的初始化
cv::Mat e = cv::Mat::eye(4,4,CV_64F);
cv::Mat z = cv::Mat::ones(2,2,CV_32F);
cv::Mat o = cv::Mat::zeros(3,3,CV_8UC1);
Mat e是4行4列的对角矩阵
Mat z是2行2列的单位矩阵
Mat o是3行3列的零矩阵
小矩阵的初始化
对于小矩阵可以使用逗号分割的初始化函数
Mat c =(Mat_<double>(3,3)<<1,2,3,0,-1,0,4,5,6);
在对图像进行模板运算时,定义模板使用这种方法是很方便的。
3.Mat的输入输出
使用imread函数,向Mat对象中写入一个图像。
a = cv::imread("f:\\psb.jpg");//读入图像
imread的原型如下
cv::Mat imread(const string& filename,int flags=1)
filename指定要读取图像的位置
flags指定图像的颜色空间  
    flags > 0 3通道的彩色图像
    flags = 0 灰度图像
    flags < 0 不作改变
也可以有以下的枚举值
CV_LOAD_IMAGE_ANYDEPTH、CV_LOAD_IMAGE_COLOR、CV_LOAD_IMAGE_GRAYSCALE
 
使用imwrite函数,将Mat对象保存到指定的文件中。
imwrite的函数原型如下:
bool imwrite(const string& filename,InputArray img,constvector<int>& params=vector<int>())
filename,指定的文件
img  要保存的Mat对象
params 用来指定图像的保存编码方式。
使用filename的扩展名来指定图像的保存格式(.jpg  .png  .bmp),对于不同的图像保存类型,params是不同的值
  • JPEG,params用来指定图像的质量(0到100),默认的是95.  CV_IMWRITE_JPEG_QUALITY
  • PNG,params用来指定图像的压缩级别(0到9),压缩级别越高图像占用的空间越小,保存图像所用的时间越久。默认值是3. CV_IMWRITE_PNG_COMPRESSION
  • PPM,PGM,PBM,params是一个标记(0或者1),默认的是1.CV_IMWRITE_PXM_BINARY
imwrite只能保存8位(或者是16位无符号(CV_16UC)的PNG,JPEG200或者TIFF图像)单通道或者三通道的图像,如果要保存的不是这样的图片,可以使用convertTo或者cvtColor来进行转变。
下面代码展示了如果使用imwrite向文件中写入一个4通道的png图像
复制代码
void createAlphaMat(Mat &mat) 
{
    for(int i = 0 ; i < mat.rows ; i ++) {
        for(int j = 0 ; j < mat.cols ; j ++) {
            Vec4b &rgba = mat.at<Vec4b>(i,j);
            rgba[0] = UCHAR_MAX ;
            rgba[1] = saturate_cast<uchar>((float (mat.cols - j)) / ((float)mat.cols) * UCHAR_MAX);
            rgba[2] = saturate_cast<uchar>((float (mat.rows - i)) / ((float)mat.rows) * UCHAR_MAX);
            rgba[3] = saturate_cast<uchar>(0.5 * (rgba[1] + rgba[2]));
        }
    }
}
int main()
{
    Mat mat(480,640,CV_8UC4);
    createAlphaMat(mat);

    vector<int> compression_params ;
    compression_params.push_back(CV_IMWRITE_PNG_COMPRESSION);
    compression_params.push_back(9);

    imwrite("alpha.png",mat,compression_params);

    return 0;
}
复制代码

 

4.Mat的显示
OpenCV提供了用以窗口的形式显示图片的方法,代码如下:
Mat img = imread("f:\psb.jpg");
const string name ="Hu";
namedWindow(name);
imshow(name,img);
waitKey();
<think>好的,我现在要解决的问题是如何将IplImage转换为cv::Mat。首先,我需要回忆一下这两种数据结构的基本知识。 IplImage是OpenCV早期版本(比如OpenCV 1.x)中用来表示图像的结构体,而cv::Mat是OpenCV 2.x及以后版本引入的矩阵类,提供了更强大的功能和自动内存管理。由于现在大多数代码都使用cv::Mat,了解如何转换它们对兼容旧代码很有帮助。 首先,我需要确认用户是否在使用较新的OpenCV版本,因为如果版本太旧,可能转换方法不同。但假设用户已经升级到至少OpenCV 2.x,那么正确的转换方法应该是使用cv::Mat的构造函数或者赋值操作符来封装IplImage。 接下来,我需要考虑如何具体实现这个转换。查阅文档的话,我记得有一个构造函数可以直接接受IplImage指针,例如cv::Mat mat(img); 其中img是IplImage*类型。这应该是最直接的方法,不过需要注意浅拷贝和深拷贝的问题。 这时候,我需要思考:这样的转换是否是浅拷贝?也就是说,新的cv::Mat是否和原始的IplImage共享同一数据存储区域。如果是的话,用户可能需要深拷贝,以避免原数据被修改或释放导致的问题。根据OpenCV的文档,使用构造函数时,默认情况下是共享数据,也就是浅拷贝。如果用户需要独立的副本,应该使用clone()方法,比如cv::Mat mat = cv::cvarrToMat(img).clone(); 或者更直接地,用cvCloneImage生成IplImage的深拷贝,然后再转换。 另外,旧函数cvConvertImage可能也被使用,但可能已经不推荐了。现在更推荐使用cv::Mat的构造函数。同时,需要注意IplImage和cv::Mat的数据布局是否兼容,比如通道顺序是否为BGR,不过通常在转换过程中会自动处理这些细节。 还需要提醒用户的是,在转换后,如果IplImage被释放了,浅拷贝的cv::Mat可能会指向无效的内存。因此,如果原IplImage可能被提前释放,应该进行深拷贝。 总结步骤应该是: 1. 使用cv::Mat的构造函数进行浅拷贝。 2. 如果需要深拷贝,则使用clone()方法。 3. 注意释放IplImage的时机,避免悬空指针。 最后,我需要验证这些步骤是否正确,可能需要查阅官方文档或示例代码确认。例如,OpenCV的官方文档指出可以使用cvarrToMat函数来转换IplImage*到cv::Mat,这可能在内部处理一些兼容性问题。或者更简单地,直接构造cv::Mat对象。 可能的代码示例: 浅拷贝: IplImage* img = cvLoadImage("image.jpg"); cv::Mat mat(img); // 浅拷贝 或者 cv::Mat mat = img; // 自动转换 深拷贝: cv::Mat mat = cv::cvarrToMat(img).clone(); 或者更传统的: IplImage* img = cvLoadImage("image.jpg"); cv::Mat mat; mat = cv::Mat(img, true); // 第二个参数是否表示深拷贝?需要确认构造函数参数。可能这个参数不存在,可能记错了。正确的深拷贝应该是clone()。 因此,正确的深拷贝方法应该是构造后再克隆,或者使用cvarrToMat。或者,更简单的是,直接使用cv::Mat的赋值,然后调用clone。 另外,在OpenCV中,当IplImage转换为cv::Mat时,可能需要注意图像的ROI(Region of Interest),因为cv::Mat的构造函数会考虑到IplImage的ROI设置,只引用该区域的数据。如果需要复制整个图像而忽略ROI,可能需要调整IplImage的ROI到整个图像后再转换。 最后,用户可能还需要注意颜色空间的转换,比如如果IplImage是单通道的,cv::Mat也会保持同样的通道数,但可能需要显式转换颜色空间,比如从BGR到RGB。 总结起来,回答应该分步骤说明如何转换,强调浅拷贝与深拷贝的区别,并提供代码示例,同时提醒注意事项。</think>### IplImage 转 cv::Mat 的步骤及注意事项 #### 1. **基本概念** - **IplImage**:OpenCV 1.x 中的图像结构体,需手动管理内存。 - **cv::Mat**:OpenCV 2.x 及更高版本的矩阵类,支持自动内存管理。 #### 2. **转换方法** ##### **浅拷贝(共享数据)** 直接通过构造函数或赋值操作符转换,新旧对象共享同一数据内存: ```cpp IplImage* ipl_img = cvLoadImage("image.jpg"); cv::Mat mat_img(ipl_img); // 通过构造函数浅拷贝 // 或 cv::Mat mat_img = ipl_img; // 通过赋值操作符浅拷贝 ``` **注意**:若原 `IplImage` 被释放(如 `cvReleaseImage(&ipl_img)`),`cv::Mat` 将指向无效内存! ##### **深拷贝(独立数据)** 使用 `clone()` 或 `cvarrToMat` 生成独立副本: ```cpp // 方法1:构造函数 + clone() cv::Mat mat_img = cv::Mat(ipl_img).clone(); // 方法2:cvarrToMat + clone() cv::Mat mat_img = cv::cvarrToMat(ipl_img).clone(); ``` #### 3. **完整代码示例** ```cpp #include <opencv2/opencv.hpp> int main() { // 读取 IplImage IplImage* ipl_img = cvLoadImage("image.jpg"); if (!ipl_img) { printf("加载图像失败!\n"); return -1; } // 浅拷贝(共享数据) cv::Mat shallow_mat(ipl_img); // 深拷贝(独立数据) cv::Mat deep_mat = cv::Mat(ipl_img).clone(); // 释放 IplImage(深拷贝后可安全释放) cvReleaseImage(&ipl_img); // 使用 cv::Mat 进行后续操作... cv::imshow("Shallow Copy", shallow_mat); cv::imshow("Deep Copy", deep_mat); cv::waitKey(0); return 0; } ``` #### 4. **注意事项** - **数据共享风险**:浅拷贝时,修改 `cv::Mat` 会影响原 `IplImage`,反之亦然。 - **内存管理**:若需要保留数据,优先使用深拷贝。 - **ROI 处理**:若 `IplImage` 设置了 ROI,转换后的 `cv::Mat` 仅包含 ROI 区域。需重置 ROI 再转换以获取完整图像: ```cpp cvResetImageROI(ipl_img); // 重置 ROI cv::Mat full_image = cv::Mat(ipl_img).clone(); ``` - **颜色空间**:OpenCV 默认使用 BGR 格式,若需 RGB 格式,需显式转换: ```cpp cv::cvtColor(mat_img, mat_img, cv::COLOR_BGR2RGB); ``` #### 5. **总结** - 浅拷贝:快速但需注意原数据生命周期。 - 深拷贝:安全独立,适合长期使用。 - 推荐优先使用 `cv::Mat` 以简化内存管理。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值