Log in

View Full Version : Chroma shift in interlaced YV12 to YUY2 conversion


Pages : [1] 2

Gavino
8th June 2009, 22:08
The wiki page on Sampling (http://avisynth.org/mediawiki/Sampling#YV12_interlaced_conversion_-.3E_YUY2) describes Avisynth's method of converting interlaced YV12 to YUY2, and states that it differs from the MPEG2 specs. It also speculates that this may be for performance reasons - can anyone confirm this?

Whatever the reason for its adoption, the method used has the undesirable effect of introducing a vertical chroma shift. Moreover, the shift is in different directions for top and bottom fields, making the effect more visible. Top fields have chroma shifted down 1/2 pixel and bottom fields up 1/2 pixel.

I couldn't find any mention of this problem in the archives (though I did find a reference to a similar problem where conversion of RGB to YUY2 introduces a horizontal chroma shift).

The shift can be demonstrated in this test script, which progressively repeats conversion of YV12 to YUY2 and back again. Even after only a single conversion, the effect is clearly visible.
function Convert(clip c, int times) {
times == 0 ? c : c.ConvertToYUY2(interlaced=true).ConvertToYV12(interlaced=true).Convert(times-1)
}

ColorBars(pixel_type="YV12").TurnRight()
Animate(0, 10, "Convert", 0, 10)
Another angle on the problem is that one would expect
ConvertToYUY2(interlaced=true).ConvertToYV12(interlaced=true)
to be almost a no-op, just a slight chroma blur. Using the MPEG2 interpolation coefficients, it would have a kernel of [3 26 3]/32, which fits the bill. Interestingly, IanB states here that the Avisynth implementation also gives this kernel, but by my calculations it actually has a kernel of [3 12 1]/16 for top fields and [1 12 3]/16 for bottom fields. Both kernels are asymmetric, resulting in shifts (each in a different direction), which is exactly what we see.

Manao
9th June 2009, 15:02
Top fields have chroma shifted down 1/2 pixel and bottom fields up 1/2 pixel.Which is normal for interlaced content. I advise you to check yuy2toyv12.h in http://manao4.free.fr/conversion-v1.0.zip for an explanation

Gavino
9th June 2009, 17:07
Which is normal for interlaced content. I advise you to check yuy2toyv12.h in http://manao4.free.fr/conversion-v1.0.zip for an explanation
Yes, I understand the horrible details of chroma placement in the different formats. When I talk about chroma shift, I mean that the code does not produce the required output chroma placement and resamples to the wrong position. (In that sense, a shift cannot be 'normal', by definition.)

The case I am talking about is YV12 to YUY2, so it is your yv12toyuy2.h that is relevant here. It says the kernels for YV12 interlaced are (7, 1) and (5, 3) for top fields and (5, 3) and (7, 1) for bottom fields. This agrees with the MPEG2 specs, whereas Avisynth uses (3, 1) and (1, 3), which produces a shift.

tritical
9th June 2009, 18:20
You are right that Avisynth's alternating 0.25/0.75, 0.75/0.25 approximation, which I think is used because it can be done quickly with pavgb, instead of 1/8,7/8 3/8,5/8 5/8,3/8 7/8,1/8 in the interlaced conversion will shift chroma after chaining yv12<->yuy2 interlaced conversions together. The fact that it is off has been mentioned before. I think nobody ever mentioned the shifting problem because chaining interlaced conversions together doesn't happen very often. It would definitely be nice if the interlaced=true/false conversions agreed on positioning. It would also be nice if the rgb<->yuy2 conversions agreed. With Avisynth being open source it just comes down to if someone wants to put in the work to change it in the code, and whether the speed hit is worth it.

To illustrate the point, here is your colorbars example after 20 conversions using avisynth's conversions on the left, and correct placement on the right. This is after a utoy() call at the end. (I added the original in the middle)

http://bengal.missouri.edu/~kes25c/chroma.png

Manao
9th June 2009, 18:49
It would also be nice if the rgb<->yuy2 conversions agreed. With Avisynth being open source it just comes down to if someone wants to put in the work to change it in the code, and whether the speed hit is worth it.Well, the link i gave point toward the yuy2 <-> yv12 conversions I had written for avs3. It comes with isse asm, and can fairly easily be upgraded to sse2 / ssse3 if need be.

Gavino
9th June 2009, 23:49
Manao,
This is distinct from the original topic, but it's interesting that your code for YUY2 to YV12 conversion uses a four-pixel kernel, ie [1 3 3 1] for progressive and [3 7 5 1] or [1 5 7 3] for interlaced, while Avisynth uses only two pixels, [1 1] for progressive and [3 1] or [1 3] for interlaced.

Your version, though slower, is theoretically better for a 2x downsampling without aliasing, but in practice is there a visible difference in quality?

Manao
12th June 2009, 09:42
but in practice is there a visible difference in quality? I hate aliasing, so I didn't even consider comparing [1 1] with [1 3 3 1]. You can easily run a comparison using avisynth : bilinearesize(clip, clip.width, clip.height / 2) uses [1 3 3 1], while mt_average(clip, clip.pointresize(clip.width, clip.height, 0, 0, 1, 0)).pointresize(clip.width, clip.height/2) uses [1 1]. The second one is far sharper, because it aliases as hell (though bilinearresize aliases too, but to a lesser extent)

Incast
12th June 2010, 16:34
Reading the above, it seems that Manao's attached files offer a more accurate level of conversion for interlaced YV12 to YUY2 than the built in avisynth conversion.

Forgive my ignorance, but can someone explain to me how I get my avisynth install to use the files?

WorBry
13th June 2010, 05:32
Reading the above, it seems that Manao's attached files offer a more accurate level of conversion for interlaced YV12 to YUY2 than the built in avisynth conversion

I think this is (being) addressed in AVISynth v2.6...is it not ?

Mini-Me
12th January 2011, 22:10
I'm sorry to raise this thread from the dead for a second time, but if I started a new thread I'd just be copy/pasting from here. I came looking for this thread because I noticed that Avisynth isn't converting my own interlaced YUY2 sources to YV12 correctly...I think.*

I'm a nobody here, but IMO this is an obstacle for people with interlaced YUY2 sources, and that includes myself. Even done as well as it can be, interlaced YV12 is a pretty screwy format. However, most Avisynth scripts only work in YV12...including the best deinterlacer (TGMC/QTGMC), ironically enough. People with interlaced YUY2 sources can't deinterlace before YV12 conversion without sub-optimal chroma deinterlacing (like using Yadif for chroma), and we can't convert to YV12 before deinterlacing (or running any other filter that affects chroma) without sub-optimal colorspace conversion. On top of that, anyone with an interlaced YUY2 avi file probably has it in a large and/or lossless format (e.g. Huffyuv) that needs to be trimmed down...but high-quality encoders like x264 only take YV12 and won't encode 4:2:2 material. People with interlaced YUY2 files cannot deinterlace optimally, cannot convert to interlaced YV12 optimally, and cannot directly compress their YUY2 source and wait out a solution to the other problems. It's currently a lose/lose/lose situation that I'd like to call "YV12 hell." (A pox on the house of whoever invented interlaced YV12! I used to blame the inventor of interlacing, but the invention of TGMC now makes me reconsider interlaced content as the larval stage of super-duper-awesome double framerate content. ;))

