OpenCV(四)————认识Mat对象(C++)

内容:

1.什么是Mat

2.Mat的属性与操作

3.创建Mat

 

1.什么是Mat

  • 图像文件的内存数据对象

对于人类来说,看到一个图像时,脑子里会想到这个图像内容,比如一张帅哥的照片,在我们看来就是帅哥,对于计算机来说,它只能识别0和1,不管是一张什么图像,在它眼中也只是由一些特定数字组成的数据,所以对它来说,一张图像就是一个二维矩阵。而Mat就是存储这个数据的对象,也就是存储图像文件的内存数据对象,而这个对象最主要的就是一些矩阵。

或者说是将imread()读取到的图像信息存储起来的一个数据结构,就是Mat。

Mat对象可分成两个部分,一个是包含图像长宽、色彩等信息的头部,存储源数据; 一个是含有像素信息的数据部分。想改变图像信息时,就要改变像素信息。

当读了一个图像信息之后,把它赋给另一个Mat对象时,这两个Mat对象都指向同一个Data Block,数据部分并没有改变,只进行了一个地址的指向。当使用克隆或者拷贝的API时,会创建一个新的Mat对象,跟原来的Mat对象是两个类层的Mat对象。

输出Mat对象所包含的一些信息,比如宽、高、通道数目、通道深度和字节类型:

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;


int main(int argc,char** argv)
{
    //  读入图像
    Mat src = imread("../test.jpg",IMREAD_COLOR);
    //  读入灰度图像
    //Mat src = imread("../test.jpg",IMREAD_GRAYSCALE);
    if(src.empty())
    {
        printf("不能找到文件。\n");
        return -1;
    }
    namedWindow("input",WINDOW_AUTOSIZE);
    imshow("input", src);
    int width = src.cols;
    int height = src.rows;
    int dim = src.channels();
    //  通道深度
    int d = src.depth();
    //  字节类型
    int t = src.type();
    //  打印宽、高、通道数
    //  8位+无符号Char+3个通道
    if(t == CV_8UC3)
        //  枚举类型是16
        printf("type:%d\n",t);
    //  只有当深度类型变为浮点数时,深度值才会改变
    printf("d:%d\n",d);
    printf("width:%d height:%d dim:%d\n",width,height,dim);
    waitKey(0);
    destroyAllWindows();
    return 0;
}

输出结果:

type:16
d:0
width:474 height:237 dim:3

关于通道深度和字节类型可参考下面图片中的内容:

 

2.Mat对象创建与使用

  • 创建空白Mat对象

1.第一种方法Mat( , , )

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;


int main(int argc,char** argv)
{
    //  创建了一个256×256大小的灰度(单通道)图像
    Mat t1 = Mat(256,256,CV_8UC1);
    //  0代表黑色
    t1 = Scalar(0);
    imshow("t1", t1);
    //  创建了一个256×256大小的三通道图像
    Mat t2 = Mat(256,256,CV_8SC3);
    //  创建了红色图像
    t2 = Scalar(0,0,255);
    imshow("t2", t2);
    waitKey(0);
    destroyAllWindows();
    return 0;
}

 运行结果:

                                    

2.第二种方法Mat(Size(), )

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;


int main(int argc,char** argv) 
{
    //  创建了一个512×512大小的三通道图像
    Mat t1 = Mat(Size(512, 512), CV_8UC3);
    t1 = Scalar(255, 255, 0);
    imshow("t1", t1);
}

 输出结果:

                                      

3.第三种方法Mat::zeros(,):

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;


int main(int argc,char** argv)
{
    //  创建了一个初始值全为0的256×256大小的三通道图像
    Mat t2 = Mat::zeros(Size(256,256),CV_8SC3);
    imshow("t2", t2);
    waitKey(0);
    destroyAllWindows();
    return 0;
}

 输出结果:

                                                                  

  • 从现有图像创建

1.赋值和克隆

从现有图像创建Mat对象,然后顺便验证一下最开始说的赋值和克隆的区别。

将t1的值赋值给t3后不改变t3的值,t4是t1克隆的值:

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;


int main(int argc,char** argv)
{
    //  创建了一个256×256大小的三通道图像
    Mat t1 = Mat(Size(256, 256), CV_8UC3);
    t1 = Scalar(255, 255, 0);
    imshow("t1", t1);
    //  赋值
    Mat t3 = t1;
    imshow("t3", t3);
    //  改变t3的值
    //t3 = Scalar(0,255,0);
    //  克隆
    Mat t4 = t1.clone();
    imshow("t4", t4);
    waitKey(0);
    destroyAllWindows();
    return 0;
}

输出结果:

           

 修改t3的值:

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;


int main(int argc,char** argv)
{
    //  创建了一个256×256大小的三通道图像
    Mat t1 = Mat(Size(256, 256), CV_8UC3);
    t1 = Scalar(255, 255, 0);
    imshow("t1", t1);
    //  赋值
    Mat t3 = t1;
    imshow("t3", t3);
    //  改变t3的值
    t3 = Scalar(0,255,0);
    //  克隆
    Mat t4 = t1.clone();
    imshow("t4", t4);
    waitKey(0);
    destroyAllWindows();
    return 0;
}

 输出改变t3前的t1、t3值,以及改变t3后t4的值:

           

