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類型,省去了一些麻煩,兩種方式對比來說,後者的性能更加高效一些。

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