Wyostrzanie bitmapy za pomocą maski

0

Jeśli powinno być w dziale C# to przepraszam, miałem dylemat.

A więc tak, napisałem program wyostrzający bitmapę za pomocą maski 3x3 w C przy użyciu metod ze strony: http://www.algorytm.org/przetwarzanie-obrazow/filtrowanie-obrazow.html Wszystko działało.

Maska, której użyłem wygląda tak:
1, -2, 1
-2, 5, -2
1, -2, 1

Teraz postanowiłem przenieść program do C# (to mój pierwszy kontakt z C#, ale postanowiłem trochę się nauczyć z tego języka). Coś jednak jest nie tak w moim kodzie, co powoduje, że nowe wartości co chwile wyskakują powyżej 255. Byłbym bardzo wdzięczny, jeśli ktoś by znalazł błąd w moim rozumowaniu.

Windows Forms:

 
namespace Testing
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Bitmapa bitmap = new Bitmapa();
            pictureBox1.Image = bitmap.bitmapIt();
            pictureBox1.SizeMode = PictureBoxSizeMode.Normal;
        }
    }
}

Zaraz pod tym znajduje się klasa odpowiedzialna za operacje na bitmapie:


/*
 * Class responsible for creating and modyfying bitmap.
 */
class Bitmapa
{
    Bitmap image = new Bitmap(Bitmap.FromFile("../av.bmp"));

    public Bitmap bitmapIt()
    {
        int[,] mask = generateMask(); //generating 3x3 mask

        for (int x = 1; x < image.Width-1; x++)
        {
            for (int y = 1; y < image.Height-1; y++)
            {
               int newR = 0, newG = 0, newB = 0;
               Color[,] pixels = generatePixels(image, x, y);
               

               for (int a = 0; a < 3; a++)
               {
                   for (int b = 0; b < 3; b++)
                   {
                       newR += mask[a, b] * pixels[a, b].R;
                       newG += mask[a, b] * pixels[a, b].G;
                       newB += mask[a, b] * pixels[a, b].B;
                       
                   }
               }

               //debugger tu pokazuje błąd jako, że pobiera inty większe od 255
               Color newColor = Color.FromArgb(255, Math.Abs(newR), Math.Abs(newG), Math.Abs(newB));
               image.SetPixel(x, y, newColor);

            }
        }

        return image;
    }

    public Color[,] generatePixels(Bitmap image, int x, int y)
    {
        Color [,]pixelsColor = new Color[3, 3];

        for (int a = 0; a < 3; a++)
        {
            for (int b = 0; b < 3; b++)
            {
                pixelsColor[a, b] = image.GetPixel(x-1+a, y-1+b);
            }
        }
        return pixelsColor;
    }

    public int[,] generateMask()
    {
        int[,] pixelsMask = new int[3, 3] { {1, -2, 1}, {-2, 5, -2}, {1, -2, 1} };
        return pixelsMask;
    }
}
 

BTW. Potrzebuje zrobić ten program na razie w C#, żeby zrozumieć zasadę i działanie. Potem procedurę wyostrzania mam zrobić w asemblerze i porównać czasy wykonania tych programów. I tu też mi się przyda C#, bo GUI i wczytywanie będę dalej wykonywał w C#, a tylko algorytm wyostrzania będzie miał miejsce w procedurze asemblerowej.

0

A jakie jest pytanie?

0

Pytanie jest takie, czy ktoś widzi błąd w moim rozumowaniu i z jakiego powodu zmienne newR, newG, newB wykraczają poza wartość 255, choć nie powinny wg zasad na jakich powinien działać ten algorytm.

Chyba wiem o co chodzi. Powiedzmy, że maska jest:
-1 -1 -1
-1 9 -1
-1 -1 -1

To teraz jeśli wartości R danych pikseli wyglądają w ten sposób (środkowy to ten który zmieniamy, reszta to sąsiadujące):
0 0 0
0 255 0
0 0 0

To wtedy R = -10 + -10 + -10 + -10 + 9255 + -10 + -10 + -10 + -1*0 = 2295. Czyli mocno przekracza 255.

Oczywiście to jest przykład skrajny, kiedy wokół danego piksela powiedzmy, że całego czerwonego są same, które w ogóle tej czerwieni nie mają.

I teraz pytanie jak to robić? Jakieś ify, że jeśli newR > 255 to ustawia newR na 255?

1

To jest źle dobrana maska, suma elementów maski musi być równa dokładnie 1 lub
musisz zliczać sumę tych elementów i na końcu newR oraz inne podzielić przez tą sumę.

