2013年1月24日 星期四

EmguCV Image Process: Filtering the Images part 5

中正大學夕陽
EmguCV Image Process: Filtering the Images

參考OpenCV 2 Computer Vision Application Programming Cookbook第六章

介紹內容如下:

Filtering images using low-pass filters

Eiltering images using a median filter

Applying directional filters to detect edges

Computing the Laplacian of an image

上一篇介紹了sobel的基本原理

而Laplacian是另一種high-pass linear filter

透過計算二階導數來測量影像的curvature

在EmguCV中要使用Laplacian的function非常簡單

就像Sobel function一樣

image.Laplace(this._Aperture);
引數中傳入Laplacian的kernel大小

就會回傳Laplacian的處理結果

這裡介紹如何把這個方法包裝起來

建立一個簡單的類別

封裝一些關於Laplacian並且有用的操作

class LaplacianZC
{
    // Original image
    private Image<Gray, Byte> _Image;

    //32-bit float image containing the Laplacian
    private Image<Gray, float> _Laplace;

    //Aperture size of the laplacian kernel
    private int _Aperture;

    /// <summary>
    /// Initial the aperture size 
    /// </summary>
    public LaplacianZC()
    {
        this._Aperture = 3;
    }

    /// <summary>
    /// Set the aperture size of the kernel
    /// </summary>
    /// <param name="size">the aperture size</param>
    public void SetAperture(int size)
    {
        this._Aperture = size;
    }

    /// <summary>
    /// Compute the floating point Laplacian
    /// </summary>
    /// <param name="image">the original image</param>
    /// <returns>the image containing the Laplacian</returns>
    public Image<Gray,float> ComputeLaplacain(Image<Gray,Byte> image)
    {
        //Keep local copy of the image
        this._Image = image.Clone();
        //Compute Laplacian
        this._Laplace = image.Laplace(this._Aperture);
        return this._Laplace;
    }
}
這個簡單的類別可以設定Laplacian的aperture size

使用的方法也很簡單

建立此類別後

設定kernel size

再把原始影像帶入ComputeLaplacain的方法即可

Image<Gray, Byte> image = new Image<Gray, Byte>("image.jpg");
//Compute Laplacian using LaplacianZC class
LaplacianZC laplacianZC = new LaplacianZC();
laplacianZC.SetAperture(5);
Image<Gray, float> laplacian = laplacianZC.ComputeLaplacain(image);
簡單操作

結果如下:
直接把結果存成圖片的結果!


那Laplacian會遇到跟之前討論Sobel時一樣的問題

結果的影像的值有正有負

所以還要在LalpacianZC的類別中加入一個方法

就跟之前處理Sobel一樣,要把結果影像從32-bit轉成8-bit的影像

/// <summary>
/// Get the Laplacian result in 8-bit image
/// The max value will be scaled to intensity 255
/// You must call ComputeLaplacian before calling this
/// </summary>
/// <returns>the Laplacian result in 8-bit image</returns>
public Image<Gray, Byte> GetLaplacianImage()
{
    double scale = 0;
    //Find sobel min or max value
    double[] mins, maxs;
    //Find sobel min or max value position
    Point[] minLoc, maxLoc;

    this._Laplace.MinMax(out mins, out maxs, out minLoc, out maxLoc);
    //Conversion to 8-bit image
    scale = Math.Max(Math.Abs(mins[0]), maxs[0]);

    Image<Gray, Byte> lapImage = this._Laplace.ConvertScale<byte>(255 / scale, 0);

    return lapImage;
}
透過這個方法就可以取得8-bit的結果影像

所以要在

Image<Gray, float> laplacian = laplacianZC.ComputeLaplacain(image);

之後再執行

Image<Gray, Byte> laplacian8bit = laplacianZC.GetLaplacianImage();

laplacian8bit的結果如下:

8-bit的結果影像


Laplacian的2D function定義為二階導數的加總


基本的3X3 kernel如下:


相較於Sobel operator

Laplacian operator比較容易受到影像中的雜訊的影響

注意到Laplacian的kernel的值相加的總合為:0

因此在intensity(亮度)部分比較平滑的部位

計算出來的值會接近0

使用Laplacian operator主要也是用來做邊緣偵測

當計算到邊緣的時候
(假設是從暗到亮)

計算出來的curvature變化會從"正"逐漸過渡到"負"

這個轉換就是在正值與負值之間,也就是邊界的位置

由於以上的特性,要找出邊界可以利用一種方法:Zero-crossing

簡單的示意圖如下:

(取自維基百科)

找出Laplacian結果影像中所有Zero-crossing的點

這些點就會是邊界的位置

由於影像是2D,所以要找出水平方向、垂直方向的Zero-crossing的位置

透過與前一個pixel的value做相乘,若是負值,則表示為Zero-crossing
(正負相乘,或負正相乘)

則把binary影像的這個位置的value設為255

再透過一個threshold來做過濾,降低敏感度

方法一樣寫在LaplacianZC的類別之中

/// <summary>
/// Get a binary image of two zero-crossings
/// if the product of the two adjascent pixels is
/// less than threshold then this zero-crossing
/// will be ignored
/// </summary>
/// <param name="threshold">the threshold</param>
/// <returns>the binary image of two zero-crossings</returns>
public Image<Gray, Byte> GetZeroCrossings(int threshold)
{
    Image<Gray, Byte> binary = new Image<Gray, byte>(this._Laplace.Width, this._Laplace.Height);
    threshold *= -1;
    for (int h = 1; h < this._Laplace.Height; h++)
    {
        for (int w = 1; w < this._Laplace.Width; w++)
        {
            //if the product of two adjascent pixel is negative 
            //then there is a sign change
            if ((this._Laplace[h, w].Intensity * this._Laplace[h - 1, w].Intensity) < threshold)
            {
                binary[h, w] = new Gray(255);//horizontal zero=crossing
            }

            if ((this._Laplace[h, w].Intensity * this._Laplace[h, w - 1].Intensity) < threshold)
            {
                binary[h, w] = new Gray(255);//vertical zero-crossing
            }
        }
    }
    return binary;
}
操作的方法就是帶入threshold

這裡要注意的是

由於是透過兩個pixel的值相乘來做計算

所以threshold的值要設定的高一點才會有作用

Image<Gray, Byte> binary = laplacianZC.GetZeroCrossings(100000);

帶入100000的結果:

Zero-crossing的結果

可以觀察到

透過Zero-crossing幾乎可以偵測出所有的邊界

但卻不容易區隔比較強的邊界,或是比較弱的邊界

而Laplacian對於雜訊是非常敏感的

基於以上兩點

就知道為什麼透過這個operator會偵測出這麼多的邊緣了!!

沒有留言:

張貼留言