2015-08-18 3 views
10

Stumped próbuje dostosować odcień określonego kanału (lub może, konkretniej, określony zakres kolorów - w tym przypadku, czerwone). Patrząc na filtrze Hue, pomyślałem, że może mógłbym dostać gdzieś przez zakomentowanie zielone i niebieskie modyfikatory, wpływ zmian tylko na czerwonym kanale:GPUImage dodać regulacje barwy/koloru dla każdego kanału RGB (dopasować czerwone, aby były bardziej różowe lub pomarańczowe)

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; 
} 
); 

Ale to właśnie opuszcza zdjęcie albo szary/niebieski i przemywa -out lub fioletowo-zielony. Czy jestem na dobrej drodze? Jeśli nie, jak mogę zmodyfikować ten filtr, aby wpływał na poszczególne kanały, pozostawiając pozostałe w nienaruszonym stanie?

Kilka przykładów:

oryginału, a efekt usiłuję osiągnąć:

(Drugi obraz jest prawie niezauważalnie jednak inna barwa kanał czerwony została wykonana nieco bardziej różowawe Muszę być w stanie dostosować go między różowy < -> pomarańczowy).

Ale oto co mam z B i G wykomentowane:

(lewy bok: < 0º, po prawej stronie:> 0 °)

Wygląda mi na to, że nie jest wpływając na odcień czerwieni w sposób, w jaki chciałbym; być może podchodzę do tego niepoprawnie, lub jeśli jestem na dobrej drodze, ten kod nie reguluje poprawnie odcienia czerwonego kanału?

(Próbowałem również osiągnąć ten efekt za pomocą GPUImageColorMatrixFilter, ale nie bardzo się z tym pogodziłam).

Edit: oto moja obecna iteracja cieniującego używając @ kodu + GPUImage owijki VB_overflow, która jest funkcjonalnie wpływających na jakość wyświetlanego obrazu w sposób podobny do tego, co ja zmierzające do:

#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 
+0

Czy jesteś absolutnie 100% zestaw na przeprowadzenie tej pracy z GPUImage czy jesteś otwarty na innych bibliotek, które mogą być niskie niski poziom? – Loxx

+0

Nie widzisz, że robi się znacznie niższy poziom niż OpenGL-ES? Wciąż nie ma nic takiego jak GPUImage. – brandonscript

+0

Tak, mam na myśli przejście do czegoś podobnego do ImageMagick, jest to o wiele bardziej wymagające niż GPUImage pod względem filtrów, kolorowania itp., Przynajmniej z mojego doświadczenia. – Loxx

Odpowiedz

8

zrobiłem przykład na ShaderToy. Użyj najnowszego Chrome, aby go zobaczyć, po mojej stronie nie działa w Firefoksie ani IE, ponieważ używa wideo jako wejścia.

Po kilku eksperymentach wydaje mi się, że aby kolory czerwieni były bardziej "różowe", należy zmniejszyć odcień, ale aby uzyskać bardziej "pomarańczowy", należy zwiększyć nasycenie.

W kodzie przekonwertowałem na HSV zamiast YIQ, ponieważ jest to szybsze, umożliwia wyrównanie nasycenia i nadal pozwala na modyfikację odcienia. Również komponenty HSV są w przedziale [0-1], więc nie trzeba obsługiwać radianów.

Więc oto jak to zrobić:

  1. wybrać odcień odniesienia lub kolor (w przypadku odcień czerwony)
  2. Shader obliczyć „odległość” od bieżącego piksela odcień do Opłaty odcień
  3. Opierając się na tej odległości, zmniejsz barwę, jeśli chcesz uzyskać różowy, zwiększ nasycenie, jeśli chcesz pomarańczowy
  4. Należy zauważyć, że odcień zachowuje się inaczej niż nasycenie i wartość: powinien być traktowany jako kąt (więcej informacji here).

Odcień referencyjny powinien być zakodowany na sztywno, wybrany przez użytkownika (według koloru) lub znaleziony przy analizie zawartości obrazu.

Istnieje wiele różnych sposobów obliczenia odległości, w przykładzie wybrałem wykorzystanie odległości kątowej między odcieniami.

Należy również zastosować pewien rodzaj filtrowania po obliczeniu odległości, aby "wybrać" tylko najbliższe kolory, takie jak ten gaussian like function.

Oto kod, bez rzeczy 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); 
} 

Więcej Orange:

enter image description here

Więcej Pink: enter image description here

--EDIT--

Wydaje mi się, że nie ustawiasz jednolitych wartości w kodzie ObjectiveC. Jeśli zapomnisz, to shader otrzyma zero dla wszystkich.

Kod powinien wyglądać następująco:

- (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 
+0

Wygląda naprawdę ślisko! Spędzę dziś trochę czasu, bawiąc się tym i zobaczę, co mogę zrobić. Twoje zdrowie! – brandonscript

+0

Wreszcie został zintegrowany z GPUImage, ale nie ma to wpływu na obraz, tak jak próbuję to zrobić. Zamiast dopasowywania czerwieni między różem a pomarańczą, wpływa na nie w taki sam sposób, jak w moim q - pomiędzy różem a kolorem zielonym. Myśli? – brandonscript

+0

Ah, i jeśli o to chodzi, nawet granie z przesunięciem odcienia na shadertoy ma ten sam efekt - więc muszę wymyślić sposób obliczania odcienia między różowy i pomarańczowy, a nie różowy i zielony. Co powiesz na znalezienie sposobu na "odcień" czerwonych (teraz, gdy je wyizolowaliśmy) zamiast zmiany odcienia? – brandonscript

Powiązane problemy