wake-up-neo.com

Java - Bildgröße ändern, ohne an Qualität zu verlieren

Ich habe 10.000 Fotos, deren Größe angepasst werden muss, also habe ich ein Java) - Programm, um das zu tun. Leider geht die Bildqualität schlecht verloren und ich habe keinen Zugriff auf die unkomprimierten Bilder.

import Java.awt.Graphics;
import Java.awt.AlphaComposite;
import Java.awt.Graphics2D;
import Java.awt.Image;
import Java.awt.RenderingHints;
import Java.awt.image.BufferedImage;
import Java.io.File;
import Java.io.IOException;


import javax.imageio.ImageIO;
/**
 * This class will resize all the images in a given folder
 * @author 
 *
 */
public class JavaImageResizer {

    public static void main(String[] args) throws IOException {

        File folder = new File("/Users/me/Desktop/images/");
        File[] listOfFiles = folder.listFiles();
        System.out.println("Total No of Files:"+listOfFiles.length);
        BufferedImage img = null;
        BufferedImage tempPNG = null;
        BufferedImage tempJPG = null;
        File newFilePNG = null;
        File newFileJPG = null;
        for (int i = 0; i < listOfFiles.length; i++) {
              if (listOfFiles[i].isFile()) {
                System.out.println("File " + listOfFiles[i].getName());
                img = ImageIO.read(new File("/Users/me/Desktop/images/"+listOfFiles[i].getName()));
                tempJPG = resizeImage(img, img.getWidth(), img.getHeight());
                newFileJPG = new File("/Users/me/Desktop/images/"+listOfFiles[i].getName()+"_New");
                ImageIO.write(tempJPG, "jpg", newFileJPG);
              }
        }
        System.out.println("DONE");
    }

    /**
     * This function resize the image file and returns the BufferedImage object that can be saved to file system.
     */
        public static BufferedImage resizeImage(final Image image, int width, int height) {
    int targetw = 0;
    int targeth = 75;

    if (width > height)targetw = 112;
    else targetw = 50;

    do {
        if (width > targetw) {
            width /= 2;
            if (width < targetw) width = targetw;
        }

        if (height > targeth) {
            height /= 2;
            if (height < targeth) height = targeth;
        }
    } while (width != targetw || height != targeth);

    final BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
    final Graphics2D graphics2D = bufferedImage.createGraphics();
    graphics2D.setComposite(AlphaComposite.Src);
    graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BILINEAR);
    graphics2D.setRenderingHint(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY);
    graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
    graphics2D.drawImage(image, 0, 0, width, height, null);
    graphics2D.dispose();

    return bufferedImage;
}

Ein Bild, mit dem ich arbeite, ist folgendes: Firwork - original - large

Dies ist die manuelle Größenänderung, die ich in Microsoft Paint durchgeführt habe:

resize - using Paint - small

und das ist die Ausgabe von meinem Programm [bilinear]:

resize - using Java program - small

PDATE: Kein signifikanter Unterschied bei Verwendung von BICUBIC

und das ist die Ausgabe von meinem Programm [bikubisch]:

enter image description here

gibt es eine Möglichkeit, die Qualität der Programmausgabe zu verbessern, damit nicht alle Fotos manuell in der Größe verändert werden müssen?

Danke im Voraus!

54
user3362954

Leider gibt es in Java) keine empfohlene Standardskalierung, die visuell gute Ergebnisse liefert. Unter anderem die folgenden Methoden, die ich für die Skalierung empfehle:

  • Lanczos3 Resampling (normalerweise visuell besser, aber langsamer)
  • Progressive Down-Skalierung (normalerweise visuell in Ordnung, kann recht schnell sein)
  • Einstufige Skalierung zur Hochskalierung (mit Graphics2d Bikubisch schnell und mit guten Ergebnissen, normalerweise nicht so gut wie Lanczos3)

Beispiele für jede Methode finden Sie in dieser Antwort.

Visueller Vergleich

Hier ist Ihr Bild mit verschiedenen Methoden/Bibliotheken auf 96x140 Skaliert. Klicken Sie auf das Bild, um es in voller Größe zu sehen:

comparison

