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

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

我的初始方法包括在L * a * b颜色空间中使用发光阈值以及候选颜色和黑/白之间的DE94。 在这两种情况下,我都选择了不应该选择的颜色和/或没有选择应该选择的颜色。

我的预感是我应该使用锥体而不是球体来进行选择。

任何人都可以了解PhotoShop正在做什么以及我是否正朝着正确的方向前进? 另外,如果有一个库可以做到这一点很棒我现在用C写的。

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

  1. 定义计算两种颜色的接近度的函数:例如,在颜色空间中使用欧几里德距离 – 即,使用欧几里德距离公式计算 RGB空间中两个像素的颜色之间的距离 。
  2. 接下来,通过使用诸如高斯函数的fallof函数来调整每个像素的强度。 您可能需要调整一些参数。 为了澄清:您计算RGB空间中两个像素的距离(不是2D像素坐标中的距离),然后将其输入到衰减函数中,该函数将提供0.0到1.0之间的结果。 将当前像素的所有颜色分量与其衰减函数的结果相乘。 对图像的每个像素执行此操作。
  3. 如果你想要添加效果的范围参数,只需再次为每个像素使用相同的衰减函数,但这次给它选择像素和像素的2D空间中的当前像素之间的欧几里德距离(像素之间的距离)图像上的坐标)。

如果您只想选择某些像素,则可以将衰减值存储在double的矩阵中,而不是直接在图像中的像素上应用效果,范围为0.0到1.0。 然后,选择一个阈值,在该阈值之上,您将选择给定的像素。

例如,如果坐标(x,y)处的像素的步骤2产生0.8并且步骤3产生0.5,则具有坐标x和y的矩阵元素的值应该是0.8*0.5=0.4 。 如果您选择的选择阈值低于0.4,则选择像素(x,y),否则,您将不会。

我受过教育的猜测是它使用的是HSL色彩空间,模糊度是一个参数,可以在明亮的窗口中选择具有特定色调和饱和度的所有颜色(基于此 )。

现在,选择可能只是进行阈值计算,找到该窗口内的所有内容(这将是颜色空间中的一个非常小的区域)。 但是,它也可能正在进行统计距离计算。 如果颜色样本实际上很小的颜色采样窗口,您可以计算颜色方差,并使用类似马哈拉诺比斯距离计算和阈值。

同样,这只是猜想,但也许它会帮助你的思考过程。

最后,虽然这个库没有直接实现这样的东西,但OpenCV有许多图像处理工具可以使实现更容易。

我不知道Photoshop是如何做到的,但这是一个简单的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() 

这是来自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; } }