您當前的位置:首頁 > 攝影

如何使用OpenCV掃描影象,查詢表格和時間測量

作者:由 coder 發表于 攝影時間:2022-06-20

前言

開源視覺庫(opencv)

目標

我們會為以下問題尋求答案:

如何透過影象的每個畫素?

OpenCV矩陣值如何儲存?

如何衡量我們的演算法的效能?

什麼是查詢表,為什麼使用它們?

我們的測試用例

讓我們考慮一種簡單的減色方法。透過對矩陣項儲存使用unsigned char C和C ++型別,畫素通道最多可以有256個不同的值。對於三通道影象,這可以允許形成太多的顏色(1600萬精確)。使用如此多的色調可能會對我們的演算法效能造成沉重打擊。然而,有時候,只要少一點工作能夠得到相同的最終結果就足夠了。

在這種情況下,我們通常會減少色彩空間。這意味著我們將顏色空間當前值與新的輸入值分開,以減少顏色。例如,零和九之間的每個值都將新的值為零,每個值在十到十十之間的值十等等。

當您使用int值將uchar(unsigned char-aka值在0和255之間)值分隔時,結果也將是char。這些值只能是char值。因此,任何分數將被向下舍入。利用這一事實,uchar域中的上層操作可以表示為:

如何使用OpenCV掃描影象,查詢表格和時間測量

簡單的顏色空間縮小演算法將包括僅透過影象矩陣的每個畫素並應用該公式。值得注意的是,我們做一個除法和乘法運算。這些操作對於系統來說是昂貴的。如果可能,透過使用更便宜的操作(如少量減法,新增或在最佳情況下是簡單的分配)來避免這種情況。此外,請注意,我們只有上限操作的輸入值有限。在uchar系統的情況下,這是256。

因此,對於較大的影象,預先計算所有可能的值,並且在分配期間透過使用查詢表來進行分配是明智的。查詢表是簡單的陣列(具有一個或多個維),對於給定的輸入值變數儲存最終的輸出值。其實力在於我們不需要進行計算,只需要讀取結果。

我們的測試用例程式(以及此處提供的示例)將執行以下操作:讀取控制檯線路引數影象(可以是顏色或灰度級 - 控制檯線路引數),並使用給定的控制檯行引數整數值。在OpenCV中,目前有三種主要透過畫素逐個透過影象的方法。為了使事情更有趣,將使用所有這些方法對每個影象進行掃描,並打印出花費多長時間。

您可以在

這裡

下載完整的原始碼

或者在OpenCV的sample目錄中檢視核心部分的cpp教程程式碼。其基本用途是:

how_to_scan_images imageName。jpg intValueToReduce [G]

最後一個引數是可選的。如果給定影象將以灰度格式載入,否則使用BGR顏色空間。首先是計算查詢表。

int divideWith = 0; // convert our input string to number - C++ style

stringstream s;

s << argv[2];

s >> divideWith;

if (!s || !divideWith)

{

cout << “Invalid number entered for dividing。 ” << endl;

return -1;

}

uchar table[256];

for (int i = 0; i < 256; ++i)

table[i] = (uchar)(divideWith * (i/divideWith));

這裡我們首先使用C ++ stringstream類將第三個命令列引數從文字轉換為整數格式。然後我們使用一個簡單的外觀和上面的公式來計算查詢表。沒有OpenCV具體的東西在這裡。

另一個問題是我們如何衡量時間?那麼OpenCV提供了兩個簡單的函式來實現這個

cv :: getTickCount()

cv :: getTickFrequency()

。第一個從某個事件返回系統CPU的刻度數(就像您啟動系統一樣)。第二次返回您的CPU在一秒鐘內發出多少次燒錄。所以為了測量秒數,兩次操作之間的時間容易如下:

double t = (double)getTickCount();

// do something 。。。

t = ((double)getTickCount() - t)/getTickFrequency();

cout << “Times passed in seconds: ” << t << endl;

影象矩陣如何儲存在記憶體中?

正如您已經閱讀我的

Mat - 基本影象容器

