W3Cschool
恭喜您成為首批注冊(cè)用戶(hù)
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
我們會(huì)為以下問(wèn)題尋求答案:
讓我們考慮一種簡(jiǎn)單的減色方法。通過(guò)對(duì)矩陣項(xiàng)存儲(chǔ)使用unsigned char C和C ++類(lèi)型,像素通道最多可以有256個(gè)不同的值。對(duì)于三通道圖像,這可以允許形成太多的顏色(1600萬(wàn)精確)。使用如此多的色調(diào)可能會(huì)對(duì)我們的算法性能造成沉重打擊。然而,有時(shí)候,只要少一點(diǎn)工作能夠得到相同的最終結(jié)果就足夠了。
在這種情況下,我們通常會(huì)減少色彩空間。這意味著我們將顏色空間當(dāng)前值與新的輸入值分開(kāi),以減少顏色。例如,零和九之間的每個(gè)值都將新的值為零,每個(gè)值在十到十十之間的值十等等。
當(dāng)您使用int值將uchar(unsigned char-aka值在0和255之間)值分隔時(shí),結(jié)果也將是char。這些值只能是char值。因此,任何分?jǐn)?shù)將被向下舍入。利用這一事實(shí),uchar域中的上層操作可以表示為:
簡(jiǎn)單的顏色空間縮小算法將包括僅通過(guò)圖像矩陣的每個(gè)像素并應(yīng)用該公式。值得注意的是,我們做一個(gè)除法和乘法運(yùn)算。這些操作對(duì)于系統(tǒng)來(lái)說(shuō)是昂貴的。如果可能,通過(guò)使用更便宜的操作(如少量減法,添加或在最佳情況下是簡(jiǎn)單的分配)來(lái)避免這種情況。此外,請(qǐng)注意,我們只有上限操作的輸入值有限。在uchar系統(tǒng)的情況下,這是256。
因此,對(duì)于較大的圖像,預(yù)先計(jì)算所有可能的值,并且在分配期間通過(guò)使用查找表來(lái)進(jìn)行分配是明智的。查找表是簡(jiǎn)單的數(shù)組(具有一個(gè)或多個(gè)維),對(duì)于給定的輸入值變量保存最終的輸出值。其實(shí)力在于我們不需要進(jìn)行計(jì)算,只需要讀取結(jié)果。
我們的測(cè)試用例程序(以及此處提供的示例)將執(zhí)行以下操作:讀取控制臺(tái)線路參數(shù)圖像(可以是顏色或灰度級(jí) - 控制臺(tái)線路參數(shù)),并使用給定的控制臺(tái)行參數(shù)整數(shù)值。在OpenCV中,目前有三種主要通過(guò)像素逐個(gè)通過(guò)圖像的方法。為了使事情更有趣,將使用所有這些方法對(duì)每個(gè)圖像進(jìn)行掃描,并打印出花費(fèi)多長(zhǎng)時(shí)間。
您可以在這里下載完整的源代碼,或者在OpenCV的sample目錄中查看核心部分的cpp教程代碼。其基本用途是:
how_to_scan_images imageName.jpg intValueToReduce [G]
最后一個(gè)參數(shù)是可選的。如果給定圖像將以灰度格式加載,否則使用BGR顏色空間。首先是計(jì)算查找表。
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類(lèi)將第三個(gè)命令行參數(shù)從文本轉(zhuǎn)換為整數(shù)格式。然后我們使用一個(gè)簡(jiǎn)單的外觀和上面的公式來(lái)計(jì)算查找表。沒(méi)有OpenCV具體的東西在這里。
另一個(gè)問(wèn)題是我們?nèi)绾魏饬繒r(shí)間?那么OpenCV提供了兩個(gè)簡(jiǎn)單的函數(shù)來(lái)實(shí)現(xiàn)這個(gè)cv :: getTickCount()和cv :: getTickFrequency()。第一個(gè)從某個(gè)事件返回系統(tǒng)CPU的刻度數(shù)(就像您啟動(dòng)系統(tǒng)一樣)。第二次返回您的CPU在一秒鐘內(nèi)發(fā)出多少次刻錄。所以為了測(cè)量秒數(shù),兩次操作之間的時(shí)間容易如下:
double t = (double)getTickCount();
// do something ...
t = ((double)getTickCount() - t)/getTickFrequency();
cout << "Times passed in seconds: " << t << endl;
正如您已經(jīng)閱讀我的Mat - 基本圖像容器教程中的矩陣大小取決于使用的顏色系統(tǒng)。更準(zhǔn)確地說(shuō),它取決于所使用的通道數(shù)量。在灰度圖像的情況下,我們有一些像:
對(duì)于多通道圖像,列包含與通道數(shù)一樣多的子列。例如在BGR顏色系統(tǒng)的情況下:
注意,通道的順序是反向的:BGR而不是RGB。因?yàn)樵谠S多情況下,內(nèi)存足夠大以便以連續(xù)的方式存儲(chǔ)行,所以這些行可以一個(gè)接一個(gè)地跟隨,創(chuàng)建一個(gè)長(zhǎng)行。因?yàn)橐磺卸荚谝粋€(gè)地方,這可能有助于加快掃描過(guò)程。我們可以使用cv :: Mat :: isContinuous()函數(shù)來(lái)詢(xún)問(wèn)矩陣是否是這種情況。繼續(xù)下一節(jié)找一個(gè)例子。
當(dāng)涉及到性能時(shí),你無(wú)法擊敗經(jīng)典的C風(fēng)格操作符[](指針)訪問(wèn)。因此,我們可以推薦使用最有效的方法進(jìn)行分配:
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<uchar>(i);
for ( j = 0; j < nCols; ++j)
{
p[j] = table[p[j]];
}
}
return I;
}
在這里,我們基本上只是獲取一個(gè)指向每行開(kāi)頭的指針,直到它結(jié)束。在特殊情況下,矩陣以連續(xù)的方式存儲(chǔ),我們只需要單次請(qǐng)求指針,直到最后。我們需要尋找彩色圖像:我們有三個(gè)通道,所以我們需要通過(guò)每行三次以上的項(xiàng)目。
還有另一種方法。Mat對(duì)象的數(shù)據(jù)數(shù)據(jù)成員返回指向第一行第一列的指針。如果此指針為空,則該對(duì)象中沒(méi)有有效的輸入。檢查這是檢查您的圖像加載是否成功的最簡(jiǎn)單的方法。如果存儲(chǔ)是連續(xù)的,我們可以使用它來(lái)遍歷整個(gè)數(shù)據(jù)指針。在灰度圖像的情況下,它將如下所示:
uchar * p = I.data;
for(unsigned int i = 0; i <ncol * nrows; ++ i)
* p ++ = table [* p];
你會(huì)得到相同的結(jié)果。但是,這段代碼稍后閱讀很難閱讀。如果你有更先進(jìn)的技術(shù),那就更難了。此外,在實(shí)踐中,我觀察到您將獲得相同的性能結(jié)果(因?yàn)榇蠖鄶?shù)現(xiàn)代編譯器可能會(huì)為您自動(dòng)實(shí)現(xiàn)這種小型優(yōu)化技巧)。
如果有效的方式確保您通過(guò)適量的uchar字段,并跳過(guò)行之間可能發(fā)生的差距是您的責(zé)任。迭代程序方法被認(rèn)為是更安全的方式,因?yàn)樗鼜挠脩?hù)接管這些任務(wù)。所有你需要做的是要求圖像矩陣的開(kāi)始和結(jié)束,然后只是增加開(kāi)始迭代程序,直到你到達(dá)結(jié)束。要獲取迭代程序指向的值,使用*運(yùn)算符(在它之前添加)。
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_<uchar> it, end;
for( it = I.begin<uchar>(), end = I.end<uchar>(); it != end; ++it)
*it = table[*it];
break;
}
case 3:
{
MatIterator_<Vec3b> it, end;
for( it = I.begin<Vec3b>(), end = I.end<Vec3b>(); it != end; ++it)
{
(*it)[0] = table[(*it)[0]];
(*it)[1] = table[(*it)[1]];
(*it)[2] = table[(*it)[2]];
}
}
}
return I;
}
在彩色圖像的情況下,我們每列有三個(gè)uchar項(xiàng)目。這可能被認(rèn)為是一個(gè)簡(jiǎn)短的uchar項(xiàng)目向量,已經(jīng)在OpenCV中使用Vec3b名稱(chēng)進(jìn)行了浸禮。要訪問(wèn)第n個(gè)子列,我們使用簡(jiǎn)單的operator []訪問(wèn)。重要的是要記住,OpenCV迭代程序遍歷列,并自動(dòng)跳到下一行。因此,如果使用簡(jiǎn)單的uchar迭代程序,您將只能訪問(wèn)藍(lán)色通道值。
最后的方法不推薦用于掃描。它是為了獲取或修改圖像中的某種方式的隨機(jī)元素。它的基本用法是指定要訪問(wèn)的項(xiàng)目的行號(hào)和列號(hào)。在我們?cè)缙诘膾呙璺椒ㄖ?,您可以通過(guò)我們正在查看的圖像來(lái)觀察這一點(diǎn)很重要。這在這里沒(méi)有什么不同,因?yàn)槟枰謩?dòng)指定在自動(dòng)查找時(shí)要使用的類(lèi)型。如果下列源代碼的灰度圖像(+ cv :: at()函數(shù)的用法),您可以觀察這一點(diǎn):
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<uchar>(i,j) = table[I.at<uchar>(i,j)];
break;
}
case 3:
{
Mat_<Vec3b> _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;
}
這些功能需要您的輸入類(lèi)型和坐標(biāo),并即時(shí)計(jì)算查詢(xún)項(xiàng)目的地址。然后返回一個(gè)引用。當(dāng)您設(shè)置值時(shí),獲取值和非常數(shù)時(shí),這可能是常數(shù)。作為調(diào)試模式的安全步驟*,執(zhí)行一個(gè)檢查,您的輸入坐標(biāo)是有效的并且確實(shí)存在。如果不是這樣,您將在標(biāo)準(zhǔn)錯(cuò)誤輸出流上獲得一個(gè)很好的輸出消息。與釋放模式中的有效方式相比,使用此方法的唯一區(qū)別是,對(duì)于圖像的每個(gè)元素,您將獲得一個(gè)新的行指針,以便我們使用C運(yùn)算符[]獲取列元素。
如果您需要使用此方法對(duì)圖像執(zhí)行多次查找,則可能會(huì)麻煩和耗時(shí)地為每個(gè)訪問(wèn)輸入類(lèi)型和at關(guān)鍵字。為了解決這個(gè)問(wèn)題OpenCV有一個(gè)cv :: Mat_數(shù)據(jù)類(lèi)型。與Mat相同,在定義中需要通過(guò)查看數(shù)據(jù)矩陣來(lái)指定數(shù)據(jù)類(lèi)型,但是您可以使用operator()快速訪問(wèn)項(xiàng)目。為了使事情變得更好,這可以很容易地從和通常的cv :: Mat數(shù)據(jù)類(lèi)型轉(zhuǎn)換。您可以在上方功能的彩色圖像的情況下看到此示例的用法。然而,重要的是要注意,cv :: at()可以完成相同的操作(具有相同的運(yùn)行時(shí)速度)功能。對(duì)于懶惰的程序員的伎倆來(lái)說(shuō),這是一個(gè)更少的事情。
這是在圖像中實(shí)現(xiàn)查找表修改的一種獎(jiǎng)勵(lì)方法。在圖像處理中,很常見(jiàn)的是要將所有給定的圖像值修改為其他值。OpenCV提供了修改圖像值的功能,無(wú)需編寫(xiě)圖像的掃描邏輯。我們使用核心模塊的cv :: LUT()函數(shù)。首先我們構(gòu)建一個(gè)Mat類(lèi)型的查找表:
Mat lookUpTable(1, 256, CV_8U);
uchar* p = lookUpTable.ptr();
for( int i = 0; i < 256; ++i)
p[i] = table[i];
最后調(diào)用函數(shù)(我是我們的輸入圖像,J是輸出的一個(gè)):
LUT(I,lookUpTable,J);
為了最好的結(jié)果,編譯程序并以自己的速度運(yùn)行它。為了使差異更加清晰,我使用了相當(dāng)大的(2560 X 1600)圖像。這里呈現(xiàn)的性能是彩色圖像。為了獲得更準(zhǔn)確的值,我將從函數(shù)調(diào)用得到的值平均為100次。
方法 | 時(shí)間 |
---|---|
高效的方式 | 79.4717毫秒 |
迭代程序 | 83.7201毫秒 |
在飛行RA | 93.7878毫秒 |
LUT功能 | 32.5759毫秒 |
我們可以總結(jié)一些事情。如果可能,請(qǐng)使用OpenCV已經(jīng)創(chuàng)建的功能(而不是重新創(chuàng)建它們)。最快的方法是LUT功能。這是因?yàn)镺penCV庫(kù)通過(guò)Intel Threaded Building Blocks啟用多線程。但是,如果你需要編寫(xiě)一個(gè)簡(jiǎn)單的圖像掃描,喜歡指針?lè)椒ā5绦虺绦?/p>
是一個(gè)更安全的賭注,但是相當(dāng)慢。使用即時(shí)參考訪問(wèn)方法進(jìn)行全圖像掃描是調(diào)試模式中最昂貴的。在釋放模式下,它可能會(huì)擊敗迭代程序方法,但是它肯定會(huì)犧牲迭代程序的安全性能。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: