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. Domains: forum.doom9.org / forum.doom9.net / forum.doom9.se |
|
|
#1 | Link |
|
Avisynth language lover
Join Date: Dec 2007
Location: Spain
Posts: 3,442
|
U and V ranges for valid RGB
Following a conversation with WorBry about the U and V ranges which correspond to valid RGB, I have put together this script which shows, for each value of Y, the valid ranges of U and V (in itself an interesting exercise in the use of MaskTools expressions
).Code:
# Demonstration of valid YUV values, showing (for both TV ranges and PC ranges):
# - ranges of U and V for each possible value of Y;
# - ranges of Y and V for each possible value of U;
# - ranges of Y and U for each possible value of V.
# Requires MaskTools v2
# Support function.
# Given MaskTools expressions for Y, U, and V,
# returns expression which yields 255 for valid RGB and 0 for invalid.
function ValidRGB(string y, string u, string v, bool "pcRange") {
pcRange = Default(pcRange, false)
# Normalise Y to [0,1] and U, V to [-1,+1]:
y = pcRange ? y+" 255 /" : y+" 16 - 219 /"
u = pcRange ? u+" 128 / 1 -" : u+" 16 - 112 / 1 -"
v = pcRange ? v+" 128 / 1 -" : v+" 16 - 112 / 1 -"
# Rec 601 coefficients:
Kr = " 0.299" Kg = " 0.587" Kb = " 0.114"
Kr1 = " 1"+Kr+" -" Kb1 = " 1"+Kb+" -"
# From http://avisynth.org/mediawiki/Color_conversions:
# R = Y + V*(1-Kr)
# G = Y - U*(1-Kb)*Kb/Kg - V*(1-Kr)*Kr/Kg
# B = Y + U*(1-Kb)
r = y + v + Kr1 + " * +"
g = y + u + Kb1 + Kb + Kg + " / * * -" + v + Kr1 + Kr + Kg + " / * * -"
b = y + u + Kb1 + " * +"
lwb = " 0 >="
upb = " 1 <="
and = " &"
good = r+lwb+r+upb+and+g+lwb+and+g+upb+and+b+lwb+and+b+upb+and
return good+" 255 0 ?"
}
function PlotYUV(clip c, string y, string u, string v, bool "pcRange") {
plot = c.mt_lutspa(yexpr=y, uexpr=u, vexpr=v, U=3, V=3)
mask = c.mt_lutspa(expr=ValidRGB(y, u, v, pcRange), U=3, V=3)
c.mt_merge(plot, mask, U=3, V=3)
}
function PlotUV(clip c, int yIn, bool "pcRange") {
# Plot valid U and V for given Y
pcRange = Default(pcRange, false)
title = "UV plot V ^-> U "+(pcRange?"PC":"TV")+" Range\nY = "+string(yIn)
y = " " + string(yIn)
u = " x 256 *"
v = " 1 y - 256 *"
c.PlotYUV(y, u, v, pcRange)
pcRange ? NOP : LetterBox(16, 16, 16, 16, color_gray)
Histogram("levels")
StackVertical(last, BlankClip(last, height=4, color=color_gray10),
\ BlankClip(last, height=44).Subtitle(title, lsp=0))
}
function PlotYU(clip c, int vIn, bool "pcRange") {
# Plot valid Y and U for given V
pcRange = Default(pcRange, false)
title = "YU plot Y ^-> U "+(pcRange?"PC":"TV")+" Range\nV = "+string(vIn)
y = " 1 y - 255 *"
u = " x 256 *"
v = " " + string(vIn)
c.PlotYUV(y, u, v, pcRange)
pcRange ? NOP : LetterBox(20, 16, 16, 16, color_gray)
Histogram("levels")
StackVertical(last, BlankClip(last, height=4, color=color_gray10),
\ BlankClip(last, height=44).Subtitle(title, lsp=0))
}
function PlotYV(clip c, int uIn, bool "pcRange") {
# Plot valid Y and V for given U
pcRange = Default(pcRange, false)
title = "YV plot Y ^-> V "+(pcRange?"PC":"TV")+" Range\nU = "+string(uIn)
y = " 1 y - 255 *"
u = " " + string(uIn)
v = " x 256 *"
c.PlotYUV(y, u, v, pcRange)
pcRange ? NOP : LetterBox(20, 16, 16, 16, color_gray)
Histogram("levels")
StackVertical(last, BlankClip(last, height=4, color=color_gray10),
\ BlankClip(last, height=44).Subtitle(title, lsp=0))
}
b = BlankClip(256, 256, 256, pixel_type="YV12")
b1 = b.Trim(16, 235)
b2 = b.Trim(16, 240)
bpc = b.ColorYUV(levels="TV->PC")
tv = Animate(b1, 0, 219, "PlotUV", 16, 235)
\ + Animate(b2, 0, 224, "PlotYU", 16, 240)
\ + Animate(b2, 0, 224, "PlotYV", 16, 240)
pc = Animate(bpc, 0, 255, "PlotUV", 0, true, 255, true)
\ + Animate(bpc, 0, 255, "PlotYU", 0, true, 255, true)
\ + Animate(bpc, 0, 255, "PlotYV", 0, true, 255, true)
return tv.ConvertToRGB32() + pc.ConvertToRGB32("PC.601")
It shows strikingly that most of the YUV space is in fact unused (ie does not correspond to a valid RGB color), especially for low and high values of Y, and that for no value of Y is the entire range of U or V valid. For Y=0 or Y=255 of course, it shrinks to a single point (black or white) with U=V=128. None of this is a problem, of course - it's just the way it works, but it might surprise some people. And it's something to be aware of when generating YUV values from scratch, such as when creating YUV color gradients, the topic of WorBry's post. An 'invalid' YUV value will produce clipping to 0 or 255 on one or more colors when converted to RGB. Edit: Script extended to add YU and YV plots, and to show plots for both TV range and PC range. Some selected examples (orig script): Last edited by Gavino; 27th May 2010 at 11:21. Reason: extended to add YU and YV plots, and for both TV range and PC range |
|
|
|
|
|
#2 | Link | ||
|
Registered User
Join Date: Jan 2004
Location: Here, there and everywhere
Posts: 1,197
|
Thanks for that. Very interesting......and revealing.
Quote:
Code:
colorbars().converttoyv12().mt_lutspa(relative=true,expr="x 255 *").greyscale()
GSG = colorbars().converttoyv12().mt_lutspa(relative=true,expr="x 256 * ").Greyscale().LanczosResize(640,640)
GSG2 = GSG.Reduceby2()
GSGL = GSG.TurnLeft()
NGry2 = GSG.mt_lut("x 128 ", y=3).Reduceby2()
UspecG = YtoUV(GSG2, NGry2, GSGL)
VSpecG = YtoUV(NGry2, GSG2, GSGL)
Return Interleave(USpecG, VSpecG)
U Spectrum with greyscale gradient http://www.mediafire.com/file/gjnhzq...USpec_Grad.png V Spectrum with greyscale gradient http://www.mediafire.com/file/dq1jnz...VSpec_Grad.png .......But your function, with the 'invalid RGB value' mask, really nails it. Quote:
In fact, what would be really useful is if the function could be developed to apply the analysis to 'real' YV12 image frames and map (maybe with separate color coding for U and V) those areas where the chroma values are rendered 'RGB invalid' and clipped. That would be of great practical value. I wonder how many folks, like myself, apply all manner of luma and chroma pixel transformations to their YUV videos, blissfully unaware that the resulting colors may not be faithfully reproduced in RGB display i.e. what they are seeing on the screen may not be the 'true' YUV colors. Pretty significant I think.
__________________
Nostalgia's not what it used to be Last edited by WorBry; 24th May 2010 at 06:13. |
||
|
|
|
|
|
#3 | Link |
|
x264 developer
Join Date: Sep 2005
Posts: 8,666
|
![]() Animated GIF version.
__________________
Follow x264 development progress | akupenguin quotes | x264 git status ffmpeg and x264-related consulting/coding contracts | Doom10 Last edited by Wilbert; 29th December 2014 at 23:34. Reason: corrected link |
|
|
|
|
|
#6 | Link |
|
Registered User
Join Date: Dec 2002
Location: UK
Posts: 1,673
|
Note to anyone who hasn't actually read the script:
It defaults to PC range, with TV range part commented out. However, valid YUV is always in TV range, so the default behaviour is misleading (to say the least)! Very cool script though. Nice use of masktools! Cheers, David. |
|
|
|
|
|
#8 | Link | ||
|
Avisynth language lover
Join Date: Dec 2007
Location: Spain
Posts: 3,442
|
Quote:
However, the calculation for valid/invalid RGB takes that into account, being done on normalised/scaled values in the range [0, 1] for luma and [-1, +1] for chroma. And the colors are converted with matrix PC.601 for output. So the general behaviour is exactly the same for TV range YUV, if you take the UV plot to represent the entire available range (16-240 for TV) and the displayed Y value in brackets to be the fraction of 'full-range'. Quote:
Last edited by Gavino; 24th May 2010 at 17:45. Reason: typo (had a -1 instead of +1) |
||
|
|
|
|
|
#9 | Link |
|
Avisynth language lover
Join Date: Dec 2007
Location: Spain
Posts: 3,442
|
I have now extended the script in the first post to add plots of YU (for each valid V) and YV (for each valid U), and to show plots for both TV range and PC range. The TV range plots have a grey border marking the smaller overall ranges available (16-235 for luma, 16-240 for chroma).
Next job will be to create a function (as requested by Worbry) to analyse arbitrary YV12 images and map the areas with 'RGB invalid' chroma. Examples from new script: |
|
|
|
|
|
#10 | Link |
|
Registered User
Join Date: Jan 2004
Location: Here, there and everywhere
Posts: 1,197
|
Once again, a very enlightening demonstration, not only of the 'RGB-invalid chroma effect', but also the psycho-visual basis of YUV color perception. I've made it my new screen-saver
![]() Thanks for your work - looking forward to the 'applied' function
__________________
Nostalgia's not what it used to be Last edited by WorBry; 28th May 2010 at 00:08. |
|
|
|
|
|
#11 | Link |
|
Registered User
Join Date: Jan 2004
Location: Here, there and everywhere
Posts: 1,197
|
While Gavino is working on the 'applied' function, I have been thinking (ahead) about possible strategies to avoid/minimize/resolve RGB-invalid YUV values.
Came across this earlier thread on the subject http://forum.doom9.org/showthread.ph...lack+overshoot The cited (fairly dated) BBC paper on 'Limiting of YUV digital video signals', is interesting reading, although I cant pretend to understand all of it http://downloads.bbc.co.uk/rd/pubs/reports/1987-22.pdf As a general (software) strategy, the author advocates a 'constant hue' approach, whereby the luma is not touched and the U and V values are scaled back (limited) to the maximum 'valid' values having the same hue as the invalid U and V values before limiting. Seems reasonable, although I cant see it being the optimum solution in all cases, for example, if one is dealing with an already under-saturated source (as was the case in the aforementioned thread). Also, if 'invalid' values are a result of deliberate alteration of the luma (for example, through creation of a contrast luma curve) a more logical approach might be to settle on a trade-off between desired luma effect and chroma (de)saturation. Maybe the selective saturation and hue (SelSah) function that I put together (allows saturation and hue control to be applied to a defined luma range) will be helpful in this regard (see the thread referred to in Gavino's first post) - we'll see. Either way, I'm anticipating that the 'applied' function will prove to be a useful visualization tool for guiding such corrective measures. If it does, maybe some auto-corrective option could be incorporated in the future. I'm jumping the gun, of course. No pressure.
__________________
Nostalgia's not what it used to be Last edited by WorBry; 29th May 2010 at 14:29. |
|
|
|
|
|
#13 | Link |
|
Registered User
Join Date: Jan 2004
Location: Here, there and everywhere
Posts: 1,197
|
Also came across this, more recent, article, proposing an improved approach to the resolution of out-of-gamut YUV values:
http://sas-origin.onstreammedia.com/...r%201/Chan.pdf I wonder if his 'in-range chroma reconstruction' model is something than could be tested with AVISynth (MaskTools maybe), although, in my minds-eye, both methods (Spill and Proportion) seem tantamount to chroma blur.
__________________
Nostalgia's not what it used to be |
|
|
|
|
|
#14 | Link |
|
Avisynth language lover
Join Date: Dec 2007
Location: Spain
Posts: 3,442
|
Here are two functions which can be used to test any YV12 clip for 'invalid' RGB values. See the comments for details.
Code:
# Returns a mask with 255 where YV12 clip has valid RGB and 0 elsewhere
function RGBMask(clip c, bool "pcRange") {
c2 = c.BilinearResize(2*c.width, 2*c.height)
return c.mt_lutxyz(c2.UToY(), c2.VToY(), ValidRGB(" x", " y", " z", pcRange))
}
# Shows the areas of a YV12 clip which contain 'invalid RGB';
# good pixels are replaced by 'color', default black.
function ShowBadRGB(clip c, bool "pcRange", int "color") {
mask = c.RGBMask(pcRange)
c.mt_merge(BlankClip(c, color=color), mask, U=3, V=3, luma=true)
}
Script loading is very slow because of the mt_lutxyz function (which has to create a lookup table with 2^24 entries). Below I have shown the results of applying ShowBadRGB to (the equivalent of) your USpec and VSpec clips, using the following code: Code:
BlankClip(2, 320, 320, pixel_type="YV12")
USpec = mt_lutspa(yexpr="1 y - 255 *", uexpr="x 255 *", U=3, V=-128)
VSpec = mt_lutspa(yexpr="1 y - 255 *", vexpr="x 255 *", U=-128, V=3)
Interleave(USpec, VSpec)
ShowBadRGB(pcRange=true)
return ConvertToRGB("PC.601")
The papers you referenced look interesting, although I haven't had time to study them in detail. The BBC one gives a fairly good explanation of the shape of the plots shown earlier. Results from ShowBadRGB(): |
|
|
|
|
|
#15 | Link |
|
Registered User
Join Date: Jan 2004
Location: Here, there and everywhere
Posts: 1,197
|
Excellent. As you say, very slow to load, but it gets there. Thanks so much for doing this. Now to set about doing some testing.
__________________
Nostalgia's not what it used to be |
|
|
|
|
|
#16 | Link |
|
Registered User
Join Date: Jan 2004
Location: Here, there and everywhere
Posts: 1,197
|
For testing, I'm using clips from a variety of sources, including some AVCHD camcorders (Panasonic HMC-41 and HMC-151). For compatability with the test system, they need to be converted from Rec.709 to Rec.601 and scaled for 'TV' and 'PC' ranges. Trouble is that, like many consumer/prosumer camcorders, the luma scale is effectively 16 - 255 i.e. allows 235-overshoot (super-white). Here's an example:
http://www.mediafire.com/file/qm5yti...20Original.png (Probably not the best example, in terms of black-point, but it serves to illustrate the 235-overshoot) The following offers a solution: Code:
AVCSource("C:\...........Test clip.dga")
clp=last
PCY = clp.YLevels(16, 1.0, 255, 0, 255)
UV601 = clp.ColorMatrix(mode="Rec.709->Rec.601")
PCUV = UV601.ColorYUV(levels="TV->PC")
mt_lutxy(PCY, PCUV, y=2, u=4, v=4)
#LanczosResize(1024,576) #Resized for Histogram
#Histogram(mode="levels")
http://www.mediafire.com/file/yiqdmm...9to601toPC.png ShowBadRGB analysis: http://www.mediafire.com/file/xiwowt...ge%20PC601.png But, it seems a little convoluted and I am wondering if there is a simpler, more direct solution. Similar considerations apply for 'TV range'. A simple ColorMatrix(mode="Rec.709->Rec.601") conversion clips the overshot luma to 235: Result: http://www.mediafire.com/file/dymdt4...ec709to601.png ShowBadRGB analysis: http://www.mediafire.com/file/uzinjx...e%20Rec601.png So, again, would it be valid (for these tests) to scale down the luma from 255 and 235 and take the chroma from Rec.709->Rec.601 Code:
AVCSource("C:\...........Test clip.dga")
clp=last
TVY = clp.YLevels(16, 1.0, 255, 16, 235)
TVUV = clp.ColorMatrix(mode="Rec.709->Rec.601")
mt_lutxy(TVY, TVUV, y=2, u=4, v=4)
#LanczosResize(1024,576) #Resized for Histogram
#Histogram(mode="levels")
http://www.mediafire.com/file/nxmnjb...20709to601.png ShowBadRGB analysis: http://www.mediafire.com/file/mywkfc...e%20Rec601.png Would appreciate advice. Edit: Also added the results of the 'ShowBadRGB' analysis for each of the above. Interesting that the 'PC Range' and 'TV Range' (using the 255->235 downscaled clip) analyses give very similar patterns - RGB-invalid values associated with the shiny glare on the knights armour (presumably the source of the overshot luma). The straight 'Rec.709->Rec.601 converted' clip however shows more extensive 'bad-RGB', presumably due to the 235 clipping. I've started to do some tests with applied luma contrast curves. Predictably, invalid values then begin to appear on the more saturated colors e.g. the bright reds on the knights tunics, and to a lesser extent the greens.......Edit: not surprising since the rec.601 YUV values for (true) red are stated to be 81, 90, 240 Is it just me, or are the knights getting shorter?
__________________
Nostalgia's not what it used to be Last edited by WorBry; 1st June 2010 at 17:01. |
|
|
|
|
|
#17 | Link |
|
Avisynth language lover
Join Date: Dec 2007
Location: Spain
Posts: 3,442
|
I'm not sure your approach is valid as you seem to be mixing Rec.709 luma with Rec.601 chroma, which does not seem correct.
Assuming the input chroma is TV range (am I right to assume this?), then for TV range output, I think you want: YLevels(16, 1.0, 255, 16, 235) ColorMatrix(mode="Rec.709->Rec.601") and for PC range, simply extend this with: ColorYUV(levels="TV->PC") Another possibility is to extend my function to work with Rec.709 and avoid doing any colorimetry conversions at all. A reminder in case you need it: for viewing the results of all these things, or producing screenshots, be sure to add ConvertToRGB(matrix=whatever) to your script unless you are viewing them in something that knows it is not dealing with TV-range Rec.601 input. PS. Once a knight, always a knight; ...
Last edited by Gavino; 1st June 2010 at 17:39. |
|
|
|
|
|
#18 | Link | |
|
Registered User
Join Date: Jan 2004
Location: Here, there and everywhere
Posts: 1,197
|
Quote:
__________________
Nostalgia's not what it used to be Last edited by WorBry; 1st June 2010 at 17:53. |
|
|
|
|
|
|
#20 | Link | |||||
|
Registered User
Join Date: Jan 2004
Location: Here, there and everywhere
Posts: 1,197
|
Just missed it in passing.
Quote:
Quote:
Quote:
Quote:
Quote:
__________________
Nostalgia's not what it used to be Last edited by WorBry; 2nd June 2010 at 01:06. |
|||||
|
|
|
![]() |
| Thread Tools | Search this Thread |
| Display Modes | |
|
|