Weitere aktuelle Java-Titel finden Sie bei dpunkt.
 Inhaltsverzeichnis   Auf Ebene Zurück   Seite Zurück   Seite Vor   Auf Ebene Vor   Eine Ebene höher   Index


12.9.2

Weitere Filter


Die Klasse RGBImageFilter ist von ImageFilter abgeleitet und zur Filterung von Farben im RGB-Modell geeignet. Es ist aber auch möglich, auf andere Arten zu filtern. Darunter fallen z. B. Drehungen oder andere Transformationen, die die Reihenfolge der Pixel des Bildes verändern. Hierbei wird nicht die Farbe der Pixel, sondern ihre Position im Bild verändert.

Eine andere Möglichkeit, ein Bild zu filtern, besteht durch Verwendung eines lokalen Operators. Ein lokaler Operator berechnet für jeden Bildpunkt des Originalbildes einen neuen Bildpunkt, indem die gewichteten Summen der umliegenden Bildpunkte gebildet werden.

Ausschlaggebend für die Wirkung eines solchen Operators sind: Auf diese Weise kann man z. B. einen Filter implementieren, der die Schärfe eines Bildes verbessert, bzw. einen Filter, der ein Bild weichzeichnet.

Die Vorgehensweise für die Implementierung eines Filters erfolgt am Beispiel des Unschärfe-Operators. Der hier vorgestellte Filter ist ein Mittelwertfilter. Für jeden Bildpunkt des neuen Bildes wird der alte Bildpunkt und alle acht umliegenden Bildpunkte addiert. Anschließend wird der Wert durch die Anzahl der Summanden geteilt und als Bildpunkt im neuen Bild eingetragen. Bei diesem Filter werden somit alle acht umliegenden Bildpunkte und der alte Bildpunkte in die Berechnung miteinbezogen und mit demselben Faktor gewichtet (Faktor 1/9). Für der Filterung im RGB-Modell muss dieser Vorgang für alle drei Farbanteile durchgeführt werden.

Bei der Implementierung eines solchen Filters muss man zunächst eine Unterklasse von ImageFilter ableiten. In der abgeleiteten Klasse werden die entsprechenden Methoden überschrieben, um dem Filter die gewünschte Funktionalität zu verleihen. Die Klasse ImageFilter definiert hierzu folgende Methoden: Alle oben genannten Methoden sind im Interface ImageConsumer definiert, das von der Klasse ImageFilter implementiert wird. Das heißt, ein ImageFilter ist auch ein ImageConsumer.

Der ImageFilter hat nur die Aufgabe, die Bilddaten zu filtern und anschließend weiterzureichen. Bei der Implementierung eines ImageFilters ist es nicht immer notwendig, alle Methoden zu implementieren. Die Klasse ImageFilter besitzt für jede Methode eine Implementierung, die jeweils die übergebenen Werte an den eigentlichen ImageConsumer weiterleitet, der die gefilterten Bilddaten vom ImageProducer anfordert. Ein Verweis auf den ImageConsumer ist im Datenelement consumer in der Klasse ImageFilter gespeichert.

Bei der Filterung eines Bildes werden vom ImageProducer die Methoden des ImageFilters in folgender Reihenfolge aufgerufen: Wie die einzelnen Methoden zusammenspielen, wird nun am Beispiel des oben beschriebenen Mittelwertfilters erläutert.

Der Mittelwertfilter implementiert die Methoden setDimensions(), setColorModel(), setPixels() und imageComplete().

In setDimensions() wird zunächst die Breite und Höhe des zu filternden Bildes gespeichert. Danach wird ein Array erzeugt, das alle Bilddaten im RGB-Format aufnehmen kann, und anschließend wird die Breite und Höhe an den ImageConsumer weitergeleitet:
  public void setDimensions(int width, int height) {
    this.width = width;
    this.height = height;
    pixbuf = new int[width*height];
    consumer.setDimensions(width, height);
  }
In diesem Fall hat das gefilterte Bild dieselbe Größe wie das Originalbild. Die Breite und Höhe wird deshalb unverändert an den ImageConsumer weitergeleitet. Bei einem Filter, der Bilder skaliert, wie z. B. ReplicateScaleFilter, müsste man die neue Größe an den ImageConsumer weitergeben.

In der Methode setColorModel() wird dem ImageConsumer mitgeteilt, dass die Mehrzahl der Pixel im RGB-Farbmodell geliefert wird:
  public void setColorModel(ColorModel model) {
    consumer.setColorModel(ColorModel.getRGBdefault());
  }
Das Farbmodell, das setColorModel() übergeben wird, zeigt allerdings nur an, in welchem Farbmodell die Mehrzahl der Pixel erfolgt. Es müssen nicht alle Pixel in diesem Farbmodell geliefert werden.

Ein Hauptteil der Arbeit erfolgt in den setPixels()-Methoden. Der Aufbau der Parameterliste ist elementar für das Verständnis dieser Methoden.

