圖像的邊緣檢測,是根據灰度的突變或者說不連續來檢測,對於其中的算子有一階導數和二價導數,這裏先說基礎的三種方法---Robert,prewitt,Sobel邊緣檢測。
一、梯度
首先介紹下梯度,梯度並非是一個數值,梯度嚴格意義上是一個向量,這個向量指向當前位置變化最快的方向,可以這麼理解,當你站在一個山上,你有360°的方向可以選擇,哪個方向下降速度最快(最陡峭),便是梯度方向,梯度的長度,表示爲向量的長度,表示最大的變化速率。
梯度的數學表達:
在二維中只取前兩項,也就是由x方向的偏微分和y方向的偏微分組成。對於圖像f中點(x,y)處的梯度,定義爲:
與上面所述保持一致,圖像梯度方向給出圖像變化最快方向,當前點的梯度長度爲:
次長度計算中有平方和開平方,所以將不再是現行操作。
爲了簡單計算,將上面求距離簡化成:
二、RObert
Roberts邊緣算子是一個2x2的模板,採用的是對角方向相鄰的兩個像素之差。從圖像處理的實際效果來看,邊緣定位較準,對噪聲敏感。適用於邊緣明顯且噪聲較少的圖像分割。Roberts邊緣檢測算子是一種利用局部差分算子尋找邊緣的算子,Robert算子圖像處理後結果邊緣不是很平滑。經分析,由於Robert算子通常會在圖像邊緣附近的區域內產生較寬的響應,故採用上述算子檢測的邊緣圖像常需做細化處理,邊緣定位的精度不是很高。標準一階差分不同,Robert採用對角線差分,邊緣定位準,但是對噪聲敏感。適用於邊緣明顯且噪聲較少的圖像分割。Roberts邊緣檢測算子是一種利用局部差分算子尋找邊緣的算子,Robert算子圖像處理後結果邊緣不是很平滑。經分析,由於Robert算子通常會在圖像邊緣附近的區域內產生較寬的響應,故採用上述算子檢測的邊緣圖像常需做細化處理,邊緣定位的精度不是很高。
<span style="font-size:18px;">// roberts算子實現
cv::Mat roberts(cv::Mat srcImage)
{
cv::Mat dstImage = srcImage.clone();
int nRows = dstImage.rows;
int nCols = dstImage.cols;
for (int i = 0; i < nRows-1; i++)
{
for (int j = 0; j < nCols-1; j++)
{
int t1 = (srcImage.at<uchar>(i, j) -
srcImage.at<uchar>(i+1, j+1)) *
(srcImage.at<uchar>(i, j) -
srcImage.at<uchar>(i+1, j+1));
int t2 = (srcImage.at<uchar>(i+1, j) -
srcImage.at<uchar>(i, j+1)) *
(srcImage.at<uchar>(i+1, j) -
srcImage.at<uchar>(i, j+1));
dstImage.at<uchar>(i, j) = (uchar)sqrt(t1 + t2);
}
}
return dstImage;
}</span>
三、prewitt
Prewitt算子是一種一階微分算子的邊緣檢測,利用像素點上下、左右鄰點的灰度差,在邊緣處達到極值檢測邊緣,去掉部分僞邊緣,對噪聲具有平滑作用 。其原理是在圖像空間利用兩個方向模板與圖像進行鄰域卷積來完成的,這兩個方向模板一個檢測水平邊緣,一個檢測垂直邊緣。
對數字圖像f(x,y),Prewitt算子的定義如下:
Prewitt算子對噪聲有抑制作用,抑制噪聲的原理是通過像素平均,但是像素平均相當於對圖像的低通濾波,所以Prewitt算子對邊緣的定位不如Roberts算子。
因爲平均能減少或消除噪聲,Prewitt梯度算子法就是先求平均,再求差分來求梯度。水平和垂直梯度模板分別爲:
檢測水平邊沿 橫向模板 檢測垂直平邊沿 縱向模板:該算子與Sobel算子類似,只是權值有所變化,但兩者實現起來功能還是有差距的,據經驗得知Sobel要比Prewitt更能準確檢測圖像邊緣。
對噪聲有抑制作用,抑制噪聲的原理是通過像素平均,但是像素平均相當於對圖像的低通濾波,所以Prewitt算子對邊緣的定位不如Roberts算子。
// roberts算子實現
Mat prewitt(Mat imageP)
{
cvtColor(imageP,imageP,CV_RGB2GRAY);
float prewittx[9] =
{
-1,0,1,
-1,0,1,
-1,0,1
};
float prewitty[9] =
{
1,1,1,
0,0,0,
-1,-1,-1
};
Mat px=Mat(3,3,CV_32F,prewittx);
cout<<px<<endl;
Mat py=Mat(3,3,CV_32F,prewitty);
cout<<py<<endl;
Mat dstx=Mat(imageP.size(),imageP.type(),imageP.channels());
Mat dsty=Mat(imageP.size(),imageP.type(),imageP.channels());
Mat dst=Mat(imageP.size(),imageP.type(),imageP.channels());
filter2D(imageP,dstx,imageP.depth(),px);
filter2D(imageP,dsty,imageP.depth(),py);
float tempx,tempy,temp;
for(int i=0;i<imageP.rows;i++)
{
for(int j=0;j<imageP.cols;j++)
{
tempx=dstx.at<uchar>(i,j);
tempy=dsty.at<uchar>(i,j);
temp=sqrt(tempx*tempx+tempy*tempy);
dst.at<uchar>(i,j)=temp;
}
}
return dst;
}
四、Sobel
其主要用於邊緣檢測,在技術上它是以離散型的差分算子,用來運算圖像亮度函數的梯度的近似值, Sobel算子是典型的基於一階導數的邊緣檢測算子,由於該算子中引入了類似局部平均的運算,因此對噪聲具有平滑作用,能很好的消除噪聲的影響。Sobel算子對於象素的位置的影響做了加權,與Prewitt算子、Roberts算子相比因此效果更好。
Sobel算子包含兩組3x3的矩陣,分別爲橫向及縱向模板,將之與圖像作平面卷積,即可分別得出橫向及縱向的亮度差分近似值。實際使用中,常用如下兩個模板來檢測圖像邊緣。
圖像的每一個像素的橫向及縱向梯度近似值可用以下的公式結合,來計算梯度的大小。
然後可用以下公式計算梯度方向。
在以上例子中,如果以上的角度Θ等於零,即代表圖像該處擁有縱向邊緣,左方較右方暗。
缺點是Sobel算子並沒有將圖像的主題與背景嚴格地區分開來,換言之就是Sobel算子並沒有基於圖像灰度進行處理,由於Sobel算子並沒有嚴格地模擬人的視覺生理特徵,所以提取的圖像輪廓有時並不能令人滿意。
Sobel 算子是一個主要用作邊緣檢測的離散微分算子 (discrete differentiation operator)。 它Sobel算子結合了高斯平滑和微分求導,用來計算圖像灰度函數的近似梯度。在圖像的任何一點使用此算子,將會產生對應的梯度矢量或是其法矢量。因爲Sobel算子結合了高斯平滑和分化(differentiation),因此結果會具有更多的抗噪性。大多數情況下,我們使用sobel函數時,取【xorder = 1,yorder = 0,ksize = 3】來計算圖像X方向的導數,【xorder = 0,yorder = 1,ksize = 3】來計算圖像y方向的導數。
計算圖像X方向的導數,取【xorder= 1,yorder = 0,ksize = 3】情況對應的內核:
C++: void Sobel (
InputArray src,//輸入圖
OutputArray dst,//輸出圖
int ddepth,//輸出圖像的深度
int dx,
int dy,
int ksize=3,
double scale=1,
double delta=0,
int borderType=BORDER_DEFAULT );
- 第一個參數,InputArray 類型的src,爲輸入圖像,填Mat類型即可。
- 第二個參數,OutputArray類型的dst,即目標圖像,函數的輸出參數,需要和源圖片有一樣的尺寸和類型。
- 第三個參數,int類型的ddepth,輸出圖像的深度,支持如下src.depth()和ddepth的組合:
- 若src.depth() = CV_8U, 取ddepth =-1/CV_16S/CV_32F/CV_64F
- 若src.depth() = CV_16U/CV_16S, 取ddepth =-1/CV_32F/CV_64F
- 若src.depth() = CV_32F, 取ddepth =-1/CV_32F/CV_64F
- 若src.depth() = CV_64F, 取ddepth = -1/CV_64F
- 第四個參數,int類型dx,x 方向上的差分階數。
- 第五個參數,int類型dy,y方向上的差分階數。
- 第六個參數,int類型ksize,有默認值3,表示Sobel核的大小;必須取1,3,5或7。
- 第七個參數,double類型的scale,計算導數值時可選的縮放因子,默認值是1,表示默認情況下是沒有應用縮放的。我們可以在文檔中查閱getDerivKernels的相關介紹,來得到這個參數的更多信息。
- 第八個參數,double類型的delta,表示在結果存入目標圖(第二個參數dst)之前可選的delta值,有默認值0。
- 第九個參數, int類型的borderType,我們的老朋友了(萬年是最後一個參數),邊界模式,默認值爲BORDER_DEFAULT。這個參數可以在官方文檔中borderInterpolate處得到更詳細的信息。
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <stdlib.h>
#include <stdio.h>
using namespace cv;
int main( int argc, char** argv )
{
Mat src, src_gray;
Mat grad;
char* window_name = "Sobel Demo - Simple Edge Detector";
int scale = 1;//默認值
int delta = 0;//默認值
int ddepth = CV_16S;//防止輸出圖像深度溢出
int c;
src = imread( "lena.jpg" );
if( !src.data )
{ return -1; }
//高斯模糊
GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT );
//變換爲灰度圖
cvtColor( src, src_gray, CV_RGB2GRAY );
//創建窗口
namedWindow( window_name, CV_WINDOW_AUTOSIZE );
//生成 grad_x and grad_y
Mat grad_x, grad_y;
Mat abs_grad_x, abs_grad_y;
// Gradient X x方向梯度 1,0:x方向計算微分即導數
//Scharr( src_gray, grad_x, ddepth, 1, 0, scale, delta, BORDER_DEFAULT );
Sobel( src_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT );
convertScaleAbs( grad_x, abs_grad_x );
// Gradient Y y方向梯度 0,1:y方向計算微分即導數
//Scharr( src_gray, grad_y, ddepth, 0, 1, scale, delta, BORDER_DEFAULT );
Sobel( src_gray, grad_y, ddepth, 0, 1, 3, scale, delta, BORDER_DEFAULT );
convertScaleAbs( grad_y, abs_grad_y );
//近似總的梯度
addWeighted( abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad );
imshow( window_name, grad );
waitKey(0);
return 0;
}
五、Matlab
clear all;
I=imread('d:\lena.jpg');
I=rgb2gray(I);%灰度圖像
subplot(1,4,1);imshow(I);
xlabel('(a)原始圖像');
%sobel算子
BW2=edge(I,'sobel');
subplot(1,4,2);imshow(BW2);
xlabel('(c)sobel算子')
%prewitt算子
BW3=edge(I,'prewitt');
subplot(1,4,3);imshow(BW3);
xlabel('(d)prewitt算子')
%roberts算子
BW4=edge(I,'roberts');
subplot(1,4,4);imshow(BW4);
xlabel('(e)roberts算子')