第一件事:訪問圖像中的像素
1. 圖像的數(shù)據(jù)诽俯,是如何以指定的顏色空間和數(shù)據(jù)類型存儲在內(nèi)存中的敲才?
同一個圖像的矩陣的大小取決于所用的顏色空間和數(shù)據(jù)類型晦墙,確切的說取決于通道數(shù)和深度孤钦。
灰度空間矩陣:
RGB空間矩陣:
可以發(fā)現(xiàn) OpenCV 在內(nèi)存中子列的通道順序與通常習慣的 RBG 順序相反——BGR歧斟。通常內(nèi)存足夠的情況下,可以實現(xiàn)圖像在內(nèi)存中的連續(xù)存儲偏形,這樣極大地提高了圖像的掃描速度静袖,可以使用 isContinuous() 函數(shù)來判斷矩陣在內(nèi)存中是否為連續(xù)存儲。
2. 顏色空間縮減
若圖像是單通道的并且使用 8 位字符類型俊扭,那么一個像素位置可能有256個不同值队橙,那么如果有三通道呢?那么一個像素位置將會有一千六百多萬種可能顏色萨惑,若果我們隊這些顏色進行逐一分類處理的話那么將對我們算法性能造成極其嚴重的影響捐康。所以我們需要對顏色空間進行一些縮減,比如顏色值 0-9 的按0計算庸蔼,10-19 的按10計算解总,以此類推。
在對整形 "/" 運算中姐仅,會自動去掉余數(shù)那么我們可以這樣操作簡單地實現(xiàn)上面的需求:
int b = 13;
int a = (b/10)*10 = (13/10)*10 = 1*10 = 10;
但是如果每一次都對每個像素進行這樣的計算過程花枫,也是需要很大的時間花銷刻盐,而且這兩種運算(乘、除)又特別費時乌昔。我們可以先將像素的可能性存在表中隙疚,需要用的時候直接從表里面取出來即可壤追。
//先存在表里面
int divideWidth = 10;
uchar table[256];
for (int i = 0,i < 256;i++){
table[i] = divideWidth * (i/divideWidth);
}
//查找對應值
int key = 111;
int value = table[key];
3. LUT 函數(shù):生成縮減矩陣
在上一點中生成了一個查找表磕道,用來查找某個通道對應的縮減值而非每次都進行計算。接下來我們就需要將目標圖像矩陣進行縮減生成縮減圖像行冰,思路是:
- 先生成查找表溺蕉;
- 遍歷目標矩陣每一個元素;
- 將目標矩陣中每一個元素在查找表中找到對應縮減值悼做;
- 將縮減值存入新的矩陣疯特。
很快就寫出了代碼。但是肛走!之前又講到OpenCV是一個開發(fā) 工具 包漓雅,作為工具包這點事情都不能幫我們輕松解決還算什么工具包?所以O(shè)penCV中為我們封裝了一個函數(shù)朽色,并且這個函數(shù)OpenCV官方文檔中是極力推薦我們使用的邻吞,那就是 LUT 函數(shù),函數(shù)原型:
void LUT(InputArray src, InputArray lut, OutputArray dst);
參數(shù) | 意義 |
---|---|
InputArray src | 源矩陣 |
InputArray lut | 查找表 |
OutputArray dst | 輸出矩陣 |
示例程序:
//建立查找表
int div = 100;//值大一點效果比較明顯
//LUT 函數(shù)中需要輸入的是一個InputArray類型
cv::Mat luTable(1,256, CV_8U);
uchar* table = luTable.data;
for (int i = 0; i < 256; i++) {
table[i] = div * (i/div);
}
cv::Mat srcImage = cv::imread("/Users/wangxin/Desktop/1.jpg");
cv::Mat dstImage;
cv::LUT(srcImage, luTable, dstImage);
cv::imshow("srcImage", srcImage);
cv::imshow("dstImage", dstImage);
cv::waitKey();
4. 計時函數(shù)
可以利用 getTickCount() 和 getTickFrequency 函數(shù)來進行計時葫男。
- getTickCount 函數(shù):該函數(shù)返回CPU自某個時間以來所經(jīng)歷的時間周期數(shù)抱冷。
- getTickFrequency() 函數(shù):該函數(shù)返回CPU一秒鐘走的時鐘周期數(shù)。這樣我們就可以對某個運算進行計時了梢褐。
//起始狀態(tài)的時間周期
double time0 = static_cast<double>(cv::getTickCount());
//圖像處理操作
//~
//結(jié)束時的時間周期數(shù)
double time1 = static_cast<double>(cv::getTickCount());double
//計算得出花費時間
time = (time1 - time0)/cv::getTickFrequency;
//輸出運行時間
cout<< "此方法運行時間為:" << time << "秒" << endl;
5. 訪問像素的三種方法
- 指針訪問:C操作符[]旺遮;(最快)
核心代碼:
int rowNumber = image.rows;
//列數(shù)*通道數(shù)
int colNumber = image.cols*image.channels();
//循環(huán)遍歷像素
for(int i = 0; i < rowNumber; i++){
uchar* data = image.ptr<uchar>(i);//獲取第i行首地址
for(int j = 0; j < colNUmber; j++){
//處理像素
data[j];
}
}
-
STL 中的迭代器 iterator(慢)
核心代碼:
Mat_<Vec3b>::iterator it = outputImage.begen<Vec3b>();//初始位置
Mat_<Vec3b>::iterator itend = outputImage,end<Vec3b>();//結(jié)束位置
for(;it != itend;it++){
//開始處理每個像素
(*it)[0];//通道1,藍色通道
(*it)[1];//通道2盈咳,綠色通道
(*it)[2];//通道3耿眉,紅色通道
}
- 動態(tài)地址計算(最慢)
利用 at 函數(shù)
核心代碼:
int rowNumber = image.rows;
//列數(shù)*通道數(shù)
int colNumber = image.cols;
//循環(huán)遍歷像素
for(int i = 0; i < rowNumber; i++){
for(int j = 0; j < colNUmber; j++){
//處理像素
image.at<Vec3b>(i,j)[0];//通道1,藍色通道
image.at<Vec3b>(i,j)[1];//通道2鱼响,綠色通道
image.at<Vec3b>(i,j)[2];//通道3鸣剪,紅色通道
}
}