圖像處理的基礎(chǔ)是對圖像每一個像素點的遍歷,即圖像掃描百匆。在本節(jié)中砌些,將介紹幾種不同的圖像遍歷方式,為了對比不同方法的效率加匈,我們不是單純的遍歷存璃,而是對圖像做更多的處理。在此矩动,我們測試的是一種簡單的顏色縮減方法有巧。為了比較不同遍歷算法的運行時間,你還將看到 OpenCV 中計時函數(shù)的用法悲没。
1. 概述
圖像處理的基礎(chǔ)是對圖像每一個像素點的遍歷篮迎,即圖像掃描男图。在本節(jié)中,將介紹幾種不同的圖像遍歷方式甜橱,為了對比不同方法的效率逊笆,我們不是單純的遍歷,而是對圖像做更多的處理岂傲。在此难裆,我們測試的是一種簡單的顏色縮減方法。為了比較不同遍歷算法的運行時間镊掖,你還將看到 OpenCV 中計時函數(shù)的用法乃戈。
2. 圖像存儲方式
在進行下面的論述之前,先對圖像矩陣在內(nèi)存中的存儲方式簡單介紹亩进。對于單通道灰度圖像:
而對于多通道圖像來說症虑,矩陣中的列會包含多個子列,子列數(shù)與通道數(shù)相等归薛,如 BGR 顏色模型的矩陣為:
3. 顏色縮減
何謂顏色縮減谍憔?對于元素類型為 uchar 的單通道圖像矩陣,每個像素點有 256 個灰度值主籍,但是對于三通道圖像习贫,每個像素點的顏色種類達(dá) 16777216 種(256 的三次方)。如此多的顏色可能會對算法性能造成嚴(yán)重影響千元,我們往往只需要顏色的一部分苫昌,也能滿足要求,因此引入了顏色縮減诅炉。
如上圖所示蜡歹,左側(cè)為顏色縮減前,右側(cè)為顏色縮減后涕烧,數(shù)字表示灰度值月而。可以看出议纯,灰度值 92 到 114 映射為 92父款;115 到 137 映射為 115,以此類推瞻凤,左側(cè) 164 種顏色縮減為右側(cè)的 7 種顏色憨攒。實現(xiàn)方法也很簡單:
//color1輸入,color2輸出
color2 = (color1 / 23) * 23;
但是,如果直接對圖像的每個像素進行上述除法和乘法阀参,這樣效率是很低的肝集。一個較好的辦法是事先生成一張顏色縮減的查找表,表中縮減前后的值都明確給定蛛壳,這樣遍歷圖像時杏瞻,利用查找表直接對相應(yīng)像素點進行賦值即可所刀。其優(yōu)勢在于只需讀取、無需計算捞挥。以下代碼生成顏色查找表 color_table:
// 生成顏色查找表
vector<int> color_table;
int width = 20;
for (int i = 0; i < 256; i++)
{
color_table.push_back(i / width * width);
}
4. 圖像遍歷
有了顏色查找表后浮创,我們便可以對圖像進行遍歷并對像素點進行顏色縮減了。我們采用了幾種不同的圖像遍歷方法砌函,為了對比它們的效率斩披,采用 OpenCV 提供的兩個簡單的計時器函數(shù) getTickCount() 和 getTickFrequency(), 它們分別返回 CPU 走過的時鐘周期數(shù)和 CPU 一秒的時鐘周期數(shù)。因此讹俊,可以這樣來計時(單位:秒):
double time_begin = (double)getTickCount();
// do something
double time_end = (double)getTickCount();
double time = (time_end - time_begin) / getTickFrequency();
4.1 利用指針遍歷
Mat& ScanImageAndReduceC(Mat& I, vector<int> color_table)
{
//只接收字符型矩陣
CV_Assert(I.depth() != sizeof(uchar));
int channels = I.channels(); //獲取圖像通道數(shù)
int row = I.rows * channels;
int col = I.cols;
uchar* p;
if (I.isContinuous()) //判斷像素是否連續(xù)存儲
{
col *= row;
row = 1;
}
for (int i = 0; i < row; i++)
{
p = I.ptr<uchar>(i);
for (int j = 0; j < col; j++)
{
p[j] = color_table[p[j]];
}
}
return I;
}
顏色縮減結(jié)果(根據(jù)查找表的width設(shè)置縮減的程度垦沉,在此 width = 20)
算法執(zhí)行時間為 0.0134007 秒。
4.2 利用迭代器遍歷
Mat& ScanImageAndReduceIterator(Mat& I, vector<int> color_table)
{
//只接收字符型矩陣
CV_Assert(I.depth() != sizeof(uchar));
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 = color_table[*it];
break;
}
case 3:
{
MatIterator_<Vec3b> it, end;
for (it = I.begin<Vec3b>(), end = I.end<Vec3b>(); it != end; it++)
{
(*it)[0] = color_table[(*it)[0]];
(*it)[1] = color_table[(*it)[1]];
(*it)[2] = color_table[(*it)[2]];
}
break;
}
}
return I;
}
算法執(zhí)行時間 0.0693951 秒劣像。用迭代器遍歷速度稍慢乡话,但是更加安全摧玫。上述代碼中耳奕,我們首先對圖像的通道數(shù)進行判斷,通道數(shù)為 1 時诬像,直接對灰度值賦值屋群;對于三通道彩色圖像,每個像素可以看做一個包含三個 uchar 元素的 vector, 在 OpenCV 中用 Vec3b 命名坏挠。對于彩色圖像衅胀,如果我們僅僅使用 uchar 而不是 Vec3b 迭代的話就只能獲得藍(lán)色通道的值(BGR模型中的第一個通道)墅冷。
4.3 通過相關(guān)返回值的 On-the-fly 地址遍歷
Mat& ScanImageAndReduceRadomAccess(Mat& I, vector<int> color_table)
{
//只接收字符型矩陣
CV_Assert(I.depth() != sizeof(uchar));
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) = color_table[I.at<uchar>(i, j)];
}
}
break;
}
case 3:
{
for (int i = 0; i < I.rows; i++)
{
for (int j = 0; j < I.cols; j++)
{
I.at<Vec3b>(i, j)[0] = color_table[I.at<Vec3b>(i, j)[0]];
I.at<Vec3b>(i, j)[1] = color_table[I.at<Vec3b>(i, j)[1]];
I.at<Vec3b>(i, j)[2] = color_table[I.at<Vec3b>(i, j)[2]];
}
}
break;
}
}
return I;
}
通過 at() 函數(shù)獲取并更改圖像中的元素。事實上,這種方法并不推薦唄用來進行圖像掃描寒屯。
4.4 核心函數(shù) LUT (The Core Function)
核心函數(shù) LUT 是最被推薦用于實現(xiàn)批量圖像元素查找和更改的方法,它并不需要你自己去掃描圖像逗栽。我們先建立一個查找表:
Mat table(1, 256, CV_8U);
uchar* p = table.data;
for(int i = 0; i < 256; i++)
p[i] = color_table[i];
然后調(diào)用函數(shù):
//image是輸入慈格,image_reduce是輸出
LUT(image, table, image_reduce);
4.5 結(jié)論
盡量使用 OpenCV 內(nèi)置函數(shù),調(diào)用 LUT 函數(shù)可以獲得最快的速度蛋褥,這是因為 OpenCV 庫可以通過英特爾線程架構(gòu)啟用多線程临燃,當(dāng)然,迭代器也是一個不錯的選擇烙心,優(yōu)點是安全膜廊,缺點是速度較慢,on-the-fly方法不推薦使用淫茵。