在圖像處理中,經常需要處理一個當前點這個點的值可能是基於附近幾個臨近像素點而得出的.當臨近像素點包含上一行或者下一行數據的時候,你需要同時掃描圖像的多行.這節會告訴你怎麼做.
Getting ready
本節,我們會用一個銳化圖像的例子舉例.它是基於拉普拉斯操作的(在第6章會討論).衆所周知,如果你對一幅圖像使用拉普拉斯算法,這個圖像的邊緣會增強,可以獲得一個銳化圖像.這個銳化操作如下:
sharpened_pixel= 5*current-left-right-up-down;
這裏left是當前像素的左側,up是當前像素上一行相同列數的點.以此類推.
How to do it ...
這一次,圖像處理不用使用in-place方法完成了,用戶需要提供一個輸出圖像.使用三個像素指針遍歷圖像,一個是當前行,一個是上一行還有一個是下一行.因爲,每一個像素計算都需要訪問臨近像素點,只使用一行(或一列)圖像的所有像素是不能實現這個操作的.這個循環如下:
void sharpen(const cv::Mat &image, cv::Mat &result) {
// allocate if necessary
result.create(image.size(), image.type());
for (int j= 1; j<image.rows-1; j++) { // for all rows
// (except first and last)
const uchar* previous=
image.ptr<const uchar>(j-1); // previous row
const uchar* current=
image.ptr<const uchar>(j); // current row
const uchar* next=
image.ptr<const uchar>(j+1); // next row
uchar* output= result.ptr<uchar>(j); // output row
for (int i=1; i<image.cols-1; i++) {
*output++= cv::saturate_cast<uchar>(
5*current[i]-current[i-1]
-current[i+1]-previous[i]-next[i]);
}
}
// Set the unprocess pixels to 0
result.row(0).setTo(cv::Scalar(0));
result.row(result.rows-1).setTo(cv::Scalar(0));
result.col(0).setTo(cv::Scalar(0));
result.col(result.cols-1).setTo(cv::Scalar(0));
}
如果我們使用一個灰度圖像,我們會獲得如下圖像:
How It works ...
爲了去訪問上一行或者下一行的臨近像素.我們必須定義額外的指針.然後在內循環中訪問這些像素值.
在輸出像素的處理中,使用了模版函數cv::staurate_case.這是因爲在使用多個像素處理後的結果可能超過像素值的界限(0~255).解決的方法是把超出的值,變成在範圍內的.這個操作使得小於0的值變爲0,超出255的值變爲255.這就是cv::sturate_cast<unchar>函數所做的事情.需要注意,如果如果輸入參數是一個浮點數,這個結果會返回一個最近的整數.顯然,你也可以對其他數據類型使用這個函數,結果會返這種類型定義的範圍.
圖像框的像素不會被處理,因爲它們的臨近值不完全存在,需要分開處理.這裏我們簡單的把他們設置爲0.在其他的例子中,它們可能會被執行一些特殊的運算,但是在大部分的情況下,沒有必要花費時間處理這部分很少的像素.在我的的函數中,使用了兩種特殊的方法把邊緣的像素值置爲0.第一個就是row和col.他們當指定一個參數時,會返回一個cv::Mat對象一個單行(或單列).這裏不會產生複製.因爲如果一維矩陣的元素被修改,在原始圖像中也會修改.這也是我們使用setTo()方法做的事情.這個方法需要指定一個像素的所有元素.如下:
result.row(0).setTo(cv::Scalar(0));
對result的第一行所有元素值都置爲0.在3通道圖像,需要使用能夠cv::Scalar(a,b,c)去指定每個通道的值.
There's more...
當做臨近像素值運算的時候,通常使用核心矩陣.這個核心描述瞭如何與臨近像素進行運算的.在本節的銳化中,這內核是:
0 -1 0
-1 5 -1
0 -1 0
除非另有規定,當前像素位於核心的中間.核心的每個因子都是可以增加的.返回的結果是,像素分別乘以相應位置的總和.內核的大小對應於臨近像素的大小(這裏是3×3).從表面上看,正像銳化所要求的,水平和垂直的四個鄰近點被乘以-1,當前像素點乘以5.使用內核更方便表示,這是基於信號處理的卷積表示.內核定義了一個應用於圖像的濾波器.
因爲在圖像處理中濾波是一個常用的操作,OpenCV定義了一個專門的功能來實現:cv::filter2D.使用這個方法,僅僅需要定義一個內核(矩陣形式表示).這個功能會調用圖像和內核,然後返回處理後的圖像.使用這個功能,我們的銳化函數可以如下更容的定義:
void sharpen2D(const cv::Mat &image, cv::Mat &result) {
// Construct kernel (all entries initialized to 0)
cv::Mat kernel(3,3,CV_32F,cv::Scalar(0));
// assigns kernel values
kernel.at<float>(1,1)= 5.0;
kernel.at<float>(0,1)= -1.0;
kernel.at<float>(2,1)= -1.0;
kernel.at<float>(1,0)= -1.0;
kernel.at<float>(1,2)= -1.0;
//filter the image
cv::filter2D(image,result,image.depth(),kernel);
}
這實現了和我們先前相同的功能.然而,對於較大的內核,使用filter2D方法更有利,這種情況下,效率更高.See also
在第六章我們會了解更多的圖像濾波.