Artykuły

A A A
Drukuj Ekportuj do PDF
Opublikowane: 2005.01.18 16:32 | User 83812 | Aktualizacja: 2010.01.21 2:04

Fuzzy Image Processing in C#, czyli algorytmy przetwarzania obrazów oparte na zbiorach rozmytych.

Artykuł omawia implemenatcję jednego z najpopularniejszych algorytmów przetwarzania obrazów opartych na zbiorach rozmytych "Fuzzy Histogram Hiperbolization"

 

Fuzzy Image Processing in C#, czyli algorytmy przetwarzania obrazów oparte na zbiorach rozmytych.

 

 

Fuzzy Sets – Zbiory rozmyte

 

         Zwykły zbiór można zdefiniować podając, jakie elementy do niego należą. Zbiry rozmyte rozszerzają nam sposób defilowanie przynależności elementów, gdyż dają nam możliwość określenia stopnia przynależności danego elementu do naszego zbioru.

 

Idea zbiorów rozmytych jest prosta i naturalna. Np., gdy chcemy zdefiniować zbiór poziomów jasności, taki ze należą do niego piksele ciemne.

W klasyczny zbiorze musimy zdefiniować próg, np. 100 i wtedy wszystkie piksele pomiędzy 0 a 100 należą do naszego zbioru, a pozostałe nie, w przypadku, gdy rozpatrujemy obraz w skali szarości o 256 kolorach to wartości od 101 do 255 inne należą do naszego zbioru (rys.1). Przy takim podejściu „ciemność” piksela nie jest ważna. Ale w teorii zbiorów rozmytych możemy taki zbiór zdefiniować w sposób o wiele lepszy i intuicyjnej. Do zdefiniowania zboru również potrzebujemy dwóch progów, powiedzmy 50 i 150. I tak wszystkie piksele o poziomie jasności mniejszej niż 50 należą całkowicie do naszego zbioru, wszystkie większe niż 150 nie należą, z kolei piksele pomiędzy należą w pewnej części do naszego zbioru (rys.2.).

            Przedstawiają to poniższe rysunki.

 

 

Typowe funkcje przynależności:

a) liniowa prawostronnie otwarta

 

b) liniowa lewostronnie otwarta

 

c) trójkątna  

 

d) trapezowa

   

e) S

 

f) Sigma

 

h) Gaussa

 

Zbiór rozmyty a obraz

 

            Gdy już wiemy, co to jest zbiór rozmyty możemy zająć się reprezentacją obrazu za pomocą takiego zbiory. Zbiór rozmyty w przestrzeni punktów X = {x} jest klasą obiektów ze stopniem przynależności z przedziału [0;1] i jest zdefiniowany jako para uporządkowana:

.

            Przenosząc ten interpretacje do obrazu o rozmiarach M x N, zbiór rozmyty opisujący obraz możemy zapisać:

 

I - obraz

M, N – wymiary obrazu

 - jasność piksela

 - wartość funkcji przynależności dla danego piksela.

            Funkcja przynależności charakteryzuje odpowiednia własność obrazu (krawędzie, jasność, tekstury) i może być dobrana dla całego obrazu lub lokalnie dla jego segmentów. W ostatnich latach wielu naukowców odkryło pojęcie rozmytości w celu wymyślenia algorytmów poprawiających kontrast, czy wykrywających krawędzie w oparciu o zbiory rozmyte.

Algorytmy przetwarzania obrazów za pomocą zbiorów rozmytych składają się z 3 podstawowych kroków:

- fuzzyfikacji, czyli sprowadzenia obrazu do zbioru rozmytego, za pomocą odpowiedniej funkcji przynależności

- modyfikacji, wartosci funkcji przynależności za pomocą odpowiednio dobranej funkcji.

-defuzyfikacji, czyli powrotu ze zbioru rozmytego do obrazu, za pomocą funkcji odwrotnej do funkcji fuzzyfikujacej.

Przedstawia to poniższy rysunek.

 

            Dość teorii przystąpmy do implementacji. Do swojego artykułu wybrałem algorytm Fuzzy Histogram Hiperbolization, na przykładzie, którego omówię etapy rozmytego przetwarzania obrazów. Na początku przyda nam się interfejs umożliwiający nam stosowanie różnych funkcji przynależności.

 

public interface IMembershipFunction

{

      double Linear(int BValue);

      double OpenRightShoulders(int BValue);

      double OpenLeftShoulders(int BValue);