WorBry asked in June if this is being addressed in Avisynth 2.6. Does anyone know the answer? In any case, Avisynth 2.6 has been in the works for a long time, and alpha 2 has been out for over a year, so I'm not sure when it will actually come to fruition. In the meantime, does anyone know of a workaround? Do Manao's routines mentioned above fix it? Without confirmation I'm assuming they don't, since they were written before Gavino and tritical brought this particular issue to Manao's attention, but it's worth asking. If not, is there any software or existing filter/plugin which can do a proper conversion to YV12?

If there's no existing solution, what would it take for someone with no experience to create one? The .h file Manao mentioned includes a description of several interlaced YV12 chroma placements, but there's no real mention of which is optimal for display and for further manipulation by other filters. This seems pretty hairy, because a lot of filters - especially deinterlacing - are probably greatly affected by their assumptions about chroma placement, so the "best" kernel to use may be a tossup. Regardless, the one being used now seems pretty broken.

*EDIT: Actually, I'm not really sure about what artifacts I'm getting under which conditions. There are four ways to convert to YV12, given the four combinations of interlaced/progressive conversion with woven/separated fields. I can then compare them in YV12, or I can convert back to YUY2 - again, four different ways - and compare them against each other and the original, both with separated fields and woven frames. On top of that, it seems the chroma placement varies a bit with the arrangement of the clip in Avisynth stacking (which I found out on accident by subtracting the original clip from the same conversion twice and stacking the same subtraction on top of itself). Anyway, that's a lot of possibilities to test. I had been using really screwed up frames with tons of combing from the beginning of a VHS capture to better compare the artifacts too, but that may not have been the best idea. ;)

Gavino
13th January 2011, 12:28
WorBry asked in June if this is being addressed in Avisynth 2.6. Does anyone know the answer? In any case, Avisynth 2.6 has been in the works for a long time, and alpha 2 has been out for over a year, so I'm not sure when it will actually come to fruition. In the meantime, does anyone know of a workaround?
I don't think the default behaviour of the converters has changed in Avisynth 2.6 (perhaps IanB can confirm?). However, 2.6 has added parameters chromaplacement and chromaresample to the Convert (http://avisynth.org/mediawiki/Convert) filters. I haven't tried it yet, but in principle I believe using chromaresample="bilinear" should give the correct results.
Do Manao's routines mentioned above fix it? Without confirmation I'm assuming they don't, since they were written before Gavino and tritical brought this particular issue to Manao's attention, but it's worth asking.
Manao's routines are correct (and were written long before this thread existed), but they are not part of Avisynth.
There are four ways to convert to YV12, given the four combinations of interlaced/progressive conversion with woven/separated fields.
Converting separated interlaced fields will always give the wrong result, as the converters are not set up to handle this.

