2012年8月19日 星期日

EmguCV Image Process: Manipulating the pixels part2

Taipei 101 with grass HDR1

EmguCV 2.3 Application Programming : Manipulating the pixels

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

承接前一章內容

來做一些EmguCV上的簡單示範,主題如下:

Scanning an image with neighbor access

Performing simple image arithmetic

Defining regions of interest


Scanning an image with neighbor access

在影像處理中

通常會透過一些處理函式來對每一個pixel做更改

而這些函式往往都會參考這個pixel周邊鄰近的pixel

這些周邊鄰近的pixel可能包含了水平方向的前、後,或是垂直方向的上、下

舉一個簡單的例子:

銳利化

我們可以透過下列的算式達到銳利化的效果:

sharpened_pixel=5*current_pixel - left_pixel - right_pixel - up_pixel - down_pixel;

我們先以Gray色階來作範例

我們必須將原圖複製一份同等大小的空白影像(copy a blank image)

然後透過上述的運算取得值,在塞入那張空白影像

如下:
private Image<Gray, Byte> Sharpen(Image<Gray, Byte> image)
{
    Image<Gray, Byte> result=image.CopyBlank(); //copy a blank image

    MIplImage MIpImg = (MIplImage)System.Runtime.InteropServices.Marshal.PtrToStructure(image.Ptr, typeof(MIplImage));
    MIplImage MIpImgResult = (MIplImage)System.Runtime.InteropServices.Marshal.PtrToStructure(result.Ptr, typeof(MIplImage));

    int imageHeight = MIpImg.height;
    int imageWidth = MIpImg.widthStep;
    unsafe
    {
        for (int height = 1; height < imageHeight - 1; height++)
        {
            //current_pixel line
            byte* currentPixel = (byte*)MIpImg.imageData+imageWidth*height; 
            //up_pixel line
            byte* uplinePixel = currentPixel - MIpImg.widthStep;            
            //down_pixel line
            byte* downlinePixel = currentPixel + MIpImg.widthStep;            
            //result current_pixel line
            byte* resultPixel = (byte*)MIpImgResult.imageData+imageWidth*height; 

            for (int width = 1; width < imageWidth - 1; width++)
            {
                //5*current_pixel-left_pixel-right_pixel-up_pixel-down_pixel
                int sharpValue = 5 * currentPixel[width] - currentPixel[width - 1]
                                    - currentPixel[width + 1] - uplinePixel[width]
                                    - downlinePixel[width];

                if (sharpValue < 0) sharpValue = 0;     //Gray level 0~255
                if (sharpValue > 255) sharpValue = 255; //Gray level 0~255

                resultPixel[width] = (byte)sharpValue;
            }
        }
    }

    return result;
}

在第一層的for loop中,利用三個指標currentPixel、uplinePixel、downlinePixel

來分別表示

current_pixel的column、up_pixel的column、down_pixel的column

還要記得把運算的結果作判斷

判斷是否超過Gray level 為byte的範圍 0~255

測試的灰階影像處理時間約:6ms

結果如下:
銳利化過後

要處理類似的方法時(需要去計算周邊鄰近的值做參考)

可以利用EmguCV提供的一個類別ConvolutionKernelF

可以看作是一個kernal matrix

然後影像可以直接對此matrix做運算

剛剛的銳利化的算式可以看成:

3X3 sharpen kernal matrix

原理就是以矩陣中對應的pixel位置去做運算

以pixel的值乘以矩陣中的值,之後再將這九個(3X3)值相加

此矩陣就是current pixel乘以5,再減掉上、下、左、右的值

這時候程式就相對簡單很多

你只要專注在matrix的內容、他如何運算的並不需要知道

float[,] matrixKernel = new float[3, 3] { 
                { 0,-1, 0 }, 
                {-1, 5,-1 }, 
                { 0,-1, 0 }
};
ConvolutionKernelF matrix = new ConvolutionKernelF(matrixKernel);
Image<Bgr, float> result = image.Convolution(matrix);
Image<Bgr, Byte> BGRResult = result2.Convert<Bgr, Byte>();

由於在Convolution是屬於浮點運算

所以在result的Image是需要宣告成浮點深度(32bits)

若是直接將result存檔,或是利用Convert轉成8bits深度的影像

會變成這樣:
灰濛濛的一張圖

需要這樣的方式轉換,才會正常
Image<Bgr, Byte> BGRResult = result2.ConvertScale<byte>(1, 0);

也可以直接用EmguCV提供的OpenCV Invoke的方法:Filter2D
Image<Bgr, Byte> result = new Image<Bgr, Byte>(image.Width, image.Height);
CvInvoke.cvFilter2D(image, result, matrix, new Point(0, 0));