Elementy maski bez problemów mogą być liczbami zmiennoprzecinkowymi, wtedy
newR = 0, newG = 0, newB = 0 - też muszą być typu zmiennoprzecinkowego, zaś przy tworzeniu koloru newColor:
Math.min(0,Math.max(255,(int)(newR+0.5))) - pozostałe kolory odpowiednio.
Math.min(255,Math.max(0,(int)(newR+0.5))) - pozostałe kolory odpowiednio.

0
_13th_Dragon napisał(a):

Math.min(0,Math.max(255,(int)(newR+0.5))) - pozostałe kolory odpowiednio.

Hmm, a taki algorytm nie będzie generował zawsze 0? Chyba, że min z maxem by zamienić to wtedy jest okej, dzięki :)

A te maski, które tu przedstawiłem przecież sumarycznie dają 1.

No i algorytm wyostrzania działa jakoś ładnie tylko dla maski
-1 -1 -1
-1 20 -1
-1 -1 -1

I wtedy dzielimy przez 12. Tutaj mamy delikatne fajne wyostrzenie.

Każda inna maska której próbowałem, np. ta:
-1 -1 -1
-1 9 -1
-1 -1 -1

często wykracza poza te 255 lub 0 przez co nieprzyjemny efekt się uzyskuje: http://oi42.tinypic.com/214qs0g.jpg

1
Color newColor = Color.FromArgb(255, Math.Abs(newR), Math.Abs(newG), Math.Abs(newB));

Nie powinieneś obliczać w powyższym wartości bezwzględnej. Powinieneś przyciąć wartości do dopuszczalnego zakresu.

newR = Math.Max(0, Math.Min(newR, 255));
newG = Math.Max(0, Math.Min(newG, 255));
newB = Math.Max(0, Math.Min(newB, 255));
Color newColor = Color.FromArgb(255, newR, newG, newB);
0

Co do przycinania do dopuszczalnego zakresu to już to mam w kodzie i działa fajnie. Co do tego, że działam na zmodyfikowanych pikselach to o tym nie pomyślałem, racja. Spróbuje przerobić swój kod ;)

0

Przetwórz też od razu całość na tablicę wejściową oraz wynikową:
byte[,,] src=new byte[image.Height,image.Width,3],dst=new byte[image.Height,image.Width,3];
Czyli już rozbite na RGB.
Ściągasz całą tablicę src, wypełniasz całą tablicę dst, zapisujesz całą tablicę dst.
Na dzień dobry dostaniesz prawie 8-krotnego przyspieszenia.

0

Co do kopii to zacząłem działać na kopii i bardzo ładnie już wyostrzanie działa, teraz już "tylko" zrobienie tej głównej/obliczeniowej funkcji w asemblerze.

@_13_Dragon
Okej dzięki, i tak muszę przygotować takie tablice, żeby przekazać je procedurze asemblera. I tam wykonywać całość przekształceń.

A czy mogę te tablice wypełnić wartościami R,G,B obrazka w jakiś szybszy sposób niż po prostu przypisując w pętlach po kolei wszystkie wartości? Może to jakieś trywialne pytanie, ale wczoraj miałem pierwszy kontakt z C#, więc jestem nowy w tym języku i moje programowanie się opiera póki co na wiedzy z C/C++ i analogii do Javy.

0

http://bit.ly/17erBCp
tą klasę radzę dopracować i zrobić częścią tej twojej.

0
Mossar napisał(a):

A czy mogę te tablice wypełnić wartościami R,G,B obrazka w jakiś szybszy sposób niż po prostu przypisując w pętlach po kolei wszystkie wartości? Może to jakieś trywialne pytanie, ale wczoraj miałem pierwszy kontakt z C#, więc jestem nowy w tym języku i moje programowanie się opiera póki co na wiedzy z C/C++ i analogii do Javy.

Możesz operować bezpośrednio na pamięci bitmapy.

GraphicsUnit unit = GraphicsUnit.Pixel;
BitmapData srcData = srcBitmap.LockBits(Rectangle.Round(srcBitmap.GetBounds(ref unit)), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
BitmapData dstData = dstBitmap.LockBits(Rectangle.Round(dstBitmap.GetBounds(ref unit)), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);

unsafe 
{
  // wskaźniki do tablic
  byte* srcptr = (byte*)srcData.Scan0.ToPointer();
  byte* dstptr = (byte*)dstData.Scan0.ToPointer();

  // niezarządzana funkcja wysostrzania
  ...
}

srcBitmap.UnlockBits(srcData);
dstBitmap.UnlockBits(dstData);

1 użytkowników online, w tym zalogowanych: 0, gości: 1