Mini-Me
13th January 2011, 21:14
I don't think the default behaviour of the converters has changed in Avisynth 2.6 (perhaps IanB can confirm?). However, 2.6 has added parameters chromaplacement and chromaresample to the Convert (http://avisynth.org/mediawiki/Convert) filters. I haven't tried it yet, but in principle I believe using chromaresample="bilinear" should give the correct results.

Manao's routines are correct (and were written long before this thread existed), but they are not part of Avisynth.
That's helpful to know. I may have to try the 2.6 alpha then, unless I can shoehorn Manao's routines into a plugin.

Converting separated interlaced fields will always give the wrong result, as the converters are not set up to handle this.

That's what I figured, but I had to try it anyway out of desperation and for completeness. ;) As expected, the actual YV12 video is screwed up when you convert with separated fields...but strangely enough, weaving again and upsampling back to YUY2 in interlaced mode actually resembles the original more closely under some circumstances than any other combination I've tried.

If you want to see what I'm talking about, here's an interesting comparison:
original = Avisource("yuy2video.avi", pixel_type = "YUY2")
progyv12 = original.AssumeTFF.ConvertToYV12()
progyv12backtoyuy2 = progyv12.ConvertToYUY2()
brokenyv12 = original.AssumeTFF.SeparateFields.ConvertToYV12.Weave()
interestingyuy2 = brokenyv12.ConvertToYUY2(interlaced = True)
return Interleave(original, interestingyuy2, progyv12backtoyuy2)

In this case, the progressively converted video has pretty much the same characteristics in YV12 and YUY2 after converting back. It looks fine for the most part, but the progressive conversion blends chroma between combed fields, which gives incorrect (blended) color in combed areas. It's better than the artifacts from built-in interlaced conversion though and seems to give the best YV12 image overall (before trying the corrected conversions), which is why I included it in the comparison instead of the interlaced conversion. Deinterlacing probably makes the blended color far more obvious though, which I haven't checked yet.

The image with brokenyv12 is broken, as expected. It seems that alternating lines from the same field end up getting different color depending on combing characteristics. However, interestingyuy2 is...interesting. Find a heavily combed clip where the progressive conversion causes noticeable blending of colors. In scenes where the progressive conversion shows blending, the "interesting" version looks far closer to the original (almost indistinguishable) than any other YUY2->YV12->YUY2 conversion I've tried...and I think I tried all 16. It retains all of the color of the original fields in certain heavily combed areas, and it has no especially obvious artifacts.

There are two downsides to the "interesting" version though: First, it's totally pointless and academic, since the goal is to convert to YV12, not to convert to YV12 and back to YUY2...and the YV12 clip looks terrible and broken before converting back to YUY2 in the exact way given. Second, while it's usually imperceptible to the naked eye with a single conversion, this method does seem to smear the color within each field (and it aliases somewhat on your test pattern). If you subtract the conversions from the original, the interesting version will show much larger differences in most areas, though the difference image will not be combed.

Anyway, I'll either try the 2.6 alpha or try to make a plugin out of Manao's routines. Hopefully the fixed interlaced conversion will be the best of both worlds. :)

Gavino
13th January 2011, 21:44
I've just tried my test program (from the first post) with Avisynth 2.60 Alpha 2. The results appear identical to 2.58 (as I expected).

I then tried again setting chromaresample="bilinear" in both Convert calls. The result was arguably worse - the shift is greater, although the effect is different, as both fields are now shifted in the same direction (upwards).

Also, using chromaresample="bilinear" even with interlaced=false also produces a shift, this time about half as big as with interlaced=true.

IanB, any idea what is going on here?

Gavino
15th January 2011, 05:09
I think the 2.60 code in class ConvertToPlanarGeneric is setting the wrong offsets for chroma resampling, when converting in either direction between YV16 and YV12 (which affects YUY2<->YV12 when chromaresample is set).

The code does not seem to take into account that:
(1) the resizers maintain the image centre, so an offset of zero should be used in non-interlaced YV12<->YUY2 conversions;
(2) different offsets are needed for top and bottom fields when interlaced=true.

I believe the required offsets and those actually produced by the code are:
From YV12 Progressive Top fields Bottom fields
Required 0 0.125 -0.125
Actual 0 0 0

To YV12
Required 0 -0.25 0.25
Actual 0.5 0.5 0.5

I think this explains the results I reported in the previous post.
Whaddaya think, Ian?

IanB
15th January 2011, 21:52
Yes that ConvertToPlanarGeneric alignment code is a bit (lot) of a mess. It's all related to problems with ConvertTo...(chromaplacement=""...") which is currently just plain wrong. It's "defined" in multiple place and sometimes confusingly gets overridden later. :confused:

Gavino
15th January 2011, 22:39
Yes that ConvertToPlanarGeneric alignment code is a bit (lot) of a mess.
That's the conclusion I came to after studying it more closely today. :(
As well as the points I already made,
- the 3rd and 4th offsets are wrongly set as they should always be just width and height respectively.
- need to distinguish when resizer is "Point", since there is no centre correction in this case.
In the meantime, does anyone know of a workaround?
Here's a workaround for the original 2.58 problems. It should be functionally equivalent to Manao's code, I think, eliminating the chroma shift on interlaced YV12->YUY2 and the chroma aliasing on YUY2->YV12.
# Replacement code for YUY2<->YV12 conversions in v2.58
function YV12ToYUY2(clip c, bool "interlaced") {
interlaced = Default(interlaced, false)
interlaced ? c.YV12ToYUY2i() : c.YV12ToYUY2p()
}

