Подтвердить что ты не робот

Java - изменение размера изображения без потери качества

У меня есть 10 000 фотографий, которые нужно изменить, поэтому у меня есть Java-программа для этого. К сожалению, качество изображения плохо потеряно, и у меня нет доступа к несжатым изображениям.

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

Изображение, с которым я работаю, это: Firwork - original - large

Это изменение размера вручную, которое я сделал в Microsoft Paint:

resize - using Paint - small

и это результат моей программы [bilinear]:

resize - using java program - small

ОБНОВЛЕНИЕ: Нет существенной разницы при использовании BICUBIC

и это результат моей программы [bicubic]:

enter image description here

В любом случае, чтобы увеличить качество вывода программы, мне не нужно вручную изменять размер всех фотографий?

Заранее благодарю вас!

4b9b3361

Ответ 1

К сожалению, в Java нет рекомендуемого стандартного масштабирования, которое обеспечивает визуально хорошие результаты. Среди прочего, вот методы, которые я рекомендую для масштабирования:

  • Lanczos3 Resampling (обычно визуально лучше, но медленнее)
  • Прогрессивное масштабирование вниз (обычно визуально хорошо, может быть довольно быстро)
  • Одноступенчатое масштабирование для масштабирования с повышением (с Graphics2d бикубическим быстрым и хорошими результатами, обычно не такими хорошими, как у Lanczos3)

Примеры для каждого метода можно найти в этом ответе.

Визуальное сравнение

Вот ваше изображение, масштабированное до 96x140 разными методами/библиотеками. Нажмите на изображение, чтобы получить полный размер:

comparison

comparison zoom

  1. Мортен Нобель Либ Лансос3
  2. Билинейное прогрессивное масштабирование Thumbnailator
  3. Imgscalr ULTRA_QUALTY (прогрессивное бикубическое масштабирование 1/7 шага)
  4. Imgscalr QUALTY (прогрессивное бикубическое масштабирование 1/2 шага)
  5. Мортен Нобелевская либ билинейная прогрессивная шкала
  6. Graphics2d Бикубическая интерполяция
  7. Graphics2d Интерполяция ближайшего соседа
  8. Photoshop CS5 Bicubic в качестве эталона

К сожалению, одного изображения недостаточно, чтобы оценить алгоритм масштабирования, вы должны проверить значки с острыми краями, фотографии с текстом и т.д.

Lanczos Resampling

Говорят, что это хорошо для up- и особенно для уменьшения масштаба. К сожалению, в текущей версии JDK нет собственной реализации, поэтому вы либо реализуете ее самостоятельно, либо используете библиотеку типа Morten Nobel lib. Простой пример с использованием указанной библиотеки:

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

Библиотека опубликована в maven-central, к сожалению, она не упоминается. Недостатком является то, что он обычно очень медленный без каких-либо хорошо оптимизированных или аппаратно ускоренных реализаций, известных мне. Нобелевская реализация примерно в 8 раз медленнее, чем алгоритм прогрессивного масштабирования 1/2 шага с Graphics2d. Подробнее об этой библиотеке читайте в его блоге.

Прогрессивное масштабирование

Упоминаемое в блоге Криса Кэмпбелла о масштабировании в Java, прогрессивное масштабирование в основном заключается в постепенном масштабировании изображения небольшими шагами, пока не будут достигнуты окончательные размеры. Кэмпбелл описывает его как половину ширины/высоты, пока вы не достигнете цели. Это дает хорошие результаты и может использоваться с Graphics2D, который может быть аппаратно ускорен, поэтому обычно имеет очень хорошую производительность с приемлемыми результатами в большинстве случаев. Основным недостатком этого является то, что уменьшение масштаба менее чем на половину с использованием Graphics2D дает такие же посредственные результаты, поскольку масштабируется только один раз.

Вот простой пример того, как это работает:

progressive scaling

Следующие библиотеки включают формы прогрессивного масштабирования на основе Graphics2d:

Thumbnailator v0.4.8

Использует прогрессивный билинейный алгоритм, если целью является как минимум половина каждого измерения, в противном случае он использует простое Graphics2d билинейное масштабирование и бикубический для повышения масштаба.

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

Это так же быстро или немного быстрее, чем одношаговое масштабирование, когда Graphics2d набрал в среднем 6,9 секунды в моем тесте.

Imgscalr v4.2

Использует прогрессивное бикубическое масштабирование. В настройке QUALITY он использует алгоритм стиля Кэмпбелла с уменьшением вдвое размеров каждого шага, в то время как ULTRA_QUALITY имеет более тонкие шаги, уменьшая размер каждого приращения на 1/7, что генерирует в целом более мягкие изображения, но минимизирует случаи, когда используется только 1 итерация,

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

Основным недостатком является производительность. ULTRA_QUALITY значительно медленнее, чем другие библиотеки. Даже QUALITY немного медленнее, чем реализация Thumbnailator. Мой простой тест показал в среднем 26,2 с и 11,1 с соответственно.

Мортен Нобель lib v0.8.6

Имеет также реализации для прогрессивного масштабирования для всех основных Graphics2d (билинейный, бикубический и ближайший сосед)

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

Несколько слов о методах масштабирования JDK

Текущий способ масштабирования изображения в формате jdk будет примерно таким:

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

но большинство очень разочарованы результатом уменьшения масштаба независимо от того, какая интерполяция или другая RenderHints используется. С другой стороны, апскейлинг производит приемлемые изображения (лучше всего бикубическое). В предыдущей версии JDK (мы говорим о 90-х годах v1.1) был представлен Image.getScaledInstance(), который давал хорошие визуальные результаты с параметром SCALE_AREA_AVERAGING, но вы не рекомендуем его использовать - прочитайте полное объяснение здесь.

Ответ 2

Thumbnailator - это библиотека, которая была написана для простого создания высококачественных миниатюр, и пакетное преобразование существующих изображений является одним из вариантов ее использования.

Выполнение пакетного изменения размера

Например, чтобы адаптировать свой пример с помощью Thumbnailator, вы сможете добиться аналогичных результатов с помощью следующего кода:

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

При этом все файлы в вашем каталоге images будут продолжаться, и они будут обрабатывать их один за другим, пытаться изменить их размер в соответствии с размерами 112 x 75, и попытаться сохранить соотношение сторон исходное изображение для предотвращения "коробления" изображения.

Thumbnailator продолжит чтение всех файлов, независимо от типов изображений (при условии, что Java Image IO поддерживает формат, Thumbnailator будет обрабатывать его), выполнит операцию изменения размера и выведет миниатюры в виде файлов JPEG, одновременно прикрепляя thumbnail. в начало имени файла.

Ниже приведена иллюстрация того, как имя файла оригинала будет использоваться в имени файла миниатюры, если приведенный выше код выполняется.

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

Создание высококачественных миниатюр

С точки зрения качества изображения, как упоминалось в Marco13 answer, метод, описанный Крисом Кэмпбеллом в его Опасностях Image.getScaledInstance(), реализован в Thumbnailator, что обеспечивает высокое качество миниатюры, не требуя сложной обработки.

Ниже приведен эскиз, созданный при изменении размера изображения фейерверка, показанного в исходном вопросе с помощью Thumbnailator:

Thumbnail of image in original question

Изображение выше было создано с помощью следующего кода:

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

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

Код показывает, что он также может принимать URL-адреса в качестве входных данных, и что Thumbnailator также способен создавать BufferedImage.


Отказ от ответственности: я поддерживаю библиотеку Thumbnailator.

Ответ 3

Учитывая ваше входное изображение, метод из ответа в первой ссылке в комментариях (слава Крису Кэмпбеллу) создает одну из следующих миниатюр:

enter image description here enter image description here

(Другой - это миниатюра, которую вы создали с помощью MS Paint. Трудно назвать один из них "лучше", чем другой...)

ОБНОВЛЕНИЕ: Просто чтобы указать на это: Основная проблема с вашим исходным кодом в том, что вы не масштабировали изображение в несколько этапов. Вы просто использовали странный цикл, чтобы "вычислить" целевой размер. Ключевым моментом является то, что вы фактически выполняете масштабирование в несколько этапов.

Просто для полноты, MVCE

(Изменение: я упомянул Криса Кэмпбелла и сослался на источник в комментариях, но чтобы прояснить это здесь: следующее основано на статье Опасности Image.getScaledInstance())

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

Ответ 4

После нескольких дней исследований я бы предпочел javaxt.

используйтеjavaxt.io.Image класс имеет конструктор, как:

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

так что вы можете сделать (another example):

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

Вот вывод:

enter image description here

Ответ 5

Мы не должны забывать о библиотеке TwelveMonkeys

Он содержит действительно впечатляющую коллекцию фильтров.

Пример использования:

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

Ответ 6

Ниже приведены мои собственные реализации прогрессивного масштабирования без использования какой-либо внешней библиотеки. Надеюсь, что это поможет.

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

Ответ 7

Результат выглядит лучше (чем результат вашей программы), если вы применяете Gaussian blur до изменения размера:

Это результат, который я получаю с помощью sigma * (scale factor) = 0.3:

Thumbnail when bluring first(sigma=15.0)

С ImageJ код для этого довольно короткий:

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: вам нужно только ij-1.49d.jar (или эквивалент для другой версии); нет необходимости устанавливать ImageJ.