2.3 OpenCV对图像的基本操作
操作图片
输入输出
加载图片文件
Mat img = imread(filename);
如果读取的文件是jpg格式的文件,那么默认就会为此文件创建一个3通道的图像.如果你仅仅是加载一个灰度图像可使用:
Mat img = imread(filename, IMREAD_GRAYSCALE);
注意
图片的文件格式由图片文件的文件头决定即图片文件的前几个字节所决定,也可以理解为文件的扩展名.保存文件.
imwrite(filename, img);
使用cv::imdecode和cv::imencode是从内存读写图片,而非从文件进行读写图片.
图片的基本操作
访问像素的值
为了得到像素的值,你首先必须知道图像的类型以及颜色通道的数量.下面是一个单通道的灰度图像(类型是8UC1)访问坐标的例子
Scalar intensity = img.at<uchar>(y, x);
intensity.val[0]的值的范围是0-255.需要注意下x,y的顺序.因为在OpenCV中,图像用和矩阵相同的结构来表示,所以我们对这两种情况都使用相同的约定--行(y)索引[从0开始]在前,列(x)索引[从0开始]在后.另外你可以使用下面的C符号
//typedef Scalar_<double> cv::Scalar
Scalar intensity = img.at<uchar>(Point(x, y));
现在我们来考虑一个BGR(imread返回的默认格式)颜色排序的3通道图像:
//typedef Vec<double, 3> cv::Vec3d
Vec3b intensity = img.at<Vec3b>(y, x);
uchar blue = intensity.val[0];
uchar green = intensity.val[1];
uchar red = intensity.val[2];
你可以对浮点图像使用相同的方法(例如你可在3通道的图像上运行Sobel得到图像)
Vec3f intensity = img.at<Vec3f>(y, x);
float blue = intensity.val[0];
float green = intensity.val[1];
float red = intensity.val[2];
同样的方法也可用来改变像素的值
img.at<uchar>(y, x) = 128;
OpenCV中有一些函数,特别是calib3d模块的函数,例如cv::projectPoints,它们以Mat的形式接受2D或3D的点阵数组.矩阵应该有确定的列,且每行对应一个点,矩阵的类型应该是32FC2,32FC3,这样的矩阵可以容易的由std::vector构造
vector<Point2f> points;
//... fill the array
Mat pointsMat = Mat(points);
使用同样的Mat::at()方法可以用来访问矩阵中的每一个点.
Point2f point = pointsMat.at<Point2f>(i, 0);
内存管理和引用计数
Mat是一个用于保存矩阵/图像特征(行,列,数据类型)和数据指针的结构体.所以没有什么可以阻止我们有有多个描述同一个图像的Mat实例.Mat还保持了一个引用计数,此引用计数告诉我们当Mat的一个特定实例被销毁的时候是否需要重新分配数据.下面是创建两个矩阵而不是复制两份数据的例子
//typedef Point3_<float> cv::Point3f
std::vector<Point3f> points;
// .. fill the array
Mat pointsMat = Mat(points).reshape(1);
上述代码我们得到一个32FC1的3列矩阵,而非一个32FC3的一列矩阵.pointsMat使用来自point的数据,并且在销毁时不会释放内存.在这个特定例子中,开放者必须确保points的声明周期要比pointsMat的声明周期更长.如果我们需要赋值数据的时候可以使用cv::Mat::copyTo()或者cv::Mat::clone():
Mat img = imread("image.jpg");
Mat img1 = img.clone();
和C API相反在C API中开发者必须创建一个输出图像,并为每个函数提供一个空的输出Mat.C++ API的每个实现均调用Mat::create()来创建目标矩阵.如果矩阵为空,则create()为其分配数据,如果不为空,并且矩阵拥有正确的大小和类型,则此方法不执行任何操作.但是如果size或type和输入参数不同,则释放数据,并重新分配新的数据.
Mat img = imread("image.jpg");
Mat sobelx;
Sobel(img, sobelx, CV_32F, 1, 0);
基本操作
在矩阵上还定义了许多方便的运算符.
从现有的灰度图生成黑色图
img = Scalar(0);
设置ROI感兴趣区域
Rect r(10, 10, 100, 100);
Mat smallImg = img(r);
Mat转换为C数据结构
Mat img = imread("image.jpg");
IplImage img1 = cvIplImage(img);
CvMat m = cvMat(img);
需要注意的是转换操作也并未复制数据
彩转灰
Mat img = imread("image.jpg"); // loading a 8UC3 image
Mat grey;
cvtColor(img, grey, COLOR_BGR2GRAY);
8UC1转32FC1
src.convertTo(dst, CV_32F);
可视化图像
在开发过程中查看算法的中间结果是很重要的,OpenCV提供了一种方便的图像可视化方法.使用如下方式可以显示8U类型的图像
Mat img = imread("image.jpg");
namedWindow("image", WINDOW_AUTOSIZE);
imshow("image", img);
waitKey();
调用waitKey()启动一个消息传递周期,等待"image"窗口中的按键操作,一个32F的图像转换为8U
Mat img = imread("image.jpg");
Mat grey;
cvtColor(img, grey, COLOR_BGR2GRAY);
Mat sobelx;
Sobel(grey, sobelx, CV_32F, 1, 0);
double minVal, maxVal;
minMaxLoc(sobelx, &minVal, &maxVal); //find minimum and maximum intensities
Mat draw;
sobelx.convertTo(draw, CV_8U, 255.0/(maxVal - minVal), -minVal * 255.0/(maxVal - minVal));
namedWindow("image", WINDOW_AUTOSIZE);
imshow("image", draw);
waitKey();
注意 cv::namedWidow()不是必须的,因为后面紧跟着cv::imshow().当然它可用来改变窗口属性,或使用cv::createTrackBar.
备注:本来以为凭着几年的软件开发经验可以搞下OpenCV了,但是实践看来虽然代码都看得懂.但是深层的原理还是不甚了解呀.有时间还是需要重新找补下丢下的线性代数和高等数学方面的知识了.