Emulate PhotoShop's “Color Range” Algorithm

2019-04-05 01:58发布

问题:

I'm trying to replace a manual process done in PhotoShop with an automated process done on the server. Currently in PhotoShop the "Color Range" tool is used to select a range of colors using the "Fuzziness" factor and starting with either Black or White depending on the part of the process.

My initial approaches included both using thresholds for Luminescence in the L*a*b color space as well is DE94 between the candidate color and Black/White. In both cases I selected colors that shouldn't be selected and/or didn't select colors that should.

My hunch is that I should be using cones instead of spheres for my selection.

Can anyone give any insight into what PhotoShop is doing and if I'm heading the right direction? Also, if there is a library out there to do this that would be awesome I'm currently writing this in C.

回答1:

From what I have seen in Photoshop, the algorithm could probably be similar to the following:

  1. Define a function that calculates the closeness of two colors: for example, use a Euclidian distance in the colorspace - that is, calculate the distance between the colors of the two pixels in the RGB space using the Euclidean distance formula.
  2. Next, adjust the intensity of each pixel by using a fallof function, such as the Gaussian function. You will probably need to tweak some parameters. To clarify: you calculate the distance of two pixels in RGB space (not the distance in 2D pixel coordinates), and then feed that into the falloff function which will provide a result between 0.0 and 1.0. Multiply all color components of the current pixel with the result of the falloff function for it. Do this for each pixel of the image.
  3. If you want to add the range parameter of the effect, simply use the same falloff function for each pixel again, but this time feed it the Euclidean distance between the selected pixel and the current pixel in the 2D space of pixels (the distance between pixel coordinates on the image).

If you only want to select certain pixels, then instead of applying the effect directly at the pixels in the image you could store the falloff values in a matrix of doubles in range from 0.0 to 1.0. Then, choose a threshold value above which you will select the given pixel.

For example, if the step 2. for pixel at the coordinate (x, y) yielded 0.8 and the step 3. yielded 0.5, then the value of the matrix element with coordinates x and y should be 0.8*0.5=0.4. If you picked a selection threshold below 0.4, you would select pixel (x, y), otherwise, you would not.



回答2:

I don't know how photoshop does it under the hood, but this is a simple RGB as XYZ 3d vector approach:

rDelta = pixel.r - color.r
gDelta = pixel.g - color.g
bDelta = pixel.b - color.b
fuzziness = 0.1  // anything 0 to 1.0
maxDistance = fuzziness * 441 // max distance, black -> white

distance = Math.sqrt(rDelta * rDelta + gDelta * gDelta + bDelta * bDelta)

if (distance < maxDistance) includePixel()
else dontIncludePixel()

This is the pixel_difference function from the gimp source:

https://github.com/GNOME/gimp/blob/125cf2a2a3e1e85172af25871a2cda3638292fdb/app/core/gimpimage-contiguous-region.c#L290

static gfloat
pixel_difference (const gfloat        *col1,
                  const gfloat        *col2,
                  gboolean             antialias,
                  gfloat               threshold,
                  gint                 n_components,
                  gboolean             has_alpha,
                  gboolean             select_transparent,
                  GimpSelectCriterion  select_criterion)
{
  gfloat max = 0.0;

  /*  if there is an alpha channel, never select transparent regions  */
  if (! select_transparent && has_alpha && col2[n_components - 1] == 0.0)
    return 0.0;

  if (select_transparent && has_alpha)
    {
      max = fabs (col1[n_components - 1] - col2[n_components - 1]);
    }
  else
    {
      gfloat diff;
      gint   b;

      if (has_alpha)
        n_components--;

      switch (select_criterion)
        {
        case GIMP_SELECT_CRITERION_COMPOSITE:
          for (b = 0; b < n_components; b++)
            {
              diff = fabs (col1[b] - col2[b]);
              if (diff > max)
                max = diff;
            }
          break;

        case GIMP_SELECT_CRITERION_R:
          max = fabs (col1[0] - col2[0]);
          break;

        case GIMP_SELECT_CRITERION_G:
          max = fabs (col1[1] - col2[1]);
          break;

        case GIMP_SELECT_CRITERION_B:
          max = fabs (col1[2] - col2[2]);
          break;

        case GIMP_SELECT_CRITERION_H:
          {
            /* wrap around candidates for the actual distance */
            gfloat dist1 = fabs (col1[0] - col2[0]);
            gfloat dist2 = fabs (col1[0] - 1.0 - col2[0]);
            gfloat dist3 = fabs (col1[0] - col2[0] + 1.0);

            max = MIN (dist1, dist2);
            if (max > dist3)
              max = dist3;
          }
          break;

        case GIMP_SELECT_CRITERION_S:
          max = fabs (col1[1] - col2[1]);
          break;

        case GIMP_SELECT_CRITERION_V:
          max = fabs (col1[2] - col2[2]);
          break;
        }
    }

  if (antialias && threshold > 0.0)
    {
      gfloat aa = 1.5 - (max / threshold);

      if (aa <= 0.0)
        return 0.0;
      else if (aa < 0.5)
        return aa * 2.0;
      else
        return 1.0;
    }
  else
    {
      if (max > threshold)
        return 0.0;
      else
        return 1.0;
    }
}


回答3:

My educated guess would be that it is using an HSL color space, and the fuzziness is a parameter that selects all colors with a specific hue and saturation in a window of lightness (based on this).

Now the selection may just be doing a threshold calculation finding everything which is within that window (which would be a very small region in the colorspace). However, it may also be doing a statistical distance calculation. If the color sample actually small samples a window around the color, you can calculated the color variance and use something like a Mahalanobis distance calculation and threshold from that.

Again, that is just all conjecture, but maybe it'll help your thinking process.

Lastly, while this library does not have something like this directly implemented, OpenCV has many image processing tools that would make implementation easier.