Mat-基本圖像容器

2018-08-29 10:58 更新

目標

我們有多種方式從現(xiàn)實世界中獲取數(shù)字圖像:數(shù)碼相機,掃描儀,計算機斷層掃描和磁共振成像等等。在任何情況下,我們(人類)看到的都是圖像。然而,當將其轉(zhuǎn)換為數(shù)字設(shè)備時,我們記錄的是圖像中每個點的數(shù)值。

Mat-基本圖像容器

例如在上述圖像中,您可以看到汽車的鏡像只不過是一個包含像素點所有強度值的矩陣。我們?nèi)绾潍@取和存儲像素值可能會根據(jù)我們的需要而有所不同,但最終,計算機世界內(nèi)的所有圖像可能會被減少到描述矩陣本身的數(shù)字矩陣和其他信息。OpenCV是一個計算機視覺庫,其主要重點是處理和操縱這些信息。因此,您需要熟悉的第一件事是OpenCV如何存儲和處理圖像。

Mat

OpenCV自2001年以來一直存在。在那些日子里,庫是圍繞C接口構(gòu)建的,并將圖像存儲在內(nèi)存中,它們使用了一個稱為IplImage的C結(jié)構(gòu)。這是大部分老版本的教程和教材。這樣做的問題是它帶來了C語言的所有缺點。最大的問題是手動內(nèi)存管理。它建立在用戶負責處理內(nèi)存分配和釋放的假設(shè)的基礎(chǔ)上。雖然這不是一個較小的程序的問題,但一旦你的代碼基礎(chǔ)的增長,處理這些代碼就更難,而不是專注于解決你的發(fā)展目標。

幸運的是C ++來了,并介紹了類的概念,使用戶更容易通過自動內(nèi)存管理(或多或少)。好消息是,C ++與C完全兼容,所以在進行更改時不會出現(xiàn)任何兼容性問題。因此,OpenCV 2.0引入了一個新的C ++界面,它提供了一種新的處理方式,這意味著您不需要調(diào)節(jié)內(nèi)存管理,使您的代碼簡潔(少寫,實現(xiàn)更多)。C ++界面的主要缺點是,目前許多嵌入式開發(fā)系統(tǒng)只支持C.因此,除非您定位嵌入式平臺,否則無需使用舊方法(除非您是一個受虐狂程序員,而且您在問為了麻煩)。

您需要了解Mat的第一件事是,您不再需要手動分配其內(nèi)存,并在不需要它時立即發(fā)布它。在執(zhí)行此操作仍然是可能的情況下,大多數(shù)OpenCV功能將自動分配其輸出數(shù)據(jù)。如果您傳遞已經(jīng)存在的Mat對象(已經(jīng)為矩陣分配了所需的空間),那么這是一個很好的獎勵,這將被重用。換句話說,我們在任何時候都使用與我們需要執(zhí)行任務(wù)一樣多的內(nèi)存。

Mat基本上是一個具有兩個數(shù)據(jù)部分的類:矩陣頭(包含矩陣的大小,用于存儲的方法,存儲在哪個地址的信息等等)和指向包含像素值(取決于所選存儲方法的任何維度)。矩陣頭大小是恒定的,然而矩陣本身的大小可以隨著圖像的不同而變化,通常會大一個數(shù)量級。

OpenCV是一個圖像處理庫。它包含大量的圖像處理功能。為了解決計算挑戰(zhàn),大多數(shù)時候你最終會使用庫的多個功能。因此,將圖像傳遞給功能是常見的做法。我們不應(yīng)該忘記,我們正在談?wù)摰膱D像處理算法,這往往是相當計算重。我們想要做的最后一件事是通過制作不必要的可能的大圖像副本進一步降低程序的速度。

為解決這個問題,OpenCV使用引用計數(shù)系統(tǒng)。這個想法是每個Mat對象都有自己的頭,但是通過使它們的矩陣指針指向相同的地址,矩陣可以在它們的兩個實例之間共享。此外,復制操作符只會將頭和指針復制到大矩陣,而不是數(shù)據(jù)本身。

Mat A, C;                          // creates just the header parts
A = imread(argv[1], IMREAD_COLOR); // here we'll know the method used (allocate matrix)
Mat B(A);                                 // Use the copy constructor
C = A;                                    // Assignment operator

所有上述對象,最后指向相同的單個數(shù)據(jù)矩陣。然而,它們的頭部是不同的,并且使用它們中的任何一個進行修改也會影響所有其他的。在實踐中,不同的對象只是向相同的底層數(shù)據(jù)提供不同的訪問方法。然而,他們的頭部不一樣。真正有趣的部分是您可以創(chuàng)建僅引用完整數(shù)據(jù)的小節(jié)的標題。例如,要在圖像中創(chuàng)建感興趣區(qū)域(ROI),您只需創(chuàng)建一個新邊界的新標題:

