《OpenCV3程式設計入門》——5.1 訪問影象中的畫素
目錄
1、影象在記憶體之中的儲存方式
影象矩陣的大小取決於所用的顏色模型,確切說,取決於所用通道數。如果是灰度影象,矩陣就會如圖5.1所示。
對於多通道影象來說,矩陣中的列會包含多個子列,其子列個數與通道數相同
可以看到,OpenCV中子列的通道順序是反過來的——BGR而不是RGB。 有時候,由於記憶體足夠大,可實現連續儲存,影象中的各行是一行一行連線起來的,形成一個長行。可以使用isContinuous()來判斷矩陣是否是連續儲存的。
2、顏色空間縮減
顏色空間縮減的做法是:將現有顏色空間值除以某個輸入值,以獲得較少的顏色數。即做減法,比如顏色值0到9可取為新值0,10到19可取為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] ]
簡單的顏色空間縮減演算法就由以下兩步組成:
- 遍歷影象矩陣的每一個畫素
- 對畫素應用上述公式
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中,有三種方式訪問影象畫素:
- 指標訪問:C操作符[];
- 迭代器iterator
- 動態地址計算
上述方法在訪問速度上略有差異。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標明瞭顏色通道號
執行結果:
原始圖 | 效果圖 |