5

I have a 2d array of depth values and need a fast way to find the maximum value within a given rectangular region. Many rectangles will be tested against a given depth buffer so a reasonable preprocess step is acceptable.

The naive approach would be to scan over each pixel in the rectangle keeping track of the max, requiring width * height iterations.

By first creating a quadtree of the depth buffer where each parent node contains the maximum value of its children the complexity can be reduced to approximately width + height iterations. This method is good but i would like to know if it can be done even faster.

I have given an example of a method for finding the average value, rather than the max value in constant time by using a linear time preprocess here.

Does anyone know of a similar technique for finding the maximum value?

Khan Maxfield
  • 399
  • 1
  • 10
  • Is the size of the rectangle variable ? –  Aug 04 '16 at 07:09
  • 2
    There is a major difference between the max operation and addition: the max is irreversible. After taking max(a, b), one of the two numbers is lost forever. This is by contrast with a+b, such that you can retrieve b knowing a. For this reason, the "integral image" trick cannot be used. –  Aug 04 '16 at 07:58
  • 2
    Probably ["Two-Dimensional Range Minimum Queries"](https://www.researchgate.net/publication/221314133_Two-Dimensional_Range_Minimum_Queries) is what you are looking for. – Evgeny Kluev Aug 04 '16 at 09:16

2 Answers2

1

Yes, you can generalize the trick of the average, but only for small color depths, for instance 8 Bit depth (0-255). Assume you have k colors (or different depth values).

For your reference, here is a good explanation for the mean calculation of a rectangle through integral images Viola/Jones CVPR 2001, see Section 2.1.

My generalized algorithm is to precompute the integral of a vector with dimension k how often do color/depth values occur. From this vector, you can take the same differences as in the trick to compute the mean. This gives you not only the maximum value within a rectangle region, but really a vector of the histogram within that rectangle within constant time. Of course, the histogram allows you to extract the maximum (or minimum, or other quantile).

Time and memory requirements of course grow with the number of colors, I think the complexity class is O(k) for lookup and O(k * width * height) for pre-computation.

(I would be interested if my idea has previously been used.)

SpamBot
  • 1,438
  • 11
  • 28
  • 1
    I like this idea. If the OP will simply use the final result for a `<` or `>` comparison, then you can get O(1) lookup times by setting the ith element of the vector to the number of elements with depth equal to *or less than* i; this shouldn't change the preprocessing time either. – j_random_hacker Aug 04 '16 at 11:20
  • Actually that should be "depth equal to *or greater than* i". – j_random_hacker Aug 04 '16 at 11:32
  • @j_random_hacker How would you obtain the maximum value within a rectangle from that pre-computed vector? – SpamBot Aug 04 '16 at 11:48
  • Actually, I now realise that my variant of histogram vector will allow the maximum to be found in just O(log k) time via binary search, since the values in v are nonincreasing! :) Start by setting L = 0 and U = 255. Now set i = (L+U)/2, and compute v[i] for the query rectangle in O(1) time using the integral image trick: if v[i] > 0, i is too low, so set L = i and repeat; otherwise, if v[i] = 0, then i is (or could be) too high, so set U = i and repeat. Stop when U-L <= 1. This i is the smallest depth for which the query rectangle contains no points >= this depth, so return i-1. – j_random_hacker Aug 04 '16 at 16:41
  • [Fixed earlier comment] Determining the maximum value would still take O(k) time: You first compute, in O(k) time, the histogram vector v[0 .. 255] for the query rectangle, and then look for the smallest i such that v[i+1] = 0. What is faster is if we already have a test depth x, and we want to know if there is any point in the query rectangle with depth > x: in this case, we need only compute the x-th element of the histogram vector (i.e., v[x]) and test whether it is > 0. – j_random_hacker Aug 04 '16 at 16:43
  • Yeah, that is a nice optimization if you want to know if a rectangle has a maximum larger than a given constant. (If that constant is known before precomputation, I'm sure there is an even simpler solution.) – SpamBot Aug 05 '16 at 08:46
0

Pad the array out to a square power of 2, by adding zeroes. Then pyramidize it into a stack, shrinking by a factor of 2 in each dimension each time. At each level, the value of each element is equal to the max of the 4 corresponding elements in the next largest array in the pyramid. This will take an extra 1/3 storage space on top of the padded array size, like mip-maps do.

Write a recursive function that takes a single element of a given array level and checks if the query region covers the bounds covered by it. If not then it checks each region in the next largest array that overlaps the query region, returns the recursively-computed max of each.

Pseudo-code:

 int getMax(Rect query_rect, int level, int level_x, int level_y)
 {
      int level_factor = 1 << level;
      // compute the area covered by this array element:
      Rect level_rect(level_x * level_factor, level_y * level_factor,
          (level_x + 1) * level_factor, (level_y + 1) * level_factor);

      // if the regions don't overlap then ignore:
      if(!query_rect.intersects(level_rect))
          return -1;

      // query rect entirely contains this region, return precomputed max:
      if(query_rect.contains(level_rect))
          return pyramid[level][level_x][level_y];

      if(level == 0)
          return -1;  // ran out of levels

      int max = getMax(query_rect, level - 1, level_x * 2, level_y * 2);
      max = maxValue(max, getMax(query_rect, level - 1, (level_x * 2) + 1, level_y * 2);
      max = maxValue(max, getMax(query_rect, level - 1, level_x * 2, (level_y * 2) + 1);
      max = maxValue(max, getMax(query_rect, level - 1, (level_x * 2) + 1, (level_y * 2) + 1);

      return max;
 }

Call it at the top level (the capstone 1x1 array in the pyramid) initially:

 int max = getMax(query_rect, 10, 0, 0);

There will be a minimum size of query rectangle, below which it will be cheaper just to iterate over all the elements. You could adapt this to work with non-square arrays, it will just require some extra tests against the array size at each level when recursing.

samgak
  • 23,944
  • 4
  • 60
  • 82