寫在前面
呼~最近開始入坑圖像+機器學習了,學習的過程中遇到了不少不懂的東西,好在自學能力還可以(自戀中= =),所以斷斷續續也算學會了一些東西~因爲這段時間一直在做邊緣檢測和提取的工作,所以本篇就總結一下一些常見的邊緣檢測方法,篇幅較長,可按點查看~
名詞解釋
圖像處理中經常用到一些名詞,以下列舉一些:
1. 濾波
所謂濾波就是對每個像素點及其鄰域點的灰度值按照一定的參數規則進行加權平均,這樣可以有效濾去理想圖像中疊加的高頻噪聲。常用的濾波有線性濾波、中值濾波、均值濾波、雙邊濾波、高斯濾波等。濾波有抑制噪聲的作用,但這會使得圖像邊緣模糊。
2. 直方圖
在圖像處理中,經常用到直方圖,如顏色直方圖、灰度直方圖等。直方圖可以直觀展現數據分佈情況,如灰度直方圖中,橫座標爲各個灰度範圍,縱座標爲處在相應範圍的像素數。圖像直方圖不關心像素所處的空間位置,因此不受圖像旋轉和平移變化的影響,可以作爲圖像的特徵。
3. 上採樣
上採樣即放大圖像(或稱圖像插值(interpolating)),從而使圖像可以顯示在更高分辨率的顯示設備上。注意對圖像的縮放操作通常會影響圖像的質量。上採樣幾乎都是採用內插值方法,即在原有圖像像素的基礎上在像素點之間採用合適的插值算法插入新的元素。
4. 下采樣
下采樣即縮小圖像(或稱爲降採樣(downsampled)),其主要目的有兩個:1、使得圖像符合顯示區域的大小;2、生成對應圖像的縮略圖。
下采樣原理:對於一幅圖像I尺寸爲M*N,對其進行s倍下采樣,即得到(M/s)*(N/s)尺寸的分辨率圖像,當然s應該是M和N的公約數才行,如果考慮的是矩陣形式的圖像,就是把原始圖像s*s窗口內的圖像變成一個像素,這個像素點的值就是窗口內所有像素的均值
Canny算子
1.原理
canny算子與LoG算子類似,屬於先平滑後求導數的方法,原理可分成下面4個部分:
1.1 高斯濾波
在所有濾波方法中,需要考慮的最重要的一點是如何平衡去噪與邊緣檢測精確之間的矛盾。實際工程經驗表明,高斯函數確定的核可以提供較好的折衷方案。
高斯濾波實現方法有兩種:離散化窗口滑動卷積、傅里葉變換。因前者比較常用,故下面只介紹前者。
離散化窗口滑動卷積主要利用高斯覈實現,即一個奇數大小的高斯模板。常用的高斯核模板有3*3 和 5*5兩種
其中的參數通過高斯函數計算,x^2+y^2表示像素點和中心像素點的距離,sigma表示標準差。
- 注:
- sigma如果選的過大,會加深濾波程度,從而導致圖像邊緣模糊,不利於下一步的邊緣檢測,如果過小,則濾波效果不佳
- 計算高斯模板參數時,需要歸一化處理,對於歸一化的原因,有一種解釋是:歸一化之後,通過卷積計算出來的模板中心像素被限制到了0-255的灰度區間中。假若某一鄰域內所有像素的灰度值爲255,利用該模板進行卷積之後,求得的模板中心像素灰度值仍然爲255;假若計算出來的高斯模板參數之和小於1,那麼通過該模板進行卷積之後,模板中心像素的灰度值將小於255,偏離了實際的灰度值,產生了誤差。
1.2 求梯度幅值和梯度方向
canny算子使用的卷積算子如下:
梯度幅值及梯度方向的計算如下,其中P表示x方向一階偏導數矩陣,Q表示y方向一階偏導數矩陣,M表示梯度幅值,θ表示梯度方向
1.3 非極大值抑制
在上面求出的梯度幅值矩陣中,值越大的元素代表其梯度越大,但它不一定是邊緣像素,因此需要進行非極大值抑制,也就是尋找像素點局部最大值,將非極大值點所對應的灰度值置爲0,這樣可以剔除掉一大部分非邊緣的點。
如上圖,判定C點是否爲8鄰域內最大梯度值點,只需要判斷C是否比C的梯度方向上dTmp1和dTmp2點大,是則保留,不是則將C點灰度值置0。而dTmp1和dTmp2的梯度值可以通過插值得到。
1.4 雙閾值法閉合邊緣
上面得到的邊緣有些爲假邊緣,且有邊緣斷裂的問題,如果根據高閾值得到一個邊緣圖像,這樣一個圖像含有很少的假邊緣,但是由於閾值較高,產生的圖像邊緣可能不閉合,因此還要採用一個低閾值,當到達輪廓的端點時,在斷點的8鄰域點中尋找滿足低閾值的點,再根據此點收集新的邊緣,直到整個圖像邊緣閉合。
2.實現
2.1 原始圖像灰度化
img = imread('lena.jpg');
img = rgb2gray(img);
2.2 調用matlab內置函數
img_edge = edge(img,'canny');
figure;imshow(img_edge);title('canny');
Roberts、Sobel、Prewitt算子
圖像的灰度值梯度可以用一階偏導的有限差分近似計算,常用梯度算子有:
Roberts算子
梯度幅值爲:
matlab中可直接調用:edge(img,'roberts')
Sobel算子
梯度幅值爲:
matlab中可直接調用:edge(img,'sobel')
Prewitt算子
matlab中可直接調用:edge(img,'prewitt')
雙邊濾波(Bilateral Filters)
1. 原理
傳統濾波方法多多少少會有模糊邊緣的缺點,而雙邊濾波作爲一種非線性濾波器,具有在降噪平滑的同時,保持邊緣的效果。該特性主要是通過在卷積的過程中組合空域(space)函數和值域(range)核函數來實現的,空域指的是像素的歐氏距離,值域指的是像素範圍域中的輻射差異(如卷積核中像素與中心像素之間相似程度、顏色強度,深度距離等)。典型的核函數爲高斯分佈函數,如下所示:
其中,權重係數w(i,j,k,l)取決於空域核和值域核的乘積:
在圖像的平坦區域,像素值變化很小,對應的像素值域權重接近於1,此時空域權重起主要作用,相當於進行高斯模糊;在圖像的邊緣區域,像素值變化很大,像素值域權重變大,從而保持了邊緣的信息。
2. 實現
上述方法的時間複雜度是O(σd^2),非常耗時。論文《Fast O(1) bilateral filtering using trigonometric range kernels》,提出了用Raised cosines函數來逼近高斯值域函數,並利用一些特性把值域函數分解爲一些列函數的疊加,從而實現函數的加速,而論文” A fast approximation of the bilateral filter using a signal processing approach”則提出了一種使用信號處理的方法,主要是在原有域上添加了信號強度這一維,構成了高維空間,在高維空間中進行下采樣,下面的代碼是作者團隊編寫的:
% 雙邊濾波函數
function output = fBilateralFilter_ReviseVer( data, edge, edgeMin, edgeMax, sigmaSpatial, sigmaRange,samplingSpatial, samplingRange )
if ~exist( 'edge', 'var' )
edge = data;
elseif isempty( edge )
edge = data;
end
inputHeight = size( data, 1 );
inputWidth = size( data, 2 );
if ~exist( 'edgeMin', 'var' )
edgeMin = min( edge( : ) );
% warning( 'edgeMin not set! Defaulting to: %f\n', edgeMin );
end
if ~exist( 'edgeMax', 'var' )
edgeMax = max( edge( : ) );
% warning( 'edgeMax not set! Defaulting to: %f\n', edgeMax );
end
edgeDelta = edgeMax - edgeMin;% hl- span of range
% hl- assign scale parameters in both spatial and range domain
if ~exist( 'sigmaSpatial', 'var' )
sigmaSpatial = min( inputWidth, inputHeight ) / 16;
fprintf( 'Using default sigmaSpatial of: %f\n', sigmaSpatial );
end
if ~exist( 'sigmaRange', 'var' )
sigmaRange = 0.1 * edgeDelta;
fprintf( 'Using default sigmaRange of: %f\n', sigmaRange );
end
if ~exist( 'samplingSpatial', 'var' )
samplingSpatial = sigmaSpatial;
end
if ~exist( 'samplingRange', 'var' )
samplingRange = sigmaRange;
end
if size( data ) ~= size( edge )
error( 'data and edge must be of the same size' );
end
% parameters
derivedSigmaSpatial = sigmaSpatial / samplingSpatial;
derivedSigmaRange = sigmaRange / samplingRange;
paddingXY = floor( 2 * derivedSigmaSpatial ) + 1;
paddingZ = floor( 2 * derivedSigmaRange ) + 1;
% allocate 3D grid
downsampledWidth = floor( ( inputWidth - 1 ) / samplingSpatial ) + 1 + 2 * paddingXY; % paddingXY - 控制延拓範圍
downsampledHeight = floor( ( inputHeight - 1 ) / samplingSpatial ) + 1 + 2 * paddingXY;
downsampledDepth = floor( edgeDelta / samplingRange ) + 1 + 2 * paddingZ;
gridData = zeros( downsampledHeight, downsampledWidth, downsampledDepth );
gridWeights = zeros( downsampledHeight, downsampledWidth, downsampledDepth );
% compute downsampled indices
[ jj, ii ] = meshgrid( 0 : inputWidth - 1, 0 : inputHeight - 1 ); % hl- create the coordinats of xy-plane; jj - y coordinates of all pixels, ii - x coordinates of all pixels
%Compute the downsampled coordinates
di = round( ii / samplingSpatial ) + paddingXY + 1; % round: Round to nearest integer四捨五入
dj = round( jj / samplingSpatial ) + paddingXY + 1;
dz = round( ( edge - edgeMin ) / samplingRange ) + paddingZ + 1;
% hl - average sampling (box sampling)
for k = 1 : numel( dz ) % numel: Number of elements in an array
dataZ = data( k ); % traverses the image column wise, same as di( k )
if ~isnan(dataZ),
dik = di( k ); %取出座標
djk = dj( k );
dzk = dz( k );
gridData( dik, djk, dzk ) = gridData( dik, djk, dzk ) + dataZ;
gridWeights( dik, djk, dzk ) = gridWeights( dik, djk, dzk ) + 1;
end
end
% make gaussian kernel
kernelWidth = 2 * derivedSigmaSpatial + 1;
kernelHeight = kernelWidth;
kernelDepth = 2 * derivedSigmaRange + 1;
halfKernelWidth = floor( kernelWidth / 2 );
halfKernelHeight = floor( kernelHeight / 2 );
halfKernelDepth = floor( kernelDepth / 2 );
[gridX, gridY, gridZ] = meshgrid( 0 : kernelWidth - 1, 0 : kernelHeight - 1, 0 : kernelDepth - 1 );
gridX = gridX - halfKernelWidth;
gridY = gridY - halfKernelHeight;
gridZ = gridZ - halfKernelDepth;
gridRSquared = ( gridX .* gridX + gridY .* gridY ) / ( derivedSigmaSpatial * derivedSigmaSpatial ) + ( gridZ .* gridZ ) / ( derivedSigmaRange * derivedSigmaRange );
kernel = exp( -0.5 * gridRSquared );
% convolve
blurredGridData = convn( gridData, kernel, 'same' );
blurredGridWeights = convn( gridWeights, kernel, 'same' );
% divide
blurredGridWeights( blurredGridWeights == 0 ) = -2; % avoid divide by 0, won't read there anyway
normalizedBlurredGrid = blurredGridData ./ blurredGridWeights;
normalizedBlurredGrid( blurredGridWeights < -1 ) = 0; % put 0s where it's undefined
% upsample
[ jj, ii ] = meshgrid( 0 : inputWidth - 1, 0 : inputHeight - 1 ); % meshgrid does x, then y, so output arguments need to be reversed
% no rounding
di = ( ii / samplingSpatial ) + paddingXY + 1;
dj = ( jj / samplingSpatial ) + paddingXY + 1;
dz = ( edge - edgeMin ) / samplingRange + paddingZ + 1;
output = interpn( normalizedBlurredGrid, di, dj, dz ); % N-D data interpolation
end
Hessian特徵
1. 原理
Hessian矩陣本質是是一個多元函數的二階偏導數矩陣,描述了函數的局部曲率。關於Hessian矩陣的由來及詳細推導證明見參考資料3,這裏直接介紹如何得到Hessian矩陣:
- 高斯函數
- 求二階偏導
- 對原圖進行卷積
- 構成Hessian矩陣
2. 實現
% 提取Hessian特徵值
function [hessianValue,Ixx,Ixy,Iyy] = edge_hessian(img)
[m n]=size(img);
w=4;
sigma=1.2;
[x y]=meshgrid(-w:w,-w:w);
% 高斯函數對應的二階偏導
Dxx = 1/(-2*pi*sigma^4)*(1-x.^2/sigma^2)*exp(-(x.^2+x.^2)/(2*sigma^2));
Dyy = 1/(-2*pi*sigma^4)*(1-y.^2/sigma^2)*exp(-(x.^2+y.^2)/(2*sigma^2));
Dxy = 1/(2*pi*sigma^6)*(x.*y)*exp(-(x.^2+y.^2)/(2*sigma^2));
Ixx=imfilter(img,Dxx,'replicate');
Iyy=imfilter(img,Dyy,'replicate');
Ixy=imfilter(img,Dxy,'replicate');
hessianValue=[];
for i=1:m
for j=1:n
hessianValue(i,j) = Ixx(i,j)*Iyy(i,j) - Ixy(i,j)*Ixy(i,j);
end
end
end
Haar特徵
1. 原理
Haar特徵是一種反映圖像的灰度變化的,像素分模塊求差值的一種特徵。常用於人臉識別中五官劃分,例如:臉部的一些特徵能由矩形模塊差值特徵簡單的描述,如:眼睛要比臉頰顏色要深,鼻樑兩側比鼻樑顏色要深,嘴巴比周圍顏色要深等。但矩形特徵只對一些簡單的圖形結構,如邊緣、線段較敏感,所以只能描述在特定方向(水平、垂直、對角)上有明顯像素模塊梯度變化的圖像結構。
它分爲三類:邊緣特徵、線性特徵、中心特徵和對角線特徵。
模板特徵值計算: 黑色矩形像素和 - 白色矩形像素和
- 注:一般通過積分圖計算Haar特徵,這樣只需求一次積分圖,就可以求出多種Haar特徵,節省計算時間
- 積分圖:主要思想是將圖像從起點開始到各個點所形成的矩形區域像素之和作爲一個數組的元素保存在內存中,當要計算某個區域的像素和時可以直接索引數組的元素,不用重新計算這個區域的像素和。示例如下:
上圖中,D塊的像素和=II(4)+II(1)-II(2)-II(3) II表示積分圖
2. 實現
這裏我只實現了Haar特徵的一種,其他的同理
% 提取Haar特徵(中心爲黑四周爲白),思路如下:
% white+black = II(i+1,j+1)+II(i-2,j-1)-II(i-2,j+1)-II(i+1,j-2)
% black = img(i,j)
% harr = white-black = white+black-2*black
function [haar,hg,hgx,hgy] = edge_haar_center(img)
close all;
II = integralImage(img);% 求積分圖
II = II(2:end,2:end);
height = size(II,1);
width = size(II,2);
total = II(4:height-1,4:width-1)+II(1:height-4,1:width-4)-II(1:height-4,4:width-1)-II(4:height-1,1:width-4);
black = img(3:height-2,3:width-2);
haar = (total - 2*black);
[hgx,hgy] = gradient(haar);
hg = sqrt(hgx.^2+hgy.^2);
end