Mat D (A, Rect(10, 10, 100, 100) ); // using a rectangle
Mat E = A(Range::all(), Range(1,3)); // using row and column boundaries

現(xiàn)在您可以詢問矩陣本身是否屬于多個Mat對象,它們在不再需要時負責清理它。簡短的答案是:使用它的最后一個對象。這是通過使用引用計數(shù)機制來處理的。每當有人復制Mat對象的標題時,矩陣的計數(shù)器就會增加。每當頭部被清潔時,這個計數(shù)器就會減少。當計數(shù)器達到零時,矩陣也被釋放。有時你也想復制矩陣本身,所以O(shè)penCV提供了cv :: Mat :: clone()cv :: Mat :: copyTo()函數(shù)。

Mat F = A.clone();
Mat G;
A.copyTo(G);

現(xiàn)在修改F或G不會影響Mat頭指向的矩陣。所有這一切你需要記住的是:

  • OpenCV功能的輸出圖像分配是自動的(除非另有說明)。
  • 您不需要考慮OpenCVs C ++界面的內(nèi)存管理。
  • 賦值運算符和復制構(gòu)造函數(shù)只復制標題。
  • 可以使用cv :: Mat :: clone()cv :: Mat :: copyTo()函數(shù)復制圖像的基礎(chǔ)矩陣。

存儲方法

這是關(guān)于如何存儲像素值。您可以選擇使用的顏色空間和數(shù)據(jù)類型。顏色空間是指我們?nèi)绾谓M合顏色分量以編碼給定的顏色。最簡單的一個是灰色,我們可以使用的顏色是黑色和白色。這些組合使我們能夠創(chuàng)建許多灰色陰影。

對于豐富多彩的方式,我們有更多的選擇方法。他們每個人都將它們分解成三到四個基本組件,我們可以使用這些組合來創(chuàng)建其他組件。最流行的是RGB,主要是因為這也是我們的眼睛如何建立顏色。其基色為紅,綠,藍。為了編碼顏色的透明度有時是第四個元素:添加了α(A)。

然而,還有許多其他顏色系統(tǒng)都有自己的優(yōu)勢:

  • RGB是最常見的,因為我們的眼睛使用類似的東西,但請記住,OpenCV標準顯示系統(tǒng)使用BGR顏色空間(紅色和藍色通道的開關(guān))組成顏色。
  • HSV和HLS將顏色分解為色調(diào),飽和度和值/亮度分量,這是我們描述顏色的更自然的方式。例如,您可能會忽略最后一個組件,使您的算法對輸入圖像的光線條件不太敏感。
  • YCrCb被流行的JPEG圖像格式使用。
  • CIE L * a * b *是感知統(tǒng)一的顏色空間,如果您需要測量給定顏色與其他顏色的距離,則可方便使用。

每個建筑組件都有自己的有效域。這導致使用的數(shù)據(jù)類型。我們?nèi)绾未鎯M件定義了我們在其域中的控件??赡艿淖钚?shù)據(jù)類型是char,這意味著一個字節(jié)或8位。這可能是無符號的(因此可以存儲從0到255的值)或帶符號(從-127到+127的值)。盡管在三個組件的情況下,這已經(jīng)提供了1600萬個可能的顏色來表示(像在RGB情況下),我們可以通過使用浮點數(shù)(4字節(jié)= 32位)或雙(8字節(jié)= 64位)數(shù)據(jù)來獲得更精細的控制每個組件的類型。然而,請記住,增加組件的大小也會增加內(nèi)存中整個畫面的大小。

明確創(chuàng)建一個Mat對象

加載,修改和保存圖像教程中,您已經(jīng)學習了如何使用cv :: imwrite()函數(shù)將矩陣寫入圖像文件。但是,為了調(diào)試目的,查看實際值更為方便。您可以使用Mat的“操作符”來執(zhí)行此操作。請注意,這僅適用于二維矩陣。

雖然Mat作為一個圖像容器非常好,但它也是一個通用的矩陣類。因此,可以創(chuàng)建和操縱多維矩陣。您可以通過多種方式創(chuàng)建Mat對象:

    Mat M(2,2,CV_8UC3,Scalar(0,0,255));
    cout << “M =” << endl << “” <<“M << endl << endl;

MatBasicContainerOut1

對于二維和多通道圖像,我們首先定義它們的大小:行和列數(shù)明智。

然后,我們需要指定用于存儲元素的數(shù)據(jù)類型和每個矩陣點的通道數(shù)。為此,我們根據(jù)以下約定構(gòu)造了多個定義:

