Welcome to Doom9's Forum, THE in-place to be for everyone interested in DVD conversion. Before you start posting please read the forum rules. By posting to this forum you agree to abide by the rules.

 Doom9's Forum Manual White Balance in YUV without converting to RGB
 Register FAQ Calendar Search Today's Posts Mark Forums Read

 3rd March 2014, 02:25 #1  |  Link Leo D9 Registered User   Join Date: Feb 2013 Posts: 10 Manual White Balance in YUV without converting to RGB 1) Firstly, when adjusting white balance in RGB, my understanding is that the correct way to do this is by simply scaling the values of one or two of the R, G and/or B channel(s) as required. Is that correct? 2) There seem to be many ways of changing Y, U and V, but what is the "correct" method (by which I guess I mean mathematically/colorimetrically correct, not subjective) of manually adjusting white balance in the YUV colorspace? (Or is it necessary/better to convert to RGB, scale the channels and convert back?) 3) Is there a simple way to translate a colour temperature conversion into a change of YUV, and/or change of RGB? (I sometimes work with RGB so an answer for each would be useful.) e.g. If I want to convert the colour temperature from 3200K to 5600K how would I do that in YUV, and how should it be done in RGB? Many thanks! PS: 4) Mildly off-topic, but when using ColorYUV(autogain=true) will any values get clipped? The documentation says, "it will scale up the luma (y) values to match the minimum and maximum values." So, if there is one value at 255 and everything else is 200 or less what happens? And if the lowest value in the image is, say, 30 what happens? (ie, does it adjust the offset for setting black as well as pure gain?) Or does it do something like clip 1% of whites and 1% of blacks. Is it purely on a per-frame basis, completely ignoring preceding and subsequent frames? Also, since (unlike in Levels() ) I can't specify a black level (eg 0 or 16) nor a white level (eg 235 or 255), how does it know what the max and min values should be? (Assuming I'm not changing the levels "TV->PC" or "PC->TV".) Thanks.
 3rd March 2014, 07:46 #2  |  Link smok3 brontosaurusrex     Join Date: Oct 2001 Posts: 2,392 1. Can't be that simple, since what you got is curved nonLinear camera data, so you'd need to uncurve that before changing each curve to fix white balance. (or fix the actual existing curve considering low,mid,high positions) (resolve lite is free and you'd get the idea how it may actually work) __________________ certain other member
 3rd March 2014, 12:26 #3  |  Link Gavino Avisynth language lover   Join Date: Dec 2007 Location: Spain Posts: 3,423 Regarding ColorYUV(autogain=true), it simply adjusts the gain_y and off_y parameters, on a per-frame basis, to put the darkest pixel in each frame at y=16 and the brightest at y=236 (not 235, for some reason!). The levels parameter ("TV->PC" or "PC->TV") is ignored in this process. EDIT: It's a little more complicated if you have existing pixels outside the range [16,236]. What actually happens is it sets gain_y and off_y so that the range [max(yMin, 16), min(yMax, 236)] is mapped to [16,236]. __________________ GScript and GRunT - complex Avisynth scripting made easier Last edited by Gavino; 3rd March 2014 at 12:45.
 4th March 2014, 06:33 #4  |  Link Leo D9 Registered User   Join Date: Feb 2013 Posts: 10 Ach, I'd forgotten about the gamma curves. So how do people manage/control white balance in AviSynth? What is the standard method? eg if I load a clip and see the white balance is off, how should it be corrected? @Gavino : So that means if I use autogain on a clip with the range 0-255, then the frames with any pixel above 235 and any pixel below 16 won't be changed at all? So I would have to do Levels(0, 1, 255, 16, 235) before autogain to ensure 0-255 clips are treated the same way as 16-235 clips? I guess that risks banding? Basically... my problem is that I'm processing and encoding dozens of video clips one by one by using a batch file to generate a temporary AVS file (to add intro, outro, title, watermark, temporal smoothing, etc) that gets encoded by FFMPEG. Some of the clips could be 16-235 and some 0-255 (such as Canon DSLR MOVs). 5.A) Is there any way to automatically identify the luma/chroma range of a clip or normalise the whole clip to a range? (Ideally without needing a plug-in.) 5.B) And when I encode the clip with FFMPEG.exe, does it just assume the range is 16-235 with Rec.709 colour? (Sorry if that's off-topic, perhaps I should have started a new thread.) Thanks. Last edited by Leo D9; 4th March 2014 at 19:06. Reason: Levels() syntax, as noted by Gavino
