View Single Post
Old 5th March 2014, 06:32   #6  |  Link
raffriff42
Retried Guesser
 
raffriff42's Avatar
 
Join Date: Jun 2012
Posts: 1,373
I don't know all the math involved, but I found some interesting things on the Wikipedia page on Color Temperature.

Take a look at this animation: it's the light spectrum of a radiating black body at various temperatures:



I took this image and carefully measured the graph values at red, green and blue (estimated from the rainbow bar at the bottom) and normalized to 0-255:


Adjusted for gamma=1.8:


I also measured the color swatch:

(not shown: red below about 6500k; it's always 255; similarly, blue above about 6500K is always 255)


Note Wikipedia does not attempt to show the high red & blue levels at the extremes; it looks like they convey the impression of reddish or bluish white (given the limits of computer imagery) by decreasing the other channels instead.

I got some transfer functions by curve regression, but I haven't attempted to use them, because (a) I'm not absolutely certain they will be accurate, and (b), there is a simple cheat that is "good enough" for most purposes:

If you view the above Wikipedia animation with a vector scope such as Histogram(mode="color2"), you will notice the color swatch is moving along a pretty straight line on the scope within the range 2000K-20000K. This means a simple call to ColorYUV with the appropriate values of offset_u and offset_v can fake color temp changes pretty well. In fact, the result looks identical to Photoshop's "warm" and "cool" photographic filters...

But that wasn't good enough for me! Simply adding U and V offsets cannot correct white, black and gray balance at the same time. To me, it looks kinda nasty (too much saturation in blacks and whites).

So to do it correctly, one should use RGB mode and adjust gain, offset and gamma on all channels. But we want correction in YUV space! So I propose another cheap trick: I create masks for low, middle and high luminance areas and apply different offsets to each range.

Here is my take at a cheating color correction function; I am calling it CheapColorTemp. Please try this with your own sources and see if it isn't pretty neat. To me luma range selectivity is more important than mathematical correctness. A "calibrated" way to alter apparent color temperature would be nice though, I admit.

Over-adjusted to show the range of the effect.
Code:
##Last=YUV  

## CheapColorTemp demonstration
## flip between original and several corrections
## (view in step mode; not for real time playback)

Interleave(
\ last.Subtitle("original"),
\ CheapColorTemp(0, -10, 0).Subtitle("mid -10"),
\ CheapColorTemp(0, 0, -10).Subtitle("hi -10"),
\ last.Subtitle("original"),
\ CheapColorTemp(0, 0, 10).Subtitle("hi +10"),
\ CheapColorTemp(0, 10, 0).Subtitle("mid +10"))

## scope(s) optional
##   RGBParade http://forum.doom9.org/showthread.php?p=1570968#post1570968
#ConvertToRGB32.HistogramRGBParade ##
#Histogram(mode="levels") 
Histogram(mode="color2")

return Last

#######################################
### emulate color temperature changes in three luma ranges
##
## @ offset_x - arbitrary units; <0 means lower temp (warmer colors)
##     (input range is unlimited, but is normally around -20 to +20; default 0)
##
function CheapColorTemp(clip C, 
\            int offset_lo, int offset_mid, int offset_hi)
{
    Assert(C.IsYUV, "CheapColorTemp: source must be YUV")

    return C.ColorYUVx3(
    \          off_u_lo =offset_lo,  off_v_lo =Round(-0.7*offset_lo),
    \          off_u_mid=offset_mid, off_v_mid=Round(-0.7*offset_mid),
    \          off_u_hi =offset_hi,  off_v_hi =Round(-0.7*offset_hi))
}

#######################################
### apply ColorYUV U & V offsets to three luma ranges
##
## @ off_x   - see ColorYUV
## @ xover_x - aproximate 50% luma blend between ranges
##     ( no reason to mess with this)
## @ showmasks - if true, show original + 3 masks in quad split
##
function ColorYUVx3(clip C,
\           float "off_u_lo", float "off_u_mid", float "off_u_hi",   
\           float "off_v_lo", float "off_v_mid", float "off_v_hi",
\           float "xover_lomid", float "xover_midhi",
\           bool "showmasks")
{
    off_u_lo  = Float(Default(off_u_lo,  0.0))
    off_u_mid = Float(Default(off_u_mid, 0.0))
    off_u_hi  = Float(Default(off_u_hi,  0.0))
    off_v_lo  = Float(Default(off_v_lo,  0.0))
    off_v_mid = Float(Default(off_v_mid, 0.0))
    off_v_hi  = Float(Default(off_v_hi,  0.0))
    xlo = Min(Max(  0, Default(xover_lomid, 102)), 127) * 3.0 / 256
    xhi = Min(Max(128, Default(xover_midhi, 191)), 255) * 3.0 / 256
    showmasks = Default(showmasks, false)

    C_lo  = C.ColorYUV(off_u=off_u_lo,  off_v=off_v_lo)
    C_mid = C.ColorYUV(off_u=off_u_mid, off_v=off_v_mid)
    C_hi  = C.ColorYUV(off_u=off_u_hi,  off_v=off_v_hi)

    ## mt_lutxyz too slow!!

    mask_lo  = C.mt_lut(
    \   yexpr="x -0.01 * "+String(xlo)+" + 256 * ",
    \   u=-128, v=-128)
    
    mask_hi  = C.mt_lut(
    \   yexpr="1 x -0.01 * "+String(xhi)+" + - 256 * ",
    \   u=-128, v=-128)
    
    mask_mid = mt_lutxy(mask_lo, mask_hi, "x  y + -1 * 256 + ", u=-128, v=-128)
    
    return (showmasks)
    \  ? StackVertical(
    \       StackHorizontal(C, mask_lo),
    \       StackHorizontal(mask_mid, mask_hi))
    \       .BilinearResize(C.Width, C.height)
    \       .Subtitle("Low",    align=9)
    \       .Subtitle("\nMid",  align=4, lsp=0)
    \       .Subtitle("\nHigh", align=6, lsp=0)
    \  : C.Overlay(C_lo,  mask=mask_lo)
    \       .Overlay(C_mid, mask=mask_mid)
    \       .Overlay(C_hi,  mask=mask_hi)
}

Last edited by raffriff42; 18th March 2017 at 01:01. Reason: (fixed image links)
raffriff42 is offline   Reply With Quote