如何掃描一幅影象
阿新 • • 發佈:2018-12-02
文章目錄
影象的儲存結構
- 單通道影象
- 多通道影象(BGR順序)
用指標掃描(官網:Efficient Way)
- 預備知識:
大部分情況下,一張影象的儲存是連續的,這樣我們可以得到首地址,然後遍歷到尾部。但是有時候你是通過擷取已有影象的一部分而建立了一個新的頭部資訊。那麼他的儲存就是不連續的,在用指標的時候,需要注意兩個row之間的gap。可以用cv::Mat::isContinuous()判斷影象儲存是否連續。
例子:
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; //如果連續,那麼只有一行, col*row 個元素
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;
}
- 或者我們也可以這樣使用
data 成員變數返回的是指標,指向第0行、第0列的元素。如果返回是NULL,則Mat物件沒有合法的輸入影象資料。
uchar* p = I.data; //
for( unsigned int i =0; i < ncol*nrows; ++i)
*p++ = table[*p];
用迭代器(iterator way : safe)
- 可以使用cv::Mat::begin()和cv::Mat::end()得到影象的迭代器進行遍歷,迭代器是自動跳到下一row,所以使用很安全。
- 還需要注意的是:如果使用uchar型別的迭代器去遍歷多通道,只能每次訪問到B分量。
- 如果物件申明成const型,用cv域下的MatConstIterator_(或者Mat_<Vec3b>::const_iterator)
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:
{
// Mat_<uchar>::const_iterator it,end; //也行
MatIterator_<uchar> it, end; //申明一個 uchar 的開始結束迭代器,
for( it = I.begin<uchar>(), end = I.end<uchar>(); it != end; ++it)
*it = table[*it];
break;
}
case 3:
{
MatIterator_<Vec3b> it, end; //申明一個 Vec3b 的開始結束迭代器
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;
}
On-the-fly RA
- 使用cv::Mat::at() ,它根據你規定type,傳入coordinate,即時計算出地址。返回引用。
- 這種方式不建議用在掃描影象上,效率低,用在隨機訪問/修改某些特定畫素。
- 如果掃描彩色影象,輸入 type、at 等關鍵詞是比較麻煩的,OpenCV提供了類 Mat_ ,它可以和Mat直接轉換。在申明的時候,規定好type,然後可以用()操作符去獲取每個畫素。注意:這種申明方式的 runtime speed 和 用at 是一樣的。只是方便碼程式碼,懶人必備。
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; //和下面註釋程式碼 是一樣的,定義成Mat_ ,用()操作符去得到元素方便使用
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;
// for( int i = 0; i < I.rows; ++i)
// for( int j = 0; j < I.cols; ++j )
// {
// I.at<Vec3b>(i,j)[0]=tablel[I.at<Vec3b>(i,j)[0]];
// I.at<Vec3b>(i,j)[1]=tablel[I.at<Vec3b>(i,j)[1]];
// I.at<Vec3b>(i,j)[2]=tablel[I.at<Vec3b>(i,j)[2]];
// }
break;
}
}
return I;
}
LUT Core Function
- OpenCV提供 cv::LUT() function of the core module,通過查詢表的方式去改變影象的畫素值
Mat lookUpTable(1, 256, CV_8U);
uchar* p = lookUpTable.ptr();
for( int i = 0; i < 256; ++i)
p[i] = i*(i/10);
Mat J;
LUT(I, lookUpTable, J); // I 輸入, J輸出
比較幾種掃描方法的效率
- OpenCV提供兩個函式: cv::getTickCount() and cv::getTickFrequency() 去測量時間。
- cv::getTickCount() :得到當前的時間戳
- cv::getTickFrequency() :每秒時間戳的個數
可以用下面方式計算耗時:
double t = (double)getTickCount();
// do something ...
t = ((double)getTickCount() - t)/getTickFrequency();
cout << "Times passed in seconds: " << t << endl;
方法 | 耗時 |
---|---|
指標 | 0.47ms |
迭代器 | 2.1ms |
On-the-fly RA | 2.3ms |
LUT Core Function | 0.44ms |