4th March 2014, 13:21   #5  |  Link
Gavino
Avisynth language lover

Join Date: Dec 2007
Location: Spain
Posts: 3,423
Quote:
 Originally Posted by Leo D9 @Gavino : So that means if I use autogain on a clip with the range 0-255, then the frames with any pixel above 235 and any pixel below 16 won't be changed at all?
If a frame has some pixels above 235 AND some below 16, it will not be changed at all. However, if it has some above 235, but the darkest pixel is (for example) 20, then the range [20, 236] will be mapped to [16,236] and the pixels above 236 will be increased in brightness. A similar effect occurs at the other end if there are pixels below 16 but none above 236 (the darkest pixels will get darker).

Quote:
 So I would have to do Levels(0, 255, 1, 16, 235) before autogain to ensure 0-255 clips are treated the same way as 16-235 clips? I guess that risks banding?
Yes [you mean Levels(0, 1, 255, 16, 235)], and if you wanted the output to remain 0-255 you would also have to do Levels(16, 1, 235, 0, 255) after.
__________________
GScript and GRunT - complex Avisynth scripting made easier

 7th March 2014, 07:53 #7  |  Link raffriff42 Retried Guesser     Join Date: Jun 2012 Posts: 1,373 Here's my gamma-adjusted raw data, converted to video, to show the color follows a straight line on a U-V vectorscope. As you can see when viewing this script, there isn't much point to doing more than simply tweaking U and V. Code: C = BlankClip(length=1, color=_rgb(226, 77, 12))._sub("2000K") \ + BlankClip(length=1, color=_rgb(224, 93, 30))._sub("2200K") \ + BlankClip(length=1, color=_rgb(219, 110, 47))._sub("2500K") \ + BlankClip(length=1, color=_rgb(216, 120, 60))._sub("2700K") \ + BlankClip(length=1, color=_rgb(212, 130, 72))._sub("2900K") \ + BlankClip(length=1, color=_rgb(206, 141, 85))._sub("3200K") \ + BlankClip(length=1, color=_rgb(198, 148, 100))._sub("3500K") \ + BlankClip(length=1, color=_rgb(192, 158, 112))._sub("3800K") \ + BlankClip(length=1, color=_rgb(182, 162, 127))._sub("4200K") \ + BlankClip(length=1, color=_rgb(176, 167, 141))._sub("4600K") \ + BlankClip(length=1, color=_rgb(167, 171, 152))._sub("5000K") \ + BlankClip(length=1, color=_rgb(160, 174, 164))._sub("5500K") \ + BlankClip(length=1, color=_rgb(152, 176, 175))._sub("6000K") \ + BlankClip(length=1, color=_rgb(148, 178, 179))._sub("6600K") \ + BlankClip(length=1, color=_rgb(141, 178, 192))._sub("7200K") \ + BlankClip(length=1, color=_rgb(136, 178, 201))._sub("7900K") \ + BlankClip(length=1, color=_rgb(132, 178, 207))._sub("8600K") \ + BlankClip(length=1, color=_rgb(127, 178, 213))._sub("9400K") \ + BlankClip(length=1, color=_rgb(124, 178, 217))._sub("10000K") \ + BlankClip(length=1, color=_rgb(120, 178, 221))._sub("11000K") \ + BlankClip(length=1, color=_rgb(116, 178, 226))._sub("12000K") \ + BlankClip(length=1, color=_rgb(112, 178, 228))._sub("13000K") \ + BlankClip(length=1, color=_rgb(109, 174, 237))._sub("15000K") \ + BlankClip(length=1, color=_rgb(105, 174, 239))._sub("16000K") \ + BlankClip(length=1, color=_rgb(101, 174, 242))._sub("18000K") \ + BlankClip(length=1, color=_rgb(101, 174, 245))._sub("19000K") \ + BlankClip(length=1, color=_rgb(101, 172, 249))._sub("21000K") \ + BlankClip(length=1, color=_rgb(100, 172, 252))._sub("23000K") \ + BlankClip(length=1, color=_rgb(100, 172, 254))._sub("25000K") \ + BlankClip(length=1, color=_rgb(100, 172, 255))._sub("28000K") C.ConvertToYV12() Histogram(mode="color2") Loop AssumeFPS(10) return Last function _rgb(int R, int G, int B) { return R*65536 + G*256 + B } function _sub(clip C, string s) { return C.Subtitle(s, text_color=\$808080, align=5, size=C.Height/16) } __END__ Black Body (Wikipedia) digitized from graphic image corrected for gamma=1.8 TEMP R G B 2000 226 77 12 2200 224 93 30 2500 219 110 47 2700 216 120 60 2900 212 130 72 3200 206 141 85 3500 198 148 100 3800 192 158 112 4200 182 162 127 4600 176 167 141 5000 167 171 152 5500 160 174 164 6000 152 176 175 6600 148 178 179 7200 141 178 192 7900 136 178 201 8600 132 178 207 9400 127 178 213 10000 124 178 217 11000 120 178 221 12000 116 178 226 13000 112 178 228 15000 109 174 237 16000 105 174 239 18000 101 174 242 19000 101 174 245 21000 101 172 249 23000 100 172 252 25000 100 172 254 28000 100 172 255 Last edited by raffriff42; 7th March 2014 at 08:08. Reason: _sub
