一种"高对比度"曲线的快速公式

5

我的内部循环包含一个计算,分析显示它存在问题。

想法是取一个灰度像素x(0 <= x <= 1),并“增加其对比度”。 我的要求相当宽松,仅如下:

  • 对于x < 0.5,0 <= f(x) < x
  • 对于x > 0.5,x < f(x) <= 1
  • f(0) = 0
  • f(x) = 1 - f(1 - x),即应该是“对称”的
  • 最好,函数应该平滑。

因此,图表必须类似于这样:

Graph

我有两种实现方法(它们的结果不同,但都符合规范):

float cosContrastize(float i) {
    return .5 - cos(x * pi) / 2;
}

float mulContrastize(float i) {
    if (i < .5) return i * i * 2;
    i = 1 - i;
    return 1 - i * i * 2;
}

所以,我要求对这些实现之一进行微优化,或者使用您自己的原始、更快速的公式。也许你们中的某个人甚至可以调整位元 ;)

也许如果您能告诉我们您使用的是什么编程语言(我猜是Java),以及涉及到的编译器/运行时,我们可以更好地帮助您。 - Tim Lin
我使用MS编译器和运行时来编写C#,但如果发现需要,我愿意用C++重写关键算法... - Stefan Monov
C# 使用驼峰式命名方法? :( - Joren
许多这样的函数在以零为中心时更快/更简单。我不确定您程序的其余部分,但如果这种代码很关键,您可以考虑重新定位数据表示,使其从-1..1而不是0..1进行缩放。 - Eamon Nerbonne
3个回答

13

考虑以下形状为sigmoid函数(已正确转换为所需的范围):

screenshot


我使用MATLAB生成了上面的图。如果感兴趣,这是代码:

x = -3:.01:3;
plot(   x, 2*(x>=0)-1, ...
        x, erf(x), ...
        x, tanh(x), ...
        x, 2*normcdf(x)-1, ...
        x, 2*(1 ./ (1 + exp(-x)))-1, ...
        x, 2*((x-min(x))./range(x))-1  )
legend({'hard' 'erf' 'tanh' 'normcdf' 'logit' 'linear'})

1
OP 的主要问题是速度。如何加快这些速度? - tom10
谢谢,至少我现在知道它们被称为“sigmoid”了 ;) 我用tanh做了一个简单的实现,它和cos的速度一样快。其余的需要更多的思考时间,我认为它们会更慢,但我们会看到的。 - Stefan Monov
1
++ 很好。个人而言,我更倾向于logit(实际上是反logit函数),因为你只需要调用一次exp()和一个除法。你可以通过缩放X来使其更加尖锐。 - Mike Dunlavey
2
@tom10,原帖的作者可以看到使用的公式,并考虑哪个最合适。 - Dykam

5

您可以简单地进行阈值处理,但我认为这太过愚蠢:

return i < 0.5 ? 0.0 : 1.0;

由于你提到了“增加对比度”,我假设输入值是亮度值。如果是这样,并且它们是离散的(可能是8位值),那么您可以使用查找表来快速完成此操作。

您的“mulContrastize”看起来相当快。一种优化方法是使用整数运算。假设您的输入值实际上可以作为8位无符号值传递[0..255]。(再次可能是一个很好的假设?)您可以大致执行以下操作...

int mulContrastize(int i) {
  if (i < 128) return (i * i) >> 7; 
  // The shift is really: * 2 / 256
  i = 255 - i;
  return 255 - ((i * i) >> 7);

一个阈值在我的情况下太过粗糙,无法使用。它们是亮度值,没错。它们不是离散的值-实际上是浮点数,由于两个原因。首先,在OpenGL中,浮点纹理最快。其次,我决定使用0.0-1.0浮点数使我的数学计算变得简单且快速。但我从未考虑过使用查找表进行对比度增强,我会研究一下,看看它是否超过了OpenGL纹理方面的问题。你发布的实现确实很好,但不如查找表好用。我的mulContrastize确实很快,但在如此紧密的内部循环中不太实用 :) - Stefan Monov
顺便提一下,你不应该两次除以255,只需要一次。所以你应该向右移7位。 - Stefan Monov
哎呀,你说得对,这样规范化过度了。我会修正这个例子。 - Sean Owen
++ 用于查找表建议。 - Mike Dunlavey
是的,我最终使用了查找表。单独使用它并没有改善什么。但它使我能够进行其他几项优化-所以-谢谢! - Stefan Monov

4
分段插值可以快速灵活。它只需要几个决策,然后是乘法和加法,并且可以近似任何曲线。它还避免了查找表可能引入的粗糙度(或两次查找后跟插值以平滑这一点的额外成本),尽管对于您的情况,查找表可能完全正常运行。

alt text

只需几个部分,就可以得到相当不错的匹配。这里颜色的渐变会有一定的粗糙度,比绝对颜色的粗糙度更难以检测。

正如Eamon Nerbonne在评论中指出的那样,可以通过“选择基于二阶导数之类的东西来最大化细节的分割点”,即斜率变化最大的地方进行优化。显然,在我的例子中,在五段情况下中间有三段并没有增加太多细节。


2
如果你真的很聪明,你可以根据像是二阶导数这样的东西来选择你的分割点来最大化细节(在中央相当直线的段中没有区别分段的意义)。 - Eamon Nerbonne
@Eamon:感谢你提供的二阶导数想法。我知道我在中心点上有些懒惰,但我真的很喜欢推广到二阶导数的想法。 - tom10

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