教程中的矩陣大小取決於使用的顏色系統。更準確地說,它取決於所使用的通道數量。在灰度影象的情況下,我們有一些像:

如何使用OpenCV掃描影象,查詢表格和時間測量

對於多通道影象,列包含與通道數一樣多的子列。例如在BGR顏色系統的情況下:

如何使用OpenCV掃描影象,查詢表格和時間測量

注意,通道的順序是反向的:BGR而不是RGB。因為在許多情況下,記憶體足夠大以便以連續的方式儲存行,所以這些行可以一個接一個地跟隨,建立一個長行。因為一切都在一個地方,這可能有助於加快掃描過程。我們可以使用

cv :: Mat :: isContinuous()

函式來詢問矩陣是否是這種情況。繼續下一節找一個例子。

有效的方式

當涉及到效能時,你無法擊敗經典的C風格運算子[](指標)訪問。因此,我們可以推薦使用最有效的方法進行分配:

Mat& ScanImageAndReduceC(Mat& I, const uchar* const table)

{

// accept only char type matrices

CV_Assert(I。depth() == CV_8U);

int channels = I。channels();

int nRows = I。rows;

int nCols = I。cols * channels;

if (I。isContinuous())

{

nCols *= nRows;

nRows = 1;

}

int i,j;

uchar* p;

for( i = 0; i < nRows; ++i)

{

p = I。ptr(i);

for ( j = 0; j < nCols; ++j)

{

p[j] = table[p[j]];

}

}

return I;

}

在這裡,我們基本上只是獲取一個指向每行開頭的指標,直到它結束。在特殊情況下,矩陣以連續的方式儲存,我們只需要單次請求指標,直到最後。我們需要尋找彩色影象:我們有三個通道,所以我們需要透過每行三次以上的專案。

還有另一種方法。Mat物件的資料資料成員返回指向第一行第一列的指標。如果此指標為空,則該物件中沒有有效的輸入。檢查這是檢查您的影象載入是否成功的最簡單的方法。如果儲存是連續的,我們可以使用它來遍歷整個資料指標。在灰度影象的情況下,它將如下所示:

uchar * p = I。data;

