Sobel變換和拉普拉斯變換都是高通濾波器。什麼是高通濾波器呢?就是保留映像的高頻分量(變化劇烈的部分),抑製圖像的低頻分量(變化緩慢的部分)。而映像變化劇烈的部分,往往反應的就是映像的邊沿資訊了。
在OpenCV中,調用sobel函數很簡單:
Mat image = imread("D:/picture/images/boldt.jpg",0);if(!image.data)return -1;imshow("源映像",image);Mat sobelX;//參數為:源映像,結果映像,映像深度,x方向階數,y方向階數,核的大小,尺度因子,增加的值Sobel(image,sobelX,CV_8U,1,0,3,0.4,128);imshow("X方向Sobel結果",sobelX);Mat sobelY;Sobel(image,sobelY,CV_8U,0,1,3,0.4,128);imshow("Y方向Sobel結果",sobelY);
注意到,這裡對sobel的結果進行了一些尺度變換來更好的顯示。
有一點需要特別注意:
由於是對X方向求導,sobelX保留了很多垂直方向的資訊,所以垂直的輪廓“看起來更加清楚”;y方向同理。
按照數學推倒,應該是把兩個方向的值平方以後相加在開方(2範數),得到梯度。而實際上,為了簡化運算,我們直接把他們的絕對值相加(1範數)得出梯度:
//合并結果Mat sobel;Sobel(image,sobelX,CV_32F,1,0);Sobel(image,sobelY,CV_32F,0,1);//計算1範數sobel= abs(sobelX)+abs(sobelY);double sobmin,sobmax;minMaxLoc(sobel,&sobmin,&sobmax);//轉換為8位元,進行尺度變換Mat sobelImage;sobel.convertTo(sobelImage,CV_8U,-255./sobmax,255);imshow("結果",sobelImage);threshold(sobelImage, sobelImage, 190, 255, cv::THRESH_BINARY);imshow("最終結果",sobelImage);
注意:由於運算結果有正有負,所以這裡沒有使用CV_8U類型,而是CV_32F類型。
如果你想精確的計算梯度,不但有大小,還有方向,你可以這樣做:
Mat norm,dir;//計算L2範數和方向cartToPolar(sobelX,sobelY,norm,dir);
拉普拉斯變換是對x和y方向求2階偏導數,然後加起來。
他當在映像邊沿作用時(例如,從暗到亮)我們可以觀察到灰階值的上升必然意味著從正曲度(強度升高)到負曲度(強度達到瓶頸)的變化。因此,拉普拉斯變換結果從正到負(或者相反)組成了一個映像邊沿的很好的指標。另一種方法表達這個事實是說,邊沿出現在拉普拉斯變換的過零點處。
OpenCV中計算拉普拉斯變換也比較容易:
//直接計算Mat laplace;//變換的結果*1+128Laplacian(image,laplace,CV_32F,7,1,128);imshow(" 直接使用的結果",laplace);//計算一個小視窗內的拉普拉斯變換的值for(int i = 0; i < 12;i++){for(int j = 0; j < 12; j++){//由於前面的變換中加了128,所以這裡要減去128cout<<setw(5)<<static_cast<int>(laplace.at<float>(i+135,j+362))-128<<" ";}cout<<endl;}cout<<endl;cout<<endl;cout<<endl;
為了更好的說明拉普拉斯變換的作用,我們先定義1個類:
#if !defined LAPLACEZC#define LAPLACEZC#include <opencv2/core/core.hpp>#include <opencv2/highgui/highgui.hpp>#include <opencv2/imgproc/imgproc.hpp>using namespace cv;class LaplacianZC{private://源映像Mat img;//拉普拉斯變換的結果,32位元Mat laplace;//拉普拉斯核的大小int aperture;public://建構函式LaplacianZC():aperture(3){}//設定核的大小void setAperture(int a){aperture = a;}//計算拉普拉斯變換Mat computeLaplacian(const Mat &image){//計算拉普拉斯變換Laplacian(image,laplace,CV_32F,aperture,1,0);//保留副本img = image.clone();return laplace;}//擷取變換後的映像Mat getLaplacianImage(double scale = -1.0){double lapmin, lapmax;if(scale < 0){//擷取變換的最大值和最小值minMaxLoc(laplace,&lapmin,&lapmax);scale = 127/ std::max(-lapmin,lapmax);}Mat laplaceImage;laplace.convertTo(laplaceImage,CV_8U,scale,128);return laplaceImage;}//獲得過零點的2值映像Mat getZeroCrossings(float threshold = 1.0){//第二行第一個元素Mat_<float>::const_iterator it= laplace.begin<float>()+laplace.step1();//最後一個元素Mat_<float>::const_iterator itend= laplace.end<float>();//第一行第一個元素Mat_<float>::const_iterator itup= laplace.begin<float>();//2值映像初始化為白色Mat binary(laplace.size(),CV_8U,Scalar(255));Mat_<uchar>::iterator itout = binary.begin<uchar>()+binary.step1();//使得門限無效threshold *= -1.0;for(;it != itend; ++it,++itup,++itout){//如果相鄰像個像素的積為負,這裡的符號就發生了變化if(*it * *(it-1) < threshold)*itout = 0;else if(*it * *(itup) < threshold )*itout = 0;}return binary;}};#endif
我們選擇一個小的地區重點觀測:
先看看主函數:
//使用LaplacianZC類計算拉普拉斯變換LaplacianZC laplacian;laplacian.setAperture(7);//變換後的結果有正有負,小於0的設為0,大於255的設為255,效果很差Mat flap = laplacian.computeLaplacian(image);double lpmin,lpmax;//擷取變換後的最大值和最小值minMaxLoc(flap,&lpmin,&lpmax);cout<<"拉普拉斯變換後的範圍為:"<<"["<<lpmin<<" ,"<<lpmax<<"]"<<endl;cout<<endl;cout<<endl;cout<<endl;laplace = laplacian.getLaplacianImage();imshow("Laplacian Image",laplace);//列印視窗內拉普拉斯變換的值for(int i = 0; i < 12; i++){for(int j = 0; j < 12; j++){cout<<setw(5)<<static_cast<int>(flap.at<float>(i+135,j+362))/100 << " ";}cout<<endl;}cout<<endl;//計算和顯示過零點Mat zeros = laplacian.getZeroCrossings(lpmax);imshow("過零點",zeros);
列印訊息中對每個點的拉普拉斯變換除以100僅僅因為計算的結果太大,如果正常顯示,無法對齊。但這並不影響過零點(因為他只用考慮加號或減號)
而過零點是:
這恰恰就是那個小視窗中的塔樓的邊沿!