programing tip

Java-품질 저하없이 이미지 크기 조정

itbloger 2021. 1. 6. 07:49
반응형

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-원본-대형

다음은 Microsoft Paint에서 수행 한 수동 크기 조정입니다.

크기 조정-그림판 사용-작음

그리고 이것은 내 프로그램의 출력입니다 [쌍 선형] :

크기 조정-Java 프로그램 사용-작음

업데이트 : 사용하여 큰 차이 없음BICUBIC

그리고 이것은 내 프로그램 [bicubic]의 출력입니다.

여기에 이미지 설명 입력

어쨌든 모든 사진의 크기를 수동으로 조정할 필요가 없도록 프로그램 출력의 품질을 높일 수 있습니까?

미리 감사드립니다!


불행히도, 시각적으로 좋은 결과를 제공하는 Java에서 권장되는 즉시 사용 가능한 확장은 없습니다. 그중에서도 내가 권장하는 확장 방법은 다음과 같습니다.

  • Lanczos3 리샘플링 (일반적으로 시각적으로 더 좋지만 느림)
  • 프로그레시브 다운 스케일링 (일반적으로 시각적으로 양호하며 매우 빠를 수 있음)
  • 확장을위한 Graphics2d원스텝 확장 ( 쌍 입방 빠르고 좋은 결과, 일반적으로 Lanczos3만큼 좋지 않음)

이 답변에서 모든 방법에 대한 예를 찾을 수 있습니다.

시각적 비교

다음은 96x140다른 메서드 / libs로 크기가 조정 된 이미지 입니다. 전체 크기를 얻으려면 이미지를 클릭하십시오.

비교

비교 줌

  1. Morten Nobel의 lib Lanczos3
  2. 썸네일 레이터 쌍 선형 점진적 스케일링
  3. Imgscalr ULTRA_QUALTY (1/7 단계 Bicubic Progressive Scaling)
  4. Imgscalr QUALTY (1/2 단계 Bicubic Progressive Scaling)
  5. Morten Nobel의 lib Bilinear Progressive Scaling
  6. Graphics2d 쌍 입방 보간
  7. Graphics2d Nearest Neighbor 보간
  8. 참조 용 Photoshop CS5 쌍 입방

안타깝게도 단일 이미지로는 크기 조정 알고리즘을 판단하기에 충분하지 않습니다. 가장자리가 날카로운 아이콘, 텍스트가있는 사진 등을 테스트해야합니다.

Lanczos 리샘플링

업 스케일링, 특히 다운 스케일링에 좋다고합니다. 불행히도 현재 JDK에는 기본 구현이 없으므로 직접 구현하고 Morten Nobel의 lib 와 같은 lib를 사용하십시오 . 상기 lib를 사용한 간단한 예 :

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

lib는 불행히도 언급되지 않은 maven-central에 게시됩니다 . 단점은 나에게 알려진 고도로 최적화되거나 하드웨어 가속 구현이 없으면 일반적으로 매우 느리다는 것입니다. Nobel의 구현은 .NET을 사용하는 1/2 단계 점진적 확장 알고리즘보다 약 8 배 느립니다 Graphics2d. 그의 블로그에서이 lib에 대해 자세히 알아보십시오 .

점진적 확장

Java에서의 스케일링대한 Chris Campbell의 블로그 에서 언급 된 점진적 스케일링은 기본적으로 최종 차원에 도달 할 때까지 더 작은 단계로 이미지를 점진적으로 스케일링하는 것입니다. Campbell은 목표에 도달 할 때까지 너비 / 높이를 절반으로 줄이는 것으로 설명합니다. 이렇게하면 좋은 결과가 생성 Graphics2D되고 하드웨어 가속 과 함께 사용할 수 있으므로 일반적으로 대부분의 경우 허용 가능한 결과와 함께 매우 좋은 성능을 제공합니다. 이것의 주요 단점은 사용하여 절반 이하로 Graphics2D축소하면 한 번만 확장되므로 동일한 평범한 결과 제공한다는 것입니다.

다음은 작동 방식에 대한 간단한 예입니다.

점진적 확장

다음 libs는에 기반한 점진적 확장 형식을 통합합니다 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 초 기록하여 1 단계 확장보다 빠르거나 약간 빠릅니다 .

Imgscalr v4.2

점진적 쌍 입방 스케일링을 사용합니다. 에서 QUALITY설정 그것은이 동시에 측정마다 단계를 이등분하여 캠벨 스타일 알고리즘을 사용하여 ULTRA_QUALITY미세한 단계를 가지며, 크기를 일반적으로 더 부드러운 이미지이지만 최소화 오직 하나의 반복이 사용되는 경우 발생 1/7 의해 모든 증분을 감소시킨다.

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

주요 단점은 성능입니다. ULTRA_QUALITY다른 libs보다 상당히 느립니다. 심지어 QUALITYThumbnailator의 구현보다 느린 비트. 내 간단한 벤치 마크 는 각각 평균 ​​26.2 초와 11.1 초였습니다.

Morten Nobel의 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 버전 (90s v1.1에 대해 이야기 함) Image.getScaledInstance()에서 매개 변수와 함께 좋은 시각적 결과를 제공 SCALE_AREA_AVERAGING했지만 사용하지 않는 것이 좋습니다 . 여기에서 전체 설명을 읽으십시오 .


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의 답변 에서 언급했듯이 Chris Campbell이 The Perils of Image.getScaledInstance () 에서 설명한 기술 은 Thumbnailator에서 구현되어 복잡한 처리없이 고품질 썸네일을 생성합니다.

다음은 Thumbnailator를 사용하여 원래 질문에 표시된 불꽃 놀이 이미지의 크기를 조정할 때 생성되는 축소판입니다.

원래 질문의 이미지 썸네일

위 이미지는 다음 코드로 생성되었습니다.

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도 BufferedImages를 생성 할 수 있음을 보여줍니다 .


면책 조항 : 저는 Thumbnailator 라이브러리 의 관리자입니다 .


입력 이미지가 주어지면 댓글의 첫 번째 링크 (Chris Campbell에 대한 쿠도스)에있는 답변의 메소드는 다음 썸네일 중 하나를 생성합니다.

여기에 이미지 설명 입력 여기에 이미지 설명 입력

(다른 하나는 MS Paint로 만든 썸네일입니다.이 중 하나를 다른 것보다 "낫다"라고 부르기는 어렵습니다 ...)

편집 : 이것을 지적하기 위해 : 원본 코드의 주요 문제는 실제로 여러 단계에서 이미지 크기를 조정 하지 않았다는 것 입니다. 방금 이상한 루프를 사용하여 대상 크기를 "계산"했습니다. 요점은 실제로 여러 단계로 확장수행한다는 것 입니다.

완전성을 위해 MVCE

(편집 : 나는 Chris Campbell을 언급하고 주석을 통해 소스를 언급했지만 여기에서 더 명확하게하기 위해 : 다음은 The Perils of 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();
    }    
}

며칠간의 연구 끝에 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);

출력은 다음과 같습니다.

여기에 이미지 설명 입력


우리는 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);

다음은 외부 라이브러리를 사용하지 않고 점진적 확장을 직접 구현 한 것입니다. 이 도움을 바랍니다.

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

크기를 조정하기 전에 가우시안 블러 를 적용하면 결과가 프로그램의 결과보다 더 좋아 보입니다 .

이것은 내가 얻는 결과입니다 sigma * (scale factor) = 0.3.

먼저 블러 링 할 때 썸네일 (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 설치할 필요가 없습니다 .

참조 URL : https://stackoverflow.com/questions/24745147/java-resize-image-without-losing-quality

반응형