java जावा-गुणवत्ता खोने के बिना छवि का आकार बदलें



image image-processing (6)

आपकी इनपुट छवि को देखते हुए, टिप्पणी में पहले लिंक में उत्तर से विधि (क्रिस कैंपबेल के लिए kudos) निम्नलिखित थंबनेल में से एक उत्पन्न करता है:

(दूसरा एक थंबनेल है जिसे आपने एमएस पेंट के साथ बनाया है। उनमें से एक को दूसरे की तुलना में "बेहतर" कहना मुश्किल है ...)

संपादित करें: बस इसे भी इंगित करने के लिए: आपके मूल कोड के साथ मुख्य समस्या यह थी कि आपने वास्तव में कई चरणों में छवि को स्केल नहीं किया था। आपने लक्ष्य आकार को "गणना" करने के लिए अभी एक अजीब पाश का उपयोग किया है। मुख्य बिंदु यह है कि आप वास्तव में कई चरणों में स्केलिंग करते हैं।

बस पूर्णता के लिए, एमवीसीई

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

मेरे पास 10,000 फ़ोटो हैं जिन्हें आकार बदलने की आवश्यकता है इसलिए मेरे पास ऐसा करने के लिए जावा प्रोग्राम है। दुर्भाग्य से, छवि की गुणवत्ता खराब हो गई है और मेरे पास असम्पीडित छवियों तक पहुंच नहीं है।

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

एक छवि जिसके साथ मैं काम कर रहा हूं वह यह है:

माइक्रोसॉफ्ट पेंट में मैंने मैन्युअल आकार बदल दिया है:

और यह मेरे कार्यक्रम [bilinear] से आउटपुट है:

अद्यतन: BICUBIC का उपयोग करके कोई महत्वपूर्ण अंतर नहीं

और यह मेरे कार्यक्रम [bicubic] से आउटपुट है:

क्या प्रोग्राम आउटपुट की गुणवत्ता में वृद्धि करने के लिए वैसे भी है इसलिए मुझे सभी फ़ोटो मैन्युअल रूप से आकार बदलने की ज़रूरत नहीं है?

आपका अग्रिम में ही बहुत धन्यवाद!


नतीजा बेहतर होता है (आपके कार्यक्रम के नतीजे से), यदि आप आकार बदलने से पहले गॉसियन ब्लर लागू करते हैं:

sigma * (scale factor) = 0.3 साथ मुझे यह परिणाम मिल गया है:

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

}

बीटीडब्ल्यू: आपको केवल ij-1.49d.jar (या अन्य संस्करण के लिए समतुल्य) की आवश्यकता है; ImageJ इंस्टॉल करने की कोई आवश्यकता नहीं है।


दुर्भाग्यवश, जावा में कोई भी स्केलिंग आउट-ऑफ-द-बॉक्स स्केलिंग नहीं है जो दृष्टिहीन अच्छे परिणाम प्रदान करता है। दूसरों के बीच, यहां स्केलिंग के लिए सुझाए गए तरीके हैं:

  • Lanczos3 Resampling (आमतौर पर दृष्टि से बेहतर, लेकिन धीमी)
  • प्रोग्रेसिव डाउन स्केलिंग (आमतौर पर दृष्टि से ठीक, काफी तेज़ हो सकता है)
  • स्केलिंग के लिए एक-चरण स्केलिंग ( Graphics2d बाइब्यूबिक तेज़ और अच्छे नतीजों के साथ, आमतौर पर लांज़ोज़ 3 के जितना अच्छा नहीं)

इस विधि में प्रत्येक विधि के लिए उदाहरण मिल सकते हैं।

दृश्य तुलना

यहां विभिन्न 96x140 / libs के साथ आपकी छवि 96x140 स्केल की 96x140 । पूर्ण आकार प्राप्त करने के लिए छवि पर क्लिक करें:

  1. मॉर्टन नोबेल की lib Lanczos3
  2. थंबनेलर Bilinear प्रगतिशील स्केलिंग
  3. Imgscalr ULTRA_QUALTY (1/7 कदम Bicubic प्रगतिशील स्केलिंग)
  4. Imgscalr गुणवत्ता (1/2 कदम Bicubic प्रगतिशील स्केलिंग)
  5. मॉर्टन नोबेल की lib Bilinear प्रगतिशील स्केलिंग
  6. Graphics2d बाइबिक इंटरपोलेशन
  7. Graphics2d निकटतम पड़ोसी इंटरपोलेशन
  8. संदर्भ के रूप में फ़ोटोशॉप सीएस 5 बाइबिक

दुर्भाग्यवश एक एकल छवि स्केलिंग एल्गोरिदम का न्याय करने के लिए पर्याप्त नहीं है, आपको तीखे किनारों, टेक्स्ट के साथ फोटो इत्यादि वाले आइकन का परीक्षण करना चाहिए।

Lanczos Resampling

कहा जाता है कि - और विशेष रूप से डाउनस्कलिंग के लिए अच्छा होना कहा जाता है। दुर्भाग्यवश वर्तमान जेडीके में कोई मूल कार्यान्वयन नहीं है, इसलिए आप इसे स्वयं लागू कर सकते हैं और मोर्टन नोबेल की लिब जैसे lib का उपयोग कर सकते हैं। कहा lib का उपयोग कर एक सरल उदाहरण:

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

Lib को मेवेन-सेंट्रल पर प्रकाशित किया गया है जिसका दुर्भाग्य से उल्लेख नहीं किया गया है। नकारात्मकता यह है कि यह आमतौर पर मुझे ज्ञात किसी भी अत्यधिक अनुकूलित या हार्डवेयर त्वरित कार्यान्वयन के बिना बहुत धीमी है। नोबेल का कार्यान्वयन Graphics2d 2 डी के साथ 1/2 चरण प्रगतिशील स्केलिंग एल्गोरिदम की तुलना में लगभग 8 गुना धीमा है। अपने ब्लॉग पर इस lib के बारे में और पढ़ें

प्रगतिशील स्केलिंग

जावा कैंपबेल के ब्लॉग में जावा में स्केलिंग के बारे में उल्लेख किया गया है, प्रगतिशील स्केलिंग मूल रूप से अंतिम चरण तक पहुंचने तक छोटे चरणों में छवि को स्केल कर रही है। कैंपबेल इसे लक्ष्य तक पहुंचने तक चौड़ाई / ऊंचाई के रूप में वर्णित करता है। यह अच्छे नतीजे पैदा करता है और इसका उपयोग Graphics2D साथ किया जा सकता है जिसे हार्डवेयर तेज किया जा सकता है, इसलिए आमतौर पर ज्यादातर मामलों में स्वीकार्य परिणामों के साथ बहुत अच्छा प्रदर्शन होता है। इसका मुख्य नकारात्मक हिस्सा यह है कि यदि Graphics2D का उपयोग करके आधे से भी कम डाउनस्केल्ड समान औसत परिणाम प्रदान करता है क्योंकि यह केवल एक बार स्केल किया जाता है।

यहां यह एक सरल उदाहरण है कि यह कैसे काम करता है:

निम्नलिखित libs Graphics2d 2 डी के आधार पर प्रगतिशील स्केलिंग के रूपों को शामिल करते हैं:

थंबनेलर v0.4.8

यदि लक्ष्य प्रत्येक आयाम का कम से कम आधा है, तो प्रगतिशील बिलीनेर एल्गोरिदम का उपयोग करता है, अन्यथा यह upscaling के लिए सरल 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 साथ एक-चरण स्केलिंग की तुलना में तेज़ या थोड़ा तेज़ है जो मेरे benchmark में 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 अन्य libs की तुलना में काफी धीमी है। थंबनेलेटर के कार्यान्वयन की तुलना में QUALITY धीमी है। मेरा सरल benchmark क्रमश: 26.2 सेकंड और 11.1 सेकंड औसत में हुआ।

मॉर्टन नोबेल की lib v0.8.6

सभी बुनियादी Graphics2d (बिलीनेर, बाइबिक और निकटतम पड़ोसी) के लिए प्रगतिशील स्केलिंग के लिए भी कार्यान्वयन है

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

जेडीके स्केलिंग तरीकों पर एक शब्द

एक छवि को स्केल करने के लिए वर्तमान जेडीके तरीका इस तरह कुछ होगा

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 के परिणाम से बहुत निराश हैं इससे कोई फर्क नहीं पड़ता कि इंटरपोलेशन या अन्य RenderHints क्या उपयोग किया जाता है। दूसरी तरफ upscaling स्वीकार्य छवियों का उत्पादन लगता है (सबसे अच्छा bicubic होगा)। पिछले Image.getScaledInstance() संस्करण में (हम 90 के दशक v1.1 बात करते हैं) Image.getScaledInstance() पेश किया गया था जो पैरामीटर SCALE_AREA_AVERAGING साथ अच्छे दृश्य परिणाम प्रदान SCALE_AREA_AVERAGING लेकिन आप इसका उपयोग करने के लिए निराश हैं - यहां पूर्ण स्पष्टीकरण पढ़ें


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 के आयामों में फिट करने के लिए उन्हें आकार देने का प्रयास करें, और यह मूल छवि के पहलू अनुपात को रोकने के लिए प्रयास करेगा छवि के "warping"।

थंबनेलर आगे बढ़ेगा और छवि फ़ाइलों के बावजूद सभी फाइलें पढ़ेगा (जब तक जावा इमेज आईओ प्रारूप का समर्थन करता है, थंबनेलेटर इसे संसाधित करेगा), थंबनेल पर काम करते समय, आकार बदलने का ऑपरेशन करें और थंबनेल को जेपीईजी फाइलों के रूप में आउटपुट करें thumbnail. फ़ाइल नाम की शुरुआत के लिए।

उपर्युक्त कोड निष्पादित होने पर मूल का फ़ाइल नाम थंबनेल के फ़ाइल नाम में कैसे उपयोग किया जाएगा इसका एक उदाहरण है।

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

उच्च गुणवत्ता वाले थंबनेल जेनरेट करना

जैसा कि मार्को 13 के उत्तर में बताया गया है, छवि गुणवत्ता के संदर्भ में, क्रिस कैंपबेल द्वारा चित्रित की गई छवि को उनके द पेल्स ऑफ़ इमेज.getScaledInstance () में थंबनेलेटर में कार्यान्वित किया गया है, जिसके परिणामस्वरूप उच्च जटिल थंबनेल बिना किसी जटिल प्रक्रिया की आवश्यकता के लागू होते हैं।

थंबनेलेटर का उपयोग करते हुए मूल प्रश्न में दिखाए गए आतिशबाजी छवि का आकार बदलते समय उत्पन्न थंबनेल निम्न है:

उपरोक्त छवि निम्नलिखित कोड के साथ बनाई गई थी:

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

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

कोड से पता चलता है कि यह इनपुट के रूप में यूआरएल भी स्वीकार कर सकता है, और BufferedImage भी BufferedImage एस बनाने में सक्षम है।

अस्वीकरण: मैं Thumbnailator लाइब्रेरी का रखरखाव हूं।


शोध के दिनों के बाद मैं 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);

आउटपुट यहां है:


नीचे किसी भी बाहरी पुस्तकालय का उपयोग किए बिना प्रगतिशील स्केलिंग का अपना कार्यान्वयन है। उममीद है कि इससे मदद मिलेगी।

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




bufferedimage