      double Triangular(int BValue);

      double Trapetsoidal(int BValue);

      double S(int BValue);

      double Sigma(int BValue);

      double Gaussian(int BValue);

      double ContrastImprovement(int BValue, double Fe);

}

 

     Następnym krokiem będzie stworzenie klasy implementującej ten interfejs, która będzie odpowiedzialna za proces fuzzyfikacji fuzzyfikacji modyfikacji.

 

public class MembershipFunction : IMembershipFunction

{

      int             i, x, y, j;                   

      int             min, med, med1 ,med2, max;

      double          FuzzyValue;

 

      public MembershipFunction()

      {

            this.i     = 0;

            this.x     = 0;

            this.y     = 0;

            this.j     = 0;                               

            this.min   = 0;

            this.med   = 127;

            this.med1  = 100;

            this.med2  = 200;

            this.max   = 255;

            this.FuzzyValue = 0;

      }

      public double Linear(int BValue)

      {                                                   

            if (BValue < 100)

                  FuzzyValue = 0;

            else if ( (BValue >= 100) && (BValue <= 200) )

                  FuzzyValue =  0.01 * BValue - 1;

            else if ( (BValue >= 200) && (BValue <= 255) )

                  FuzzyValue = 1;                   

            return FuzzyValue;

      }

      public double OpenRightShoulders(int BValue)

      {                

            if (BValue < min)

                  FuzzyValue = 0;

            else if ( (BValue >= min) && (BValue <= max) )

                FuzzyValue =  (BValue - min) / (double)(max-min);

            else if ( (BValue >= max) && (BValue <= 255) )

                  FuzzyValue = 1;

            return FuzzyValue;

      }

      public double OpenLeftShoulders(int BValue)

      {                

            if (BValue < min)

                  FuzzyValue = 1;

            else if ( (BValue >= min) && (BValue <= max) )

                  FuzzyValue =  (max - BValue) / (double)(max-min);

            else if ( (BValue >= max) && (BValue <= 255) )

                  FuzzyValue = 0;

            return FuzzyValue;

      }

     

 

 

 

 

 

 

 

public double Triangular(int BValue)