result可以直接宣告成Image<Bgr,Byte>然後放到cvFilter2D的第二個引數

內部就會自動幫你轉換

出來的結果就會是正常的樣子!


銳利化後的結果!


Performing simple image arithmetic

EmguCV承襲了OpenCV上的功能

對於影像之間的運算

可以透過簡單的函式來處理

不必一個pixel一個pixel來計算

假設我們有image跟image2分別是兩張同等大小的影像
(一定要同等大小!!)

基本的運算有下列幾種:
//image3[i]=image[i]+image2[i]
image3 = image.Add(image2);

//image3[i]=image[i]+k
image3 = image.Add(new Gray(k));

//image3[i]=alpha*image[i]+beta*image2[i]+gamma
image3 = image.AddWeighted(image2, alpha, beta, gamma);

//if(mask[i]) image3[i]=image[i]+image2[i]
image3 = image.Add(image2, mask);
(在mask的部分一定得是Gray level的影像)

在AddWeighted裡的alpha、beta、gamma分別是double的型別

假設我們想將兩張圖疊加在一起

就能夠很簡單的用Add的方法來做

例如我們想將下面這張圖疊加到101的圖片上:
image2 看不出來是什麼東西

經過Add函式過後,就會變成下面這樣:
就能夠輕易地把兩張圖片疊加在一起了!!

而更方便的是

EmguCV也有實作所謂的:Overloaded image operators

所以可以輕鬆地利用運算子來作運算:
image3 = image + image2;
image3 = 0.7 * image + 0.3 * image2;
image3 = image & mask;   //image mask
image3 = ~image;         //image inverse

EmguCV所複寫的運算子共有下列這些:

+ - * / & | ~
(加、減、乘、除、AND運算、OR運算、反向)

但這些運算的前提是

兩張的影像一定要一樣大喔!


也可以透過簡單的方法將影像的色域切割出來

並分別做運算處理

像是常用的RGB色域的影像

就能夠切割出各色域(色版)R、G、B的部分處理

例如我們想將剛剛的image2疊加到101的藍色色域上:
Image<Bgr, Byte> image = new Image<Bgr, Byte>(@"E:\101.jpg");
Image<Bgr, Byte> image2 = new Image<Bgr, Byte>(@"E:\image2.jpg");
Image[] imageChannels= image.Split(); 

//imageChannels[0] Blue,imageChannels[1] Green,imageChannels[2] Red
imageChannels[0] = imageChannels[0] + image2;
Image<Bgr, Byte> image3 = new Image<Bgr, Byte>(imageChannels);

利用Split()函式可以切割出三張影像

回傳的是一個影像的陣列、裡面包含Blue、Green、Red三色色域的影像

便可以分別對個別色域的影像作處理

最後再將這個影像陣列組成一張影像

結果如下:
由於是加在藍色色域上,所以原本白色的光芒看起來就變成藍色的了


Defining regions of interest

我們也可以定義影像中的限定的區域 ROI 來做處理

也就是局部的影像處理

但這裡的區域僅限矩形

透過設定這樣的區域,可以對此區域做處理,而不影像到其他區域

這樣就能夠加入不一樣大的影像進來做運算

例如我們想將下面這個logo影像加入就可以用此方法:

logo

Image<Bgr, Byte> image = new Image<Bgr, Byte>(@"E:\101.jpg");
Image<Bgr, Byte> image2 = new Image<Bgr, Byte>(@"E:\logo.jpg");          
image.ROI = new Rectangle(450, 300, image2.Width, image2.Height);
image2.CopyTo(image);
image.ROI = new Rectangle();

透過設定所要貼上logo的位置和區域

區域的大小一定要跟要貼上的影像一樣大

使用CopyTo()這個方法把影像貼上去

再把image的ROI恢復成初始即可
(否則影像就會變的跟logo一樣小)

但是這樣直接貼上的效果不好

如下圖所示:
這樣的logo實在有點搞笑...

於是在多加一行程式碼:
Image<Bgr, Byte> image = new Image<Bgr, Byte>(@"E:\101.jpg");
Image<Bgr, Byte> image2 = new Image<Bgr, Byte>(@"E:\logo.jpg");          
image.ROI = new Rectangle(450, 300, image2.Width, image2.Height);
image2 = image2 + image;
image2.CopyTo(image);
image.ROI = new Rectangle();

先讓logo的影像跟原圖的區塊相加

然後再貼到原圖上面去

這樣的效果就會好很多,如下所示:
這樣的簽名檔logo看起來是不是順眼多了!

當然,你也可以利用剛剛所學的運算方式

把image2 = image2 + image;

作一些調整,像是改變各個影像的權重等等

就能夠有所變化

可以自己試試看嚕~!!


沒有留言:

張貼留言