Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to compress image? #551

Closed
turboleee opened this issue Sep 9, 2017 · 9 comments
Closed

How to compress image? #551

turboleee opened this issue Sep 9, 2017 · 9 comments
Labels

Comments

@turboleee
Copy link

Could you tell me is there any way to compress image data that I got from wecam?

@sarxos
Copy link
Owner

sarxos commented Sep 9, 2017

Of course there is. You can for example save it as JPEG (https://en.wikipedia.org/wiki/JPEG) using build-in Java ImageIO class.

You can save it directly into file:

BufferedImage image = webcam.getImage();
ImageIO.write(image, "JPG", new File("image.jpg"));

Or save it into byte[] array and store in memory:

BufferedImage image = webcam.getImage();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(image, "JPG", baos);
byte[] data = baos.toByteArray();

It depends on what you would like to do with this compressed image later.

If you want to specify your own JPEG compression rate (instead of using default one from ImageIO), then you should take a look at this:

https://stackoverflow.com/questions/17108234/setting-jpg-compression-level-with-imageio-in-java

@sarxos sarxos added the question label Sep 9, 2017
@sarxos
Copy link
Owner

sarxos commented Sep 14, 2017

I hope I was able to answer your question. I'm closing this issue.

@sarxos sarxos closed this as completed Sep 14, 2017
@turboleee
Copy link
Author

turboleee commented Sep 17, 2017

I still want to get the bufferedimage data but not byte[ ]data ,is that work if I change the byte[ ] data I got into bufferedimage data like this?

public BufferedImage getimageData(BufferedImage image) throws IOException {
			 BufferedImage Im;
	    BufferedImage im=null;
		im = webcam.getImage();
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		ImageIO.write(im, "JPG", baos);
		byte[] data = baos.toByteArray();
		ByteArrayInputStream stream = new ByteArrayInputStream(data);  
        image  = ImageIO.read(stream);
        return image;
}

and if I change the webcam size like this is that means the data I get will change their size like this automatically?

Webcam webcam ;
public WebcamNew (){
    webcam = Webcam.getDefault();
    webcam.setCustomViewSizes(new Dimension[] { new Dimension(1920, 1080) });

@sarxos
Copy link
Owner

sarxos commented Sep 17, 2017

In regards to your questions:

  1. Yes, you can convert BufferedImage into byte[] array and vice-versa, that is byte[] into ByfferedImage, but please note that these two images (the original one and a copy you made from byte[]) will not be the same. There will be very small differences in pixel data because you wrote is as JPEG, which is a lossy compression, and therefore some pixels data is lost forever.
  2. In your code from above post you do not change webcam resolution. You just configure what non-standard resolutions are supported (in your case 1920x1080). To change webcam resolution you must call webcam.setViewSize(new Dimension(1920, 1080)).

Please read this:

https://github.com/sarxos/webcam-capture/raw/master/webcam-capture/src/etc/resources/webcam-capture-handbook.doc

It may be a little outdated, but still helpful.

@sarxos
Copy link
Owner

sarxos commented Sep 17, 2017

I'm still wondering why would you want to convert BufferedImage into byte[] and then back to BufferedImage. These two images will be the same size (because they has the same resolution and the same color space). You waste your CPU cycles and memory to do that.

@turboleee
Copy link
Author

I want to compress my image data ,and in the end I want to get an image date but not byte[ ] data .But I learned from internet that the image date can be compressed only if it changed into byte[ ].So I have no way but to do that .If there is any why can compress my image data directly ,I will not try that way . I hope you can understand my word , as I am a Chanese and poor in English.

@turboleee
Copy link
Author

turboleee commented Sep 17, 2017

My work is to get BufferedImage image and ByteBuffer buf throw two ways . And I also need to compress the BufferedImage image I got into a short one that less than 20000 Bytes. I have tried lots of way buf all failed.this is what I wrote ,could you plese help me with it?

public class WebcamNew implements webcam_interfacenew{
	 
	 Webcam webcam ;
	 public WebcamNew (){
	 webcam = Webcam.getDefault();
	webcam.setViewSize(new Dimension(120, 160));
	}
	
public BufferedImage getimageData(BufferedImage image) throws IOException {
		BufferedImage im=null;
		im = webcam.getImage();
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		ImageIO.write(im, "JPG", baos);
		byte[] data = baos.toByteArray();
		ByteArrayInputStream stream = new ByteArrayInputStream(data);  
        image  = ImageIO.read(stream);
        return image; 
	}
	public ByteBuffer getbufData(ByteBuffer buf) {
		buf = WebcamUtils.getImageByteBuffer(webcam, "JPG");
		return buf;
	}
}

thank you very very much!

@sarxos sarxos changed the title how to compress iamge? How to compress image? Sep 17, 2017
@sarxos
Copy link
Owner

sarxos commented Sep 17, 2017

Ok, @turboleee, please read this whole post and ask if you do not understand what I wrote.

To make it clear - the BufferedImage can be compressed only when you convert it to byte[] array. This newly created byte[] array will contain image data in compressed form.

When you convert it back from byte[] to BufferedImage, then result, that is new BufferedImage will be the same size as before (that is not compressed). If you need compressed image data then you have to convert it to byte[] array. There is no way to have compressed image in form of BufferedImage.

The byte[] array with compressed data can be converted to ByteBuffer. Method to convert byte[] into ByteBuffer:

byte[] bytes = ...
ByteBuffer buffer = ByteBuffer.wrap(bytes);

The only way to make BufferedImage consume less memory is to make it smaller, e.g. convert image with size 640x480 to image with size 320x240. Image size can be changed very easily:

// 640x480
BufferedImage original = webcam.getImage();
// 320x240
BufferedImage smaller = original.getScaledInstance(320, 240, Image.SCALE_SMOOTH); 

If you need to calculate how much memory BufferedImage is consuming, the you can do that with this code:

BufferedImage bi = webcam.getImage();
int howManyBytes = bi.getWidth() * bi.getHeight() * 3;
System.out.println("This image consume "+ howManyBytes + " bytes of memory");

(for example, not compressed image with size 640x480 will consume 921600 bytes in memory)


Below is a code I wrote to compress image with any resolution into byte[] array with a maximum given size:

import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
import javax.imageio.stream.MemoryCacheImageOutputStream;


/**
 * This class will save {@link BufferedImage} into a byte array and compress it so the number of
 * compressed bytes is not higher than a given maximum level. It is not thread-safe.
 *
 * @author Bartosz Firyn (sarxos)
 */
public class AdaptiveSizeWriter {

	private final int max;
	private final ByteArrayOutputStream baos = new ByteArrayOutputStream();

	private float q = 1f; // 1f = 100% quality, at the beginning
	private int w = 0;
	private int h = 0;

	public AdaptiveSizeWriter(int size) {
		this.max = size;
	}

	public byte[] write(BufferedImage bi) {

		final int iw = bi.getWidth();
		final int ih = bi.getHeight();
		if (w != iw || h != ih) {
			w = iw;
			h = ih;
			q = 1f;
		}

		// loop and try to compress until compressed image bytes array is not longer than a given
		// maximum value, reduce quality by 25% in every step

		int size = 0;
		do {
			size = compress(bi, q);
			if (size > max) {
				q *= 0.75;
			}
		} while (size > max);

		return baos.toByteArray();
	}

	/**
	 * Compress {@link BufferedImage} with a given quality into byte array.
	 *
	 * @param bi the {@link BufferedImage} to compres into byte array
	 * @param quality the compressed image quality (1 = 100%, 0.5 = 50%, 0.1 = 10%, etc)
	 * @return The size of compressed data (number of bytes)
	 */
	private int compress(BufferedImage bi, float quality) {

		baos.reset();

		final JPEGImageWriteParam params = new JPEGImageWriteParam(null);
		params.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
		params.setCompressionQuality(quality);

		try (MemoryCacheImageOutputStream mcios = new MemoryCacheImageOutputStream(baos)) {
			final ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg").next();
			writer.setOutput(mcios);
			writer.write(null, new IIOImage(bi, null, null), params);
		} catch (IOException e) {
			throw new IllegalStateException(e);
		}

		final int size = baos.size();

		System.out.printf("Quality %.2f, resolution %dx%d, bytes count = %d\n", quality, bi.getWidth(), bi.getHeight(), size);

		return size;
	}
}

How to use it:

import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

import com.github.sarxos.webcam.Webcam;


public class ExampleWebcam {

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

		final Dimension resolution = new Dimension(1280, 720); // HD720p
		final Webcam webcam = Webcam.getDefault();
		webcam.setCustomViewSizes(new Dimension[] { resolution });
		webcam.setViewSize(resolution);
		webcam.open();

		final BufferedImage bi = webcam.getImage();
		final AdaptiveSizeWriter writer = new AdaptiveSizeWriter(20000); // max number of bytes
		final byte[] data = writer.write(bi);

		try (FileOutputStream fos = new FileOutputStream(new File("example.jpg"))) {
			fos.write(data);
		}
	}
}

I tested it with two resolutions:

  • HD720p (1280x720), the image quality went down to 10% in 9 cycles,
  • VGA (640x480), the image quality went down to 56% in 3 cycles.

Result for HD720p - 1280x720 (9 cycles, 10% quality, 19706 bytes):

x

Result for VGA - 640x480 (3 cycles, 56% quality, 18678 bytes):

y

@turboleee
Copy link
Author

Thank you very much for answer me so particularly ! And I don't know how to express my appreciation to you.
About the question , I guess is that because when we use the byte[] ,we can compress the image batched , but for the image we can just compress the picture one by one ,so it has a lower compression?However my work is not output video , I just need to get a compressed BufferedImage data , and others will use the data I got to do other things.
Anyway I learned a lot from your help, than you very very much!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants