Java - redimensionner l'image sans perte de qualité

j'ai 10 000 photos qui doivent être redimensionnées donc, j'ai un programme Java pour le faire. Malheureusement, la qualité de l'image est mal perdu et je n'ai pas accès aux images non compressées.

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;
}

une image avec laquelle je travaille est celle-ci: Firwork - original - large

voici le redimensionnement manuel que J'ai fait dans Microsoft Paint:

resize - using Paint - small

et voici le résultat de mon programme [bilinéaire]:

resize - using java program - small

mise à jour: aucune différence significative en utilisant BICUBIC

et c'est la sortie de mon programme [bicubique]:

enter image description here

est-ce qu'il y a de toute façon pour augmenter la qualité de la sortie du programme pour que je n'aie pas à redimensionner manuellement toutes les photos?

Merci à l'avance!

43
demandé sur user3362954 2014-07-15 00:15:22

7 réponses

malheureusement, il n'y a pas de mise à l'échelle recommandée en Java qui donne de bons résultats visuels. Entre autres, voici les méthodes que je recommande pour la mise à l'échelle:

  • Lanczos3 Rééchantillonnage (généralement mieux visuellement, mais plus lent)
  • Progressif vers le Bas d'Échelle (généralement visuellement fine, peut être très rapide)
  • mise à l'échelle en une étape pour la mise à l'échelle (avec Graphics2d bicubic résultats rapides et bons, généralement pas aussi bon que Lanczos3)

des exemples pour chaque méthode peuvent être trouvés dans cette réponse.

Comparaison Visuelle

Voici votre image ajustée à 96x140 avec différentes méthodes / libs. Cliquez sur l'image pour obtenir la taille complète:

comparison

comparison zoom

  1. Morten Nobel lib Lanczos3
  2. Thumbnailator Bilinear Progressive Mise à l'échelle
  3. Imgscalr ULTRA_QUALTY (1/7 Step Bicubic Progressive Scaling)
  4. Imgscalr QUALITÉ (pas de 1/2 Bicubique Progressive mise à l'Échelle)
  5. Morten Nobel lib Bilinear Progressive Scaling
  6. Graphics2d interpolation bicubique
  7. Graphics2d interpolation du voisin le plus proche
  8. Photoshop CS5 bicubique comme référence

malheureusement une seule image n'est pas suffisante pour juger un algorithme de mise à l'échelle, vous devriez test des icônes avec des bords tranchants, des photos avec du texte, etc.

Lanczos Resampling

on dit qu'il est bon pour la montée et surtout la descente. Malheureusement il n'est pas natif de la mise en œuvre dans le courant de JDK alors soit vous l'implémentez vous-même et utilisez une lib comme Morten Nobel lib. Un exemple simple utilisant ladite lib:

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

La lib est publié sur maven central qui n'est pas mentionné malheureusement. Le l'inconvénient est qu'il est généralement très lent sans aucune implémentation hautement optimisée ou accélérée du matériel connu de moi. L'implémentation de Nobel est environ 8 fois plus lente qu'un algorithme de graduation progressive de 1/2 pas avec Graphics2d. en savoir plus sur ce lib sur son blog.

Graduation Progressive

Mentionné dans Chris Campbell blog à propos de la mise à l'échelle en Java, la mise à l'échelle progressive est essentiellement la mise à l'échelle progressive d'une image en plus petit étapes jusqu'à ce que les dimensions finales sont atteintes. Campbell décrit cela comme la moitié de la largeur / hauteur jusqu'à ce que vous atteigniez la cible. Cela donne de bons résultats et peut être utilisé avec Graphics2D qui peut être accéléré par le matériel, ayant donc généralement de très bonnes performances avec des résultats acceptables dans la plupart des cas. Le principal inconvénient de cette méthode est qu'elle réduit l'échelle de moins de la moitié en utilisant Graphics2D fournit les mêmes résultats médiocres puisqu'il n'est mis à l'échelle qu'une seule fois.

Voici un exemple simple sur la façon dont il fonctionne:

progressive scaling

les lib suivantes incorporent des formes de graduation progressive basées sur Graphics2d:

Thumbnailator v0.4.8

utilise l'algorithme bilinéaire progressif si la cible est au moins la moitié de chaque dimension, sinon il utilise simple Graphics2d mise à l'échelle Bilinéaire et bicubique pour la mise à l'échelle.

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);

il est aussi rapide ou légèrement plus rapide qu'une échelle à un pas avec Graphics2d marquant une moyenne de 6.9 sec dans mon test.

Imgscalr v4.2

utilise l'écaillage bicubique progressif. Dans le QUALITY paramétrage il utilise un algorithme de style Campbell avec une réduction de moitié des dimensions à chaque étape tandis que le ULTRA_QUALITY a des pas plus fins, réduisant la taille de chaque incrément de 1/7 ce qui génère généralement des images plus douces mais minimise les cas où seulement 1 itération est utilisée.

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

le major l'inconvénient, c'est la performance. ULTRA_QUALITY est considérablement plus lent que les autres libs. Même QUALITY un peu plus lent que L'implémentation de Thumbnailator. My simple test ont donné respectivement une moyenne de 26,2 sec et de 11,1 sec.

Morten Nobel lib v0.8.6

a également des implémentations pour la mise à l'échelle progressive pour tous lesGraphics2d (bilinéaire, bicubique, & du plus proche voisin)

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

un mot sur L'échelle JDK Méthodes

la façon JDK actuelle de dimensionner une image serait quelque chose comme ça

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();

mais la plupart sont très déçus du résultat de la réduction d'échelle, quelle que soit l'interpolation ou autre RenderHints sont utilisés. D'un autre côté, l'échelle ascendante semble produire des images acceptables (le meilleur serait bicubique). Dans la version JDK précédente (nous parlons 90s v1.1)Image.getScaledInstance() a été introduit qui a fourni de bons résultats visuels avec le paramètre SCALE_AREA_AVERAGING mais vous décourager de l'utiliser - lire l'explication complète ici.

44
répondu for3st 2018-02-03 19:05:04

Thumbnailator est une bibliothèque qui a été écrite pour créer des vignettes de haute qualité d'une manière simple, et faire une conversion par lots d'images existantes est l'un de ses cas d'utilisation.

Effectuer le redimensionnement par lots

Par exemple, pour adapter votre exemple à l'aide de Thumbnailator, vous devriez être en mesure d'obtenir des résultats similaires avec le code suivant:

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

Cela va aller de l'avant et prend tous les fichiers dans votre images répertoire et passer à les traiter un par un, essayez de les redimensionner pour s'adapter aux dimensions de 112 x 75, et il va tenter de conserver les proportions de l'image d'origine pour éviter la "déformation" de l'image.

Thumbnailator va de l'avant et lire tous les fichiers, quels que soient les types d'image (aussi longtemps que L'IO D'Image Java supporte le format, Thumbnailator va le traiter), effectuer l'opération de redimensionnement et de sortie les thumbnails comme des fichiers JPEG, tout en tacking sur un thumbnail. au début du fichier nom.

ce qui suit est une illustration de la façon dont le nom de fichier de l'original sera utilisé dans le nom de fichier de la vignette si le code ci-dessus est exécuté.

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

Générer un haut niveau de qualité des vignettes

en termes de qualité d'image, comme mentionné dans réponse de Marco13, la technique décrite par Chris Campbell dans son les périls de L'Image.getScaledInstance () est implémenté dans Thumbnailator, résultant en une qualité élevée vignettes sans aucun traitement compliqué.

voici la vignette générée lors du redimensionnement de l'image des feux d'artifice présentée dans la question originale à L'aide de la vignette:

Thumbnail of image in original question

l'image ci-dessus a été créée avec le code suivant:

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

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

le code montre qu'il peut aussi accepter des URLs comme entrée, et que Thumbnailator est aussi capable de créer BufferedImages bien.


Disclaimer: je suis le responsable de l' Thumbnailator bibliothèque.

32
répondu coobird 2017-05-23 11:47:11

compte tenu de votre image d'entrée, la méthode de la réponse dans le premier lien dans les commentaires (bravo à Chris Campbell) produit une des vignettes suivantes:

enter image description hereenter image description here

(l'autre est la vignette que vous avez créée avec MS Paint. Il est difficile d'appeler l'un d'entre eux "mieux" que l'autre...)

EDIT: juste pour souligner ceci aussi bien: le problème principal avec votre code original était que vous n'avez pas vraiment échelle l'image en plusieurs étapes. Vous avez juste utilisé une boucle étrange pour" calculer " la taille de la cible. Le point clé est que vous exécutez réellement le mise à l'échelle en plusieurs étapes.

Juste pour être complet, le 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();
    }    
}
15
répondu Marco13 2014-07-14 21:21:45

nous ne devrions pas oublier un TwelveMonkeys Library

il contient une collection de filtres vraiment impressionnante.

exemple d'Utilisation:

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);
3
répondu Quark 2016-06-25 13:29:22

après des jours de recherche, je préférerais javaxt.

javaxt.io.Image classe a un constructeur de la forme:

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

alors vous pouvez le faire (another example):

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

Voici la sortie:

enter image description here

2
répondu MaMaRo N0 2017-11-17 17:23:55

Le résultat semble être mieux (que le résultat de votre programme), si vous appliquez flou Gaussien avant de redimensionner:

C'est le résultat que j'obtiens, avec sigma * (scale factor) = 0.3:

Thumbnail when bluring first(sigma=15.0)

ImageJ le code pour faire cela est très courte:

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);
    }

}

BTW: Vous avez seulement besoin d' ij-1.49d.jar (ou l'équivalent pour une autre version); il n'y a pas besoin de installer ImageJ.

1
répondu fabian 2014-07-14 23:04:44

ci-dessous sont ma propre mise en œuvre de L'échelle Progressive, sans utiliser de bibliothèque externe. Espérons que cette aide.

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
répondu Maxwell Cheng 2018-01-31 10:38:27