CV_[The number of bits per item][Signed or Unsigned][Type Prefix]C[The channel number]

例如,CV_8UC3意味著我們使用8位長的無符號字符類型,每個像素有三個形成三個通道。這是最多四個通道號預定義的。該CV ::標量是四個元件短矢量。指定這一點,您可以使用自定義值初始化所有矩陣點。如果您需要更多功能,您可以使用上部宏來創(chuàng)建類型,在括號中設(shè)置通道號,如下所示。

  • 使用C / C ++數(shù)組并通過構(gòu)造函數(shù)進行初始化
    int sz[3] = {2,2,2};
    Mat L(3,sz, CV_8UC(1), Scalar::all(0));

上面的例子顯示了如何創(chuàng)建一個具有兩維以上的矩陣。指定其維度,然后傳遞一個包含每個維的大小的指針,其余的保持不變。

    M.create(4,4,CV_8UC(2));
    cout << “M =” << endl << “”   <<“M << endl << endl;

MatBasicContainerOut2

您無法使用此結(jié)構(gòu)初始化矩陣值。如果新的大小不適合舊的,它將僅重新分配其矩陣數(shù)據(jù)存儲器。

    Mat E = Mat :: eye(4,4,CV_64F);
    cout << “E =” << endl << “” << E << endl << endl;
    Mat O = Mat :: ones(2,2,CV_32F);
    cout << “O =” << endl << “” <<“O << endl << endl;
    Mat Z = Mat :: zeros(3,3,CV_8UC1);
    cout << “Z =” << endl << “” << Z << endl << endl;

MatBasicContainerOut3

  • 對于小矩陣,您可以使用逗號分隔的初始化器或初始化器列表(在最后一種情況下需要C ++ 11支持):
    Mat C =(Mat_ <double>(3,3)<< 0,-1,0,-1,5,-1,0,-1,0);
    cout << “C =” << endl << “”“ << C << endl << endl;
    C =(Mat_ <double>({0,-1,0,-1,5,-1,0,-1,0}))。reshape(3);
    cout << “C =” << endl << “”“ << C << endl << endl;

MatBasicContainerOut6

為現(xiàn)有的Mat對象和cv :: Mat :: clonecv :: Mat :: copyTo創(chuàng)建一個新標題。

    Mat RowClone = C.row(1).clone();
    cout << “RowClone =” << endl << “” << RowClone << endl << endl;

MatBasicContainerOut7

注意
您可以使用cv :: randu()函數(shù)填入隨機值的矩陣。您需要給出隨機值的較低和較高值:
    Mat R = Mat(3,2,CV_8UC3);
    randu(R,Scalar :: all(0),Scalar :: all(255));

輸出格式

在上述示例中,您可以看到默認的格式化選項。然而,OpenCV允許您格式化矩陣輸出:

  • Default
    cout << “R(default)=” << endl << R << endl << endl;

MatBasicContainerOut8

  • Python

    cout << "R (python)  = " << endl << format(R, Formatter::FMT_PYTHON) << endl << endl;

MatBasicContainerOut16

  • Comma separated values (CSV)

    cout << “R(csv)=” << endl << format(R,F(xiàn)ormatter :: FMT_CSV)<< endl << endl;

MatBasicContainerOut10

  • NumPy

    cout << "R (numpy)   = " << endl << format(R, Formatter::FMT_NUMPY ) << endl << endl;

MatBasicContainerOut9

  • C

    cout << “R(c)=” << endl << format(R,F(xiàn)ormatter :: FMT_C)<< endl << endl;

MatBasicContainerOut11

其他常用項??目的輸出

OpenCV還通過<<運算符來支持其他常見OpenCV數(shù)據(jù)結(jié)構(gòu)的輸出:

  • 2D Point
    Point2f P(5,1);
    cout << “Point(2D)=” << P << endl << endl;

MatBasicContainerOut12

  • 3D Point

    Point3f P3f(2, 6, 7);
    cout << "Point (3D) = " << P3f << endl << endl;

MatBasicContainerOut13

    vector<float> v;
    v.push_back( (float)CV_PI);   v.push_back(2);    v.push_back(3.01f);
    cout << "Vector of floats via Mat = " << Mat(v) << endl << endl;

MatBasicContainerOut14

  • std::vector of points

    vector<Point2f> vPoints(20);
    for (size_t i = 0; i < vPoints.size(); ++i)
        vPoints[i] = Point2f((float)(i * 5), (float)(i % 7));
    cout << "A vector of 2D Points = " << vPoints << endl << endl;

MatBasicContainerOut15

這里的大多數(shù)樣品已被包含在一個小的控制臺應(yīng)用程序中。您可以從這里或cpp示例的核心部分下載。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號