【问题标题】:Improve Histogram改善直方图
【发布时间】:2017-08-29 18:03:13
【问题描述】:

我用这种方法来获取图像的像素值,我用它来比较 1 张图像和 50 张其他图像。然而,它需要永远产生输出。有谁知道我可以加快这种方法的速度吗?将图像转换为灰度是一种更快的方法吗?如果有人可以帮助编写代码,那就太好了!

public static double[] GetHistogram (BufferedImage img) {
    double[] myHistogram = new double [16777216];

     for (int y = 0; y < img.getHeight(); y += 1)
     {
         for (int x = 0; x < img.getWidth(); x += 1) 
         {
              int clr =  img.getRGB(x,y); 

              Color c = new Color(img.getRGB(x, y));

              int pixelIntValue = (int) c.getBlue() * 65536 + c.getGreen() * 256 + c.getRed();

             myHistogram[pixelIntValue]++;

         }

     }
     return myHistogram;
 }

【问题讨论】:

  • 嘿,这两个答案对你有用吗?如果是这样,您应该接受它,以便其他 SO 用户知道它解决了问题。如果您需要调整一些事情或使用不同的解决方案,请发表评论或添加您自己的答案以与他人分享这些知识。

标签: java image-processing histogram grayscale


【解决方案1】:

计算包含 16777216 个类的直方图是很不寻常的。 大多数直方图是针对每个通道分别计算得出的,因此 R、G 和 B 各有 256 个类别的直方图。如果将图像转换为灰度,则只有一个。

我不是 Java 专家。我不知道编译器优化代码有多聪明。 但是您为图像的每一行调用 img.getHeight(),为每一列调用 img.getWidth()。 我不知道实际评估这些表达式的频率,但如果您只使用 2 个变量来分配图像的宽度和高度,然后开始循环,也许您可​​以节省一些处理时间。

您还为每个像素调用了两次 img.getRGB(x,y)。相同的故事。也许只做一次会更快。函数调用通常比从内存中读取变量要慢。

你也应该想想你在这里做什么。 img.getRGB(x,y) 为您提供颜色的整数表示。 然后你将该整数放入一个 contrustor 中以从中生成一个 Color 对象。然后使用 c.getBlue() 等从 Color 对象中获取红色、绿色和蓝色的整数值。只是为了把它重新组合成一个整数?

您可以直接使用 getRGB 的返回值,至少可以节省 4 次函数调用、3 次乘法、3 次求和...

再次考虑到我最后一次编写 Java 是在 10 年前,我的函数看起来更像这样:

public static double[] GetHistogram (BufferedImage img) {

double[] myHistogram = new double [16777216];

 int width = img.getWidth()
 int height = img.getHeight()
 for (int y = 0; y < height; y += 1)
 {
     for (int x = 0; x < width; x += 1) 
     {
          int clr =  img.getRGB(x,y);  

         myHistogram[clr]++;

     }

 }
 return myHistogram;

}

当然,数组类型和大小是不正确的,整个 16777216 类直方图没有意义,但也许这有助于加快速度。 我只需使用位掩码从该整数中获取红色、绿色和蓝色值并创建三个直方图。

【讨论】:

  • 如果有 Java 大师可以纠正我在 Java 代码中犯的小错误 :)
  • 如果您将 R、G 和 B 直方图分开,您将无法区分具有两个红色像素和两个绿色像素的图像与具有两个黄色像素和两个黑色像素的图像。
  • ...但是您确实对所有无关的函数调用提出了很好的观点。
【解决方案2】:

TLDR:使用较小的图像并阅读this 论文。

您应该尝试消除@Piglet 提到的任何不必要的函数调用,但您绝对应该将颜色保留在一个直方图中,而不是 R、G 和 B 的单独直方图。除了摆脱额外的函数调用之外,我认为你可以做四件事来加速你的算法——创建和比较直方图——并减少内存使用(因为更少的页面缓存意味着更少的磁盘抖动和更快的速度)。


使用较小的图片

颜色直方图索引的优点之一是它相对独立于分辨率。物体的颜色不会随着图像的大小而改变。显然,这是有局限性的——想象一下尝试使用 1×1 图像匹配对象。但是,如果您的图像有数百万像素(就像现在大多数智能手机中的图像),您绝对应该调整它的大小。这些作者发现,只有 16×11 的图像分辨率仍能产生非常好的结果 [see page 17],但即使将尺寸缩小到 ~100×100 像素,仍应提供显着的加速效果。

BufferedImageImage 继承方法getScaledInstance,您可以使用它来获得更小的图像。

double scalingFactor = 0.25; //You need to choose this value to work with your images
int aSmallHeight = myBigImage.getHeight() * scalingFactor;
int aSmallWidth = myBigImage.getWidth() * scalingFactor;
Image smallerImage = myBigImage.getScaledInstance(aSmallWidth, aSmallHeight, SCALE_FAST);