function YUY2ToYV12(clip c, bool "interlaced") {
interlaced = Default(interlaced, false)
interlaced ? c.YUY2ToYV12i() : c.YUY2ToYV12p()
}

# ------- Support functions --------

function YV12ToYUY2p(clip c) {
U = c.UToY().BilinearResize(c.width/2, c.height).ConvertToYUY2()
V = c.VToY().BilinearResize(c.width/2, c.height).ConvertToYUY2()
YToUV(U, V, c.ConvertToYUY2()) #crashes on v2.60
}

function YV12ToYUY2i(clip c) {
f = c.AssumeTFF().SeparateFields()
top = f.SelectEven()
bot = f.SelectOdd()
topU = top.UToY().BilinearResize(c.width/2, c.height/2, 0, 0.125)
botU = bot.UToY().BilinearResize(c.width/2, c.height/2, 0, -0.125)
U = Interleave(topU, botU).ConvertToYUY2()
topV = top.VToY().BilinearResize(c.width/2, c.height/2, 0, 0.125)
botV = bot.VToY().BilinearResize(c.width/2, c.height/2, 0, -0.125)
V = Interleave(topV, botV).ConvertToYUY2()
YToUV(U, V, f.ConvertToYUY2()).Weave() #crashes on v2.60
}

function YUY2ToYV12p(clip c) {
U = c.UToY().BilinearResize(c.width/2, c.height/2).ConvertToYV12()
V = c.VToY().BilinearResize(c.width/2, c.height/2).ConvertToYV12()
YToUV(U, V, c.ConvertToYV12())
}

function YUY2ToYV12i(clip c) {
f = c.AssumeTFF().SeparateFields()
top = f.SelectEven()
bot = f.SelectOdd()
topU = top.UToY().BilinearResize(c.width/2, c.height/4, 0, -0.25)
botU = bot.UToY().BilinearResize(c.width/2, c.height/4, 0, 0.25)
U = Interleave(topU, botU).ConvertToYV12()
topV = top.VToY().BilinearResize(c.width/2, c.height/4, 0, -0.25)
botV = bot.VToY().BilinearResize(c.width/2, c.height/4, 0, 0.25)
V = Interleave(topV, botV).ConvertToYV12()
YToUV(U, V, f.ConvertToYV12()).Weave()
}
Don't use the above code on the current 2.60 as it will crash due to a bug in YToUV (fixed but not yet released).
In any case, the new formats available in 2.6 make this more efficient version possible:
# Replacement code for YUY2<->YV12 conversions in v2.60
function YV12ToYUY2_26(clip c, bool "interlaced") {
interlaced = Default(interlaced, false)
interlaced ? c.YV12ToYUY2i_26() : c.YV12ToYUY2p_26()
}

function YUY2ToYV12_26(clip c, bool "interlaced") {
interlaced = Default(interlaced, false)
interlaced ? c.YUY2ToYV12i_26() : c.YUY2ToYV12p_26()
}

# ------- Support functions --------

function YV12ToYUY2p_26(clip c) {
U = c.UToY8().BilinearResize(c.width/2, c.height).ConvertToYV16()
V = c.VToY8().BilinearResize(c.width/2, c.height).ConvertToYV16()
YToUV(U, V, c.ConvertToYV16()).ConvertToYUY2()
}

function YV12ToYUY2i_26(clip c) {
f = c.AssumeTFF().SeparateFields()
top = f.SelectEven()
bot = f.SelectOdd()
topU = top.UToY8().BilinearResize(c.width/2, c.height/2, 0, 0.125)
botU = bot.UToY8().BilinearResize(c.width/2, c.height/2, 0, -0.125)
U = Interleave(topU, botU).ConvertToYV16()
topV = top.VToY8().BilinearResize(c.width/2, c.height/2, 0, 0.125)
botV = bot.VToY8().BilinearResize(c.width/2, c.height/2, 0, -0.125)
V = Interleave(topV, botV).ConvertToYV16()
YToUV(U, V, f.ConvertToYV16()).ConvertToYUY2().Weave()
}

function YUY2ToYV12p_26(clip c) {
U = c.UToY8().BilinearResize(c.width/2, c.height/2)
V = c.VToY8().BilinearResize(c.width/2, c.height/2)
YToUV(U, V, c.ConvertToY8())
}

function YUY2ToYV12i_26(clip c) {
f = c.AssumeTFF().SeparateFields()
top = f.SelectEven()
bot = f.SelectOdd()
topU = top.UToY8().BilinearResize(c.width/2, c.height/4, 0, -0.25)
botU = bot.UToY8().BilinearResize(c.width/2, c.height/4, 0, 0.25)
U = Interleave(topU, botU)
topV = top.VToY8().BilinearResize(c.width/2, c.height/4, 0, -0.25)
botV = bot.VToY8().BilinearResize(c.width/2, c.height/4, 0, 0.25)
V = Interleave(topV, botV)
YToUV(U, V, f.ConvertToY8()).Weave()
}

