模拟PhotoShop的“色彩范围”算法

11

我正在尝试用服务器上的自动化流程替代Photoshop中的手动流程。目前,在Photoshop中,“颜色范围”工具用于使用“模糊度”因子选择一系列颜色,并根据流程的不同部分从黑色或白色开始。

我的初始方法包括在L*a*b颜色空间中对亮度使用阈值和候选颜色与黑色/白色之间的DE94。在这两种情况下,我选择了不应选择的颜色和/或未选择应选择的颜色。

我有预感应该使用圆锥形而不是球形进行选择。

有人能否洞察Photoshop正在做什么,以及我是否朝着正确的方向前进?此外,如果有一个库可以做到这一点,那就太棒了。我目前正在用C语言编写它。


关于编程语言的选择,我强烈推荐至少在原型设计阶段使用Matlab。C++中也有一些STL库可以帮助你。 - ElKamina
1
我会考虑将 r、g、b 分量视为三维向量。然后,通过计算这两个点之间的向量长度,可以找到目标颜色和源颜色之间的距离。Gimp 与 Photoshop 有类似的功能。您可以查看其源代码,寻找经过验证的方法。 - enhzflep
值得一提的是,请查看此OpenFrameworks C++答案,了解RGB <-> CIE XYZ转换(通过L * a * b)。该示例使用感知色彩空间(CIE XYZ)中的欧几里德距离来查找平均颜色最接近的图像。希望作为概念验证,可以将其调整为颜色范围(具有可调阈值的XYZ欧几里德距离)。 - George Profenza
3个回答

5

根据我在Photoshop中看到的,算法可能类似于以下内容:

  1. 定义一个函数,计算两种颜色的相似度:例如,在颜色空间中使用欧几里得距离 - 即,使用欧几里得距离公式计算两个像素的颜色之间的距离。
  2. 接下来,通过使用衰减函数(例如高斯函数)调整每个像素的强度。您可能需要微调一些参数。为了澄清:您在RGB空间中计算两个像素的距离(而不是2D像素坐标中的距离),然后将其输入到衰减函数中,该函数将提供介于0.0和1.0之间的结果。将当前像素的所有颜色分量与其衰减函数的结果相乘。对图像的每个像素都这样做。
  3. 如果要添加效果的范围参数,请再次为每个像素使用相同的衰减函数,但这次将其输入到所选像素和当前像素在像素的2D空间中的欧几里得距离中(即图像上像素坐标之间的距离)。
如果您仅想选择某些像素,则可以将衰减值存储在0.0到1.0范围内的double矩阵中,而不是直接在图像中应用效果。然后,选择一个高于给定像素的阈值值。例如,如果步骤2对坐标为(x,y)的像素产生了0.8的结果,并且步骤3产生了0.5的结果,则具有坐标x和y的矩阵元素的值应为0.8 * 0.5 = 0.4 。 如果您选择低于0.4的选择阈值,则会选择像素(x,y),否则不会选择。

我实际上并不想应用任何效果,我想以与选择菜单下的颜色范围工具相同的方式选择像素。 - k.parnell

3
我不知道 Photoshop 在内部是如何实现的,但这是一种简单的 RGB 转 XYZ 三维向量方法:
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()

这是来自GIMP源代码的pixel_difference函数: 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;
    }
}

这听起来像是问题中所述的算法,只不过使用了RGB而不是LAB颜色空间 - 对于这种应用程序,LAB可能更好。我还建议将maxDistance平方并且为了提高速度,去掉sqrt调用。 - Mark Ransom
同意,使用两个方块会更快。不过出于清晰起见,我想保留代码原样。 - hunterloftis

2
我的猜测是它使用的是HSL颜色空间,模糊度是一个参数,可以选择在亮度窗口中具有特定色调和饱和度的所有颜色(基于这个)。现在选择可能只是进行一个阈值计算,找到所有在该窗口内的颜色(这将是颜色空间中非常小的区域)。然而,它也可以进行统计距离计算。如果颜色样本实际上对颜色周围的窗口进行了小样本采样,您可以计算出颜色方差,然后使用类似Mahalanobis距离计算并从中进行阈值处理。再次说明,这只是一种猜测,但也许它会帮助您的思考过程。最后,虽然这个库没有直接实现这样的功能,但OpenCV有许多图像处理工具,可以使实现变得更加容易。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接