一、浅入Mat
Mat就是opencv中图片的数据类型,好想一口气详细说完啊,但是还是慢点吧,先来看看怎么加载一个图片吧,然后慢慢解释其中的奥秘。
#include<opencv2/opencv.hpp> #include<iostream> using namespace std; //这个很重要哦,99%的opencv函数都在这个命名空间里面,没有这个代码直接泛红 using namespace cv; int main() { //创建一个Mat类型的img Mat img; //imread可以根据文件路径读入一个图片并赋给Mat矩阵,这时候img就有值了 img = imread("1.png"); //你也可以写成 Mat img = imread("1.png"); //namedWindow就是创建一个用来展示图片的窗口,并且标题是“图片” namedWindow("图片"); //imshow就是用来show的把你的img图片放入“图片”窗口show出来 imshow("图片", img); //这个不能忘了,不然你的图片就一闪而过了,当然你还可以写成waitKey(1000);,就是展示1000毫秒,没有值就是一直展示 waitKey(); return 0; }
执行结果:
二、图片在计算机中的存储
一般来说计算机中图片是用一个二维矩阵来存储的,矩阵中的每一个点就是一个像素,而像素的值就代表它们的颜色。图片又分为彩色图片、灰度图片、二值图片,这三种虽然都是二维像素矩阵,但是它们单个像素的组成却不同。下面我们来详细分析它们的不同之处。
1.彩色图片
彩色图片通常有RGB,HSV,YCrBr等格式,为了不让大家一头雾水,本节用RGB格式作为例子介绍彩色图片,HSV和YCrBr在以后用到的时候会详细介绍,当然你也可以自己去搜索相关资料。虽然在图片矩阵上每一个点都是一个像素,但是彩色图片的像素却是由RGB三色组成的(我们知道RGB三色可以通过组合变为任何色彩)也就是说RGB图片的一个像素点由三个值组成,也就是R(red)、G(green)、B(blue)三个值决定。我们令pixel是彩色图片的一个像素,那么我们可以通过数组的方式来访问RGB三个值,在opencv中pixel[0]代表B的值(0~255),pixel[1]代表G的值(0~255),pixel[2]代表R的值(0~255),是的是和RGB反着来的,要记住了。所以一个像素由三个值组成,我们就把这种图片成为三通道图片。
我们来创建一个三通道彩色图片吧;
#include<opencv2/opencv.hpp> #include<iostream> using namespace std; using namespace cv; int main() { //上节的Size还记得吗?我们让这个图片的大小是200*200 Size sz(200, 200); //Scalar也是上节的,我们让这个图片的颜色是蓝色 /* 你发现没有Scalar的三个值其实就是每个像素的三个通道,也就是BGR的值,你可以试着输入不同的组合 如(25,32,230),看看它们是什么颜色,当然每个值的取值范围都是0~255 */ Scalar color(255, 0, 0); //创建一个Mat类型的img,大小是sz,类型是CV_8UC3(下面会解释),颜色是蓝色(这是不是读入图片了,我们手动创建一个图) Mat img = Mat(sz, CV_8UC3, color); namedWindow("图片"); imshow("图片", img); waitKey(); return 0; }
执行结果:
那么代码里面的CV_8CU3是什么意思呢,CV就是opencv的意思,8U就是8位uchar类型的意思,C3就是channel(通道)数有3个,看吧,就是8位3通道图片的意思,类似的还有CV_8UC1,CV_8UC2好多好多。
2.灰度图片
灰度图片和彩色图片一样,但是在像素的组成上它只有一个值组成,也就是单通道。所以所有的灰度图就像它的名字一样灰呼呼的,这个像素的取值范围也是0~255,我们把它成为灰度图片的梯度,0是最深的灰度也就是黑色,255就是白色。
我们来展示一个灰度图片吧
其实三通道的彩色图片可以通过公式转为单通道的灰度图,Gray = R*0.299 + G*0.587 + B*0.114,我们来用公式转换一个吧,下面代码很重要哦,涉及到像素点的访问
先把三种访问方式都说一下,我们本次用最慢最慢的数组访问(最直观,最新手,真的超级慢)
1. 利用指针访问
通过调用函数 Mat::ptr(i) 来得到第i行的首地址地址,然后在行内访问像素;
for (int i = 0; i < img.rows; i++) { for (int j = 0; j < img.cols; j++) { img.ptr<Vec3b>(i)[j][0] = 0; } }
2. 利用迭代器访问
创建一个Mat::Iterator对象it,通过it=Mat::begin()来的到迭代首地址,递增迭代器知道it==Mat::end()结束迭代;
Mat::Iterator it; it=img.begin(); while (it != img.end<Vec3b>()) { //(*it)[0] = 0;//蓝色通道置零; (*it)[1] = 0;//绿色通道置零; //(*it)[2] = 0;//红色通道置零; it++; }
3. 动态访问
这种方法是最慢的一种方法,但是比较好理解,使用at函数来得到像素,Mat::at(i,j)为一个像素点的像素值数组,是一个大小为3的数组。从0到2存放了BGR三种颜色的灰度值。
for (int i = 0; i < img.rows; i++) { for (int j = 0; j < img.cols; j++) { img.at<Vec3b>(i, j)[2] = 0; } }
学完了三种访问方式(我知道你可能没看懂,没关系,来看详细代码吧),我们来试着把彩色图片转化为灰度图吧, 公式拿过来放着:Gray = R*0.299 + G*0.587 + B*0.114
#include<opencv2/opencv.hpp> #include<iostream> using namespace std; using namespace cv; int main() { //老样子,imread读入图片 Mat img = imread("1.png"); //创建一个用于接收灰度图片gray_img容器,他有和img一样的大小,是CV_8UC1(单通道嘛)类型的 Mat gray_img = Mat(img.size(), CV_8UC1); //我们来用数组访问 //img.rows就是图片的行数,如果图片是平面坐标系,这个就是y的意思 //那为什么不先访问x呢?这个问题。。。没人拦着你,但是约定成俗的就是先y,后x for (int i = 0; i < img.rows; i++) { //访问列,也就是x for (int j = 0; j < img.cols; j++) { //取到蓝色的值 int b = img.at<Vec3b>(i, j)[0]; //取到绿色的值 int g = img.at<Vec3b>(i, j)[1]; //取到红色的值 int r = img.at<Vec3b>(i, j)[2]; /*啥?没看懂 img.at<Vec3b>(i, j)[2]是啥,快去补补c++模板知识,十分钟就看懂了*/ //公式Gray = R*0.299 + G*0.587 + B*0.114 //saturate_cast<uchar>就是把数值控制在uchar的取值范围(0-255)内,因为灰度梯度最大就是255 int gray = saturate_cast<uchar>(r*0.299 + g * 0.587 + b * 0.114); //单通道,所以没有后面的数组了,记得这次是uchar(灰度访问专用)了,不是Vec3b(彩色专用,有个3你也知道是3通道了) gray_img.at<uchar>(i, j) = gray; } } //其实不写namedWindow也可以 imshow("图片", img); //namedWindow里面默认是1,窗口不可变,如果是0的话窗口可拉大拉小 namedWindow("灰度图", 0); imshow("灰度图", gray_img); waitKey(); return 0; }
执行结果:(为了让你更容易理解namedWindow("",0);我故意把窗口拉的很大)
模板还不知道什么意思,代码里面Vec3b什么意思?来看看这个大佬的介绍博文,https://blog.csdn.net/qq_29540745/article/details/52517269
和你说个小秘密,其实opencv自带彩色图转灰度图函数,来看一下吧
#include<opencv2/opencv.hpp> #include<iostream> using namespace std; using namespace cv; int main() { Mat img = imread("1.png"); Mat gray_img; /* * 函数cvtColor *参数:输入的彩色图,输出的图, 转化模式“COLOR_BGR2GRAY”(BGR转灰度,BGR to GRAY嘛) */ cvtColor(img, gray_img, COLOR_BGR2GRAY); imshow("图片", img); imshow("灰度图", gray_img); waitKey(); return 0; }
是不是很简单,你不用去关心什么类型了,函数就完事了,opencv内部帮你转换,刚刚的例子其实使用来说明像素点的访问的,自己运行下试试吧!
3.二值图
划重点了啊,二值图是opencv处理图片最最最最常用的图片,其实他就是特殊的灰度图,只不过他的梯度只有两个0和255,1-254通通没有,二值,两个值嘛。
那么怎么得到一个彩色图的二值图呢,先把彩色图转为灰度图,然后设定一个阈值,灰度图中大于等于阈值的像素点通通置为255,小于阈值的像素点置为0。
你第一次听阈值一定很懵逼吧?huo值?fa值?不不不,是yu值。我来解释一下吧,比如有一个数列1,2,3,4,5......100,我们可以取1-100内任意一个数作为阈值,例如53,那么1-52就是小于阈值的,53-100就是大于等于阈值的。明白了吧。
来看看代码
#include<opencv2/opencv.hpp> #include<iostream> using namespace std; using namespace cv; int main() { Mat img = imread("1.png"); Mat gray_img; Mat thresh_img; cvtColor(img, gray_img, COLOR_BGR2GRAY); //这个函数是将灰度图转为二值图的函数,由于太长就不在代码里面解释 threshold(gray_img, thresh_img, 70, 255, THRESH_BINARY); imshow("图片", img); imshow("灰度图", gray_img); imshow("二值图", thresh_img); waitKey(); return 0; }
执行结果:
我们来解释一下threshold函数的用法吧
参数说明
src:源图像,可以为8位的灰度图,也可以为32位的彩色图像。(两者由区别)
dst:输出图像
thresh:阈值
maxval:dst图像中最大值
type:阈值类型,可以具体类型如下:
(这段内容截取自博客:https://blog.csdn.net/u012566751/article/details/77046445)
4.作业
尝试着用像素点访问的方式,自己实现threshold函数吧
三、总结
其实Mat还有很多用法,但考虑到会迷惑新手,有很多知识就没说,如果想和Mat生猴子的话,点击下面链接: http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/core/mat%20-%20the%20basic%20image%20container/mat%20-%20the%20basic%20image%20container.html#matthebasicimagecontainer