Mini-Me
16th January 2011, 03:14
Wow, thanks Gavino! I'll test those routines out in just a bit. Assuming they're correct - which I'm sure they are - that eliminates the need for me to figure out how to turn Manao's source into a plugin. :D

yup
16th January 2011, 08:01
Gavino!:thanks:
It is very interesting where we can find problem. I am like Mini-Me use ConvertToYV12() before calling QTGMC deinterlacer and using Set 2.6 build. Which script I could use (for 2.5.8 or 2.6)?
yup.

Gavino
16th January 2011, 10:33
... using Set 2.6 build. Which script I could use (for 2.5.8 or 2.6)?
I don't have that build installed, so I can't test it.
But I think the 2.6 version of my script should work.

IanB
17th January 2011, 05:04
A theoretical improvement for a correctly working 2.6 (yes there are still more bugs in the 'fixed' YtoUV code :o)# ------- Support functions --------

function YV12ToYUY2p_26(clip c) {
UV = Interleave(c.UToY8(), c.VToY8()).BilinearResize(c.width/2, c.height)
YToUV(UV.SelectEven(), UV.SelectOdd(), c).ConvertToYUY2()
}

function YV12ToYUY2i_26(clip c) {
f = c.AssumeTFF().SeparateFields()
top = f.SelectEven()
bot = f.SelectOdd()
topUV = Interleave(top.UToY8(), top.VToY8()).BilinearResize(c.width/2, c.height/2, 0, 0.125)
botUV = Interleave(bot.UToY8(), bot.VToY8()).BilinearResize(c.width/2, c.height/2, 0, -0.125)
U = Interleave(topUV.SelectEven(), botUV.SelectEven())
V = Interleave(topUV.SelectOdd(), botUV.SelectOdd())
YToUV(U, V, f).ConvertToYUY2().Weave()
}
In planar mode the intended operation of YtoUV is to auto-determine the output pixel format from the relative dimensions of the input clips, so you can use Y8 everywhere for the intermediate clips. Also a cunning trick to share the resizer between U and V paths.

Note the above script blows up on both the released 2.6 and the current CVS code. I'll commit the latest fixes in the next few days.

Mounir
17th January 2011, 17:37
I find the Gavino tweak interesting but sadly i have no idea how to use this workaround in the v2.6. Should i save it as .avsi and import it, or what ? You tell me

Gavino
17th January 2011, 18:13
... to use this workaround in the v2.6. Should i save it as .avsi and import it, or what ?
You can either
- just copy it into every script that needs it, or
- save it as either .avs or .avsi, and then Import() it, or
- save it as .avsi to Avisynths plugin folder, then it will be imported automatically.

Don't forget to include IanB's performance improvements (but only once the 2.60 UVToY bug is fixed - see his post #20).

The functions you actually want to call in your script are YV12ToYUY2_26 (which replaces ConvertToYUY2 for YV12 inputs) and YUY2ToYV12_26 (which replaces ConvertToYV12 for YUY2 inputs).

Mounir
17th January 2011, 19:11
Thanks for the reply, that's helping.
So i must completely replace the Gavino's coding part for "Support functions " YV12ToYUY2p_26 and YV12ToYUY2i_26 (progressive & interlaced respectively) with IanB tweaks if i understand it right.

Edit:
Well i tried importing and using YUY2ToYV12_26 in my script i get an error in avsp
script error: end of file reached without matching }

Gavino
17th January 2011, 19:31
You probably missed a bit when copying and pasting.
Note that some of the code as shown on the forum page is scrolled off the bottom of the displayed 'code' box. Be sure you scroll to the end to get it all.

Mounir
17th January 2011, 19:51
It seems to be working now ,i forgot a } in the script.

I have a tie-breaker: Is it normal that there is no matrix parameter ?
Edit: nevermind the question it's YUV, no matrix involved

jmac698
17th January 2011, 20:58
Updated http://avisynth.org/mediawiki/Known_Issues
thanks.

Gavino
17th January 2011, 23:17
Is it normal that there is no matrix parameter ?
Edit: nevermind the question it's YUV, no matrix involved
That's right, but it raises a related point.
RGB conversions to and from YV12 go via YUY2 internally in Avisynth. So, for example, an interlaced YV12 to RGB conversion will also suffer the chroma shift problem. If that's a concern, you need to convert to YUY2 using the workaround code, then do the final conversion to RGB yourself.

