简体   繁体   English

将 TYPE_INT_RGB 转换为 TYPE_BYTE_GRAY 图像会产生错误的结果

[英]Converting TYPE_INT_RGB to TYPE_BYTE_GRAY image creates wrong result

I'm trying to convert a grayscale image in 24-bit RGB format to a grayscale image in 8-bit format.我正在尝试将 24 位 RGB 格式的灰度图像转换为 8 位格式的灰度图像。 In other words, input and output should be visually identical, only the number of channels changes.也就是说,input和output在视觉上应该是一致的,只是通道数有变化。 Here's the input image:这是输入图像:

输入

The code used to convert it to 8-bit:用于将其转换为 8 位的代码:

File input = new File("input.jpg");
File output = new File("output.jpg");

// Read 24-bit RGB input JPEG.
BufferedImage rgbImage = ImageIO.read(input);
int w = rgbImage.getWidth();
int h = rgbImage.getHeight();

// Create 8-bit gray output image from input.
BufferedImage grayImage = new BufferedImage(w, h, BufferedImage.TYPE_BYTE_GRAY);
int[] rgbArray = rgbImage.getRGB(0, 0, w, h, null, 0, w);
grayImage.setRGB(0, 0, w, h, rgbArray, 0, w);

// Save output.
ImageIO.write(grayImage, "jpg", output);

And here's the output image:这是 output 图像:

输出

As you can see, there's a slight difference.如您所见,存在细微差别。 But they should be identical.但它们应该是相同的。 For those who can't see it, here's the difference between the two images (when viewed with Difference blending mode in Gimp, full black would indicate no difference).对于那些看不到它的人,这是两张图像之间的差异(在 Gimp 中使用差异混合模式查看时,全黑表示没有差异)。 The same problem happens if I use PNG instead for input and output.如果我使用 PNG 代替输入和 output,也会出现同样的问题。

After doing grayImage.setRGB , I tried comparing color values for the same pixel in both images:在做grayImage.setRGB之后,我尝试比较两个图像中相同像素的颜色值:

int color1 = rgbImage.getRGB(230, 150);  // This returns 0xFF6D6D6D.
int color2 = grayImage.getRGB(230, 150);  // This also returns 0xFF6D6D6D.

Same color for both.两者颜色相同。 However, if I do the same comparison with the images in Gimp, I get 0xFF6D6D6D and 0xFF272727 respectively... huge difference.但是,如果我与 Gimp 中的图像进行相同的比较,我会分别得到0xFF6D6D6D0xFF272727 ......巨大的差异。

What's happening here?这里发生了什么事? Is there any way I can obtain an identical 8-bit image from a grayscale 24-bit image?有什么方法可以从灰度 24 位图像中获得相同的 8 位图像? I'm using Oracle JDK 1.8 for the record.我正在使用 Oracle JDK 1.8 作为记录。

First two things I tested, I printed out the two images.我测试的前两件事,我打印了两张图片。

BufferedImage@544fa968: type = 5 ColorModel: #pixelBits = 24 numComponents = 3 color space = java.awt.color.ICC_ColorSpace@68e5eea7 transparency = 1 has alpha = false isAlphaPre = false ByteInterleavedRaster: width = 400 height = 400 #numDataElements 3 dataOff[0] = 2 BufferedImage@544fa968: type = 5 ColorModel: #pixelBits = 24 numComponents = 3 color space = java.awt.color.ICC_ColorSpace@68e5eea7 透明度 = 1 has alpha = false isAlphaPre = false ByteInterleavedRaster: width = 400 3 height = 400 datanumData [0] = 2

BufferedImage@11fc564b: type = 10 ColorModel: #pixelBits = 8 numComponents = 1 color space = java.awt.color.ICC_ColorSpace@394a2528 transparency = 1 has alpha = false isAlphaPre = false ByteInterleavedRaster: width = 400 height = 400 #numDataElements 1 dataOff[0] = 0 BufferedImage@11fc564b: type = 10 ColorModel: #pixelBits = 8 numComponents = 1 color space = java.awt.color.ICC_ColorSpace@394a2528 透明度 = 1 has alpha = false isAlphaPre = false ByteInterleavedRaster: width = 400 height = OffData 400 #numElement 数据[0] = 0

We can see the images have a different color space, and the data offset is different.我们可以看到图像有不同的色彩空间,数据偏移也不同。

And I used a graphics to draw the original image on the output.并且我用图形在output上绘制了原图。

Graphics g = grayImage.getGraphics();
g.drawImage(rgbImage, 0, 0, null);

This worked fine.这工作得很好。 I saved the image as png, not that it changes the effect your seeing, and when I took a difference between the two images, They were the same.我将图像保存为 png,并不是说它会改变您看到的效果,当我在两张图像之间进行区分时,它们是相同的。

Bottom line is, the rgb values are different for the two different image types.底线是,两种不同图像类型的 rgb 值不同。 So while you see the same value with get rgb, they're interpreted as different values when they're displayed.因此,当您使用 get rgb 看到相同的值时,它们在显示时被解释为不同的值。

Using the graphics is a bit slower, but it gets the correct image out.使用图形有点慢,但它会得到正确的图像。

I think a distinction here is setRGB/getRGB are operating on the data in a non-intuitive way.我认为这里的一个区别是 setRGB/getRGB 以非直观的方式对数据进行操作。

DataBuffer rgbBuffer = rgbImage.getRaster().getDataBuffer();
DataBuffer grayBuffer = grayImage.getRaster().getDataBuffer();

