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; } );
但是,这张照片就是灰色/蓝色,被冲洗掉或是紫色的绿色.我在正确的轨道上吗?如果没有,如何修改此过滤器以影响个别渠道,同时保留其他渠道?
一些例子:
原来,我试图实现的效果:
(第二个图像几乎没有什么不同,然而红色通道的色调已经变得更加粉红色了,我需要能够在粉红色的橙色之间进行调整).
但是,这里是我得到的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,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,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
解决方法
经过一些实验,在我看来,红色的色调要更加“粉红色”,你需要减少色调,但要获得更多的“橙色”,你需要增加饱和度.
在代码中,我转换为HSV而不是YIQ,因为它更快,使调整饱和成为可能,仍然允许调整色调. HSV组件也在[0-1]间隔,所以不需要处理弧度.
所以这里是这样做的:
>您选择参考色调或颜色(在您的情况下为红色色调)
着色器计算从当前像素色调到参考色调的“距离”
>根据这个距离,如果你想粉红色,减少色调,如果你想要橙色增加饱和度
>重要的是要注意,色调的表现与饱和度和值的不同:它应该被视为一个角度(更多信息here).
参考色调应该被硬编码,由用户(通过颜色选择图像)选择,或者通过分析图像内容来找到.
在计算距离方面有许多不同的可能方法,在这个例子中,我选择使用色相之间的角距离.
您也需要在计算距离之后应用某种过滤,以“选择”最近的颜色,如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); vec4 p = mix(vec4(c.bg,c.g)); vec4 q = mix(vec4(p.xyw,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)),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,3.0); vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); return c.z * mix(K.xxx,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,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); }
更多橙色:
– 编辑 –
在我看来,您没有在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