一、概要:
使用Canny邊緣檢測算法作爲例子,介紹圖像的平滑方法和邊緣檢測。
Canny邊緣檢測算法分爲四步:
step1:用高斯濾波器平滑圖像;
step2:用一階偏導的有限差分來計算梯度的幅值和方向;(在橫豎兩個方向上計算邊緣,再求平方和的開方)
step3:對梯度幅值進行非極大值抑制;
step4:用雙閾值算法檢測和連接邊緣。
demo&效果:
原圖(lenna.bmp):
高斯濾波後的圖像:
初步求邊緣後的圖像:
非極大值抑制後的圖像:
雙閾值檢測後的圖像:
以下對這四步進行詳細介紹:
二、圖像平滑與高斯濾波
圖像平滑的目的是消除或儘量減少噪聲的影響,改善圖像的質量。在假定加性噪聲是隨機獨立分佈的條件下,利用鄰域的平均或加權平均可以有效的抑制噪聲干擾。
方法是做鄰域計算,比如:
高斯濾波:
採用高斯函數作爲加權函數。
原因一:二維高斯函數具有旋轉對稱性,保證濾波時各方向平滑程度相同;
原因二:離中心點越遠權值越小。確保邊緣細節不被模糊。
設定σ2和n,確定高斯模板權值。如σ2 =2和n=5,整數化和歸一化後得:
代碼:
1.直接用系統函數的方法
img=imread('lenna.bmp'); imshow(img); [m n]=size(img); img=double(img); %高斯濾波 w=fspecial('gaussian',[5 5]); %[ 5 5 ]是模板尺寸,默認是[ 3 3 ] 模板即上文所提的模板 img=imfilter(img,w,'replicate'); figure; imshow(uint8(img))
2.自己實現的高斯濾波(使用上面的模板)
img=imread('lenna.bmp'); img=im2double(img); [m n]=size(img); h=zeros(m,n); for i=1:m for j=1:n if i<3 || i>(m-3) || j<3 || j>(n-3) h(i,j)=img(i,j); else %代碼長度原因,分行相加。 h(i,j)=h(i,j)+img(i-2,j-2)+img(i-2,j+2)+img(i+2,j-2)+img(i+2,j+2); h(i,j)=h(i,j)+(img(i-1,j-2)+img(i+1,j-2)+img(i-2,j-1)+img(i+2,j-1)+img(i-2,j+1)+img(i+2,j+1)+img(i-1,j+2)+img(i+1,j+2))*2; h(i,j)=h(i,j)+3*(img(i,j-2)+img(i,j+2)+img(i+2,j)+img(i-2,j)); h(i,j)=h(i,j)+4*(img(i-1,j-1)+img(i-1,j+1)+img(i+1,j-1)+img(i+1,j+1)); h(i,j)=h(i,j)+6*(img(i,j-1)+img(i,j+1)+img(i+1,j)+img(i-1,j)); h(i,j)=h(i,j)+7*img(i,j); h(i,j)=h(i,j)/79; end end end imshow(h,[]);
三、初步邊緣檢測
對高斯平滑後的圖像進行sobel邊緣檢測。這裏需要求橫的和豎的還有聯合的,所以一共三個需要sobel邊緣檢測圖像。
代碼:
img=imread('lenna.bmp'); [m n]=size(img); img=double(img); w=fspecial('gaussian',[5 5]); img=imfilter(img,w,'replicate'); figure; %%sobel邊緣檢測 w=fspecial('sobel'); img_w=imfilter(img,w,'replicate'); %求橫邊緣 w=w'; %轉置矩陣 img_h=imfilter(img,w,'replicate'); %求豎邊緣 img=sqrt(img_w.^2+img_h.^2); %平方和再開方 .^表示每一位都和自己乘,不清楚的自己百度 figure; imshow(uint8(img))
四、非極大值抑制以及雙閾值檢測邊緣
什麼是非極大值抑制?
非極大值抑制是在梯度方向上的極大值。
代碼:(覺得看看註釋就差不多明白思路了)
img=imread('lenna.bmp'); imshow(img); [m n]=size(img); img=double(img); %%canny邊緣檢測的前兩步相對不復雜,所以我就直接調用系統函數了 %%高斯濾波 w=fspecial('gaussian',[5 5]); img=imfilter(img,w,'replicate'); figure; imshow(uint8(img)) %%sobel邊緣檢測 w=fspecial('sobel'); img_w=imfilter(img,w,'replicate'); %求橫邊緣 w=w'; img_h=imfilter(img,w,'replicate'); %求豎邊緣 img=sqrt(img_w.^2+img_h.^2); %注意這裏不是簡單的求平均,而是平方和在開方。我曾經好長一段時間都搞錯了 figure; imshow(uint8(img)) %%下面是非極大抑制 new_edge=zeros(m,n); for i=2:m-1 for j=2:n-1 Mx=img_w(i,j); My=img_h(i,j); if My~=0 o=atan(Mx/My); %邊緣的法線弧度 elseif My==0 && Mx>0 o=pi/2; else o=-pi/2; end %Mx處用My和img進行插值 adds=get_coords(o);%邊緣像素法線一側求得的兩點座標,插值需要 M1=My*img(i+adds(2),j+adds(1))+(Mx-My)*img(i+adds(4),j+adds(3)); %插值後得到的像素,用此像素和當前像素比較 adds=get_coords(o+pi);%邊緣法線另一側求得的兩點座標,插值需要 M2=My*img(i+adds(2),j+adds(1))+(Mx-My)*img(i+adds(4),j+adds(3));%另一側插值得到的像素,同樣和當前像素比較 isbigger=(Mx*img(i,j)>M1)*(Mx*img(i,j)>=M2)+(Mx*img(i,j)<M1)*(Mx*img(i,j)<=M2); %如果當前點比兩邊點都大置1 if isbigger new_edge(i,j)=img(i,j); end end end figure; imshow(uint8(new_edge)) %%下面是滯後閾值處理 up=120; %上閾值 low=100; %下閾值 set(0,'RecursionLimit',10000); %設置最大遞歸深度 for i=1:m for j=1:n if new_edge(i,j)>up &&new_edge(i,j)~=255 %判斷上閾值 new_edge(i,j)=255; new_edge=connect(new_edge,i,j,low); end end end figure; imshow(new_edge==255)
function re=get_coords(angle) %angle是邊緣法線角度,返回法線前後兩點 sigma=0.000000001; x1=ceil(cos(angle+pi/8)*sqrt(2)-0.5-sigma); y1=ceil(-sin(angle-pi/8)*sqrt(2)-0.5-sigma); x2=ceil(cos(angle-pi/8)*sqrt(2)-0.5-sigma); y2=ceil(-sin(angle-pi/8)*sqrt(2)-0.5-sigma); re=[x1 y1 x2 y2]; end
function nedge=connect(nedge,y,x,low) %種子定位後的連通分析 neighbour=[-1 -1;-1 0;-1 1;0 -1;0 1;1 -1;1 0;1 1]; %八連通搜尋 [m n]=size(nedge); for k=1:8 yy=y+neighbour(k,1); xx=x+neighbour(k,2); if yy>=1 &&yy<=m &&xx>=1 && xx<=n if nedge(yy,xx)>=low && nedge(yy,xx)~=255 %判斷下閾值 nedge(yy,xx)=255; nedge=connect(nedge,yy,xx,low); end end end end