Fehler im Gaussalgorithmus



  • Moin Moin liebe Community,

    ich wollte mich ein wenig tiefer mit der Materie der digitalen Bildverarbeitung einarbeiten und bin deshalb dabei ein paar grundlegende Algorithmen zu implementieren. In diesem Fall geht es konkret um das Gauss-smoothing.

    Im Prinzip muss ja nur diese Formel als Funktion abgebildet werden:
    I(x,y)=12πσ2ex2+y22σ2I(x,y) = \frac{1}{2 \pi \sigma^2} e^{-\frac{x^2+y^2}{2 \sigma^2}}
    [url]http://www.matheboard.de/latex2png/latex2png.php?I(x,y) = \frac{1}{2%20\pi%20\sigma2}%20e{-\frac{x2+y2}{2%20\sigma^2}}[/url]
    Das sieht bei mir konkret jetzt so aus:

    // Berechnet die Gewichtung eines Pixels
    float gaussian(const int dx,
                    const int dy,
                    const float sigma)
    {
        return exp(-1*(dx*dx+dy*dy)/(2*sigma*sigma))/(2*M_PI*sigma*sigma);
    }
    

    Wenn ich aber mein Programm durchlaufen lasse, so erhalte ich an einigen Stellen hässliche Streifen, die ich mir nicht erklären kann.
    Dies wäre das Orginalbild:
    http://vic.pytalhost.org/images/org.bmp
    Und das ist das falsche Ergebnis:
    http://vic.pytalhost.org/images/myGauss.bmp
    So sollte es aber eigentlich aussehen:
    http://vic.pytalhost.org/images/cvGauss.bmp

    Zum nachstehenden Code ein paar Worte:

    von der Main aus wird smooth aufgerufen. Diese Methode geht Pixel für Pixel das Orginalbild durch, lässt den neuen Wert berechnen und speichert diesen in das Ziel.
    Die Wertberechnung des einzelnen Pixels geschieht in der smooth_pixel Funktion. In einem Bereich um den angegeben Pixel herum (die Maske also) wird gewichtet und mit dem Bildwert verrechnet.
    Die Summe der Werte wird zurückgegeben.

    #include <stdlib.h>
    #include <iostream>
    #include <cv.h>
    #include <cassert>
    #include <cmath>
    #include <highgui.h>
    #include <exception>
    
    // Berechnet aus den x/y Koordinaten eine Arrayposition
    // Sollte die angegebene x/y Position nicht mit den Bilddimensionen überein-
    // stimmen, wird -1 als Fehlerwert zurückgegeben
    // Die berechnete Position gibt immer den Wert des 1. channels zurück
    int calcArrayPosition(const IplImage* src,
                                    const int x,
                                    const int y)
    {
        if (x >= src->width || y >= src->height || x < 0 || y < 0)
            return -1;
    
        return (y*src->width+x)*src->nChannels;
    }
    
    // Berechnet die Gewichtung eines Pixels
    float gaussian(const int dx,
                    const int dy,
                    const float sigma)
    {
        return exp(-1*(dx*dx+dy*dy)/(2*sigma*sigma))/(2*M_PI*sigma*sigma);
    }
    
    // Berechnet den neuen Wert eines einzelnen Pixels unter zuhilfenahme der
    // gausschen Glockenkurve. 
    float smooth_pixel(const IplImage* src,
                        int x,
                        int y,
                        const float sigma)
    {
        assert(src != 0);
    
        int row = 0, col = 0;
        const int mask = static_cast<int>(2.5*sigma);
        float value = 0.0f;
        float gauss = 0.0f;
        float meanbgr = 0.0f;
        int pos = 0, chan = 0;
    
        // Gebe den Orginalpixel zurück, falls die Maske aus dem Bild ragt.
        if(x-mask < 0 || x+mask > src->width || y-mask < 0 || y+mask > src->height)
        {
            int pos = calcArrayPosition(src, x, y);
            assert(pos >= 0);
            return src->imageData[pos];
        }
    
        // Gewichte und addiere alle Pixel in der Maskenumgebung
        for (row = -mask; row <= mask; ++row)
        {
            for (col = -mask; col <= mask; ++col)
            {
                pos = calcArrayPosition(src, x+col, y+row);
                if (pos >= 0)
                {
                    gauss = gaussian(col, row, sigma);
                    for (chan = 0; chan < src->nChannels; ++chan)
                    {
                        meanbgr += src->imageData[pos+chan];
                    }
                    value += gauss*meanbgr/src->nChannels;
                }
                meanbgr = 0;
                chan = 0;
            }
            col = 0;
        }
        return value;
    }
    
    // Berechnet pixelweise den neuen Wert unter Verwendung des Gauss Algorithmus
    void smooth (const IplImage* src, IplImage* dst, const float sigma)
    {
        assert(src != 0);
        assert(dst != 0);
    
        int row = 0, col = 0, chan = 0;
        unsigned int arraypos = 0;
        char out;
    
        if (src->width != dst->width || src->height != dst->height)
        {
            cout << "Image dimensions are not OK" << endl;
            return;
        }
    
        for(row = 0; row < src->height; ++row)
        {
            for (col = 0; col < src->width; ++col)
            {
                float pixel_value = smooth_pixel(src, col, row, sigma);
                out = static_cast<char>(static_cast<int>(pixel_value) & 0x000000ff);
                for (chan = 0; chan < src->nChannels; ++chan)
                {
                    dst->imageData[arraypos+chan] = out;
                }
                chan = 0;
                arraypos = arraypos+src->nChannels;
            }
            col = 0;
        }
    }
    
    int main(int argc, char** argv)
    {
        cvNamedWindow("Object", 1);
        cvNamedWindow("Smooth", 1);
        cvNamedWindow("org", 1);
        IplImage* object = cvLoadImage("box.png");
        IplImage* gauss = cvCreateImage(cvGetSize(object), object->depth, object->nChannels);
        IplImage* smo = cvCreateImage(cvGetSize(object), object->depth, object->nChannels);
        cvZero(gauss);
        cvZero(smo);
        cvSmooth(object, smo, CV_GAUSSIAN, 3, 3);
        cvShowImage( "org", object );
        cvShowImage( "Smooth", smo );
        smooth(object,gauss, 1);
        cvShowImage( "Object", gauss );
        cvWaitKey(0);
        cvSaveImage("org.bmp", object);
        cvSaveImage("cvGauss.bmp", smo);
        cvSaveImage("myGauss.bmp", gauss);
        cvDestroyWindow("Object");
        cvDestroyWindow("Smooth");
        cvDestroyWindow("org");
        return (EXIT_SUCCESS);
    }
    

    Wäre nett, wenn ihr mir Hinweise geben könntet, was die Ursache für diese Streifen darstellen könnte.

    Gruß

    Vic



  • Du musst die resultierende Intensitaet noch durch die Summe der Filtermatrixelemente dividieren damit diese wieder in 0..255 liegt.



  • Du kannst dir ja auch mal das hier anschauen:

    http://www.gamedev.net/reference/articles/article2193.asp

    Da hat es auch einen Algo drin, der zur Laufzeit recht gut läuft.



  • hellihjb schrieb:

    Du musst die resultierende Intensitaet noch durch die Summe der Filtermatrixelemente dividieren damit diese wieder in 0..255 liegt.

    Du meinst in etwa so?

    (Ausschnitt aus smooth_pixel)
    Änderungen in den Zeilen 9 und 21.

    for (row = -mask; row <= mask; ++row)
        {
            for (col = -mask; col <= mask; ++col)
            {
                pos = calcArrayPosition(src, x+col, y+row);
                if (pos >= 0)
                {
                    gauss = gaussian(col, row, sigma);
                    sumgauss += gauss;
                    for (chan = 0; chan < src->nChannels; ++chan)
                    {
                        meanbgr += src->imageData[pos+chan];
                    }
                    value += gauss*meanbgr/src->nChannels;
                }
                meanbgr = 0;
                chan = 0;
            }
            col = 0;
        }
        return value/sumgauss;
    

    Verhält sich immernoch genau so. Eigenltich auch logisch, da die Summe aller Gewichtungen ja 1 ergibt.



  • char *imageData, gell?
    char ist meistens signed. Alles klar? 🙂



  • Nee nichts ist klar. Worauf willst du hinaus?



  • Die IplImage struct ist darauf ausgelegt Bilddaten in verschiedenen Formaten zu beschreiben. Verschiedene Formate heisst auch verschiedene Pixel-Formate. Oft bestehen die Samples aus vorzeichenlosen 8 Bit Werten, es können aber genausogut floats, shorts, mit oder ohne Vorzeichen etc. sein. Der imageData Zeiger zeigt also auf einen Speicherbereich wo im Prinzip irgendwas drinstehen kann. Daher wurde als Typ einfach char* gewählt - da man "Rohen Speicher" in C++ üblicherweise mit char-Zeigern oder void-Zeigern anspricht.
    Das heisst aber nicht dass du über diesen char-Zeiger einfach zugreifen kannst, und der Compiler auf magische Art & Weise erkennt, welchen Typ er verwenden muss (unsigned char, float, char, short, ...).

    Ich denke die Wahl von char* ist in dem Fall etwas unvorteilhaft - mit void* könnten solche Fehler wie du in deinem Programm hast nicht passieren, da man void-Zeiger nicht dereferenzieren kann.

    ----

    In deinem Fall lädst du vermutlich Bilder die aus vorzeichenlosen 8 Bit Samples bestehen, der korrekte Typ wäre also unsigned char bzw. uint8_t.

    Wenn du alle Stellen wo du auf imageData zugreifst durch static_cast<unsigned char*>(imageData) ersetzt sollte es funktionieren.
    Also so:

    // dst->imageData[arraypos+chan] = out;
    // ->
    static_cast<unsigned char*>(dst->imageData)[arraypos+chan] = out;
    
    // ... und ...
    
    // meanbgr += src->imageData[pos+chan];
    // ->
    meanbgr += static_cast<unsigned char*>(src->imageData)[pos+chan];
    
    // etc.
    

    War das jetzt so schwer? Bisschen Grundlagenkenntnisse über das was man tut + etwas Selbständigkeit schaden nicht...



  • Ja danke, jetzt funktioniert es.


Anmelden zum Antworten