提高从 BufferedImage 生成的灰度图像的质量
Increase quality of grayscale image produced from BufferedImage
我正在尝试提高从 BufferedImage 生成的图像的质量。最终目标是输入 JPEG(这里是从计算机上的文件中检索),转换为灰度 TIFF,然后输出为字节数组。我包含了将最终图像保存到 PC 的代码,因此更容易辨别问题。
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.IndexColorModel;
import java.awt.image.MultiPixelPackedSampleModel;
import java.awt.image.SampleModel;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
import javax.media.jai.ImageLayout;
import javax.media.jai.JAI;
import javax.media.jai.KernelJAI;
import javax.media.jai.LookupTableJAI;
import javax.media.jai.PlanarImage;
import javax.media.jai.operator.ErrorDiffusionDescriptor;
public class ByteConversionService {
private static ByteArrayOutputStream baos;
private static ImageWriter writer;
private static ImageOutputStream ios;
private static ImageWriteParam writeParam;
public static void main(String args[]) {
try {
convertBufferedImageToByteArray();
} catch (Exception e) {
}
}
private static byte[] convertBufferedImageToByteArray()
throws Exception {
byte[] convertedByteArray = null;
resourceSetup();
try {
File file = new File("../proj/src/image.jpg");
BufferedImage image = ImageIo.read(file);
convertImageToTif(image);
createImage(baos);
convertedByteArray = baos.toByteArray();
} finally {
resourceCleanup();
}
return convertedByteArray;
}
private static void resourceSetup() throws Exception {
baos = new ByteArrayOutputStream();
writer = ImageIO.getImageWritersByFormatName(
"tif").next();
ios = ImageIO.createImageOutputStream(baos);
writer.setOutput(ios);
writeParam = writer.getDefaultWriteParam();
writeParamSetUp(writeParam);
}
private static void writeParamSetUp(ImageWriteParam writeParam) {
writeParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
writeParam.setCompressionType("CCITT T.4");
}
private static void convertImageToTif(BufferedImage image) throws Exception {
try {
BufferedImage blackAndWhiteImage = imageToBlackAndWhite(image);
writeToByteArrayStream(blackAndWhiteImage);
IIOImage iioImage = new IIOImage(blackAndWhiteImage, null, null);
writer.write(null, iioImage, writeParam);
} finally {
image.flush();
}
}
private static BufferedImage imageToBlackAndWhite(BufferedImage image) {
PlanarImage surrogateImage = PlanarImage.wrapRenderedImage(image);
LookupTableJAI lut = new LookupTableJAI(new byte[][] {
{ (byte) 0x00, (byte) 0xff }, { (byte) 0x00, (byte) 0xff },
{ (byte) 0x00, (byte) 0xff } });
ImageLayout layout = new ImageLayout();
byte[] map = new byte[] { (byte) 0x00, (byte) 0xff };
ColorModel cm = new IndexColorModel(1, 2, map, map, map);
layout.setColorModel(cm);
SampleModel sm = new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE,
surrogateImage.getWidth(), surrogateImage.getHeight(), 1);
layout.setSampleModel(sm);
RenderingHints hints = new RenderingHints(JAI.KEY_IMAGE_LAYOUT, layout);
PlanarImage op = ErrorDiffusionDescriptor.create(surrogateImage, lut,
KernelJAI.ERROR_FILTER_FLOYD_STEINBERG, hints);
BufferedImage blackAndWhiteImage = op.getAsBufferedImage();
return blackAndWhiteImage;
}
private static void writeToByteArrayStream(BufferedImage image) throws Exception {
ImageIO.write(image, "tif", baos);
}
private static void createImage(ByteArrayOutputStream baos) throws Exception {
ByteArrayInputStream bis = new ByteArrayInputStream(baos.toByteArray());
ImageReader reader = (ImageReader) ImageIO.getImageReadersByFormatName(
"tif").next();
Object source = bis;
ImageInputStream iis = ImageIO.createImageInputStream(source);
reader.setInput(iis, true);
ImageReadParam param = reader.getDefaultReadParam();
Image image = reader.read(0, param);
BufferedImage buffered = new BufferedImage(image.getWidth(null),
image.getHeight(null), BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = buffered.createGraphics();
g2.drawImage(image, null, null);
File file = new File("../proj/src/image2.tif");
ImageIO.write(buffered, "tif", file);
}
private static void resourceCleanup() throws Exception {
ios.flush();
ios.close();
baos.flush();
baos.close();
writer.dispose();
}
}
当前的问题是最终图像的质量很差 - 放大显示构成图片的像素之间有很多白色 space。我的理解是这可能是由于使用了抖动算法 (Floyd-Steinberg),因此图像在技术上不是以灰度再现的。
我尝试了多种解决方案,我会在评论中post,但没有成功。我的问题是我当前的解决方案是否可以提高最终质量,或者我的灰度转换是否存在缺陷并且 imageToBlackAndWhite
方法不符合我的需要。
现在我们已经确定所需的结果确实是灰度图像,我们可以修复代码以便生成灰度 TIFF。
需要更改两件事,首先是颜色 space 从 RGB 到灰色的转换:
private static BufferedImage imageToBlackAndWhite(BufferedImage image) {
ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
ColorConvertOp op = new ColorConvertOp(cs, null);
return op.filter(image, null);
}
我更喜欢 ColorConvertOp
,因为它是最多的 "correct",并且在大多数平台上使用本机代码。但是您列出的任何其他方法也应该有效。为清楚起见,您可能还需要考虑将该方法重命名为 imageToGrayScale
。
此外,您需要更改 TIFF 压缩设置,因为 CCITT T.4 压缩只能用于二进制 black/white 图像(它是为传真传输创建的)。如果您可以接受有损压缩,我建议您使用 Deflate 或 LZW 压缩,或者 JPEG。这些都适用于灰度数据:
private static void writeParamSetUp(ImageWriteParam writeParam) {
writeParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
writeParam.setCompressionType("Deflate"); // or LZW or JPEG
}
PS:您还应该摆脱 writeToByteArrayStream
方法,因为您当前的代码将 TIFF 写入两次,一次使用 ImageIO.write(...)
解压缩,然后使用 [=17] 压缩一次=].
PPS:createImage
方法也可以简化很多,因为 ByteArrayOutputStream
已经包含完整的 TIFF。
private static void createImage(ByteArrayOutputStream baos) throws Exception {
File file = new File("../proj/src/image2.tif");
Files.write(file.toPath(), baos.toByteArray(), StandardOpenOption.CREATE);
}
我正在尝试提高从 BufferedImage 生成的图像的质量。最终目标是输入 JPEG(这里是从计算机上的文件中检索),转换为灰度 TIFF,然后输出为字节数组。我包含了将最终图像保存到 PC 的代码,因此更容易辨别问题。
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.IndexColorModel;
import java.awt.image.MultiPixelPackedSampleModel;
import java.awt.image.SampleModel;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
import javax.media.jai.ImageLayout;
import javax.media.jai.JAI;
import javax.media.jai.KernelJAI;
import javax.media.jai.LookupTableJAI;
import javax.media.jai.PlanarImage;
import javax.media.jai.operator.ErrorDiffusionDescriptor;
public class ByteConversionService {
private static ByteArrayOutputStream baos;
private static ImageWriter writer;
private static ImageOutputStream ios;
private static ImageWriteParam writeParam;
public static void main(String args[]) {
try {
convertBufferedImageToByteArray();
} catch (Exception e) {
}
}
private static byte[] convertBufferedImageToByteArray()
throws Exception {
byte[] convertedByteArray = null;
resourceSetup();
try {
File file = new File("../proj/src/image.jpg");
BufferedImage image = ImageIo.read(file);
convertImageToTif(image);
createImage(baos);
convertedByteArray = baos.toByteArray();
} finally {
resourceCleanup();
}
return convertedByteArray;
}
private static void resourceSetup() throws Exception {
baos = new ByteArrayOutputStream();
writer = ImageIO.getImageWritersByFormatName(
"tif").next();
ios = ImageIO.createImageOutputStream(baos);
writer.setOutput(ios);
writeParam = writer.getDefaultWriteParam();
writeParamSetUp(writeParam);
}
private static void writeParamSetUp(ImageWriteParam writeParam) {
writeParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
writeParam.setCompressionType("CCITT T.4");
}
private static void convertImageToTif(BufferedImage image) throws Exception {
try {
BufferedImage blackAndWhiteImage = imageToBlackAndWhite(image);
writeToByteArrayStream(blackAndWhiteImage);
IIOImage iioImage = new IIOImage(blackAndWhiteImage, null, null);
writer.write(null, iioImage, writeParam);
} finally {
image.flush();
}
}
private static BufferedImage imageToBlackAndWhite(BufferedImage image) {
PlanarImage surrogateImage = PlanarImage.wrapRenderedImage(image);
LookupTableJAI lut = new LookupTableJAI(new byte[][] {
{ (byte) 0x00, (byte) 0xff }, { (byte) 0x00, (byte) 0xff },
{ (byte) 0x00, (byte) 0xff } });
ImageLayout layout = new ImageLayout();
byte[] map = new byte[] { (byte) 0x00, (byte) 0xff };
ColorModel cm = new IndexColorModel(1, 2, map, map, map);
layout.setColorModel(cm);
SampleModel sm = new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE,
surrogateImage.getWidth(), surrogateImage.getHeight(), 1);
layout.setSampleModel(sm);
RenderingHints hints = new RenderingHints(JAI.KEY_IMAGE_LAYOUT, layout);
PlanarImage op = ErrorDiffusionDescriptor.create(surrogateImage, lut,
KernelJAI.ERROR_FILTER_FLOYD_STEINBERG, hints);
BufferedImage blackAndWhiteImage = op.getAsBufferedImage();
return blackAndWhiteImage;
}
private static void writeToByteArrayStream(BufferedImage image) throws Exception {
ImageIO.write(image, "tif", baos);
}
private static void createImage(ByteArrayOutputStream baos) throws Exception {
ByteArrayInputStream bis = new ByteArrayInputStream(baos.toByteArray());
ImageReader reader = (ImageReader) ImageIO.getImageReadersByFormatName(
"tif").next();
Object source = bis;
ImageInputStream iis = ImageIO.createImageInputStream(source);
reader.setInput(iis, true);
ImageReadParam param = reader.getDefaultReadParam();
Image image = reader.read(0, param);
BufferedImage buffered = new BufferedImage(image.getWidth(null),
image.getHeight(null), BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = buffered.createGraphics();
g2.drawImage(image, null, null);
File file = new File("../proj/src/image2.tif");
ImageIO.write(buffered, "tif", file);
}
private static void resourceCleanup() throws Exception {
ios.flush();
ios.close();
baos.flush();
baos.close();
writer.dispose();
}
}
当前的问题是最终图像的质量很差 - 放大显示构成图片的像素之间有很多白色 space。我的理解是这可能是由于使用了抖动算法 (Floyd-Steinberg),因此图像在技术上不是以灰度再现的。
我尝试了多种解决方案,我会在评论中post,但没有成功。我的问题是我当前的解决方案是否可以提高最终质量,或者我的灰度转换是否存在缺陷并且 imageToBlackAndWhite
方法不符合我的需要。
现在我们已经确定所需的结果确实是灰度图像,我们可以修复代码以便生成灰度 TIFF。
需要更改两件事,首先是颜色 space 从 RGB 到灰色的转换:
private static BufferedImage imageToBlackAndWhite(BufferedImage image) {
ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
ColorConvertOp op = new ColorConvertOp(cs, null);
return op.filter(image, null);
}
我更喜欢 ColorConvertOp
,因为它是最多的 "correct",并且在大多数平台上使用本机代码。但是您列出的任何其他方法也应该有效。为清楚起见,您可能还需要考虑将该方法重命名为 imageToGrayScale
。
此外,您需要更改 TIFF 压缩设置,因为 CCITT T.4 压缩只能用于二进制 black/white 图像(它是为传真传输创建的)。如果您可以接受有损压缩,我建议您使用 Deflate 或 LZW 压缩,或者 JPEG。这些都适用于灰度数据:
private static void writeParamSetUp(ImageWriteParam writeParam) {
writeParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
writeParam.setCompressionType("Deflate"); // or LZW or JPEG
}
PS:您还应该摆脱 writeToByteArrayStream
方法,因为您当前的代码将 TIFF 写入两次,一次使用 ImageIO.write(...)
解压缩,然后使用 [=17] 压缩一次=].
PPS:createImage
方法也可以简化很多,因为 ByteArrayOutputStream
已经包含完整的 TIFF。
private static void createImage(ByteArrayOutputStream baos) throws Exception {
File file = new File("../proj/src/image2.tif");
Files.write(file.toPath(), baos.toByteArray(), StandardOpenOption.CREATE);
}