Canny邊緣檢測是最好用的邊緣查找,它的實現也不是很複雜,咱們分四個階段來進行分析。算法
一、高斯模糊,這裏很少說,難度不大。spa
二、卷積code
將圖像同可以獲得x方向的梯度和y方向的梯度的核進行卷積,得出Gx和Gy以及梯度方向。這是整個程序的開端,雖然簡單,可是很重要。blog
這裏,咱們介紹能夠被做爲核的算子:遞歸
這裏咱們能夠看到,這樣的算子有一個特色:進行邊緣檢測的矩陣的各個元素之和爲0。get
在咱們的Canny實現中,咱們採用另一個簡單的算子:圖像處理
這裏咱們須要將x方向和y方向上的梯度轉換爲灰度,再根據公式來獲取最後的梯度大小。class
這裏的內容與以前的Sobel邊緣檢測並無什麼不一樣,可是,後面的內容應該說是比較有趣了。遍歷
Canny算法之因此說是最有效的邊緣檢測算法,其中的特色:程序
(1) 低錯誤率:意味着對存在的邊緣有着很好的檢測
(2) 好的定位:將偵測到的邊緣像素與真實邊緣像素之間的距離最小化
(3) 最小響應:對一個邊緣只有一個偵測響應
這些特色對於邊緣檢測完成後的後續步驟的更好實現有必定的保障。
如何實現這些特色?咱們應該採起如下措施:
三、進行非極大抑制
咱們知道,在梯度方向上變化率最大,所以咱們能夠對已通過卷積的而且求出梯度的圖像中進行最大值的查找:對於圖像中的任意點A,得出A的梯度以及梯度方向。由於咱們已經得知在梯度方向上梯度的變化率最大,因此咱們可以輕易的在這個方向中找到最大的點。因爲咱們在圖像處理這個條件下,因此要將梯度方向離散化,這裏,根據點A的8個鄰域,咱們將梯度方向離散爲四個大的區域,8個小區。由於咱們已經知道最大值就在這裏,並且非極大抑制的做用就是將邊緣細化,因此將點A的梯度同其餘兩點(8個鄰域,離散後的梯度方向過兩個點)進行比較,若是grad(A)最大,則保持grad(A)不變,不然將其設爲0。
四、雙閾值法
咱們進行了非極大抑制以後,再進行閾值操做,當grad(A)>th時,將grad(A)=255不然grad(A)=0。通過這一步以後,咱們發現圖像的邊緣並不連續,這對於咱們以後的圖像處理並無任何用處。所以,咱們必須作一些操做來使邊緣連續。這裏咱們提出了雙閾值法。這個方法可以將真邊緣保留下來,剔除假邊緣。這裏的真邊緣有兩部分組成:強邊緣與弱邊緣。強邊緣就是那些grad(A)>th的點所構成,弱邊緣的定義以下:設有點B,在圖像中存在符合(1) grad(B)>th1而且grad(B)<=th2(2)B點位於真邊緣的某點C的鄰域中;其中th1爲小閾值,th2爲大閾值。
對這個方法的實現,咱們能夠經過使用圖的相關遍歷法則進行。在下面的代碼中,咱們是用的是圖的廣度優先遍歷。深度優先遍歷也是能夠的,可是應當注意的是,因爲圖像中的像素數量較多,容易引發棧溢出,所以在使用遞歸遍歷時,須要多加註意。
代碼以下:
卷積代碼
void getValue(Mat in,int **kernel,int pos_heigh,int pos_width,double&t) { int temp_width_kernel_start,temp_width_kernel_end; int temp_heigh_kernel_start,temp_heigh_kernel_end; if(pos_width>=pos_archor_width) temp_width_kernel_start=0; else temp_width_kernel_start=pos_archor_width-pos_width; if(mat_width-1-pos_width>=kernel_width-1-pos_archor_width) temp_width_kernel_end=kernel_width-1; else temp_width_kernel_end=mat_width-1-pos_width+pos_archor_width; if(pos_heigh>=pos_archor_heigh) temp_heigh_kernel_start=0; else temp_heigh_kernel_start=pos_archor_heigh-pos_heigh; if(mat_heigh-1-pos_heigh>=kernel_heigh-1-pos_archor_heigh) temp_heigh_kernel_end=kernel_heigh-1; else temp_heigh_kernel_end=mat_heigh-1-pos_heigh+pos_archor_heigh; double all1=0; double all2=0; double all3=0; int x=0; Vec3b vec; //cout<<in.depth(); for(int i=temp_heigh_kernel_start;i<=temp_heigh_kernel_end;++i) { int y=0; for(int j=temp_width_kernel_start;j<=temp_width_kernel_end;++j) { vec=in.at<Vec3b>((pos_heigh-(pos_archor_heigh-temp_heigh_kernel_start)+x),(pos_width-(pos_archor_width-temp_width_kernel_start)+y)); all1+=kernel[i][j]*(int)vec[0]; all2+=kernel[i][j]*(int)vec[1]; all3+=kernel[i][j]*(int)vec[2]; y++; } x++; } t=0.114*all1+0.587*all2+0.299*all3; }
非極大抑制:
void dec(Mat in,double **angel) { int z1=0; int z2=0; int z3=0; int z4=0; for(int i=0;i!=in.rows;++i) { for(int j=0;j!=in.cols;++j) { if((angel[i][j]>67.5&&angel[i][j]<=112.5)||(angel[i][j]>-112.5&&angel[i][j]<=-67.5)) { z1++; if(i+1<in.rows) { if(in.at<uchar>(i,j)<=in.at<uchar>(i+1,j)) in.at<uchar>(i,j)=0; } if(i-1>=0) { if(in.at<uchar>(i,j)<=in.at<uchar>(i-1,j)) in.at<uchar>(i,j)=0; } } if((angel[i][j]>112.5&&angel[i][j]<=157.5)||(angel[i][j]>-67.5&&angel[i][j]<=-22.5)) { z2++; if(i-1>=0&&j-1>=0) { if(in.at<uchar>(i,j)<=in.at<uchar>(i-1,j-1)) in.at<uchar>(i,j)=0; } if(i+1<in.rows&&j+1<in.cols) { if(in.at<uchar>(i,j)<=in.at<uchar>(i+1,j+1)) in.at<uchar>(i,j)=0; } } if((angel[i][j]>157.5&&angel[i][j]<=180)||(angel[i][j]>-22.5&&angel[i][j]<=22.5)||(angel[i][j]>-180&&angel[i][j]<=-157.5)) { z3++; if(j-1>=0) { if(in.at<uchar>(i,j)<=in.at<uchar>(i,j-1)) in.at<uchar>(i,j)=0; } if(j+1<in.cols) { if(in.at<uchar>(i,j)<=in.at<uchar>(i,j+1)) in.at<uchar>(i,j)=0; } } if((angel[i][j]>-157.5&&angel[i][j]<=-112.5)||(angel[i][j]>22.5&&angel[i][j]<67.5)) { z4++; if(i+1<in.rows&&j-1>=0) { if(in.at<uchar>(i,j)<=in.at<uchar>(i+1,j-1)) in.at<uchar>(i,j)=0; } if(i-1>=0&&j+1<in.cols) { if(in.at<uchar>(i,j)<=in.at<uchar>(i-1,j+1)) in.at<uchar>(i,j)=0; } } } } }
雙閾值
void canny_yuzhi(Mat in,int min,int max) { int count=0; queue<Point> q; for(int i=0;i!=in.rows;++i) { for(int j=0;j!=in.cols;++j) { if(in.at<uchar>(i,j)>max) { in.at<uchar>(i,j)=255; q.push(Point(i,j)); } if(in.at<uchar>(i,j)<min) in.at<uchar>(i,j)=0; } } imshow("3",in); while(!q.empty()) { Point pt=q.front(); in.at<uchar>(pt.x,pt.y)=255; if(pos_can(pt.x-1,pt.y-1,in.rows,in.cols)&&value_can(in,pt.x-1,pt.y-1,min,max)) { q.push(Point(pt.x-1,pt.y-1)); count++; } if(pos_can(pt.x-1,pt.y,in.rows,in.cols)&&value_can(in,pt.x-1,pt.y,min,max)) { q.push(Point(pt.x-1,pt.y)); count++; } if(pos_can(pt.x-1,pt.y+1,in.rows,in.cols)&&value_can(in,pt.x-1,pt.y+1,min,max)) { q.push(Point(pt.x-1,pt.y+1)); count++; } if(pos_can(pt.x,pt.y-1,in.rows,in.cols)&&value_can(in,pt.x,pt.y-1,min,max)) { q.push(Point(pt.x,pt.y-1)); count++; } if(pos_can(pt.x,pt.y+1,in.rows,in.cols)&&value_can(in,pt.x,pt.y+1,min,max)) { q.push(Point(pt.x,pt.y+1)); count++; } if(pos_can(pt.x+1,pt.y-1,in.rows,in.cols)&&value_can(in,pt.x+1,pt.y-1,min,max)) { q.push(Point(pt.x+1,pt.y-1)); count++; } if(pos_can(pt.x+1,pt.y,in.rows,in.cols)&&value_can(in,pt.x+1,pt.y,min,max)) { q.push(Point(pt.x+1,pt.y)); count++; } if(pos_can(pt.x+1,pt.y+1,in.rows,in.cols)&&value_can(in,pt.x+1,pt.y+1,min,max)) { q.push(Point(pt.x+1,pt.y+1)); count++; } q.pop(); } }