7th March 2014, 21:09   #9  |  Link
raffriff42
Retried Guesser

Join Date: Jun 2012
Posts: 1,373
Hi Leo D9, thanks for your feedback.

Yes, I am leaving the Y channel completely alone, and adding offsets to the U and V channels.

Re: green-magenta shift, you could call ColorYUVx3() directly instead of through CheapColorTemp(), and adjust the offsets as required; I'd suggest adding a vectorscope, eg Histogram(mode="color2"), so you can see what you are doing. Find something in the video that should be white, or black, or gray, and adjust the appropriate offsets to center the color in the vectorscope.

Sorry about the plugin requirement, but I consider Masktools2 ("alpha 48") to be fundamental, as it's used by so many scripts. It's one of a very few plugins I allow to auto load.

re gamma 1.8, it was a judgement call; I happen to agree with the author of
The Monitor calibration and Gamma assessment page
Quote:
 My personal preference is to set a system gamma of 1.8, even though I use a PC. I'll explain why I made this decision, and you can choose to agree or ignore me, as you wish. Reason 1, and most fundamental, is that a gamma of 2.2 is just too damned dark! ...
I don't know if this method makes sense - it's a hack. Any "real" color corrector would convert to RGB. Avisynth is not really the right tool for color correction - except that it's scriptable and fits into an existing Avisynth workflow. As suggested by smok3, Da Vinci Resolve Lite is an excellent color correction tool.

This method is very subjective and not mathematically correct. That said, I think it looks good and it simple to use, with only 3 parameters to worry about (only two of which are really used - offset_lo should be left alone unless black balancing is needed).

By the way, to do white/black/gray balancing in RGB without plugins, you can do this:
Code:
MergeRGB(
\    ShowRed("Y8")   .Levels(0, 1.0, 255, 0, 255, coring=false),
\    ShowGreen("Y8") .Levels(0, 1.0, 255, 0, 255, coring=false),
\    ShowBlue("Y8")  .Levels(0, 1.0, 255, 0, 255, coring=false))
...it's probably best to find the parameters using a visual tool like Photoshop, then copy the numbers over. Too much work if you are correcting scene-by-scene.

