opencv从入门到放弃(3)——与Mat深入接触

一、浅入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

猜你喜欢

转载自blog.csdn.net/qq_40238526/article/details/90551030