1. 程式人生 > >《OpenCV3程式設計入門》——5.1 訪問影象中的畫素

《OpenCV3程式設計入門》——5.1 訪問影象中的畫素

目錄

1、影象在記憶體之中的儲存方式

2、顏色空間縮減

3、LUT函式:Look up table操作

4、計時函式

5、訪問影象中畫素的三類方法

        5.1、指標訪問畫素

        5.2、迭代器操作畫素

        5.3、動態地址計算


1、影象在記憶體之中的儲存方式

影象矩陣的大小取決於所用的顏色模型,確切說,取決於所用通道數。如果是灰度影象,矩陣就會如圖5.1所示。

對於多通道影象來說,矩陣中的列會包含多個子列,其子列個數與通道數相同

,如圖5.2所示RGB顏色模型的矩陣。

可以看到,OpenCV中子列的通道順序是反過來的——BGR而不是RGB。 有時候,由於記憶體足夠大,可實現連續儲存,影象中的各行是一行一行連線起來的,形成一個長行。可以使用isContinuous()來判斷矩陣是否是連續儲存的。


2、顏色空間縮減

顏色空間縮減的做法是:將現有顏色空間值除以某個輸入值,以獲得較少的顏色數。即做減法,比如顏色值0到9可取為新值0,10到19可取為10,以此類推。

有一個簡單的公式來實現顏色空間縮減:I_{new}=\left ( I_{old} /10\right)\times 10

在處理畫素時,每個畫素需要進行一遍上述公式計算,也需要一定的時間花銷。我們可以把256中計算好的結果提前存在列表table中,這樣每種情況不需計算,直接從table中取取結果即可。

int divideWith = 10;
unchar table[256];
for( int i =0; i<256; ++i)
    table[i] = divideWith*(i/divideWith);

於是table[i]存放的是值為 i 的畫素減小顏色空間的結果,這樣就可以理解上述方法中的操作:p [j] = table[ p [j] ]

簡單的顏色空間縮減演算法就由以下兩步組成:

  1. 遍歷影象矩陣的每一個畫素
  2. 對畫素應用上述公式

3、LUT函式:Look up table操作

Look up table操作使用operationsOnArrays:LUT()<lut>函式,用於批量進行影象元素查詢、掃描與操作影象。使用方法如下:

//首先建立一個mat型用於查表
Mat lookUpTable(1, 256, CV_8U);
uchar* p = lookUpTable.data;
for(int i = 0; i < 256; ++i)
    p[i] = table[i];

//呼叫函式(I是輸入,J是輸出)
for (int i = 0; i < times; ++i)
    LUT(I, lookUpTable, J);

4、計時函式

簡便的計時函式:getTickCount()和get TickFrequency()

getTickCount()函式:返回CPU自某個事件以來走過的時鐘週期數

getTickFrequency()函式:返回CPU一秒鐘所走的時鐘週期數。這樣,就能以秒為單位對某運算計時。

兩個函式組合起來使用的例項如下:

double time0 = static_cast<double>(getTickCount());  //記錄起始事件
//進行影象處理操作……
time0 = ((double) getTickCount()-time0)/getTickFrequency();
cout << "此方法執行時間為: " << time0 << "秒" << endl; //輸出執行時間

5、訪問影象中畫素的三類方法

 

OpenCV中,有三種方式訪問影象畫素: 

  1. 指標訪問:C操作符[];
  2. 迭代器iterator
  3. 動態地址計算

上述方法在訪問速度上略有差異。debug模式下,差異非常明顯,在release模式下,差異就不太明顯。

下邊通過一組例子來說明這三種方法的使用,程式的作用是減少顏色的數量,比如原來的影象是256種顏色,我們希望將它變成64中顏色,只需要將原來的顏色除以4(整除)以後再乘以4即可。

主程式程式碼如下:

//---------------------------------【標頭檔案、名稱空間包含部分】--------------------------
//		描述:包含程式所使用的標頭檔案和名稱空間
//---------------------------------------------------------------------------------------
#include <opencv2/opencv.hpp>   
#include <iostream>  

using namespace std;
using namespace cv;

//-----------------------------------【全域性函式宣告部分】-----------------------------------
//          描述:全域性函式宣告
//------------------------------------------------------------------------------------------
//減少顏色
void colorReduce(Mat& inputImage, Mat& outputImage, int div);


//--------------------------------------【main( )函式】---------------------------------------
//          描述:控制檯應用程式的入口函式,我們的程式從這裡開始執行
//-----------------------------------------------------------------------------------------------
int main()
{
    //【1】建立原始圖並顯示
    Mat srcImage = imread("1.jpg");
    imshow("原始影象", srcImage);

    //【2】按原始圖的引數規格來建立建立效果圖
    Mat dstImage;
    dstImage.create(srcImage.rows, srcImage.cols, srcImage.type());//效果圖的大小、型別與原圖片相同 

    //【3】記錄起始時間
    double time0 = static_cast<double>(getTickCount());

    //【4】呼叫顏色空間縮減函式
    colorReduce(srcImage, dstImage, 32);

    //【5】計算執行時間並輸出
    time0 = ((double)getTickCount() - time0) / getTickFrequency();
    cout << "\t此方法執行時間為: " << time0 << "秒" << endl;  //輸出執行時間

    //【6】顯示效果圖
    imshow("效果圖", dstImage);
    waitKey(0);
}

