GPUImage为每个RGB通道添加色调/颜色调整(将红色调整为更粉红色或橙色)

时间:2015-08-18 19:09:10

标签: ios objective-c opengl-es gpuimage

难以尝试调整特定频道的色调(或者更具体地说,特定范围的颜色 - 在这种情况下,红色)。看着色调过滤器,我想也许我可能会通过评论绿色和蓝色修饰符来获得某个地方,只影响红色通道上的变化:

 precision highp float;
 varying highp vec2 textureCoordinate;

 uniform sampler2D inputImageTexture;
 uniform mediump float hueAdjust;
 const highp  vec4  kRGBToYPrime = vec4 (0.299, 0.587, 0.114, 0.0);
 const highp  vec4  kRGBToI     = vec4 (0.595716, -0.274453, -0.321263, 0.0);
 const highp  vec4  kRGBToQ     = vec4 (0.211456, -0.522591, 0.31135, 0.0);

 const highp  vec4  kYIQToR   = vec4 (1.0, 0.9563, 0.6210, 0.0);
 const highp  vec4  kYIQToG   = vec4 (1.0, -0.2721, -0.6474, 0.0);
 const highp  vec4  kYIQToB   = vec4 (1.0, -1.1070, 1.7046, 0.0);

 void main ()
 {
     // Sample the input pixel
     highp vec4 color   = texture2D(inputImageTexture, textureCoordinate);

     // Convert to YIQ
     highp float   YPrime  = dot (color, kRGBToYPrime);
     highp float   I      = dot (color, kRGBToI);
     highp float   Q      = dot (color, kRGBToQ);

     // Calculate the hue and chroma
     highp float   hue     = atan (Q, I);
     highp float   chroma  = sqrt (I * I + Q * Q);

     // Make the user's adjustments
     hue += (-hueAdjust); //why negative rotation?

     // Convert back to YIQ
     Q = chroma * sin (hue);
     I = chroma * cos (hue);

     // Convert back to RGB
     highp vec4    yIQ   = vec4 (YPrime, I, Q, 0.0);
     color.r = dot (yIQ, kYIQToR);
//  -->    color.g = dot (yIQ, kYIQToG); 
//  -->   color.b = dot (yIQ, kYIQToB);

     // Save the result
     gl_FragColor = color;
 }
);

但是,这只会使照片灰色/蓝色,褪色或紫绿色。我是在正确的轨道上吗?如果没有,我如何修改此过滤器以影响单个通道,同时保留其他通道?

一些例子:

原创,以及我想要达到的效果:

(第二张图片几乎没有明显不同,但红色通道的色调略微变得更加粉红色。我需要能够在粉红色< - > orange 之间调整它。

但是这是B和G得到的结论:

(左侧:<0º,右侧:>0º)

在我看来,它并没有像我希望的那样影响红色的色调;可能我接近这个错误,或者如果我在正确的轨道上,这段代码没有正确调整红色通道色调?

(我也尝试使用GPUImageColorMatrixFilter来达到这个效果,但我对它没有太大的了解。)

编辑:这是我使用@ VB_overflow的代码+ GPUImage包装器的着色器的当前迭代,它以类似于我的目标的方式在功能上影响输入图像:

#import "GPUImageSkinToneFilter.h"

@implementation GPUImageSkinToneFilter

NSString *const kGPUImageSkinToneFragmentShaderString = SHADER_STRING
(
 varying highp vec2 textureCoordinate;

 uniform sampler2D inputImageTexture;

 // [-1;1] <=> [pink;orange]
 uniform highp float skinToneAdjust; // will make reds more pink

 // Other parameters
 uniform mediump float skinHue;
 uniform mediump float skinHueThreshold;
 uniform mediump float maxHueShift;
 uniform mediump float maxSaturationShift;

 // RGB <-> HSV conversion, thanks to http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl
 highp vec3 rgb2hsv(highp vec3 c)
{
    highp vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
    highp vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
    highp vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));

    highp float d = q.x - min(q.w, q.y);
    highp float e = 1.0e-10;
    return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}

 // HSV <-> RGB conversion, thanks to http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl
 highp vec3 hsv2rgb(highp vec3 c)
{
    highp vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
    highp vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
    return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}

 // Main
 void main ()
{

    // Sample the input pixel
    highp vec4 colorRGB = texture2D(inputImageTexture, textureCoordinate);

    // Convert color to HSV, extract hue
    highp vec3 colorHSV = rgb2hsv(colorRGB.rgb);
    highp float hue = colorHSV.x;

    // check how far from skin hue
    highp float dist = hue - skinHue;
    if (dist > 0.5)
        dist -= 1.0;
    if (dist < -0.5)
        dist += 1.0;
    dist = abs(dist)/0.5; // normalized to [0,1]

    // Apply Gaussian like filter
    highp float weight = exp(-dist*dist*skinHueThreshold);
    weight = clamp(weight, 0.0, 1.0);

    // We want more orange, so increase saturation
    if (skinToneAdjust > 0.0)
        colorHSV.y += skinToneAdjust * weight * maxSaturationShift;
    // we want more pinks, so decrease hue
    else
        colorHSV.x += skinToneAdjust * weight * maxHueShift;

    // final color
    highp vec3 finalColorRGB = hsv2rgb(colorHSV.rgb);

    // display
    gl_FragColor = vec4(finalColorRGB, 1.0);
}
);

#pragma mark -
#pragma mark Initialization and teardown
@synthesize skinToneAdjust;
@synthesize skinHue;
@synthesize skinHueThreshold;
@synthesize maxHueShift;
@synthesize maxSaturationShift;

- (id)init
{
    if(! (self = [super initWithFragmentShaderFromString:kGPUImageSkinToneFragmentShaderString]) )
    {
        return nil;
    }

    skinToneAdjustUniform = [filterProgram uniformIndex:@"skinToneAdjust"];
    skinHueUniform = [filterProgram uniformIndex:@"skinHue"];
    skinHueThresholdUniform = [filterProgram uniformIndex:@"skinHueThreshold"];
    maxHueShiftUniform = [filterProgram uniformIndex:@"maxHueShift"];
    maxSaturationShiftUniform = [filterProgram uniformIndex:@"maxSaturationShift"];

    self.skinHue = 0.05;
    self.skinHueThreshold = 50.0;
    self.maxHueShift = 0.14;
    self.maxSaturationShift = 0.25;

    return self;
}

#pragma mark -
#pragma mark Accessors

- (void)setSkinToneAdjust:(CGFloat)newValue
{
    skinToneAdjust = newValue;
    [self setFloat:newValue forUniform:skinToneAdjustUniform program:filterProgram];
}

- (void)setSkinHue:(CGFloat)newValue
{
    skinHue = newValue;
    [self setFloat:newValue forUniform:skinHueUniform program:filterProgram];
}

- (void)setSkinHueThreshold:(CGFloat)newValue
{
    skinHueThreshold = newValue;
    [self setFloat:newValue forUniform:skinHueThresholdUniform program:filterProgram];
}

- (void)setMaxHueShift:(CGFloat)newValue
{
    maxHueShift = newValue;
    [self setFloat:newValue forUniform:maxHueShiftUniform program:filterProgram];
}

- (void)setMaxSaturationShift:(CGFloat)newValue
{
    maxSaturationShift = newValue;
    [self setFloat:newValue forUniform:maxSaturationShiftUniform program:filterProgram];
}

@end

1 个答案:

答案 0 :(得分:8)

我在ShaderToy上做了一个例子。使用最新的Chrome浏览器,在我看来它不适用于Firefox或IE,因为它使用视频作为输入。

经过一些实验后,在我看来,红色色调更多&#34;粉红色&#34;你需要减少色调,但要获得更多&#34;橙色&#34;你需要增加饱和度。

在代码中我转换为HSV而不是YIQ,因为这样更快,使调整饱和成为可能并且仍然允许调整色调。此外,HSV组件的间隔为[0-1],因此无需处理弧度。