      {                

            if (BValue < min)

                  FuzzyValue = 0;             

            else if ( (BValue >= min) && (BValue < med1) )

                  FuzzyValue =  (BValue - min) / (double)(med1 - min);

            else if ( (BValue >= med1) && (BValue < med2) )

                  FuzzyValue =  (med2 - BValue) / (double)(med2 - med1             else if ( (BValue >= med2))

                  FuzzyValue = 0;             

            return FuzzyValue;

      }

      public double Trapetsoidal(int BValue)

      {                

            if (BValue < min)

                  FuzzyValue = 0;

            else if ( (BValue >= min) && (BValue < med1) )

                  FuzzyValue =  (BValue - min)/(double)(med1 - min);

            else if ( (BValue >= med1) && (BValue < med2) )

                  FuzzyValue =  1;

            else if ( (BValue >= med2) && (BValue < max) )

                  FuzzyValue =  (max - BValue)/(double)(max - med2);

            else if ( (BValue >= max))

                  FuzzyValue = 0;

            return FuzzyValue;

      }

      public double S(int BValue)

      {                

            if (BValue < min)

            {

                  this.FuzzyValue = 0;

            }

            else if ( (BValue >= min) && (BValue < med) )

            {

this.FuzzyValue =  2 * Math.Pow( ((BValue - min)/(double)(med - min)),2.0);

            }

            else if ( (BValue >= med) && (BValue < max) )

            {

this.FuzzyValue =  1 - 2 * Math.Pow( ((BValue - max)/(double)(max - min)),2.0);           

            }

            else if ( (BValue >= max))

            {

                  this.FuzzyValue = 1;          

            }

            return this.FuzzyValue;

      }

      public double Sigma(int BValue, double t)

      {

            FuzzyValue = 1 + exp( -t * (BValue - med) );              

            FuzzyValue = 1 / FuzzyValue;            

            return this.FuzzyValue;

      }

      public double Gaussian(int BValue)

      {                

            FuzzyValue = exp( -t * pow((BValue - med),2.0) );                      return this.FuzzyValue;

      }

     

 

public double ContrastImprovement(int BValue, double Fe)

      {

            double Fd;

            double wyk = (Math.Pow(0.5,(-1/Fe))-1);

            Fd = ((max - med) / wyk );              

            double licznik = (max-BValue)/Fd;             

            this.FuzzyValue = Math.Pow((1+licznik),-Fe);

            return this.FuzzyValue;

      }

public double ModyficationMemberhipFunctionByINT(double cFuzzyValue)

      {

            if ( (cFuzzyValue >= 0 ) && (cFuzzyValue <= 0.5) )

            {

                  double wyn = 2 * ( Math.Pow( cFuzzyValue, 2 ) );

                  cFuzzyValue = wyn;

            }

            else if ( (cFuzzyValue >= 0.5 ) && (cFuzzyValue <= 1) )

            {

double wyn  = 1 - (2*(Math.Pow((1-cFuzzyValue),2)));

cFuzzyValue = wyn;

            }

            return cFuzzyValue;

      }    

            Gdy już mamy klasę, która oblicza wartość funkcji przynależności dla różnych wartości pikseli oraz modyfikuje funkcje przynależności, możemy przejść dalej. Ale zanim przystąpimy, do szczegołów algorytmy FHH warto omówić dalszy tok postępowania.

            Jak już wspomniałem na początku proces przetwarzania obrazów składa się z trzech części:

            W pierwszej za pomocą funkcji przynależności sprowadzanym obraz do zbioru rozmytego, gdzie argumentem jest wartość danego piksela, a wartością funkcji stopień przynależności do zbioru. Łatwo wiec zauważyć ten i następny etap jest niezwykle ważny, gdyż od wyboru funkcji fuzzyfikujacej i modyfikującej zależy jakie efekty uzyskamy.

            Dodatkowo, można wartości min, med., med1, med2 i max uczynić parametrami i pobierać je od użytkownika wtedy będziemy mogli uzyskać rozmaite efekty.

 

            W algorytmie FHH funkcja fuzzyfikująca jest z reguły: Liniowa i lewostronnie otwarta funkcja przynależności, ale dla zabawy możemy spróbować, jakie rezultaty uzyskamy stosując inne funkcje przynależności.

            Funkcja modyfikująca dla algorytmu FHH jest postaci:

, którą zdefiniował Tizhoosh (twórca tego algorytmu).

            Teraz możemy przystąpić do implementacji algorytmu FHH, w tym celu tworzymy sobie klasę, która będzie zawierała wszystkie potrzebne zmienne i korzystała z klasy poprzednie zdefiniowanej podczas etapu fuzzyfikacji:

            Przy implementacji tego algorytmu, do działania na obrazie wykorzystałem klasę Bitmap z przestrzeni nazw System.Drawing dziedziczącą po klasie Image. Posiada ona metody pozwalające na odczyt i zapis wartości piksela, zwracające obiekt klasy Color, gdzie mamy dostęp do pojedynczych składowych koloru RGB lub HSB.

 

            Algorytm stał się łatwiejszy do implementacji dzięki metodom statycznym klasy Color:

Color.FromArgb(int r, int g, int b)

Color.FromArgb(int a, int r, int g, int b)

Color.FromArgb(int a, Color color)

Color.FromArgb(int argbPacked)

            Z tej pierwszej korzystałem przy implementacji tego algorytmu.

 

public class FuzzyHistogramHiperbolization

{

      int             x, y, sum;

      double          modificationBeta;

      double          m;

      double          wsp;         

 

      Zmienna modificationBeta przyjmuje wartości z zakresu [0;2], co powoduje ze wartości funkcji przynależności są modyfikowane pierwiastkiem 2 stopnia lub f. Kwadratową. Przedstawiają to poniższe rysunki.

 

rys a) liniowa funkcja przynależności

rys b) modificationBeta = 0.5

rys c) modificationBeta = 2

 

     

public FuzzyHistogramHiperbolization()

{

      this.wsp   = 0.5;           

      this.x     = 0;

      this.y     = 0;             

      this.sum   = 0;             

      this.m     = 0;

      this.modificationBeta = 0.5 + this.wsp * 0.01;

}

 

W tym przypadku mamy konstruktor bezparametrowy przypisujący wartości domyślne parametrom algorytmu, dopisując konstruktor, który przyjmuje jako parametry zmienne: wsp oraz min, med, med1, med2, max możemy uczynić ten algorytmy bardziej interakcyjnym.

 

 

      W związku z tym ze wartości kolorów pikseli są wartościami całkowitymi z zakresu 0-255 przyda nam się funkcja, która sprawdza, czy wyliczona przez nas wartość piksela ma dopuszczalna wartość

 