5.1、指標訪問畫素

指標訪問畫素利用的是C語言中的操作符[]。這種方法最快,但是略有點抽象。實驗條件下單詞執行時間為0.00370487秒,程式碼如下:

//---------------------------------【colorReduce( )函式】---------------------------------
//          描述:使用【指標訪問:C操作符[ ]】方法版的顏色空間縮減函式
//----------------------------------------------------------------------------------------------
void colorReduce(Mat& inputImage, Mat& outputImage, int div)
{
    //引數準備
    outputImage = inputImage.clone();  //拷貝實參到臨時變數
    int rowNumber = outputImage.rows;  //行數
    int colNumber = outputImage.cols*outputImage.channels();  //列數 x 通道數=每一行元素的個數

    //雙重迴圈,遍歷所有的畫素值
    for (int i = 0; i < rowNumber; i++)  //行迴圈
    {
        uchar* data = outputImage.ptr<uchar>(i);  //獲取第i行的首地址
        for (int j = 0; j < colNumber; j++)   //列迴圈
        {
            // ---------【開始處理每個畫素】-------------     
            data[j] = data[j] / div*div;
            // ----------【處理結束】---------------------
        }  //行處理結束
    }
}

分析講解上述程式碼:

Mat類屬性:

  • 公有變數cols和rows給出影象的寬和高
  • channels():返回影象的通道數。灰度圖通道數為1,彩色通道數為3
  • ptr()函式:得到任意行的首地址。ptr是一個模板函式,它返回第i行的首地址:uchar* data = outputImage.ptr<uchar>(i);  //獲取第i行的首地址

每行的畫素值個數以下語句得到:

int colNumber = outputImage.cols*outputImage.channels();  //列數 x 通道數=每一行元素的個數

執行結果:

                                            原始圖

                                           效果圖

5.2、迭代器操作畫素

迭代器需要做的僅僅是獲得影象矩陣的begin和end,然後增加迭代直至從begin到end。將 * 操作符新增在迭代指標前,即可訪問當前指向的內容。

相比使用指標可能越界問題,迭代器絕對是非常安全的方法。

//-------------------------------------【colorReduce( )函式】-----------------------------
//		描述:使用【迭代器】方法版的顏色空間縮減函式
//----------------------------------------------------------------------------------------------
void colorReduce(Mat& inputImage, Mat& outputImage, int div)
{
    //引數準備
    outputImage = inputImage.clone();  //拷貝實參到臨時變數
    //獲取迭代器
    Mat_<Vec3b>::iterator it = outputImage.begin<Vec3b>();  //初始位置的迭代器
    Mat_<Vec3b>::iterator itend = outputImage.end<Vec3b>();  //終止位置的迭代器

    //存取彩色影象畫素
    for (; it != itend; ++it)
    {
        // ------------------------【開始處理每個畫素】--------------------
        (*it)[0] = (*it)[0] / div*div;  //藍色通道
        (*it)[1] = (*it)[1] / div*div;  //綠色通道
        (*it)[2] = (*it)[2] / div*div;  //紅色通道
        // ------------------------【處理結束】----------------------------
    }
}

執行結果:  

                                            原始圖

                                           效果圖

5.3、動態地址計算

動態地址運算配合at方法。這種方法簡單明瞭,符合我們對畫素的認識。

//----------------------------------【colorReduce( )函式】-------------------------------
//          描述:使用【動態地址運算配合at】方法版本的顏色空間縮減函式
//----------------------------------------------------------------------------------------------
void colorReduce(Mat& inputImage, Mat& outputImage, int div)
{
    //引數準備
    outputImage = inputImage.clone();  //拷貝實參到臨時變數
    int rowNumber = outputImage.rows;  //行數
    int colNumber = outputImage.cols;  //列數

    //存取彩色影象畫素
    for (int i = 0; i < rowNumber; i++)
    {
        for (int j = 0; j < colNumber; j++)
        {
            // ------------------------【開始處理每個畫素】--------------------
            outputImage.at<Vec3b>(i, j)[0] = outputImage.at<Vec3b>(i, j)[0] / div*div;  //藍色通道
            outputImage.at<Vec3b>(i, j)[1] = outputImage.at<Vec3b>(i, j)[1] / div*div;  //綠色通道
            outputImage.at<Vec3b>(i, j)[2] = outputImage.at<Vec3b>(i, j)[2] / div*div;  //紅是通道
            // -------------------------【處理結束】----------------------------
        }  // 行處理結束     
    }
}

分析講解程式碼:

Mat:

  • 成員函式at(int y, int x):用來儲存影象元素,但是必須在編譯期知道影象的資料型別。需要注意的是,一定要確保指定的資料型別和矩陣中的資料型別符合,因為at方法本身不會對任何資料型別進行轉換。

Vec3b:

  • 一個影象的Mat會返回一個由三個8位陣列成的向量,,將其定義為Vec3b,即由三個unsigned char組成的向量,所以存取彩色影象畫素的程式碼可以寫出如下形式:
image.at<Vec3b>(i, j)[chanel] = value;  //索引值channel標明瞭顏色通道號

執行結果:

                                            原始圖

                                           效果圖