Last edited by raffriff42; 7th March 2014 at 21:50. Reason: vectorscope, code

 8th March 2014, 19:54 #10  |  Link Reel.Deel Registered User   Join Date: Mar 2012 Location: Texas Posts: 1,566 Hey there Leo D9, thanks for creating this thread, it reminded me of something I had started but had forgotten. You might want to try WhiteBalance (recently added to the wiki ). The documentation contains some good information regarding white balance. Another suggestion is AWB (Automatic White Balance); the script is heavily commented with useful information. Hope this helps.
 10th March 2014, 09:53 #11  |  Link pandy Registered User   Join Date: Mar 2006 Posts: 1,051 My two cents is - you can do white balance in RGB space, read offsets in YUV and then repeat but only in YUV space - so do measurements in RGB but correction in YUV - should be comparable without conversions RGB<>YUV.
 4th April 2014, 14:19 #17  |  Link raffriff42 Retried Guesser     Join Date: Jun 2012 Posts: 1,373 Hello Bernardd, I'm not sure what this script should be doing, as I am not familiar with AvsPmod scripts. I got an error (expected ':') when attempting to preview the script and had to make a change: Code: mask_mid_color = /*(mask_version == 0) ? */ \ mt_lutxy(mask_lo_color, mask_hi_color, "x y + -1 * 256 + ", u=-128, v=-128) Also, should there be "=" here? Code: mask_lo_color /* = */ mt_lut(clip, \ yexpr="x -0.01 * "+String(xlo_color)+" + 256 * ", \ u=-128, v=-128) mask_hi_color /* = */ mt_lut(clip, \ yexpr="1 x -0.01 * "+String(xhi_color)+" + - 256 * ", \ u=-128, v=-128) I didn't see any sliders, but again, I'm not too familiar with the program. As far as customizing the lut equations, maybe this speadsheet will help. It should probably have a term for width at peak gain, but I must leave it for later: Last edited by raffriff42; 18th March 2017 at 00:53. Reason: (fixed image link)
 5th April 2014, 01:55 #19  |  Link raffriff42 Retried Guesser     Join Date: Jun 2012 Posts: 1,373 OK here is a better set of curves, I think. Here is the new spreadsheet. The spreadsheet formula is: Code: MIN(MAX(0; (1+((WIDTH_2/512)*(256/RAMP_2)))-((ABS(A8-CENTER_2)/256)*(256/RAMP_2))); 1) It translates to Avisynth with the following code: Code: ImageSource("Grayscale_816x72.png", fps=30) TurnRight Lanczos4Resize(56, 450) ConvertToYV12(matrix="PC.709") a_ctr = 128 a_rmp = 80 a_wid = 60 s1 = ("x * min(max((1+((\$w/512)*(256/\$r)))-((abs(x-\$c)/256)*(256/\$r)), 0), 1)") \ .StrReplace("\$c", String(a_ctr)) \ .StrReplace("\$r", String(a_rmp)) \ .StrReplace("\$w", String(a_wid)) \ .mt_polish mt_lut(yexpr=s1) return Last.Histogram function StrReplace(string base, string findstr, string repstr) { pos = FindStr(base, findstr) return (StrLen(findstr)==0) || (pos==0) \ ? base \ : StrReplace( \ LeftStr(base, pos-1) + repstr + \ MidStr(base, pos+StrLen(findstr)), \ findstr, repstr) } which gives you (before & after) (image source) (EDIT looks like there is a gamma effect happening here; fixing it is left as an exercise for the reader) A final point: I am pretty sure you want the gain of all the curves to add up to unity at all input levels. This was done in my previous script by defining mid range as a difference: mid = input - (low+high). Last edited by raffriff42; 18th March 2017 at 00:52. Reason: (fixed image links)
 5th April 2014, 21:58 #20  |  Link Bernardd Registered User   Join Date: Jan 2012 Location: Toulon France Posts: 243 Thanks for this new curves. Thanks also for example script and for StrReplace function. You are master in knowledges that I have not learnt.

 Tags colour temperature, manual white balance, rgb, white balance, yuv