减小图像大小是加快算法速度的最有效的方法。如果你什么都不做,至少要这样做。


使用每个颜色通道的更少信息

这不会对生成直方图产生太大影响,因为它实际上需要更多的计算,但它会显着加快比较直方图的速度。总体思路称为quantization。基本上,如果您有 0..255 范围内的红色值,它们可以表示为一个字节。在该字节中,某些位比其他位更重要。

考虑这个彩色示例图像。我在左上角放置了一个几乎任意的红色阴影,在其他每个角落,我忽略了红色通道中的一个或多个位(由颜色字节中的下划线表示)。我故意选择了一种颜色,其中包含很多位,以便我可以显示忽略位的“最坏”情况。 (“最佳”情况,当我们忽略零位时,对颜色没有影响。)

即使我们忽略了一点,右上角和左上角也没有太大区别。即使我们忽略了 3 位,左上角和左下角也有明显的差异,但差异很小。左上角和右下角非常不同,尽管我们只忽略了一位,因为它是最重要的位。通过战略性地忽略不太重要的位,您可以减小直方图的大小,这意味着 JVM 移动的次数更少,并且在比较它们时也更少的 bin。

这里有一些可靠的数字。目前,您有 28×28×28 = 16777216 个 bin。如果您忽略每个颜色通道中的 3 个最低有效位,您将得到 25×25×25 = 32768 个 bin,是您当前使用的 bin 数量的 1/512。您可能需要对您的一组图像进行试验,以了解何种程度的量化仍能产生可接受的结果。

量化实现起来非常简单。您可以通过执行位移操作来忽略最右边的位。

int numBits = 3;
int quantizedRed = pixelColor.getRed() >> numBits;
int quantizedGreen = pixelColor.getGreen() >> numBits;
int quantizedBlue = pixelColor.getBlue() >> numBits;


使用不同的色彩空间

虽然灰度可能更快,但您不应该使用灰度,因为这样会丢失所有颜色信息。当您使用颜色直方图匹配对象时,实际的色调或色度比某物的亮度或暗度更重要。 (其中一个原因是,照明强度可能在图像之间甚至在图像之间发生变化。)您可以使用其他颜色表示,而不需要使用 3 个颜色通道。

例如,L*a*b* (see also this) 使用一个通道 (L) 对亮度进行编码,并使用两个通道 (a, b) 对颜色进行编码。 a 和 b 通道的范围从 -100 到 100,因此如果您只使用 a 和 b 创建直方图,则只需要 40000 个 bin。只有 a 和 b 的直方图的缺点是您失去了记录黑白像素的能力。对于您的算法,其他颜色空间各有优缺点。

颜色空间之间的转换通常不是很困难,因为在 Internet 上可以免费获得许多现有的颜色空间转换功能实现。例如,这里是Java conversion from RGB to L*a*b*

如果您确实选择使用不同的色彩空间,也要小心使用量化。您应该在进行色彩空间转换后应用任何量化,并且您将需要测试不同的量化级别,因为新的色彩空间可能比 RGB 对量化更敏感或更不敏感。我的偏好是将图像保留为 RGB,因为量化在减少 bin 数量方面已经非常有效。


使用不同的数据类型

我做了一些调查,发现BufferedImage 将图像存储为Raster,它使用SampleModel 来描述像素如何存储在数据缓冲区中。这意味着仅检索一个像素的值就有很多开销。如果您的图像存储为byte[]int[],您将获得更快的结果。您可以使用

获取 byte 数组
byte[] pixels = ((DataBufferByte) bufferedImage.getRaster().getDataBuffer()).getData();

请参阅the answer to this previous question 了解更多信息和一些将其转换为二维数组的示例代码。

最后这件事可能没有太大区别,但我注意到您使用double 来存储您的直方图。您应该考虑 int 是否可以代替。在 Java 中,int 的最大值大于 20 亿,因此溢出应该不是问题(除非您正在制作超过 20 亿像素的图像的直方图,在这种情况下,请参阅我的第一点)。 int 使用的内存只有 double 的一半(当您有数千或数百万个直方图箱时,这很重要),并且对于许多数学运算,它们可以更快(尽管这取决于您的硬件)。


如果您想了解更多关于用于对象匹配的颜色直方图的信息,请直接访问源代码并阅读 1991 年以来的 Swain and Ballard's Color Indexing paper

【讨论】:

    猜你喜欢
    • 2014-01-15
    • 1970-01-01
    • 1970-01-01
    • 2020-09-10
    • 2014-08-09
    • 2016-11-30
    • 1970-01-01
    • 2020-10-18
    • 2019-01-28
    相关资源
    最近更新 更多