可以看到由于t3的改变,t1的值也随之变化,t4的值也随t1的值发生改变。这说明t1和被t1赋值后的t3是指向同一个数据的。修改其一,数据就会发生改变。

若只改变克隆t1的t4的值,输出改变后的t1、t3、t4的值:

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;


int main(int argc,char** argv)
{
    //  创建了一个256×256大小的三通道图像
    Mat t1 = Mat(Size(256, 256), CV_8UC3);
    t1 = Scalar(255, 255, 0);
    //  赋值
    Mat t3 = t1;
    //  克隆
    Mat t4 = t1.clone();
    //  改变t4的值
    t4 = Scalar(0,255,0);
    imshow("t1", t1);
    imshow("t3", t3);
    imshow("t4", t4);
    waitKey(0);
    destroyAllWindows();
    return 0;
}

输出结果:

           

 很明显可以看出克隆t1的t4的值的改变是影响不了t1、t3的值的。

2.拷贝

还有一种就是拷贝:

    Mat t5;
    t1.copyTo(t5);

 意为将t1的值拷贝给t5,t5和t4的性质是相同的,在这就不进行验证了,有兴趣可以自己尝试一下。

3.Mat::zeros(,)

还可以创建和原始图像大小、类型相同,但内容可能不同的图像:

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;


int main(int argc,char** argv)
{
    //  创建了一个256×256大小的三通道图像
    Mat t1 = Mat(Size(256, 256), CV_8UC3);
    t1 = Scalar(255, 255, 0);

    //  创建了一个与t1大小、类型相同的黑色图像;
    Mat t6 = Mat::zeros(t1.size(),t1.type());
    imshow("t1", t1);
    imshow("t6", t6);
    waitKey(0);
    destroyAllWindows();
    return 0;
}

 输出结果:

                                                    

  • 遍历与访问像素值

1.基于数组方式遍历

为了更容易看出遍历过一边图像的所有像素值,我会在遍历过程中改变一下遍历过的像素值,更容易看出遍历操作。

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;


int main(int argc,char** argv)
{
    Mat src = imread("../test.jpg");

    //  遍历每个像素值
    int height = src.rows;
    int width = src.cols;
    int ch = src.channels();
    for(int row =0;row < height; row++)
    {
        for(int col = 0;col < width; col++)
        {
            if(ch == 3)
            {
                Vec3b pixel = src.at<Vec3b>(row,col);
                int blue = pixel[0];
                int green = pixel[1];
                int red = pixel[2];
                //  字节类型+3通道+bit 通过Vec3b获得
                src.at<Vec3b>(row,col)[0] = 255 - blue;
                src.at<Vec3b>(row,col)[1] = 255 - green;
                src.at<Vec3b>(row,col)[2] = 255 - red;
            }
            if(ch == 1)
            {
                //  单通道类型
                int pv = src.at<uchar>(row,col);
                src.at<uchar>(row,col) = (255 - pv);
            }
        }
    }
    imshow("pixel-demo",src);
    waitKey(0);
    destroyAllWindows();
    return 0;
}

输出结果:

                                              

可以看出来,与原图明显有差别,每个像素的值都改变过了。

2.用指针的方式遍历

把每个用指针遍历过的像素的值拷贝给另一个图像,最后可以输出一个和原图完全相同的图像。

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;


int main(int argc,char** argv)
{
    Mat src = imread("../test.jpg");
    imshow("原图",src);
    //  遍历每个像素值
    int height = src.rows;
    int width = src.cols;
    int ch = src.channels();
    //  记录结果,用于后面把原图像中的遍历过的每个像素值赋值给result;
    Mat result = Mat::zeros(src.size(),src.type());
    //  所有像素的数据都作为一个个uchar类型的数据块放在内存地址当中,要从指向开头的指针开始遍历数据
    for(int row =0;row < height; row++)
    {
        //  获取当前行的指针
        uchar* curr_row = src.ptr<uchar>(row);
        uchar* result_row = result.ptr<uchar>(row);
        for(int col = 0;col < width; col++)
        {
            if(ch == 3)
            {
                int blue = *curr_row++;
                int green = *curr_row++;
                int red = *curr_row++;
                *result_row++ = blue;
                *result_row++ = green;
                *result_row++ = red;
            }
            if(ch == 1)
            {
                int pv = *curr_row++;
                src.at<uchar>(row,col) = (255 - pv);
                *result_row++ = pv;
            }
        }
    }
    imshow("pixel-demo",result);
    waitKey(0);
    destroyAllWindows();
    return 0;
}

运行结果:

  

使用数组方式时要明确图像像素的具体类型是什么,而对于指针方式来说,使用的都是uchar类型,省去了一些麻烦,两种方式对比来说,后者的性能更加高效一些。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章