Another thing: I forgot that IanB's changes won't work until there is an updated 2.60 (see his post#20), so don't include them just yet. I've edited my earlier post to reflect that.

Mounir
18th January 2011, 00:07
I see that's interesting.One can now see the benefit of capturing videos in YUY2 directly.

poisondeathray
18th January 2011, 01:48
The thread title refers to interlaced conversions, but Gavino can you confirm if this code affect progressive conversions? for example RGB<=>YV12 . The "support functions" seem to have a "p" version - I'm assuming for progressive ?

It seems to be more accurate in my tests, less blurring/shifting after consecutive conversions


#start with progressive RGB image
ConverttoYUY2()
YUY2ToYV12_26()
ConvertToRGB()

vs.

#progressive RGB image
ConvertToYV12()
ConvertToRGB()


After several rounds of conversions, the old method is noticably worse

Is Manao's "four pixel kernal method" a similar method that NLE's use? Because several rounds of RGB<=>YV12 conversions there do not incur the same losses as the old avisynth method either

Speed was mentioned as slower ; is this negligible or is there a big difference?

Gavino
18th January 2011, 14:26
The "support functions" seem to have a "p" version - I'm assuming for progressive ?
Yes, the "p" versions are for the progressive case.
YUY2ToYV12 avoids potential aliasing in chroma subsampling (in both interlaced and progressive);
YV12ToYUY2(interlaced=false) is not strictly needed, since both 2.58 and 2.60 handle this case correctly, but is included for consistency and completeness.
for example RGB<=>YV12
Once you bring RGB into the frame, you open a different (but related) can of worms in the current implementation.
The RGB->YUY2 code (also used for RGB->YV12 on 2.58) adds a horizontal shift (see this thread, also referenced in the first post here).
On 2.60, RGB<->YV12 goes via YV24 (not YUY2), but this path currently also has bugs - YV24->YV12 adds both a horizontal and a vertical shift, while YV12->YV24 has an exactly opposite horizontal shift (which cancels out in YV24->YV12->YV24).

To summarise, for progressive RGB->YV12->RGB, you get the following chroma shifts (Horizontal and/or Vertical):
2.58 2.60
RGB->YV12: H H+V
YV12->RGB: OK -H
RGB->YV12->RGB: H V
For interlaced, it is:
2.58 2.60
RGB->YV12: H H+V
YV12->RGB: V -H
RGB->YV12->RGB: H+V V
Is Manao's "four pixel kernal method" a similar method that NLE's use? Because several rounds of RGB<=>YV12 conversions there do not incur the same losses as the old avisynth method either
I don't know what NLEs do, but once 2.60 is working properly, the RGB<->YV12 conversions will be about as good as you can get, with bicubic chroma interpolation and anti-aliased downsampling.
Speed was mentioned as slower ; is this negligible or is there a big difference?
It will be slower, but I don't know how noticeable - perhaps IanB can quantify?

poisondeathray
18th January 2011, 16:07
Thanks for clarifying ,Gavino


once 2.60 is working properly, the RGB<->YV12 conversions will be about as good as you can get, with bicubic chroma interpolation and anti-aliased downsampling.


Sounds wonderful. Any idea of a timeframe ?

IanB
18th January 2011, 22:58
The resizer core is very fast for what it does, but if one has an explicit job to do then maybe one could write even faster code. It comes down to the law of diminishing returns. If something takes 1% of the total time making it 10 times faster only changes the total by 0.9%. The currently released versions still needlessly blit the chroma planes before resizing them, the CVS code no longer does this, that saves about ~12%.

The bugfix list has become pretty meaty, perhaps I should devote some cycles to give people a release. I'll try to put some effort into fixing the offset errors in the planar conversions and then roll out another alpha release soon.

Nevilne
18th January 2011, 23:36
Is progressive YV12 presented as RGB affected as well?
Basically, ConvertToRGB32(chromaresample="point") from YV12 source (this is not done by avisynth but rather similar by NLE), and then ConvertToYV12(chromaresample="point")?

Gavino
19th January 2011, 01:08
If the conversion is done entirely independently of Avisynth, it obviously depends on the algorithm used. If the algorithm is the equivalent of ConvertToRGB32(chromaresample="point"), there will be a vertical chroma shift up 1/2 pixel. However, if then converted back by the equivalent of ConvertToYV12(chromaresample="point"), there would be a corresponding shift down again, so the two would cancel out.

On the other hand, tools designed to work directly with Avisynth (such as AvsP) will use Avisynth's own ConvertToRGB32(), so will inherit any problems therein.

yup
10th February 2011, 10:40
Hi all!
I have collection cleaned VHS rip. I use tsp Avisynth MT 0.7 build. My video is interlaced. Can I repairing, remove shift introduced by conversion YUY2->YV12?
yup.

Gavino
10th February 2011, 19:17
I use tsp Avisynth MT 0.7 build. My video is interlaced. Can I repairing, remove shift introduced by conversion YUY2->YV12?
There is no shift if going in the direction YUY2->YV12, but there is a potential for aliasing if you have high frequency chroma content.
My 2.58 fix for YUY2ToYV12 (post #16 above) should work for tsp's Avisynth MT 0.7 build too (and for vanilla 2.57).

yup
11th February 2011, 07:43
Gavino! :thanks:
Now I have only filtered YV12 AVI files, source YUY2 erased. If i right understand in my files problem aliasing at U and V plane and I need use AA filters for color planes?
yup.

Gavino
11th February 2011, 10:58
That's right - my 'fix' is a replacement for ConvertToYV12, and doesn't help if you've already done the conversion and no longer have the original source.

For that, as you say, you would need to use AA on the chroma planes. However, the extent of the problem depends on the source and there may no visible effects. It's more likely to occur on material with sharp color edges, such as anime or CGI graphics.

yup
11th February 2011, 11:52
Gavino!
My source is VHS rip for lectures, not very sharp. May I can do not worry? And do nothing.
yup.

Rumbah
12th February 2011, 02:47
A little bit off topic but is it possible to do a chroma resampling YV12 -> YV12?

I have a mjpeg source with mpeg1/jpeg chroma sampling and want to convert it to h264. As every h264 decoder I know upsamples assuming the mpeg2/4 chroma location it would be good to resample the chroma from mpeg1 location to mpeg2 location. Is this possible with avisynth?

Gavino
12th February 2011, 10:58
... to resample the chroma from mpeg1 location to mpeg2 location. Is this possible with avisynth?
Yes, you can use the extra parameters of the resizers to do a sub-pixel shift, and use MergeChroma to combine the original luma with the shifted chroma.

# mpeg1 to mpeg2:
MergeChroma(BilinearResize(width(), height(), src_left=-0.5)) # or any other resizer

Rumbah
12th February 2011, 13:37
Ah, thank you, I thought that parameter was only for cropping. Who would have thought that it would be that easy :D

Does it make any sense to use a better resize filter for this case?

Gavino
12th February 2011, 18:03
Does it make any sense to use a better resize filter for this case?
Probably not, but you might want to use a sharper resizer if you have strong chroma edges.

Rumbah
12th February 2011, 22:27
Ok, thank you for your help.
I think I'll use Spline36resize then as it is followed by needi and MCTemporalDenoise so the chroma shift computing time will be small in comparison.

Mini-Me
2nd March 2011, 12:25
I finally got around to playing with the new routines, and I found something interesting. I may be wrong - this may be the intended behavior, and it may be necessary to prevent other issues - but I think the shifts in the resize are actually incorrect. EDIT: Using Gavino's original offsets does in fact "prevent other issues." They result in gradual smearing, but they are in fact correct, and doubling them results in chroma bobbing from field to field, relative to luma. I suppose the smearing simply comes from Avisynth's resizer core not playing nicely with the smaller offsets. View post 51 further down the page for more details. The "ConvertAlmostEquivalent" methods in the post below avoid both smearing and significant chroma bobbing though.

The new routines don't have the awful combed chroma shifts that the old routines had, but they still smear color more and more with every iteration. You can verify this by testing them with [a modified version of] Gavino's color bar test script (at least in 2.6). Eliminating or halving the offsets/shifts in the resize calls will make this worse, but if you double the offsets/shifts in the resize calls (0.125 -> 0.25, and 0.25 -> 0.5), the smearing stops after just a few iterations. Even though this is an unrealistic number of conversions, converting back and forth between YUY2 and YV12 a few hundred times shows an enormous difference.

Unless the original offsets were just wrong, maybe the difference is that Resize bases the offsets on the clip's current size before resizing, instead of its size after resizing? That's just a guess though, and some of you guys might know for a fact I'm wrong. :p

Anyway, it seems the support functions should be modified as follows:

# ------- Support functions --------

function YV12ToYUY2i_26(clip c) {
f = c.AssumeTFF().SeparateFields()
top = f.SelectEven()
bot = f.SelectOdd()
topU = top.UToY8().BilinearResize(c.width/2, c.height/2, 0, 0.25)
botU = bot.UToY8().BilinearResize(c.width/2, c.height/2, 0, -0.25)
U = Interleave(topU, botU).ConvertToYV16()
topV = top.VToY8().BilinearResize(c.width/2, c.height/2, 0, 0.25)
botV = bot.VToY8().BilinearResize(c.width/2, c.height/2, 0, -0.25)
V = Interleave(topV, botV).ConvertToYV16()
YToUV(U, V, f.ConvertToYV16()).ConvertToYUY2().Weave()
}

function YUY2ToYV12i_26(clip c) {
f = c.AssumeTFF().SeparateFields()
top = f.SelectEven()
bot = f.SelectOdd()
topU = top.UToY8().BilinearResize(c.width/2, c.height/4, 0, -0.5)
botU = bot.UToY8().BilinearResize(c.width/2, c.height/4, 0, 0.5)
U = Interleave(topU, botU)
topV = top.VToY8().BilinearResize(c.width/2, c.height/4, 0, -0.5)
botV = bot.VToY8().BilinearResize(c.width/2, c.height/4, 0, 0.5)
V = Interleave(topV, botV)
YToUV(U, V, f.ConvertToY8()).Weave()
}

I tested the two versions by renaming all six functions associated with my modified version (I created copies of the functions, replacing all instances of "_26" with "_26DoubleShifts"). I used the following script (actually transcribed from another computer, so there may be typos or something). (Also, note that there are two other much simpler if "unapproved" formulations that give similar results.)
function ConvertBad(clip c, int times) {
times == 0 ? c : c.ConvertToYUY2(interlaced=true).ConvertToYV12(interlaced=true).ConvertBad(times-1)
}

function ConvertSmallShifts(clip c, int times) {
times == 0 ? c : c.YV12ToYUY2_26(interlaced=true).YUY2ToYV12_26(interlaced=true).ConvertSmallShifts(times-1)
}

function ConvertDoubleShifts(clip c, int times) {
times == 0 ? c : c.YV12ToYUY2_26DoubleShifts(interlaced=true).YUY2ToYV12_26DoubleShifts(interlaced=true).ConvertDoubleShifts(times-1)
}

# Someone mentioned earlier that separating fields and converting doesn't work,
# because it wasn't intended that way. Nevertheless, the below methods seem to
# work very well for regular sources, and they're also well-behaved in this script
# (they stop accumulating error after a while). In both YV12 and YUY2, they look
# better than "double shifts" in some areas of the image and worse in others
# (e.g. in YUY2, these look better at the red/blue transition and worse at others).
# I'm not sure which behavior is preferable or why, and they're only subtly
# different, both in this comparison and in real sources. Either way, they are far
# better than any methods I tried (sane or insane ;)) until Gavino posted the
# updated routines, and they still seem to do better than anything except for
# maybe the "double shifts" version, so I decided it couldn't hurt posting about
# them. ;)
function ConvertAlmostEquivalent(clip c, int times) {
times == 0 ? c : c.SeparateFields.ConvertToYUY2.ConvertToYV12.Weave.ConvertAlmostEquivalent(times-1)
# Another version exactly identical to this one, at least in Avisynth 2.6
# times == 0 ? c : c.ConvertToYUY2(interlaced=true).SeparateFields.ConvertToYV12.Weave.ConvertAlmostEquivalent(times-1)
}


bars = ColorBars(pixel_type="YV12").TurnRight()
bad = bars.Animate(0, 200, "ConvertBad", 0, 200)
smallshifts = bars.Animate(0, 200, "ConvertSmallShifts", 0, 200)
doubleshifts = bars.Animate(0, 200, "ConvertDoubleShifts", 0, 200)
similar = bars.Animate(0, 200, "ConvertAlmostEquivalent", 0, 200)

return StackHorizontal(bad, smallshifts, doubleshifts, similar)

STC-Fan
2nd March 2011, 20:23
Just thought I'd post the colour bar test from AviSynth 2.5.8, using 10 iterations, based on what Mini-Me posted above - the bars from left to right are in the order described in the above script (ConvertBad, ConvertSmallShifts, ConvertDoubleShifts and ConvertAlmostEquivalent):
http://stc.hacking-cult.org/images/colourconversiontest_avisynth2.58.png
From my observations it would seem that DoubleShifts and AlmostEquivalent are superior to the standard script Gavino wrote. Admittedly the differences are a lot smaller than comparisons between Gavino's conversion scripts and the old ConvertToYV12/YUY2 functions built into AviSynth, but it is an improvement nonetheless.

Mini-Me
2nd March 2011, 20:39
Just thought I'd post a colour bar test from AviSynth 2.5.8 based on what Mini-Me posted above - the bars from left to right are in the order described in the above script (ConvertBad, ConvertSmallShifts, ConvertDoubleShifts and ConvertAlmostEquivalent):
http://stc.hacking-cult.org/images/colourconversiontest_avisynth2.58.png
From my observations it would seem that DoubleShifts and AlmostEquivalent are superior to the standard script Gavino wrote. Admittedly the differences are a lot smaller than comparisons between Gavino's conversion scripts and the old ConvertToYV12/YUY2 functions built into AviSynth, but it is an improvement nonetheless.

How many iterations did you use? That's about what I get if I use 10 iterations in 2.6, but if I use 200 it looks more like this, where the differences become much more apparent:
http://i368.photobucket.com/albums/oo130/UnrealMiniMe/BadSmallShiftsDoubleShiftsSimilar.png
(From left to right: Bad (Avisynth 2.6's built-in conversions), SmallShifts (Gavino's updated conversions), DoubleShifts(Gavino's updated conversions with double resize offsets), and AlmostEquivalent (from the above script).

STC-Fan
2nd March 2011, 21:02
How many iterations did you use? That's about what I get if I use 10 iterations in 2.6
It was 10, yes - although it said 500 in the original script in your post, but that caused MeGUI to crash. However, I tried 200 iterations in 2.5.8 just now, and I got virtually the same results as you posted there - now I see what you meant!

Mini-Me
2nd March 2011, 21:08
It was 10, yes - although it said 500 in the original script in your post, but that caused MeGUI to crash. However, I tried 200 iterations in 2.5.8 just now, and I got virtually the same results as you posted there - now I see what you meant!

Come to think of it, I never got it up to a full 500 without crashing either, actually. :p It's because Animate has to evaluate every previous frame in order to render the current frame. I assume frame/result caching helps at low frame numbers, but once you get high enough, I think Avisynth has to calculate each frame starting from frame 0...which somehow makes it choke. Either that or it tries to cache all the results and ends up exceeding memory, but it runs so slowly that I don't know if that makes sense. (Someone more experienced will know for a fact, of course.) Either way, I never should have written 500 iterations into the above script, so I'll change that to 200 now.

Didée
2nd March 2011, 23:08
BTW, speaking of ye good old ConvertBackToYUY2. What was its reason for existance? :p