System.out.println(grayBuffer.size() + ", " + rgbBuffer.size() );
for(int i = 0; i<10; i++){
    System.out.println(
        grayBuffer.getElem(i) + "\t"
        + rgbBuffer.getElem(3*i) + ", " 
        + rgbBuffer.getElem(3*i+1) + ", " 
        + rgbBuffer.getElem(3*i + 2) );
}

Shows data that we expect.显示我们预期的数据。 The rgb buffer is 3x's the size, the pixels correspond directly. rgb缓冲区是3x的大小,像素直接对应。

160000, 480000 160000, 480000
255 255, 255, 255 255 255、255、255
255 255, 255, 255 255 255、255、255
254 254, 254, 254 254 254、254、254
253 253, 253, 253 253 253、253、253
252 252, 252, 252 252 252、252、252
252 252, 252, 252 252 252、252、252
251 251, 251, 251 251 251、251、251
251 251, 251, 251 251 251、251、251
250 250, 250, 250 250 250、250、250
250 250, 250, 250 250 250、250、250

When we check the corresponding rgb values.当我们检查相应的 rgb 值时。

for(int i = 0; i<10; i++){
    System.out.println( 
        Integer.toHexString( grayImage.getRGB(i, 0) ) + ", "
        +  Integer.toHexString( rgbImage.getRGB(i, 0) ) + "  " );
}

ffffffff, ffffffff噗噗噗噗噗
ffffffff, ffffffff噗噗噗噗噗
ffffffff, fffefefe噗噗噗噗噗
fffefefe, fffdfdfd fffefefe, fffdfdfd
fffefefe, fffcfcfc fffefefe, fffcfcfc
fffefefe, fffcfcfc fffefefe, fffcfcfc
fffdfdfd, fffbfbfb fffdfdfd, fffbfbfb
fffdfdfd, fffbfbfb fffdfdfd, fffbfbfb
fffdfdfd, fffafafa fffdfdfd, fffafafa
fffdfdfd, fffafafa fffdfdfd, fffafafa

So for the image to be correct, it has to have different rgb values.因此,要使图像正确,它必须具有不同的 rgb 值。

I dived a little into Open JDK implementation and found this:我深入研究了 Open JDK 实现并发现了这一点:

When calling setRGB , values are modified by the image color model.调用setRGB时,值由图像颜色 model 修改。 In this case, the following formula was being applied:在这种情况下,应用了以下公式:

float red = fromsRGB8LUT16[red] & 0xffff;
float grn = fromsRGB8LUT16[grn] & 0xffff;
float blu = fromsRGB8LUT16[blu] & 0xffff;
float gray = ((0.2125f * red) +
              (0.7154f * grn) +
              (0.0721f * blu)) / 65535.0f;
intpixel[0] = (int) (gray * ((1 << nBits[0]) - 1) + 0.5f);

This basically tries to find the luminosity of a given color to find its gray shade.这基本上试图找到给定颜色的亮度以找到它的灰色阴影。 But with my values already being gray, this should give the same gray shade, right?但是由于我的值已经是灰色的,这应该给出相同的灰色阴影,对吧? 0.2125 + 0.7154 + 0.0721 = 1 so with an input of 0xFF1E1E1E should result in a gray value of 0xFE . 0.2125 + 0.7154 + 0.0721 = 1所以输入0xFF1E1E1E应该导致灰度值0xFE

Except, the fromsRGB8LUT16 array used doesn't map values linearly... Here's a plot I made:除了,使用的fromsRGB8LUT16数组不是 map 值线性...这是我制作的 plot :

在此处输入图像描述

So an input of 0xFF1E1E1E actually results in a gray value of 0x03 , I'm not entirely sure why it's not linear.所以0xFF1E1E1E的输入实际上会导致0x03的灰度值,我不完全确定为什么它不是线性的。 but it certainly explains why my output image was so dark compared with the original.但它确实解释了为什么我的 output 图像与原始图像相比如此暗。

Using Graphics2D works for the example I gave.使用Graphics2D适用于我给出的示例。 But this example had been simplified and in reality I needed to tweak some values, so I can't used Graphics2D .但是这个例子已经被简化了,实际上我需要调整一些值,所以我不能使用Graphics2D Here's the solution I found.这是我找到的解决方案。 We completely avoid the color model remapping the values and instead sets them directly on the raster.我们完全避免颜色 model 重新映射值,而是直接在栅格上设置它们。

BufferedImage grayImage = new BufferedImage(w, h, BufferedImage.TYPE_BYTE_GRAY);
int[] rgbArray = buffImage.getRGB(0, 0, w, h, null, 0, w);
grayImage.getRaster().setPixels(0, 0, w, h, rgbArray);

Why does this work?为什么这行得通? An image of type TYPE_BYTE_ARRAY has a raster of type ByteInterleavedRaster where data is stored in byte[] and each pixel value take a single byte. TYPE_BYTE_ARRAY类型的图像具有ByteInterleavedRaster类型的栅格,其中数据存储在byte[]中,每个像素值占用一个字节。 When calling setPixels on the raster, the values of the passed array are simply cast to a byte.在光栅上调用setPixels时,传递的数组的值简单地转换为一个字节。 So 0xFF1E1E1E effectively becomes 0x1E (only lowest bits are kept), which is what I wanted.所以0xFF1E1E1E实际上变成了0x1E (只保留最低位),这就是我想要的。

EDIT: I just saw this question and apparently the non linearity is just part of the standard formula.编辑:我刚看到这个问题,显然非线性只是标准公式的一部分。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM