如何使用OpenCV掃描圖像,查找表格和時(shí)間測(cè)量

2018-08-29 11:23 更新

目標(biāo)

我們會(huì)為以下問(wèn)題尋求答案:

  • 如何通過(guò)圖像的每個(gè)像素?
  • OpenCV矩陣值如何存儲(chǔ)?
  • 如何衡量我們的算法的性能?
  • 什么是查找表,為什么使用它們?

我們的測(cè)試用例

讓我們考慮一種簡(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域中的上層操作可以表示為:

QQ圖片20170829111236

簡(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;

圖像矩陣如何存儲(chǔ)在內(nèi)存中?

正如您已經(jīng)閱讀我的Mat - 基本圖像容器教程中的矩陣大小取決于使用的顏色系統(tǒng)。更準(zhǔn)確地說(shuō),它取決于所使用的通道數(shù)量。在灰度圖像的情況下,我們有一些像:

tutorial_how_matrix_stored_1

對(duì)于多通道圖像,列包含與通道數(shù)一樣多的子列。例如在BGR顏色系統(tǒng)的情況下:

tutorial_how_matrix_stored_2

注意,通道的順序是反向的: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)色通道值。

參考返回的即時(shí)地址計(jì)算

最后的方法不推薦用于掃描。它是為了獲取或修改圖像中的某種方式的隨機(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毫秒
在飛行RA93.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ì)犧牲迭代程序的安全性能。


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

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)