以下是这样做的方法:

  1. 您选择参考色调或颜色(在您的情况下是红色调)
  2. 着色器计算距离&#34;从当前像素色调到参考色调
  3. 根据此距离,如果你想要粉色,减少色调,如果你想要橙色,则增加饱和度
  4. 重要的是要注意色调的行为与饱和度和值不同:它应该被视为一个角度(更多信息here)。
  5. 参考色调应该是硬编码的,由用户选择(通过颜色选择图像),或通过分析图像内容找到。

    计算距离有许多不同的可能方式,在我选择使用色调之间的角距离的例子中。

    在计算距离&#34;选择&#34;之后,您还需要应用某种过滤。只有最接近的颜色,例如gaussian like function

    这是代码,没有ShaderToy的东西:

    precision highp float;
    
    // [-1;1] <=> [pink;orange]
    const float EFFECT_AMOUNT = -0.25; // will make reds more pink
    
    // Other parameters 
    const float SKIN_HUE = 0.05;
    const float SKIN_HUE_TOLERANCE = 50.0;    
    const float MAX_HUE_SHIFT = 0.04;
    const float MAX_SATURATION_SHIFT = 0.25;
    
    // RGB <-> HSV conversion, thanks to http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl
    vec3 rgb2hsv(vec3 c)
    {
        vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
        vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
        vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
    
        float d = q.x - min(q.w, q.y);
        float e = 1.0e-10;
        return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
    }
    
    // HSV <-> RGB conversion, thanks to http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl
    vec3 hsv2rgb(vec3 c)
    {
        vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
        vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
        return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
    }
    
    // Main
    void main ()
    {   
        // Sample the input pixel
        vec4 colorRGB = texture2D(inputImageTexture, textureCoordinate);
    
        // get effect amount to apply
        float skin_tone_shift = EFFECT_AMOUNT;
    
        // Convert color to HSV, extract hue
        vec3 colorHSV = rgb2hsv(colorRGB.rgb);  
        float hue = colorHSV.x;
    
        // check how far from skin hue
        float dist = hue - SKIN_HUE;        
        if (dist > 0.5)
            dist -= 1.0;
        if (dist < -0.5)
            dist += 1.0;
        dist = abs(dist)/0.5; // normalized to [0,1]
    
        // Apply Gaussian like filter
        float weight = exp(-dist*dist*SKIN_HUE_TOLERANCE);  
        weight = clamp(weight, 0.0, 1.0);
    
        // We want more orange, so increase saturation
        if (skin_tone_shift > 0.0)
            colorHSV.y += skin_tone_shift * weight * MAX_SATURATION_SHIFT;
        // we want more pinks, so decrease hue
        else
            colorHSV.x += skin_tone_shift * weight * MAX_HUE_SHIFT;
    
        // final color
        vec3 finalColorRGB = hsv2rgb(colorHSV.rgb);     
    
        // display
         gl_FragColor = vec4(finalColorRGB, 1.0);
    }
    

    更多橙色

    enter image description here

    更多粉红色: enter image description here

    <强> - 编辑 -

    在我看来,您没有在ObjectiveC代码中设置统一值。如果你忘了这个着色器将为所有这些着色器获得零。

    代码应如下所示:

    - (id)init
    {
        if(! (self = [super initWithFragmentShaderFromString:kGPUImageSkinToneFragmentShaderString]) )
        {
            return nil;
        }
    
        skinToneAdjustUniform = [filterProgram uniformIndex:@"skinToneAdjust"];
        [self setFloat:0.5 forUniform:skinToneAdjustUniform program:filterProgram]; // here 0.5 so should increase saturation
    
        skinHueUniform = [filterProgram uniformIndex:@"skinHue"];
        self.skinHue = 0.05;
        [self setFloat:self.skinHue forUniform:skinHueUniform program:filterProgram];
    
        skinHueToleranceUniform = [filterProgram uniformIndex:@"skinHueTolerance"];
        self.skinHueTolerance = 50.0;
        [self setFloat:self.skinHueTolerance forUniform:skinHueToleranceUniform program:filterProgram];
    
        maxHueShiftUniform = [filterProgram uniformIndex:@"maxHueShift"];
        self.maxHueShift = 0.04;
        [self setFloat:self.maxHueShift forUniform:maxHueShiftUniform program:filterProgram];
    
        maxSaturationShiftUniform = [filterProgram uniformIndex:@"maxSaturationShift"];    
        self.maxSaturationShift = 0.25;        
        [self setFloat:self.maxSaturationShift forUniform:maxSaturationShiftUniform program:filterProgram];
    
        return self;
    }
    @end