Эмулировать алгоритм PhotoShop "Color Range"

Я пытаюсь заменить ручной процесс, выполняемый в PhotoShop, автоматическим процессом, выполняемым на сервере. В настоящее время в PhotoShop инструмент "Цветовой диапазон" используется для выбора диапазона цветов с использованием фактора "Fuzziness" и начиная с черного или белого цвета в зависимости от части процесса.

Мои первоначальные подходы включали как использование пороговых значений для люминесценции в цветовом пространстве L * a * b, так и DE94 между цветом-кандидатом и черным/белым. В обоих случаях я выбрал цвета, которые не следует выбирать и/или не выбирать цвета, которые должны быть.

Моя догадка заключается в том, что я должен использовать конусы вместо сфер для моего выбора.

Может ли кто-нибудь дать представление о том, что делает PhotoShop, и если я направляюсь в правильном направлении? Кроме того, если есть библиотека, чтобы сделать это, это было бы потрясающе, я сейчас пишу это на C.

Ответ 1

Из того, что я видел в Photoshop, алгоритм, вероятно, может быть похож на следующий:

  • Определите функцию, которая вычисляет близость двух цветов: например, используйте эвклидово расстояние в цветовом пространстве, то есть вычислите расстояние между цветами двух пикселей в пространстве RGB, используя Евклидова формула расстояния.
  • Затем отрегулируйте интенсивность каждого пикселя с помощью функции fallof, например, функция Гаусса. Вам, вероятно, потребуется настроить некоторые параметры. Чтобы уточнить: вы вычисляете расстояние до двух пикселей в пространстве RGB (а не на расстоянии в двумерных пиксельных координатах) и затем подайте это в функцию falloff, которая даст результат между 0.0 и 1.0. Умножьте все цветовые компоненты текущего пиксель с результатом функции falloff для него. Сделайте это для каждый пиксель изображения.
  • Если вы хотите добавить параметр диапазона эффекта, просто используйте эта же функция спада для каждого пикселя снова, но на этот раз подайте это евклидовое расстояние между выбранным пикселем и текущим пиксель в 2D-пространстве пикселей (расстояние между пикселями координаты на изображении).

Если вы хотите выбрать только определенные пиксели, вместо того, чтобы применять эффект непосредственно к пикселям изображения, вы можете сохранить значения спада в матрице double в диапазоне от 0.0 до 1.0. Затем выберите пороговое значение, выше которого вы выберете данный пиксель.

Например, если шаг 2. для пикселя в координате (x, y) дал 0,8, а шаг 3 дал 0,5, то значение матричного элемента с координатами x и y должно быть 0.8*0.5=0.4. Если вы выбрали порог выбора ниже 0,4, вы должны выбрать пиксель (x, y), в противном случае вы бы этого не сделали.

Ответ 2

Моя образованная догадка заключается в том, что она использует цветовое пространство HSL, а нечеткость - это параметр, который выбирает все цвета с определенным оттенком и насыщенностью в окне легкости (на основе это).

Теперь выбор может просто делать пороговый расчет, находя все, что находится внутри этого окна (что было бы очень маленькой областью в цветовом пространстве). Однако он также может выполнять статистический расчет расстояния. Если образец цвета на самом деле маленький, образец отображает окно вокруг цвета, вы можете рассчитать дисперсию цвета и использовать что-то вроде Mahalanobis расчет расстояния и пороговое значение от этого.

Опять же, это всего лишь гипотеза, но, возможно, это поможет вашему процессу мышления.

Наконец, в то время как в этой библиотеке не реализовано что-то подобное непосредственно, OpenCV имеет множество инструментов обработки изображений, которые облегчили бы реализацию.

Ответ 3

Я не знаю, как фотошоп делает это под капотом, но это простой RGB, как XYZ 3d-векторный подход:

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()

Это функция pixel_difference от источника gimp:

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;
    }
}