image.getRaster().getDataBuffer() returns 负值数组

image.getRaster().getDataBuffer() returns array of negative values

answer suggests that it's over 10 times faster to loop pixel array 而不是使用 BufferedImage.getRGB。这种差异太重要了,不能在我的计算机视觉程序中忽略。出于这个原因,O 重写了我的 IntegralImage 使用像素数组计算积分图像的方法:

/* Generate an integral image. Every pixel on such image contains sum of colors or all the
     pixels before and itself.
  */
  public static double[][][] integralImage(BufferedImage image) {
    //Cache width and height in variables
    int w = image.getWidth();
    int h = image.getHeight();
    //Create the 2D array as large as the image is
    //Notice that I use [Y, X] coordinates to comply with the formula
    double integral_image[][][] = new double[h][w][3];

    //Variables for the image pixel array looping
    final int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
    //final byte[] pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
    //If the image has alpha, there will be 4 elements per pixel
    final boolean hasAlpha = image.getAlphaRaster() != null;
    final int pixel_size = hasAlpha?4:3;
    //If there's alpha it's the first of 4 values, so we skip it
    final int pixel_offset = hasAlpha?1:0;
    //Coordinates, will be iterated too
    //It's faster than calculating them using % and multiplication
    int x=0;
    int y=0;

    int pixel = 0;
    //Tmp storage for color
    int[] color = new int[3];
    //Loop through pixel array
    for(int i=0, l=pixels.length; i<l; i+=pixel_size) {
      //Prepare all the colors in advance
      color[2] = ((int) pixels[pixel + pixel_offset] & 0xff); // blue;
      color[1] = ((int) pixels[pixel + pixel_offset + 1] & 0xff); // green;
      color[0] = ((int) pixels[pixel + pixel_offset + 2] & 0xff); // red;
      //For every color, calculate the integrals
      for(int j=0; j<3; j++) {
        //Calculate the integral image field
        double A = (x > 0 && y > 0) ? integral_image[y-1][x-1][j] : 0;
        double B = (x > 0) ? integral_image[y][x-1][j] : 0;
        double C = (y > 0) ? integral_image[y-1][x][j] : 0;
        integral_image[y][x][j] = - A + B + C + color[j];
      }
      //Iterate coordinates
      x++;
      if(x>=w) {
        x=0;
        y++;        
      }
    }
    //Return the array
    return integral_image;
  }

问题是,如果我在 for 循环中使用此调试输出:

  if(x==0) {
    System.out.println("rgb["+pixels[pixel+pixel_offset+2]+", "+pixels[pixel+pixel_offset+1]+", "+pixels[pixel+pixel_offset]+"]");
    System.out.println("rgb["+color[0]+", "+color[1]+", "+color[2]+"]");
  }

这是我得到的:

rgb[0, 0, 0]
rgb[-16777216, -16777216, -16777216]
rgb[0, 0, 0]
rgb[-16777216, -16777216, -16777216]
rgb[0, 0, 0]
rgb[-16777216, -16777216, -16777216]
rgb[0, 0, 0]
rgb[-16777216, -16777216, -16777216]
rgb[0, 0, 0]
rgb[-16777216, -16777216, -16777216]
rgb[0, 0, 0]
rgb[-16777216, -16777216, -16777216]
rgb[0, 0, 0]
rgb[-16777216, -16777216, -16777216]
rgb[0, 0, 0]
...

那么我应该如何正确检索 BufferedImage 图像的像素阵列?

上面代码中的一个很容易被忽略的错误是 for 循环没有像您预期的那样循环。 for 循环更新 i,而循环体使用 pixel 作为其数组索引。因此,您只会看到像素 1、2 和 3 的值。


除此之外:

具有负像素值的 "problem" 很可能是代码假定 BufferedImage 以 "pixel interleaved" 形式存储其像素,但是,它们存储 "pixel packed".也就是说,一个像素的所有样本(R、G、B 和 A)都存储在一个样本中,即一个 int。所有 BufferedImage.TYPE_INT_* 类型都是这种情况(而 BufferedImage.TYPE_nBYTE_* 类型是交错存储的)。

光栅中有负值是完全正常的,任何透明度小于 50%(不透明大于或等于 50%)的像素都会发生这种情况,因为 4 个样本是如何打包到int,因为 int 是 Java 中的有符号类型。

在这种情况下,使用:

int[] color = new int[3];

for (int i = 0; i < pixels.length; i++) {
    // Assuming TYPE_INT_RGB, TYPE_INT_ARGB or TYPE_INT_ARGB_PRE
    // For TYPE_INT_BGR, you need to reverse the colors.

    // You seem to ignore alpha, is that correct?
    color[0] = ((pixels[i] >> 16) & 0xff); // red;
    color[1] = ((pixels[i] >>  8) & 0xff); // green;
    color[2] = ( pixels[i]        & 0xff); // blue;

    // The rest of the computations...
}

另一种可能性是,您创建了一个自定义类型图像 (BufferedImage.TYPE_CUSTOM),每个样本实际上使用 32 位 unsigned int。这是可能的,但是,int 仍然是 Java 中的签名实体,因此您需要屏蔽掉符号位。使这个稍微复杂一点,在 Java -1 & 0xFFFFFFFF == -1 中,因为对 int 的任何计算仍然是 int,除非你明确说明(在 byteshort 值将具有 "scaled up" 到 int)。要获得正值,您需要使用这样的 long 值:-1 & 0xFFFFFFFFL(即 4294967295)。

在这种情况下,使用:

long[] color = new long[3];

for(int i = 0; i < pixels.length / pixel_size; i += pixel_size) {
    // Somehow assuming BGR order in input, and RGB output (color)
    // Still ignoring alpha
    color[0] = (pixels[i + pixel_offset + 2] & 0xFFFFFFFFL); // red;
    color[1] = (pixels[i + pixel_offset + 1] & 0xFFFFFFFFL); // green;
    color[2] = (pixels[i + pixel_offset    ] & 0xFFFFFFFFL); // blue;

    // The rest of the computations...
}

我不知道你的图像是什么类型,所以我不能确定哪一张有问题,但它是其中之一。 :-)

PS:BufferedImage.getAlphaRaster() 可能是一种昂贵且不准确的判断图像是否具有 alpha 的方法。最好只使用 image.getColorModel().hasAlpha()。另见 hasAlpha vs getAlphaRaster.