comparison zoom

  1. Morten Nobels lib Lanczos3
  2. Thumbnailator Bilineare progressive Skalierung
  3. Imgscalr ULTRA_QUALTY (1/7-stufige bikubische progressive Skalierung)
  4. Imgscalr QUALTY (1/2-stufige bikubische progressive Skalierung)
  5. Morten Nobels lib Bilinear Progressive Scaling
  6. Graphics2d Bikubische Interpolation
  7. Graphics2d Nearest Neighbor Interpolation
  8. Photoshop CS5 bikubisch als Referenz

Leider reicht ein einzelnes Bild nicht aus, um einen Skalierungsalgorithmus zu beurteilen. Sie sollten daher Symbole mit scharfen Kanten, Fotos mit Text usw. testen.

Lanczos Resampling

Ist angeblich gut für Up- und vor allem Downscaling. Leider gibt es im aktuellen JDK keine native Implementierung . Sie implementieren es also entweder selbst und verwenden eine Bibliothek wie Morten Nobels Bibliothek . Ein einfaches Beispiel mit der Bibliothek:

ResampleOp resizeOp = new ResampleOp(dWidth, dHeight);
resizeOp.setFilter(ResampleFilters.getLanczos3Filter());
BufferedImage scaledImage = resizeOp.filter(imageToScale, null);

Die lib ist auf maven-central veröffentlicht, was leider nicht erwähnt wird. Der Nachteil ist, dass es normalerweise sehr langsam ist, ohne dass mir hochoptimierte oder hardwarebeschleunigte Implementierungen bekannt sind. Die Implementierung von Nobel ist ungefähr 8-mal langsamer als ein progressiver 1/2 Schritt-Skalierungsalgorithmus mit Graphics2d. Lesen Sie mehr über diese Bibliothek in seinem Blog .

Progressive Skalierung

Erwähnt in Chris Campbells Blog über die Skalierung in Java bedeutet progressive Skalierung im Grunde, ein Bild in kleineren Schritten inkrementell zu skalieren, bis die endgültigen Abmessungen erreicht sind. Campbell beschreibt es als Halbierung der Breite/Höhe, bis Sie das Ziel erreichen. Dies führt zu guten Ergebnissen und kann mit Graphics2D Verwendet werden, das hardwarebeschleunigt werden kann und daher in den meisten Fällen eine sehr gute Leistung mit akzeptablen Ergebnissen aufweist. Der größte Nachteil ist, dass eine Verkleinerung um weniger als die Hälfte mit Graphics2D Die gleichen mittelmäßigen Ergebnisse liefert, da sie nur einmal skaliert wird.

Hier ist ein einfaches Beispiel, wie es funktioniert:

progressive scaling

Die folgenden Bibliotheken enthalten Formen der progressiven Skalierung basierend auf Graphics2d:

Thumbnailator v0.4.8

Verwendet den progressiven bilinearen Algorithmus, wenn das Ziel mindestens die Hälfte aller Dimensionen ist, andernfalls wird die einfache bilineare Skalierung Graphics2d Und die bikubische Skalierung für die Hochskalierung verwendet.

Resizer resizer = DefaultResizerFactory.getInstance().getResizer(
  new Dimension(imageToScale.getWidth(), imageToScale.getHeight()), 
  new Dimension(dWidth, dHeight))
BufferedImage scaledImage = new FixedSizeThumbnailMaker(
  dWidth, dHeight, false, true).resizer(resizer).make(imageToScale);

Es ist genauso schnell oder etwas schneller als das Skalieren in einem Schritt, wobei Graphics2d In meinem Benchmark einen Durchschnitt von 6,9 Sekunden erzielt.

Imgscalr v4.2

Verwendet progressive bikubische Skalierung. In der Einstellung QUALITY wird ein Campbell-Algorithmus verwendet, bei dem die Dimensionen bei jedem Schritt halbiert werden, während ULTRA_QUALITY Feinere Schritte aufweist. Dadurch wird die Größe bei jedem Inkrement um 1/7 verringert, was im Allgemeinen weichere Bilder erzeugt, aber die Instanzen minimiert wobei nur 1 Iteration verwendet wird.

BufferedImage scaledImage = Scalr.resize(imageToScale, Scalr.Method.ULTRA_QUALITY, Scalr.Mode.FIT_EXACT, dWidth, dHeight, bufferedImageOpArray);

Der Hauptnachteil ist die Leistung. ULTRA_QUALITY Ist erheblich langsamer als die anderen Bibliotheken. Auch QUALITY etwas langsamer als die Thumbnailator-Implementierung. Mein einfacher Benchmark ergab einen Durchschnitt von 26,2 bzw. 11,1 Sekunden.

Morten Nobels lib v0.8.6

Hat auch Implementierungen für progressive Skalierung für alle grundlegenden Graphics2d (Bilinear, bikubisch & nächster Nachbar)

BufferedImage scaledImage = new MultiStepRescaleOp(dWidth, dHeight, RenderingHints.VALUE_INTERPOLATION_BILINEAR).filter(imageToScale, null);

Ein Wort zu JDK-Skalierungsmethoden

Die derzeitige jdk-Methode zum Skalieren eines Bildes wäre etwa so

scaledImage = new BufferedImage(dWidth, dHeight, imageType);
Graphics2D graphics2D = scaledImage.createGraphics();
graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
graphics2D.drawImage(imageToScale, 0, 0, dWidth, dHeight, null);
graphics2D.dispose();

die meisten sind jedoch sehr enttäuscht über das Ergebnis des Downscalings, unabhängig davon, welche Interpolation oder andere RenderHints verwendet werden. Andererseits scheint das Hochskalieren akzeptable Bilder zu erzeugen (am besten wäre es bikubisch). In der Vorgängerversion von JDK (wir sprechen von 90s v1.1) wurde Image.getScaledInstance() eingeführt, das mit dem Parameter SCALE_AREA_AVERAGING Gute visuelle Ergebnisse lieferte, von der Verwendung wird jedoch abgeraten - Lesen Sie hier die vollständige Erklärung .

62
patrickf

Thumbnailator ist eine Bibliothek, die geschrieben wurde, um auf einfache Weise hochwertige Thumbnails zu erstellen, und eine Stapelkonvertierung vorhandener Bilder ist einer ihrer Anwendungsfälle.

Stapel-Größenänderung durchführen

Um beispielsweise Ihr Beispiel mit Thumbnailator anzupassen, sollten Sie in der Lage sein, mit dem folgenden Code ähnliche Ergebnisse zu erzielen:

File folder = new File("/Users/me/Desktop/images/");
Thumbnails.of(folder.listFiles())
    .size(112, 75)
    .outputFormat("jpg")
    .toFiles(Rename.PREFIX_DOT_THUMBNAIL);

In diesem Fall werden alle Dateien in Ihrem images -Verzeichnis verarbeitet und nacheinander auf die Abmessungen von 112 x 75 angepasst. Dabei wird versucht, das Seitenverhältnis von beizubehalten das Originalbild, um ein "Verziehen" des Bildes zu verhindern.

Der Thumbnailator liest alle Dateien unabhängig vom Bildtyp (solange das Java Image IO unterstützt das Format, Thumbnailator verarbeitet es)) ausgeführt wird den Größenänderungsvorgang und die Ausgabe der Thumbnails als JPEG-Dateien, während ein thumbnail. an den Anfang des Dateinamens.

Das Folgende ist eine Illustration, wie der Dateiname des Originals im Dateinamen der Miniaturansicht verwendet wird, wenn der obige Code ausgeführt wird.

images/fireworks.jpg     ->  images/thumbnail.fireworks.jpg
images/illustration.png  ->  images/thumbnail.illustration.png
images/mountains.jpg     ->  images/thumbnail.mountains.jpg

Erzeugen hochwertiger Thumbnails

In Bezug auf die Bildqualität wird, wie in Marco13s Antwort erwähnt, die von Chris Campbell in The Perils of Image.getScaledInstance () beschriebene Technik in Thumbnailator implementiert, was zu einer hohen Qualität führt Miniaturansichten in hoher Qualität, ohne dass eine komplizierte Verarbeitung erforderlich ist.

Im Folgenden sehen Sie die Miniaturansicht, die beim Ändern der Größe des in der ursprünglichen Frage gezeigten Feuerwerksbilds mithilfe von Thumbnailator erstellt wird:

Thumbnail of image in original question

Das obige Bild wurde mit folgendem Code erstellt:

BufferedImage thumbnail = 
    Thumbnails.of(new URL("http://i.stack.imgur.com/X0aPT.jpg"))
        .height(75)
        .asBufferedImage();

ImageIO.write(thumbnail, "png", new File("24745147.png"));

Der Code zeigt, dass er auch URLs als Eingabe akzeptieren kann und dass Thumbnailator auch BufferedImages erstellen kann.


Haftungsausschluss: Ich bin der Betreuer der Bibliothek Thumbnailator .

37
coobird

Ausgehend von Ihrem Eingabebild ergibt die Methode aus der Antwort im ersten Link in den Kommentaren (Lob an Chris Campbell) eine der folgenden Miniaturansichten:

enter image description hereenter image description here

(Die andere ist die Miniaturansicht, die Sie mit MS Paint erstellt haben. Es ist schwierig, eine davon als "besser" als die andere zu bezeichnen ...)

EDIT: Nur um dies zu verdeutlichen: Das Hauptproblem bei Ihrem ursprünglichen Code war, dass Sie nicht wirklich skalieren das Bild in mehreren Schritten. Sie haben gerade eine seltsame Schleife verwendet, um die Zielgröße zu "berechnen". Der entscheidende Punkt ist, dass Sie die Skalierung tatsächlich in mehreren Schritten ausführen.

Der Vollständigkeit halber die MVCE

import Java.awt.Graphics2D;
import Java.awt.RenderingHints;
import Java.awt.Transparency;
import Java.awt.image.BufferedImage;
import Java.io.File;
import Java.io.FileOutputStream;
import Java.io.IOException;
import Java.io.OutputStream;
import Java.util.Iterator;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;
import javax.imageio.stream.MemoryCacheImageOutputStream;

public class ResizeQuality
{
    public static void main(String[] args) throws IOException
    {
        BufferedImage image = ImageIO.read(new File("X0aPT.jpg"));
        BufferedImage scaled = getScaledInstance(
            image, 51, 75, RenderingHints.VALUE_INTERPOLATION_BILINEAR, true);
        writeJPG(scaled, new FileOutputStream("X0aPT_tn.jpg"), 0.85f);
    }

    public static BufferedImage getScaledInstance(
        BufferedImage img, int targetWidth,
        int targetHeight, Object hint, 
        boolean higherQuality)
    {
        int type =
            (img.getTransparency() == Transparency.OPAQUE)
            ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
        BufferedImage ret = (BufferedImage) img;
        int w, h;
        if (higherQuality)
        {
            // Use multi-step technique: start with original size, then
            // scale down in multiple passes with drawImage()
            // until the target size is reached
            w = img.getWidth();
            h = img.getHeight();
        }
        else
        {
            // Use one-step technique: scale directly from original
            // size to target size with a single drawImage() call
            w = targetWidth;
            h = targetHeight;
        }

        do
        {
            if (higherQuality && w > targetWidth)
            {
                w /= 2;
                if (w < targetWidth)
                {
                    w = targetWidth;
                }
            }

            if (higherQuality && h > targetHeight)
            {
                h /= 2;
                if (h < targetHeight)
                {
                    h = targetHeight;
                }
            }

            BufferedImage tmp = new BufferedImage(w, h, type);
            Graphics2D g2 = tmp.createGraphics();
            g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
            g2.drawImage(ret, 0, 0, w, h, null);
            g2.dispose();

            ret = tmp;
        } while (w != targetWidth || h != targetHeight);

        return ret;
    }

    public static void writeJPG(
        BufferedImage bufferedImage,
        OutputStream outputStream,
        float quality) throws IOException
    {
        Iterator<ImageWriter> iterator =
            ImageIO.getImageWritersByFormatName("jpg");
        ImageWriter imageWriter = iterator.next();
        ImageWriteParam imageWriteParam = imageWriter.getDefaultWriteParam();
        imageWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
        imageWriteParam.setCompressionQuality(quality);
        ImageOutputStream imageOutputStream =
            new MemoryCacheImageOutputStream(outputStream);
        imageWriter.setOutput(imageOutputStream);
        IIOImage iioimage = new IIOImage(bufferedImage, null, null);
        imageWriter.write(null, iioimage, imageWriteParam);
        imageOutputStream.flush();
    }    
}
16
Marco13

Nach Tagen der Recherche würde ich Javaxt vorziehen.

benutze das javaxt.io.Image class hat einen Konstruktor wie:

public Image(Java.awt.image.BufferedImage bufferedImage)

also kannst du ( another example ):

javaxt.io.Image image = new javaxt.io.Image(bufferedImage);
image.setWidth(50);
image.setOutputQuality(1);

Hier ist die Ausgabe:

enter image description here

5
MaMaRo N0

Wir sollten eine TwelveMonkeys Library nicht vergessen

Es enthält eine wirklich beeindruckende Filtersammlung.

Anwendungsbeispiel:

BufferedImage input = ...; // Image to resample
int width, height = ...; // new width/height

BufferedImageOp resampler = new ResampleOp(width, height, ResampleOp.FILTER_LANCZOS);
BufferedImage output = resampler.filter(input, null);
4
Quark

Das Ergebnis scheint besser zu sein (als das Ergebnis Ihres Programms), wenn Sie Gaußsche Unschärfe anwenden, bevor Sie die Größe ändern:

Dies ist das Ergebnis, das ich mit sigma * (scale factor) = 0.3 erhalte:

Thumbnail when bluring first(sigma=15.0)

Mit ImageJ ist der Code dafür ziemlich kurz:

import ij.IJ;
import ij.ImagePlus;
import ij.io.Opener;
import ij.process.ImageProcessor;

public class Resizer {

    public static void main(String[] args) {
        processPicture("X0aPT.jpg", "output.jpg", 0.0198, ImageProcessor.NONE, 0.3);
    }

    public static void processPicture(String inputFile, String outputFilePath, double scaleFactor, int interpolationMethod, double sigmaFactor) {
        Opener opener = new Opener();
        ImageProcessor ip = opener.openImage(inputFile).getProcessor();
        ip.blurGaussian(sigmaFactor / scaleFactor);
        ip.setInterpolationMethod(interpolationMethod);
        ImageProcessor outputProcessor = ip.resize((int)(ip.getWidth() * scaleFactor), (int)(ip.getHeight()*scaleFactor));
        IJ.saveAs(new ImagePlus("", outputProcessor), outputFilePath.substring(outputFilePath.lastIndexOf('.')+1), outputFilePath);
    }

}

Übrigens: Sie brauchen nur ij-1.49d.jar (oder gleichwertig für andere Version); Es ist nicht nötig, install ImageJ.

1
fabian

Im Folgenden ist meine eigene Implementierung von Progressive Scaling ohne Verwendung einer externen Bibliothek aufgeführt. Ich hoffe das hilft.

private static BufferedImage progressiveScaling(BufferedImage before, Integer longestSideLength) {
    if (before != null) {
        Integer w = before.getWidth();
        Integer h = before.getHeight();

        Double ratio = h > w ? longestSideLength.doubleValue() / h : longestSideLength.doubleValue() / w;

        //Multi Step Rescale operation
        //This technique is describen in Chris Campbell’s blog The Perils of Image.getScaledInstance(). As Chris mentions, when downscaling to something less than factor 0.5, you get the best result by doing multiple downscaling with a minimum factor of 0.5 (in other words: each scaling operation should scale to maximum half the size).
        while (ratio < 0.5) {
            BufferedImage tmp = scale(before, 0.5);
            before = tmp;
            w = before.getWidth();
            h = before.getHeight();
            ratio = h > w ? longestSideLength.doubleValue() / h : longestSideLength.doubleValue() / w;
        }
        BufferedImage after = scale(before, ratio);
        return after;
    }
    return null;
}

private static BufferedImage scale(BufferedImage imageToScale, Double ratio) {
    Integer dWidth = ((Double) (imageToScale.getWidth() * ratio)).intValue();
    Integer dHeight = ((Double) (imageToScale.getHeight() * ratio)).intValue();
    BufferedImage scaledImage = new BufferedImage(dWidth, dHeight, BufferedImage.TYPE_INT_RGB);
    Graphics2D graphics2D = scaledImage.createGraphics();
    graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
    graphics2D.drawImage(imageToScale, 0, 0, dWidth, dHeight, null);
    graphics2D.dispose();
    return scaledImage;
}
1
Maxwell Cheng