本文是对OpenCV2.4.13文档的部分翻译,作个人学习之用,并不完整。
Mat是对旧的CvMat和IplImage的替代。
OpenCV2中有许多模块,每个都包含与某种任务相关的数据结构和函数,使用时只需要一个子集即可,所以在引入头文件时只要引入需要的。
所有OpenCV相关的都放入了名空间:cv来避免与其他库的数据结构以及函数冲突。因此,当需要时使用前缀cv::或在include后加一句using namespace cv;。
记住要引入需要的所有模块,如果在Windows,需要使用DLL系统,还要将所有二进制文件加入到路径中去。
将IplImage或CvMat转换为Mat:
Mat I;
IplImage pI=I;
CvMat mI=I;
如果想要使用指针,编译器不能自动断定你想要什么,所以需要显式指定目标,调用他们的operator并用&获得指针:
Mat I;
IplImage* pI=&I.operator IplImage();
CvMat* mI=&I.operator CvMat();
C中最大的问题是需要自己进行内存管理,在OpenCV中引入了一种智能指针,可以在对象不再使用时自动释放。声明就是将Ptr具体化:
Ptr<IplImage> piI=&I.operator IplImage();
从C的数据结构转换到Mat可以通过传递内部的构造器:
Mat K(piL),L;
L=Mat(pI);
#include <stdio.h>
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
using namespace cv; // 新的C++接口API在这个名空间中
using namespace std;
// comment out the define to use only the latest C++ API
#define DEMO_MIXED_API_USE
int main()
{
const char* imagename = "lena.jpg";//argc > 1 ? argv[1] :
#ifdef DEMO_MIXED_API_USE//如果混用了API
Ptr<IplImage> IplI = cvLoadImage(imagename); // Ptr<T> 是安全的指针类型
if(IplI.empty())
{
cerr << "Can not load image " << imagename << endl;
return -1;
}
Mat I(IplI); // 转换成新的容器,只创建头部,不复制图像
#else
Mat I = imread(imagename); // cvLoadImage的代替, MATLAB式函数
if( I.empty() ) // 和!I.data一样
{
cerr << "Can not load image " << imagename << endl;
return -1;
}
#endif
// 因为想要看到图像的亮度分量,将图像转换成YUV颜色空间,输出的图像会自动创建
Mat I_YUV;
cvtColor(I, I_YUV, COLOR_BGR2YCrCb);//新的结构没有指针问题,尽管可能用到旧的函数,但最终结果会转换为Mat对象
vector<Mat> planes; // 使用STL向量结构来存储多个Mat对象
split(I_YUV, planes); // 将图像分割到不同的颜色通道中
#if 1 // 如果想要看到模糊、复杂的处理过程,改为0
// 扫描Mat,这里使用了OpenCV中三种主要的图像扫描算法:[]操作符、迭代器、单元素访问,将原有格式转换成新的只需要将前面的cv前缀去掉并使用Mat数据结构即可
// Method 1. 用迭代器处理Y通道
MatIterator_<uchar> it = planes[0].begin<uchar>(), it_end = planes[0].end<uchar>();
for(; it != it_end; ++it)
{
double v = *it * 1.7 + rand()%21 - 10;
*it = saturate_cast<uchar>(v*v/255);
}
for( int y = 0; y < I_YUV.rows; y++ )
{
// Method 2. 使用预存储行指针处理U通道
uchar* Uptr = planes[1].ptr<uchar>(y);
for( int x = 0; x < I_YUV.cols; x++ )
{
Uptr[x] = saturate_cast<uchar>((Uptr[x]-128)/2 + 128);
// Method 3. 使用单元素访问处理V通道
uchar& Vxy = planes[2].at<uchar>(y, x);
Vxy = saturate_cast<uchar>((Vxy-128)/2 + 128);
}
}
#else//加权平均函数的例子
//加入了高斯噪音再通过一些公式将通道混合
Mat noisyI(I.size(), CV_8U); // 创建一个特定大小和类型的矩阵
// 用正态分布的随机值(在偏差数附近)填充矩阵
// 也有randu()来生成均匀分布的随机数
randn(noisyI, Scalar::all(128), Scalar::all(20));
// 将noisyI模糊一点,卷积模板矩阵是3x3两个sigma都设为0.5
GaussianBlur(noisyI, noisyI, Size(3, 3), 0.5, 0.5);
const double brightness_gain = 0;
const double contrast_gain = 1.7;
#ifdef DEMO_MIXED_API_USE
// 为了将新的矩阵传递给函数,对IplImage或CvMat执行:
// step 1) 转换头部(不复制数据)
// step 2) 调用函数(tip: 为了传递一个指针,不要忘了加&)
IplImage cv_planes_0 = planes[0], cv_noise = noisyI;
cvAddWeighted(&cv_planes_0, contrast_gain, &cv_noise, 1, -128 + brightness_gain, &cv_planes_0);
#else
addWeighted(planes[0], contrast_gain, noisyI, 1, -128 + brightness_gain, planes[0]);
#endif
const double color_scale = 0.5;
// Mat::convertTo() 代替了 cvConvertScale.
// 其中一个参数必须显示指定输出矩阵的类型,我们将其完好保存在planes[1].type()
planes[1].convertTo(planes[1], planes[1].type(), color_scale, 128*(1-color_scale));
// cv::convertScale的替代格式(如果我们知道编译时的数据类型——uchar)
// 这个表达式不会创建任何临时数组,所以应该会和上述方法一样快
planes[2] = Mat_<uchar>(planes[2]*color_scale + 128*(1-color_scale));
// Mat::mul 代替了 cvMul(). 使用简单表达式,没有临时数组创建
planes[0] = planes[0].mul(planes[0], 1./255);
#endif
//通道变量也是Mat类型,将Mat转换为IplImage很简单,只需要一个赋值操作符即可
merge(planes, I_YUV); // 合并结果
cvtColor(I_YUV, I, CV_YCrCb2BGR); // 处理输出的RGB图像
namedWindow("image with grain", WINDOW_AUTOSIZE); // 创建图像
#ifdef DEMO_MIXED_API_USE
// 证明I和IplI真的共享数据(上面的结果)
// 处理存储在I所以也在IplI
cvShowImage("image with grain", IplI);
#else
imshow("image with grain", I); // 新MATLAB形式的函数来显示图像,对于Mat和IplImage都能接受
#endif
waitKey();
// Tip: 不需要释放内存
// 所有内存都会被Vector<>、Mat、Ptr<>析构函数自动释放
return 0;
}
结果:
第一种处理:
第二种处理: