Implementasi Gaussian Blur

Sebagai pengguna perangkat lunak manipulasi citra/foto teknik gaussian blur merupakan salah satu yang sering digunakan. Belum lama, ada yang menanyakan tentang dekomposisi wavelet (LL,LH,HL,HH) dan saya pikir perlu ada tulisan ini sebagai pengantar tulisan selanjutnya.

Gaussian blur sebetulnya merupakan konvolusi citra dengan fungsi gaussian. Mengapa perlu nama khusus? hal ini karena gaussian memiliki beberapa sifat yang cukup unik. Pertama karena hasil transformasi fourier fungsi gaussian ternyata menghasilkan gaussian juga. Kedua yang menarik dari fungsi gaussian adalah untuk implementasi fungsi gaussian 2D. Fungsi gaussian 2D secara matematis memiliki sifat dapat dipisahkan (separable, silakan cari sendiri bagi yang minat utak-atik simbol matematis :D). Sifat inilah yang sangat membantu dalam mengimplementasi proses pengaburan (blurring).

Sebagaimana telah ditulis sebelumnya, dalam operasi perata-rataan tetangga (konvolusi) digunakan jendela atau area yang menyatakan hubungan ketetanggaan yang dilibatkan dalam operasi. Semakin besar ukuran jendela tersebut maka gambar akan semakin kabur dan juga waktu yang diperlukan untuk melakukan operasi tersebut. Dengan adanya operasi yang bersifat dapat dipisahkan, proses konvolusi bisa dilakukan dua kali namun hanya menggunakan jendela berdimensi satu. proses konvolusi pertama kali dilakukan untuk tiap baris dan dilanjutkan dengan konvolusi untuk tiap kolom sebagai data berdimensi satu juga. Akibat partisi ini operasi konvolusi bisa mengeksploitasi paralelisme baik menggunakan thread ataupun prosesor grafik.

Kode dibawah sudah didekomposisi konvolusi untuk elemen baris dan kolom sebagai upafungsi tersendiri sehingga dapat memudahkan untuk memproses sebagian saja (seperti dekomposisi LL,LH,HL,HH di atas).

function gaussian( b: TBitmap; sigma: real ): TBitmap;
var
  mid, ksize        : integer;
  ker               : array of real;
  p                 : array of PArrRGB;
  b2                : TBitmap;

  procedure gauss( sigma: real );
  var
    i               : integer;
    sum, w          : real;
    norm            : real;
    ediv            : real;
  begin
    norm := 1 / ( sigma * sqrt( 2 * pi ) );
    ediv := 0.5 / ( sigma * sigma );
    ksize := 1 + 2 * round( 3 * sigma );
    if ksize < 3 then ksize := 3;
    if ( ( ksize and 1 ) <> 1 ) then ksize := ksize + 1;
    setlength( ker, ksize );
    mid := ksize div 2;
    sum := 0;
    for i := 0 to ksize - 1 do begin
      w := norm * exp( -( i - mid ) * ( i - mid ) * ediv );
      ker[i] := w;
      sum := sum + w;
    end;
    for i := 0 to high( ker ) do
      ker[i] := ker[i] / sum;
  end;

  function Hconv( b: TBitmap ): TBitmap;
  var
    j, i, k         : integer;
    corr            : real;
    sum             : real;
    pp              : ParrRGB;
  begin
    result := citra_clone( b );
    for j := 0 to high( p ) do
      p[j] := b.ScanLine[j];
    for j := 0 to b.Height - 1 do begin
      pp := result.ScanLine[j];
      for i := 0 to b.Width - 1 do begin
        sum := 0;
        corr := 0;
        for k := -mid to mid do begin
          if ( i + k < 0 ) or ( i + k >= b.Width ) then begin
            corr := corr + ker[mid + k];
            continue;
          end;
          sum := sum + ker[mid + k] * p[j][i + k].r;
        end;

        if corr > 0 then sum := sum * ( 1 / ( 1 - corr ) ); //        	memo1.lines.add(format('corr %.8f', [corr]));
        pp[i] := warna_create( clamp( round( sum ) ), clamp( round( sum ) ), clamp( round( sum ) ) );
      end;
    end;
  end;

  function Vconv( b: TBitmap ): TBitmap;
  var
    j, i, k         : integer;
    corr            : real;
    sum             : real;
    pp              : ParrRGB;
  begin
    result := citra_clone( b );
    for j := 0 to high( p ) do
      p[j] := b.ScanLine[j];
    for j := 0 to b.Width - 1 do begin
      for i := 0 to b.Height - 1 do begin
        pp := result.ScanLine[i];
        sum := 0;
        corr := 0;
        for k := -mid to mid do begin
          if ( i + k < 0 ) or ( i + k >= b.Height ) then begin
            corr := corr + ker[mid + k];
            continue;
          end;
          sum := sum + ker[mid + k] * p[i + k][j].r;
        end;
        if corr > 0 then sum := sum * ( 1 / ( 1 - corr ) );
        pp[j] := warna_create( clamp( round( sum ) ), clamp( round( sum ) ), clamp( round( sum ) ) );
      end;
    end;
  end;
begin
  setlength( p, b.Height );
  gauss( sigma );
  b2 := hconv( b );
  result := vconv( b2 );
  b2.Free;
end;

coming up next.. edge sharpening/edge-preserving smoothing berbasis dekomposisi LH (lowpass-highpass filtering) dan canny edge detection 😉

9 comments

  1. limaapril · Desember 27, 2009

    kk pebbie, bisa minta dijelaskan kenapa kalo operasinya dipisah (horizontal dan vertikal) jadi lebih cepet dari konvolusi biasa ?

    Lalu, pada implementasinya untuk wavelet, berarti dengan modal gaussian blur, kita dapat melakukan dekomposisi, minimal satu level ya ? (jadi LL,LH,HL, dan HH ) , soalnya lagi cari referensi untuk implementasinya kok ngeliat rumus2 matematikanya rasanya jauh dari jangkauan 😦

  2. pebbie · Desember 27, 2009

    begini, misalkan konvolusi biasa (nggak dipisah) suatu kernel gaussian jadinya berukuran NxN. tiap piksel kita harus melakukan operasi perkalian sejumlah N^2 sedangkan kalau dipisah tiap piksel kita cuma perlu melakukan operasi perkalian sejumlah 2N (karena kernelnya jadi 1 dimensi N untuk baris dan N untuk kolom).

    untuk dekomposisi, kayaknya iya minimal satu level. sama kok, masih jauh dari jangkauan saya juga. 😀

  3. andi · Maret 3, 2010

    ka, ini pake bahasa apa ya?

    • pebbie · Maret 3, 2010

      Object Pascal (delphi) mas

  4. erre · April 15, 2010

    ka, kalo gaussian nya memproses bagian pinggir2 kiri kanan atas n bawah itu kan nanti indeks p[ ] nya kan jadi negatif. trus ngatasinnya biar ndak error (indeks out of bound) gimana? N variabel corr itu fungsinya apa ?

    • pebbie · April 15, 2010

      ada berbagai cara :

      1. menganggap yang berindeks negatif bernilai 0
      2. mengambil nilai piksel dari sisi satunya lagi, kalau negatif ke kiri maka mengambil nilai piksel dari kanan citra.
      3. tidak memproses yang menghasilkan indeks negatif

      kode di atas menggunakan alternatif pertama (dianggap 0) dengan tambahan variabel corr (koreksi) untuk mengantisipasi dampak penggelapan dari nilai yang dianggap 0 tsb.

  5. GuyFreakz · Februari 28, 2011

    Mas, bisa tolong dijelasin variabel2 pd proc gauss yg dibuat itu apa saja..?
    ==> w = ? norm = normal? sum = ? ediv = ?
    trus rumus gaussian yang dipake seperti apa ya? soalnya stlh sy crosscheck dng wiki kok rasanya ga ada rumus matematik spt yg mas Peb pakai. kalau boleh tahu referensinya dr mn…?

    trus variabel utk func gaussian jg msg2 apa ya? p = array of pixelS? ker = kernel.
    variabel mid untuk dapet nilai tengah tp buat apa jg msh blm paham…
    ksize = kernel size? .. mohon penerangannya…

    sedang utk func conv, variabel yg sy ga tahu itu corr dan pp…
    masih awam dlm hal image processing tp bakal kepake buat skripsi…

  6. Ping-balik: Implementasi Canny Edge Detector « GAIBlog

Tinggalkan komentar

Situs ini menggunakan Akismet untuk mengurangi spam. Pelajari bagaimana data komentar Anda diproses.