for(unsigned int i = 0; i

* p ++ = table [* p];

你會得到相同的結果。但是,這段程式碼稍後閱讀很難閱讀。如果你有更先進的技術,那就更難了。此外,在實踐中,我觀察到您將獲得相同的效能結果(因為大多數現代編譯器可能會為您自動實現這種小型最佳化技巧)。

迭代程式(安全)方法

如果有效的方式確保您透過適量的uchar欄位,並跳過行之間可能發生的差距是您的責任。迭代程式方法被認為是更安全的方式,因為它從使用者接管這些任務。所有你需要做的是要求影象矩陣的開始和結束,然後只是增加開始迭代程式,直到你到達結束。要獲取迭代程式指向的值,使用*運算子(在它之前新增)。

Mat& ScanImageAndReduceIterator(Mat& I, const uchar* const table)

{

// accept only char type matrices

CV_Assert(I。depth() == CV_8U);

const int channels = I。channels();

switch(channels)

{

case 1:

{

MatIterator_ it, end;

for( it = I。begin(), end = I。end(); it != end; ++it)

*it = table[*it];

break;

}

case 3:

{

MatIterator_ it, end;

for( it = I。begin(), end = I。end(); it != end; ++it)

{

(*it)[0] = table[(*it)[0]];

(*it)[1] = table[(*it)[1]];

(*it)[2] = table[(*it)[2]];

}

}

}

return I;

}

在彩色影象的情況下,我們每列有三個uchar專案。這可能被認為是一個簡短的uchar專案向量,已經在OpenCV中使用Vec3b名稱進行了浸禮。要訪問第n個子列,我們使用簡單的operator []訪問。重要的是要記住,OpenCV迭代程式遍歷列,並自動跳到下一行。因此,如果使用簡單的uchar迭代程式,您將只能訪問藍色通道值。

參考返回的即時地址計算

最後的方法不推薦用於掃描。它是為了獲取或修改影象中的某種方式的隨機元素。它的基本用法是指定要訪問的專案的行號和列號。在我們早期的掃描方法中,您可以透過我們正在檢視的影象來觀察這一點很重要。這在這裡沒有什麼不同,因為您需要手動指定在自動查詢時要使用的型別。如果下列原始碼的灰度影象(+

cv :: at()

函式的用法),您可以觀察這一點:

Mat& ScanImageAndReduceRandomAccess(Mat& I, const uchar* const table)

{

// accept only char type matrices

CV_Assert(I。depth() == CV_8U);

const int channels = I。channels();

switch(channels)

{

case 1:

{

for( int i = 0; i < I。rows; ++i)

for( int j = 0; j < I。cols; ++j )

I。at(i,j) = table[I。at(i,j)];

break;

}

case 3:

{

Mat_ _I = I;

for( int i = 0; i < I。rows; ++i)

for( int j = 0; j < I。cols; ++j )

{

_I(i,j)[0] = table[_I(i,j)[0]];

_I(i,j)[1] = table[_I(i,j)[1]];

_I(i,j)[2] = table[_I(i,j)[2]];

}

I = _I;

break;

}

}

return I;

}

這些功能需要您的輸入型別和座標,並即時計算查詢專案的地址。然後返回一個引用。當您設定值時,獲取值和非常數時,這可能是常數。作為除錯模式的安全步驟*,執行一個檢查,您的輸入座標是有效的並且確實存在。如果不是這樣,您將在標準錯誤輸出流上獲得一個很好的輸出訊息。與釋放模式中的有效方式相比,使用此方法的唯一區別是,對於影象的每個元素,您將獲得一個新的行指標,以便我們使用C運算子[]獲取列元素。

如果您需要使用此方法對影象執行多次查詢,則可能會麻煩和耗時地為每個訪問輸入型別和at關鍵字。為了解決這個問題OpenCV有一個

cv :: Mat_

資料型別。與Mat相同,在定義中需要透過檢視資料矩陣來指定資料型別,但是您可以使用operator()快速訪問專案。為了使事情變得更好,這可以很容易地從和通常的

cv :: Mat

資料型別轉換。您可以在上方功能的彩色影象的情況下看到此示例的用法。然而,重要的是要注意,

cv :: at()

可以完成相同的操作(具有相同的執行時速度

功能。對於懶惰的程式設計師的伎倆來說,這是一個更少的事情。

核心功能

這是在影象中實現查詢表修改的一種獎勵方法。在影象處理中,很常見的是要將所有給定的影象值修改為其他值。OpenCV提供了修改影象值的功能,無需編寫影象的掃描邏輯。我們使用核心模組的

cv :: LUT()

函式。首先我們構建一個Mat型別的查詢表:

Mat lookUpTable(1, 256, CV_8U);

uchar* p = lookUpTable。ptr();

for( int i = 0; i < 256; ++i)

p[i] = table[i];

最後呼叫函式(我是我們的輸入影象,J是輸出的一個):

LUT(I,lookUpTable,J);

效能差異

為了最好的結果,編譯程式並以自己的速度執行它。為了使差異更加清晰,我使用了相當大的(2560 X 1600)影象。這裡呈現的效能是彩色影象。為了獲得更準確的值,我將從函式呼叫得到的值平均為100次。

方法

時間

高效的方式

79。4717毫秒

迭代程式

83。7201毫秒

在飛行RA

93。7878毫秒

LUT功能

32。5759毫秒

我們可以總結一些事情。如果可能,請使用OpenCV已經建立的功能(而不是重新建立它們)。最快的方法是LUT功能。這是因為OpenCV庫透過Intel Threaded Building Blocks啟用多執行緒。但是,如果你需要編寫一個簡單的影象掃描,喜歡指標方法。迭代程式程式

是一個更安全的賭注,但是相當慢。使用即時參考訪問方法進行全影象掃描是除錯模式中最昂貴的。在釋放模式下,它可能會擊敗迭代程式方法,但是它肯定會犧牲迭代程式的安全效能。

標簽: 影象  Table  int  uchar  mat