protected int round(double Pixel)

{

      double iPixel = Math.Floor(Pixel + 0.5);

      if (iPixel < 0) iPixel = 0;

      if (iPixel > 255) iPixel = 255;

      return (int)iPixel;

}

 

      Teraz pora na to, co nas najbardziej interesuje a mianowicie kod algorytmu.

 

public Bitmap Algorithm(Bitmap bitmap)

{

MembershipFunction mFun = new MembershipFunction();

      Bitmap newBitmap = new Bitmap(bitmap.Width,bitmap.Height );            Color newPixelColor = new Color();

      double FuzzyValueR, FuzzyValueG, FuzzyValueB ,powerR, powerG, powerB;

      int newPixelValueA, newPixelValueR, newPixelValueG, newPixelValueB;

                 

      for (y = 0; y < bitmap.Height; y++)

            for (int x = 0; x < bitmap.Width; x++)

            {

                  //krok 1 fuzzyfifikacja

                  modificationBeta = 0.5;                        FuzzyValueR = mFun.OpenRightShoulders(bitmap.GetPixel(x,y).R);    

      FuzzyValueG = mFun.OpenRightShoulders(bitmap.GetPixel(x,y).G);         FuzzyValueB = mFun.OpenRightShoulders(bitmap.GetPixel(x,y).B);                       //krok 2 modyfikacja

                  powerR = Math.Pow( FuzzyValueR, modificationBeta );

                  powerG = Math.Pow( FuzzyValueG, modificationBeta );

                  powerB = Math.Pow( FuzzyValueB, modificationBeta );

                 

//krok 3 defuzzyfikacja

m = 255.0 / (0.367879 - 1.0);

                  newPixelValueR = round(m*(Math.Exp((-1*powerR))-1));

                  newPixelValueG = round(m*(Math.Exp((-1*powerG))-1));                   newPixelValueB = round(m*(Math.Exp((-1*powerB))-1));                  

                  newPixelValueA = bitmap.GetPixel(x, y).A;                                   

newPixelColor = Color.FromArgb(newPixelValueR, newPixelValueG, newPixelValueG);

                  newBitmap.SetPixel(x,y,newPixelColor);                                                                                                  }

            return newBitmap;

 

}

}

            Zmienne powerR, powerG, powerB służą do przechowywania zmodyfikowanej wartości funkcji przynależności dla danego piksela. Z kolei newPixelValueR, newPixelValueG, newPixelValueB do nowo wyliczonej wartości koloru piksela po procesie defuzyfikacji.

 

 

 

LITERATURA:

 

[1] MICROSOFT .NET SDK Tool Developer’s Documentation (Common Language Infrastructure)

[2] MSDN

[3] Home Page of Professor Carl G. Looney http://ultima.cs.unr.edu:80/index.php

[4] Homepage of Fuzzy Image Processing http://watfor.uwaterloo.ca/tizhoosh/

[5] Homepage of Hamid R. Tizhoosh http://pami.uwaterloo.ca/tizhoosh/

[6] J. Kautsky, N. K. Nichols, and D. L. B. Jupp. Smoothed histogram modification for image processing. CVGIP,

26(3):271–291, Jun. 1984.

[7] Andrew Laine, Jian Fan and Wuhai Yang, “Wavelets for Contrast Enhancement

of Digital Mammography”, Accepted for the Specical Issue of the EMBS Magazine

[8] G. Krell, H. R. Tizhoosh, T. Lilienblum, C. J. Moore, B. Michaelis

“Fuzzy Image Enhancement and Associative Feature Matching in Radiotherapy”

Presented at the International Conference on Neural Networks (ICNN ’97), Houston, Texas, June 9-12, 1997

 

 

             

     

 

Załączniki:


Podobne artykuły

Komentarze 0 Masz uwagi do tej strony? Napisz

Dodaj komentarz

avatar

Zaloguj się lub Zarejestruj się aby wykonać tę czynność.

Autor User 83812
avatar
 

"He who laughs last probably uses simplest architecture of developing .NET application"

Załóż konto
CodeGuru to miejsce dla każdego programisty. Przez lata portal rozwijany był siłami społeczności i to właśnie społeczność programistów jest tutaj najważniejsza. CG od wielu lat gromadzi wokół siebie coraz większą grupę pasjonatów. Warto być jej częścią!

Dowiedz się więcej o CodeGuru

Geek Club - Windows Phone

 

MetroOne

Idź na górę strony