Beim Aufruf von setPixels() sind im Array pixels die Daten eines Bildausschnitts des zu filternden Bildes enthalten. Der ImageProducer ruft ein- bis mehrmals die setPixels()-Methode des Filters auf, bis alle Daten übertragen sind.

Bei einer Variante von setPixels() besteht dieses Array aus byte-Werten, bei der anderen Variante aus int-Werten. Das liegt daran, dass bei jedem Aufruf von setPixels() ebenfalls das Farbmodell übergeben wird, in dem die Pixel codiert sind. Bei der Verwendung des RGB-Farbmodells wird für die Codierung eines Bildpunktes ein int-Wert benötigt. Im Gegensatz hierzu braucht man für ein Pixel, dessen Farbwert durch ein IndexColorModel dargestellt wird, nur ein byte. Im letzten Fall ist die Farbe eindeutig durch ihren Index im IndexColorModel definiert.

Prinzipiell sind jedoch beide setPixels()-Methoden ebenbürtig. Da bei unterschiedlichen Farbkodierungen die Bilddaten in anderer Form gespeichert werden, sind jedoch zwei Varianten notwendig.

Der zweite wichtige Punkt bei der Implementierung von setPixels() ist der Zusammenhang zwischen den Daten im Array pixels und dem ungefilterten Originalbild. Die Parameter x, y, w und h definieren den rechteckförmigen Bildausschnitt im Originalbild, dessen Daten geliefert werden. Das Array pixels kann aber mehr Elemente enthalten, als ein Ausschnitt der obigen Größe Pixel besitzt.

Das Array pixels kann nämlich die Daten eines größeren rechteckförmigen Bildausschnitts speichern, von dem allerdings nur eine Teilmenge benötigt wird. Für den Zugriff auf die relevanten Bilddaten besitzt setPixels() deshalb die zusätzlichen Parameter offset und scansize.

offset definiert den Index des Array pixels, ab dem die relevanten Bilddaten gespeichert werden. scansize enthält die Breite des Bildausschnitts, der von dem kompletten pixels-Array repräsentiert wird. Über folgende Formel kann man auf den Bildpunkt (x1, y1) im Array pixels zugreifen (unter der Voraussetzung, (x1, y1) ist im Rechteck, das durch x, y, w und h definiert ist, enthalten):
  pixels[(x1-x)+offset+(y1-y)*scansize]
Bei der hier vorgestellten Implementierung des Mittelwertfilters werden in den setPixels()-Methoden einfach alle Pixeldaten in das RGB-Farbmodell konvertiert, soweit noch nicht geschehen, und in dem Array gespeichert, das in der Methode setDimensions(int, int) angelegt wurde. Das ist in diesem Fall notwendig, da der Mittelwertfilter nur richtig funktioniert, wenn alle Bilddaten bei Anwendung der Filterung verfügbar sind. Es ist nicht möglich, die Filterung nur mit einem Teilausschnitt vorzunehmen, da dadurch das Ergebnis verfälscht werden würde. Für das Beispiel der setPixels()-Variante, bei der die Bilddaten in byte-Form gespeichert werden, sieht die Implementierung folgendermaßen aus:
  public void setPixels(int x, int y, int w, int h,
         ColorModel model, byte pixels[], int off, int scansize) {
    int src=off;
    int dst=y*width+x;
    for (int i=0; i < h; i++) {
      for (int j = 0; j < w; j++) {
        pixbuf[dst++] = model.getRGB(pixels[src++] & 0xff);
      }
      src=src+scansize-w;
      dst=dst+width-w;
    }
  }

Jedes Pixel wird über das Farbmodell in seinen entsprechenden RGB-Wert konvertiert und anschließend in dem zuvor angelegten Array gespeichert. Bei der Umwandlung des byte-Werts in den entsprechenden RGB-Wert ist zu beachten, dass nicht der byte-Wert alleine an die Methode getRGB() übergeben werden kann. Das ist deshalb nicht möglich, da der byte-Wert eventuell ein Index eines Arrays ist. Da ein byte auch negativ sein kann, sind somit nicht alle Werte zulässig. Indem man bei der Übergabe den byte-Wert mit 0xFF über logisches UND verknüpft, wird das byte korrekt, ohne Vorzeichen, übergeben.

