本节内容是OpenCV中的一些图像的操作,内容上与前面有重复。但是tutorials有这个文档,所以还是再梳理一遍吧。原文有C++、Java、Python三种代码示例,这里只说明C++部分。
- 原文网址Operations with images
- 本地目录D:opencvsourcesdoctutorialscore
- 代码目录D:opencvsourcessamplescpptutorial_codecoremat_operations
- GitHub 有相应文档和OpenCV源代码
- 版本OpenCV4.1.2(版本兼容性见英文原文,部分文档适用于OpenCV2.0和3.0)
- 环境Windows、C++、VS2019 Community
输入/输出
从文件中读取图像:
Mat
如果读取一个jpg图片,默认是三个通道。如果想读取为灰度图像,用下面方法:
Mat img = imread(filename, IMREAD_GRAYSCALE);
保存图像到文件:
imwrite(filename, img);
图像文件格式由扩展名决定。另外imdecode和imencode函数是读取和写入到内存,而不是文件。
图像的基本操作
获取像素的灰度值Accessing pixel intensity values
为了获取图像灰度值,需要知道图像的数据类型和通道数。
下面是单通道灰度图像(8UC1)和像素坐标(x,y):
Scalar intensity = img.at<uchar>(y, x);//注意是行、列坐标
其中,intensity.val[0]就是该点的灰度值,取值范围是0到255。需要注意x和y的顺序,是从0开始的列索引(column对应x坐标)还是行索引(row对应y坐标)。也可以用下面的:
Scalar intensity = img.at<uchar>(Point(x, y));//注意是点(x,y)
下面,考虑3通道的BGR图像
Vec3b intensity = img.at<Vec3b>(y, x);//注意是点的行、列位置
uchar blue = intensity.val[0];//蓝色分量
uchar green = intensity.val[1];//绿色分量
uchar red = intensity.val[2];//红色分量
浮点图像,像素的数据类型是float,不是前面的uchar。(例如,三个通道进行sobel运算得到的结果就是float型的图像)
Vec3f intensity = img.at<Vec3f>(y, x);
float blue = intensity.val[0];
float green = intensity.val[1];
float red = intensity.val[2];
上面的方法也可用于改变像素值,如:
Memory manimg.at<uchar>(y, x) = 128;
OpenCV的某些函数,尤其是calib3d模块中,如cv::projectPoints,会使用2D和3D点组成Mat数据。这些点存储为一列,每行只对应一个点。矩阵类型分别对应32FC2或32FC3。这样的矩阵可以用std::vector来构造。
vector<Point2f> points;
//...对points数组进行赋值,如points.push(Point2f(x,y))等等
Mat pointsMat = Mat(points);//生成Mat类型数据,这里是2通道
Point2f point = pointsMat.at<Point2f>(i, 0);//访问Mat中第i个点
内存管理与参考计数Memory Management and reference counting
前面讲过Mat数据类型,Mat由包含矩阵或者图像的参数(行数、列数、数据类型等)和一个指向数据的指针构成。所以对应同一个数据,我们可以创建多个Mat对象。 Mat用引用计数的方法来表明当一个Mat对象销毁时,数据是否必须释放。下面是不用拷贝数据,创建两个矩阵的例子:
std::vector<Point3f> points;
// .. 填充数组
Mat pointsMat = Mat(points).reshape(1);//reshap将通道变为1
//先将points转换为32FC3的Mat,再reshape为32FC1的pointsMat
最后,我们得到了一个32FC1的3列矩阵,取代了32FC3的1列矩阵。当pointsMat 使用来自points的数据,并且pointsMat销毁时不会释放内存。在这种特殊情况下,points的使用寿命必须必pointsMat长才行。如果需要拷贝数据,用下面的方法。cv::Mat::copyTo 或者cv::Mat::clone。
Mat img = imread("image.jpg");
Mat img1 = img.clone();
提供给一个函数空的Mat对象作为输出,函数内部会调用Mat::create,为该矩阵分配数据内存。如果非空,Mat对象大小和数据类型正确,则什么也不做。如果大小或数据类型不同于输入参数,矩阵数据会释放并且分配新的数据。如下:
Mat img = imread("image.jpg");
Mat sobelx;
Sobel(img, sobelx, CV_32F, 1, 0);//函数执行,并为sobelx分配内存
一些基本操作 Primitive operations
有一些很方便的定义矩阵的方式。例如,下面是由一副灰度图像img创建一副黑色图像
img = Scalar(0);//所有元素赋值为0
选择感兴趣区域:
Rect r(10, 10, 100, 100);
Mat smallImg = img(r);
彩色转换为灰度:
Mat img = imread("image.jpg"); // 加载8UC3图像
Mat grey;
cvtColor(img, grey, COLOR_BGR2GRAY);
图像类型从8UC1到32FC1:
src.convertTo(dst, CV_32F);
显示图像 Visualizing images
在调试程序过程中可以显示图像。显示8U图像:
Mat img = imread("image.jpg");
namedWindow("image", WINDOW_AUTOSIZE);//如果不改变窗口属性,可以不写该行
imshow("image", img);
waitKey();
32F图像需要转换为8U类型才能显示:
Mat img = imread("image.jpg");
Mat grey;
cvtColor(img, grey, COLOR_BGR2GRAY);
Mat sobelx;
Sobel(grey, sobelx, CV_32F, 1, 0);//Soble结果为32F图像
double minVal, maxVal;
minMaxLoc(sobelx, &minVal, &maxVal); //找到最小和最大值
Mat draw;
//sobelx图像灰度拉伸,并转换为8U显示
sobelx.convertTo(draw, CV_8U, 255.0/(maxVal - minVal), -minVal * 255.0/(maxVal - minVal));
namedWindow("image", WINDOW_AUTOSIZE);
imshow("image", draw);
waitKey();