Nachdem der ImageProducer alle Bilddaten an den Filter geliefert hat, wird die Methode imageComplete(int) aufgerufen. imageComplete(int) erhält eine der in ImageConsumer definierten Konstanten übergeben. Der übergebene Wert gibt Auskunft darüber, auf welche Weise die Lieferung der Pixel abgeschlossen wurde. Folgende Konstanten werden hierfür definiert: Die Implementierung der imageComplete(int)-Methode des Mittelwertfilters hat folgenden Aufbau:
  public void imageComplete(int status) {
    if ((status==IMAGEERROR) || (status==IMAGEABORTED)) {
      consumer.imageComplete(status);
      return;
    }
    int[] buffer = new int[width*height];
    long rgb[] = new long[3];
    int weights[][] = {{1,1,1},{1,1,1},{1,1,1}};
    int xx=1;
    int yy=1;
    int val;
    for (int i=0; i < height; i++) {
      for (int j=0; j < width; j++) {
        for (int n=0; n < 3; n++)
          rgb[n] = 0;
        for (int k=0; k < weights.length; k++) {
          for (int l=0; l < weights[0].length; l++) {
            val = pixbuf[((i+k-yy+height)
                % height)*width+(j+l-xx+width) % width];
            rgb[0]+=weights[k][l]*((val >> 16) & 0xFF);
            rgb[1]+=weights[k][l]*((val >> 8) & 0xFF);
            rgb[2]+=weights[k][l]*(val & 0xFF);
          }
        }
        for (int n=0; n < 3; n++) {
          rgb[n] = rgb[n]/9;
        }
        buffer[i*width+j] = (int)rgb[2] |
                            ((int)rgb[1] << 8) |
                            ((int)rgb[0] << 16) |
                            0xFF000000;
      }
      // Übergebe Zeile an den ImageConsumer
    }
    consumer.setPixels(0, 0, width, height,
      ColorModel.getRGBdefault(), buffer, 0, width);
    consumer.imageComplete(status);
  }

Ein Abbruch der Pixel-Lieferung durch den ImageProducer wird an den ImageConsumer gemeldet und anschließend wird die Methode beendet.

Wird imageComplete(int) ein anderer Wert übergeben, so wird daraufhin die eigentliche Filterung durchgeführt. Alle Daten des Bildes wurden zuvor von den setPixels()-Methoden in den angelegten Puffer geschrieben und werden nun auf die zu Beginn beschriebene Weise verarbeitet. Die gefilterten Daten werden hier in einem neuen Puffer zwischengespeichert. Nachdem alle Bilddaten gefiltert wurden, werden die gefilterten Pixel an den ImageConsumer durch Aufruf von dessen setPixels()-Methode weitergeleitet. Anschließend erfolgt ein Aufruf der imageComplete(int)-Methode des ImageConsumers, um anzuzeigen, dass alle Daten des Bildes komplett geliefert wurden.

Auf der CD befindet sich das vollständige Listing des hier beschriebenen Filters. Zusätzlich ist dort ein Testprogramm und ein zweiter Filter zu finden, der die Schärfe eines Bildes verbessert. Das Testprogramm enthält ein Textfeld, in dem die URL zu einem Bild eingetragen werden muss. Das Bild kann hierbei sowohl lokal als auch über einen WWW-Server erreichbar sein. Eine URL-Eingabe muss jeweils mit Return abgeschlossen werden, damit das neue Bild geladen wird. Am unteren Rand der Oberfläche befindet sich eine Auswahlbox, über die einzelnen Filteroperationen auf das aktuell geladene Bild angewendet werden können.

Seit dem[1.2] JDK 1.2 kann man die Filterung mit lokalen Operatoren sehr viel einfacher durchführen. Hierzu wurde die Klasse ConvolveOp eingeführt. ConvolveOp repräsentiert einen Faltungsoperator, der auf ein BufferedImage angewendet werden kann. Die Implementierung des Mittelwertfilters, wie er zuvor beschrieben wurde, besteht mit dieser Klasse aus drei Zeilen Code:
  Kernel k = new Kernel(3, 3, elements);
  ConvolveOp conv = new ConvolveOp(k);
  conv.filter(sourceImage, destImage);
Zunächst muss ein Exemplar der Klasse Kernel erzeugt werden. Kernel repräsentiert den Faltungskern. Das ist die Matrix mit Gewichten, über die das Originalbild zum gefilterten Bild verrechnet wird. Dem Konstruktor von Kernel werden als Parameter die Ausmaße der Matrix und die Matrix selbst übergeben. In elements ist die Matrix gespeichert, die im Falle des Mittelwertfilters folgende Gestalt hat:
  public static final float[] SMOOTH3x3 = {
                               0.1f, 0.1f, 0.1f,
                               0.1f, 0.1f, 0.1f,
                               0.1f, 0.1f, 0.1f};

Danach wird eine neue Instanz von ConvolveOp angelegt und mit dem neuen Kernel initialisiert. Durch Aufruf der filter()-Methode wird der Operator letztendlich auf das Bild angewendet. filter() besitzt zwei Parameter vom Typ BufferedImage: Einen Verweis auf das Originalbild und einen Verweis auf ein Exemplar, in dem das gefilterte Bild gespeichert werden soll. Wird als Zielbild der Wert null übergeben, so erzeugt filter() intern automatisch ein neues BufferedImage, in dem das Ergebnis gespeichert wird, und liefert es als Ergebnis zurück.


 Inhaltsverzeichnis   Auf Ebene Zurück   Seite Zurück   Seite Vor   Auf Ebene Vor   Eine Ebene höher   Index

Copyright © 2002 dpunkt.Verlag, Heidelberg. Alle Rechte vorbehalten.