View Full Version : UHD 50p to FULL HD 25i TFF, aliasing control and SinPowerResize()
FranceBB
30th May 2025, 17:44
Hi there.
My Austrian colleagues and I are trying to encode a drone footage.
The source is:
- UHD 3840x2160 H.265 Main Profile, Level 6 120 Mbit/s yv12 50p 8bit planar BT709 SDR
Link (available for 3 days):
https://we.tl/t-sL511mnelQ
The target is:
- FULL HD 1920x1080 MPEG-2 High 4:2:2 Profile, Level High 50 Mbit/s yv16 25i TFF 8bit planar BT709 SDR
Link (available for 3 days):
https://we.tl/t-ZqxeWMVoY4
ffmpeg.exe -hide_banner -i "A:\MEDIA\temp\DJI_20250512144258_0212_D_HD_25.MOV.avs" -an -pix_fmt yuv422p -vcodec mpeg2video -s 1920:1080 -aspect 16:9 -vf setfield=tff -flags +ildct+ilme+cgop -b_strategy 0 -mpv_flags +strict_gop -sc_threshold 1000000000 -r 25 -b:v 50000k -minrate 50000k -maxrate 50000k -bufsize 36408333 -qmin 5 -qmax 28 -qsquish 99 -g 12 -bf 2 -profile:v 0 -level:v 2 -color_range 1 -color_primaries 1 -color_trc 1 -colorspace 1 -y "A:\MEDIA\temp\raw_video.m2v"
ffmpeg.exe -hide_banner -i "A:\MEDIA\temp\DJI_20250512144258_0212_D_HD_25.MOV.avs" -c:a pcm_s24le -ar 48000 -rf64 always -f wav -y "\\mibctvan000\Ingest\MEDIA\temp\audio.wav"
ffmpeg.exe -hide_banner -i "A:\MEDIA\temp\raw_video.m2v" -i "A:\MEDIA\temp\audio.wav" -c:v copy -c:a copy -f mxf -y "A:\MEDIA\temp\pre-final_output.mxf"
bmxtranswrap.exe -p -y 10:00:00:00 -t op1a -o "A:\MEDIA\temp\final_output.mxf" "A:\MEDIA\temp\pre-final_output.mxf"
pause
Given that 4:2:0 in UHD has 3840x2160 luma and 1920x1080 chroma I'm preserving the chroma to get 1920x1080 4:4:4 before further downscaling to 4:2:2.
#Indexing
video=LWLibavVideoSource("A:\MEDIA\temp\DJI_20250512144523_0215_D_UHD_50.MOV")
audio=BlankClip(length=1531, fps=50, audio_rate=48000, channels=8, sample_type="24bit")
AudioDub(video, audio)
propClearAll()
#From UHD 4:2:0 to FULL HD 4:4:4
Y = ConvertToY8(matrix="Rec709").SinPowerResize(1920, 1080)
U = UToY8()
V = VToY8()
YToUV(U, V, Y)
#Downscale the chroma to 4:2:2
Converttoyv16(matrix="Rec709", interlaced=false)
#Divide in fields to get 25i TFF
assumeTFF()
separatefields()
selectevery(4,0,3)
weave()
however the source looks badly conditioned which means that SinPowerResize() with the default settings introduces aliasing when displayed via Bob():
https://i.imgur.com/pb8FN0a.png
Full Picture: https://i.imgur.com/bPJsCUX.jpeg
The sweet spot seems to be p=4
#Indexing
video=LWLibavVideoSource("A:\MEDIA\temp\DJI_20250512144523_0215_D_UHD_50.MOV")
audio=BlankClip(length=1531, fps=50, audio_rate=48000, channels=8, sample_type="24bit")
AudioDub(video, audio)
propClearAll()
#From UHD 4:2:0 to FULL HD 4:4:4
Y = ConvertToY8(matrix="Rec709").SinPowerResize(1920, 1080, p=4)
U = UToY8()
V = VToY8()
YToUV(U, V, Y)
#Downscale the chroma to 4:2:2
Converttoyv16(matrix="Rec709", interlaced=false)
#Divide in fields to get 25i TFF
assumeTFF()
separatefields()
selectevery(4,0,3)
weave()
https://i.imgur.com/zdzXZ2q.png
Full Picture: https://i.imgur.com/LGeMlJF.jpeg
GaussResize() with a p parameter of 10 achieves similar results, albeit more blurry:
https://i.imgur.com/mLpVnzs.png
Full Picture: https://i.imgur.com/EIMXyT5.jpeg
Windowed Sinc resizers like LanczosResize() and BlackmanResize() produce outputs with far too much aliasing:
https://i.imgur.com/1AVcxul.png
https://i.imgur.com/TXPYsfD.png
Full Picture (Lanczos): https://i.imgur.com/MLF7FEV.jpeg
Full Picture (Blackman): https://i.imgur.com/pN1QNtc.jpeg
Performing vertical blur after SinPowerResize() achieves similar if not worse results:
#Indexing
video=LWLibavVideoSource("A:\MEDIA\temp\DJI_20250512144523_0215_D_UHD_50.MOV")
audio=BlankClip(length=1531, fps=50, audio_rate=48000, channels=8, sample_type="24bit")
AudioDub(video, audio)
propClearAll()
#From UHD 4:2:0 to FULL HD 4:4:4
Y = ConvertToY8(matrix="Rec709").SinPowerResize(1920, 1080)
U = UToY8()
V = VToY8()
YToUV(U, V, Y)
#Downscale the chroma to 4:2:2
Converttoyv16(matrix="Rec709", interlaced=false)
#Blur Vertically to avoid aliasing
Blur(0.00, 1.58)
#Divide in fields to get 25i TFF
assumeTFF()
separatefields()
selectevery(4,0,3)
weave()
https://i.imgur.com/U3gX0iR.png
Full Image: https://i.imgur.com/RRrVlJQ.jpeg
Is this really the best we can do?
For reference, here's the source (cropped):
https://i.imgur.com/ijEeyqs.png
Full Picture (Source): https://i.imgur.com/Mie0PCq.jpeg
(the link to download the actual file is at the top).
If you need interlaced 1080i - you need to set 1/4 of input height for each field from UHD 2160p.
Setting p=4 for SinPowResize is not good idea - it will make too blur.
Half vertical bandwidth 1080p to make interlaced downsampling is about
LoadPlugin("ffms2.dll")
FFmpegSource2("DJI_20250512144523_0215_D_UHD_50.MOV")
UserDefined2Resize(width/2, height/2, b=80, c=-20)
UserDefined2Resize(width, height/2, b=80, c=-20) # adjust b/c pairs to tune V-details in the interlaced output bewteen aliasing and V-sharpness
# like down to b=60, c=-40 (more V-overshoots will be visible)
SincLin2Resize(width, height*2)
#next try to sample to 2 fields
#Divide in fields to get 25i TFF
assumeTFF()
separatefields()
selectevery(4,0,3)
weave()
But to quality-check it some deinterlacing engine is required like QTGMC - not simple fields separation I think. Though you can make
SeparateFields()
SincLin2Resize(width, height*2)
to get 1080 height each field. It saves from aliasing but not best in progressive to interlaced conversion because too much vertical blur even in static areas. Better may be make motion mask and mix
full_Vband_1080=UserDefined2Resize(width/2, height/2, b=80, c=-20)
half_Vband_1080=UserDefined2Resize(width/2, height/4, b=80, c=-20).SincLin2Resize(width/2, height/2) #it looks like reads width and height for last filter from input clip
via motion mask (Layer ? Overlay ?) to get static areas from full_Vband_1080 and moting areas from half_Vband_1080 clips.
Though if it is shot with flying camera only and all parts of frames are moving - simply use half_Vband for interlacing.
Single line 2160p to 1080i-ready solution (without additional V-sharpness control)
LoadPlugin("ffms2.dll")
FFmpegSource2("DJI_20250512144523_0215_D_UHD_50.MOV")
UserDefined2Resize(width/2, height/4, b=80, c=-20).SincLin2Resize(width/2, height/2)
#Divide in fields to get 25i TFF
assumeTFF()
separatefields()
selectevery(4,0,3)
weave()
"which means that SinPowerResize() with the default settings introduces aliasing when displayed via Bob():"
It mean you can not downsample 1080p image with pointresize(width, height/2) to fields without aliased result. So good broadcast 1080i cameras expect to have additional V-details tuning settings to save from too much details loss on 1/2 vertical resolution if user want some balance between V-sharpness and aliasing at fine H-structures.
To save 4:2:2 chroma it may be simple (about failsafe but may be not best for V-chroma sharpness):
LoadPlugin("ffms2.dll")
FFmpegSource2("DJI_20250512144523_0215_D_UHD_50.MOV")
ConvertToYUV444()
UserDefined2Resize(width/2, height/4, b=80, c=-20).SincLin2Resize(width/2, height/2)
#Divide in fields to get 25i TFF
assumeTFF()
separatefields()
selectevery(4,0,3)
weave()
ConvertToYUV422()
wonkey_monkey
30th May 2025, 19:03
I wouldn't overthink it too much. It's in the nature of bob deinterlacing to need a blur if you want to avoid shimmer, on a CRT at least - can't do much about that. But you can throw in a Sharpen after the fields are separated to recrisp it a bit, at the cost of a tiny amount of shimmer, which a modern display will probably suppress anyway.
BSVideoSource("DJI_20250512144523_0215_D_UHD_50.MOV", cachemode = 4)
ReduceBy2
FastBlur(0,1)
SeparateFields
Sharpen(0,1) # optional, see if you like it best with or without
SelectEvery(4,0,3)
Weave
Or you can try
...
Blur(0,1) # not as strong as FastBlur
SeparateFields
Sharpen(0, 0.5) # doesn't need to be as strong
...
One more possible way to finetune balance bewteen V-sharpness and aliasing (using some master control monitor with reference deinterlacer ?) is to adjust V-downsampling ratio:
LoadPlugin("ffms2.dll")
FFmpegSource2("DJI_20250512144523_0215_D_UHD_50.MOV")
ConvertToYUV444()
V_sharp=3.7 # valid range 2.0 to 4.0 where 4.0 - low to no aliasing and 2.0..3 is higher V-sharpness and more aliasing
UserDefined2Resize(width/2, Int(height/V_sharp), b=80, c=-20).SincLin2Resize(width/2, height/2)
#Divide in fields to get 25i TFF
assumeTFF()
separatefields()
selectevery(4,0,3)
weave()
ConvertToYUV422()
You can try to replace UserDefined2Resize(b=80, c=-20) with SinPowerResize() but it is not recommended for high quality work. Also for possibly some higher quality it is better to set UserDefined2Resize(, s=3) (support 3) or higher.
Our company also still produce 1080i but for downconversion from 2160p sources typically uses something from ffmpeg (scale with some filtering in ffmpeg) or something from FFastrans (any default downsizer). Or something avaialble in Adobe Media Encoder or Adobe Premiere.
Transcoded with this script and your ffmpeg settings - https://www.sendspace.com/file/ikvkzc
Tried Adobe Media Encoder 2022 - it loads file and transcoded to FHD interlaced but with much more aliasing and moire https://www.sendspace.com/file/p34sa5
"#Downscale the chroma to 4:2:2
Converttoyv16(matrix="Rec709", interlaced=false)"
Convert also supports resampling kernel setup and to 1/2 H-bandwidth chroma with less chroma-aliasing it also may be recommended to set SinPower of UserDefined2 kernel for chroma downsampling. Default kernel is bicubic and may give worse results.
https://avisynthplus.readthedocs.io/en/latest/avisynthdoc/corefilters/convert.html
ConvertToYUV422(clip, [ string matrix, bool interlaced,
string ChromaInPlacement,
string chromaresample,
string ChromaOutPlacement,
float param1, float param2, float param3] )
May be try
ConvertToYUV422(chromaresample="userdefined2", param1=80, param2=-20, param3=3) if you use direct mapping of 2160p 4:2:0 chroma to 1080 lines frame. matrix and interlaced do not required if you only downsample chroma for YUV format and 4:2:2.
But
std_bic=ConvertToYUV422().Subtitle("std")
ud2=ConvertToYUV422(chromaresample="userdefined2", param1=80, param2=-20, param3=3).Subtitle("ud2")
sub=Subtract(std_bic, ud2).Levels(127-10,1,127+10, 0,255)
shows a few LSB difference for chroma exist. Mostly significant effect on the red flowers (?) at some tree and sharp red windows frames.
Difference image: https://postimg.cc/bdf1TRSB
Second version encoded with script:
LoadPlugin("ffms2.dll")
FFmpegSource2("DJI_20250512144523_0215_D_UHD_50.MOV")
V_sharp=3.8 # valid range 2.0 to 4.0+ where 4.0 - low to no aliasing and 2.0..3 is higher aliasing
Y = ConvertToY8().UserDefined2Resize(width/2, Int(height/V_sharp), b=80, c=-20, s=3).SincLin2Resize(width/2, height/2)
U = UToY8()
V = VToY8()
YToUV(U, V, Y)
#Divide in fields to get 25i TFF
assumeTFF()
separatefields()
selectevery(4,0,3)
weave()
ConvertToYUV422(chromaresample="userdefined2", param1=80, param2=-20, param3=3)
https://www.sendspace.com/file/cttfn7
Difference comparison shows lots changes with first version:
LoadPlugin("ffms2.dll")
v1=FFmpegSource2("video.mp4").Subtitle("v1")
v2=FFmpegSource2("video2.mp4").Subtitle("v2")
sub=Subtract(v1, v2).Levels(127-10,1,127+10, 0,255)
Interleave(v1, v2, sub)
It visibly sharper - may be because of support=3 instead of default 2.3 in UserDefined2Resize for Y. Though it may enchance medium frequencies and makes finest details somehow worse. But average sharpness is mostly defined by medium high frequencies and not highest.
Adjustment of support between about 2.0 and 3.0 for UserDefined2Resize allow to additionally tune fine and medium fine details rendering and visible sharpness from standard viewing distance. And SinPowResize do not have support adjustment because of its design and hacky truncated kernel to support always 2.0.
Your final_output.mxf looks like less sharpened - to make something close with UserDefined2Resize you may try lower overshoot b/c pairs like 95/-10 or 105/0 and/or lower s-param to 2.1 or to the 2.0.
Didée
1st June 2025, 07:21
That's the nature of interlaced-fields coding, and a plain dumb-bob afterwards can't tell you all too much. There is a good reason for the oldscool rule: before creating an interlaced version of a progressive source, the source first needs to get vertically lowpassed/blurred before getting interlaced. Which is to avoid excessive (bob)-shimmer upon playback (inherently with a bob-deinterlacer). Point is, what is the target audience, what kind of deinterlacing is supposed to be used or be available. When you assume that a top-notch deinterlacer like Q/TGMC is used (unlikely), then you can just separate fields and interleave. If the target is just any random end device, then their deinterlacing possibilities will be much more primitive, and the source should be vertically lowpassed before getting interlaced. Vertical 1:2:1 seems crude, but does a reasonable job for this purpuse.
FranceBB
2nd June 2025, 15:12
But you can throw in a Sharpen after the fields are separated to recrisp it a bit, at the cost of a tiny amount of shimmer, which a modern display will probably suppress anyway.
Hey David, fun fact: ages ago you were actually the one who helped me blur vertically before interlacing and I've been doing that ever since. Still, modern TVs seem to be particularly bad. Not all of them of course. Some perform a fairly decent job when post-processing the output, but some other really don't. I've always been blurring vertically ever since we had that conversation, but I always wondered if there was a better way, hence this topic. :)
If you need interlaced 1080i - you need to set 1/4 of input height for each field from UHD 2160p
Right. Each field in 25i FULL HD is 1920x540, so in 3840x2160 if we divide the horizontal resolution by 2 we get 1920 and if we divide the vertical resolution by 4 we get 540.
Essentially this
UserDefined2Resize(width/2, height/4, b=80, c=-20).SincLin2Resize(width/2, height/2)
is downscaling to 1920x540 and then upscaling the vertical resolution back to 1080 so that we get 1920x1080 before dividing in fields to get back 1920x540 for each field.
I've just changed the script to:
#Indexing
video=LWLibavVideoSource("A:\MEDIA\temp\DJI_20250512144523_0215_D_UHD_50.MOV")
audio=BlankClip(length=1531, fps=50, audio_rate=48000, channels=8, sample_type="24bit")
AudioDub(video, audio)
propClearAll()
#Upscale the chroma
Converttoyv24(matrix="Rec.709", interlaced=false)
#Downscale to 1920x1080 with vertical lowpass (1920x540)
UserDefined2Resize(width/2, height/4, b=80, c=-20).SincLin2Resize(width/2, height/2)
#Downscale the chroma to 4:2:2
Converttoyv16(matrix="Rec709", interlaced=false, chromaresample="userdefined2", param1=80, param2=-20, param3=3)
#Divide in fields to get 25i TFF
assumeTFF()
separatefields()
selectevery(4,0,3)
weave()
and sure enough if we then use Bob() to display the output we get a perfect, aliasing free, output:
https://i.imgur.com/xFW1vnA.png
Full Picture: https://i.imgur.com/Dy1vIrE.jpeg
That worked like a charm. The moment I wrote this topic I knew you were gonna reply with UserDefined2Resize() ehehehhehe
or something from FFastrans
I'm actually particularly happy to see that you're using FFAStrans as it's a project I have close to my heart and that I've been contributing to over the past few years alongside emcodem, momocampo and of course steipal which is the mind behind it all and the one driving it forward. Here in the company I work for we're also based on FFAStrans for pretty much all our workflows and we're extremely proud of that. In an ideal world, all companies would be using and contributing to open source projects the likes of Avisynth, x26x, BMX Transwrap etc. Anyway, in FFAStrans, if one isn't using a Custom AVS Script to perform this kind of downscale, it will be up to the filter_builder.a3x to do that. The filter_builder is essentially a component that - given an input and a target output - creates the required filterchain so that the uncompressed A/V stream living in RAM is exactly as the encoder expects it to be so that the encoding step can occur. The filter_builder solves one problem at a time, from the various interlacing detection to the chroma location to the chroma conversion and chroma placement, to the conversion of matrix, transfer, primaries to the bit depth and so on and so forth. We've been downscaling UHD sports feed to FULL HD for a while and although we have a dedicated workflow for this that blurs vertically, we were aware of the aliasing issue and that it might have caused some problems for other people who didn't write a workflow with a custom Avisynth script, so, even if it's left to the filter_builder to do the conversion, if it detects that it has to go from a progressive higher resolution to an interlaced lower resolution (like in this case), it applies a low pass filtering.
If $src.height >= $tpl.stretch.height * 1.75 And $tpl.interl > 0 And $src.interl = 0 Then
$b_lpf = True
EndIf
#EndRegion
So in this case $b_lpf would be set to true, which means that this other conditional is true:
If $b_lpf Then $f.flags = 'cvlpf'
which means that we're setting flags=cvlpf in the tinterlace filter. Link (https://github.com/FFmpeg/FFmpeg/blob/93e53e253a5b723db18cf3c804569191cb1d610a/libavfilter/vf_tinterlace.c#L55)
Unfortunately, although it helped with some cameras like when we downscaled UHD 50p football games to FULL HD 25i TFF, it didn't help much with drones like in this case. :(
a plain dumb-bob afterwards can't tell you all too much.
Yep, but surprisingly a dumb Bob() actually roughly shows how it's gonna look like once it's played back via SDI using an hardware playback port and bobbed by a broadcast monitor. By the way, once the editing etc is done and the final file is created, this FULL HD 1920x1080 MPEG-2 High 4:2:2 Profile, Level High 50 Mbit/s yv16 25i TFF 8bit planar BT709 SDR file will eventually be played back by an hardware playback ports (Omneon from Harmonic), carried via SDI, re-encoded by an hardware encoder in FULL HD 1920x1080 H.264 High Profile Level 4.1 12 Mbit/s yv12 25i TFF 8bit planar BT709 SDR, coupled in the multiplexer and beamed to Hotbird so that the decoders will decode it comes down from the satellite as a .ts stream. I don't expect a consumer TV to be much better than a simple Bob() in bobbing an interlaced stream, to be fair, which is why I've always used Bob() as a reference.
There is a good reason for the oldscool rule: before creating an interlaced version of a progressive source, the source first needs to get vertically lowpassed/blurred before getting interlaced.
Yeah, I've actually faced this a while ago and I've been blurring vertically with Blur(0, 1.58) ever since, however I always wondered if it was possible to do something better. Downscaling by 4 the vertical resolution so that we get 1920x540 before upscaling it back so that it's actually 1920x1080 (DTL's method) acts as a low-pass filter and seems to be producing good results. :)
If the target is just any random end device, then their deinterlacing possibilities will be much more primitive, and the source should be vertically lowpassed before getting interlaced. Vertical 1:2:1 seems crude, but does a reasonable job for this purpuse.
Yeah this is definitely the case: end user devices. Luckily with the UHD migration the world moved to progressive, but unfortunately there are still plenty of FULL HD channels that still need to be 25i TFF... :(
By the way, it's great to see you active again, Didée! I've been using your filters for a very long time! A big fat thank you for everything you've done over the years :D
Didée
2nd June 2025, 23:08
Don't call me active, rather consider me devoluted to a n00b again. Imagine I've no clue about all those "SinPowerResize" or "UserDefined2Resize" or "SincLin2Resize" ... I've never heard of them!
About the vertical lowpass, methinks a blur(0,1.58) is too strong, this corresponds to an 1-1-1 linear box blur. Blur(0,1) seems more appropriate, that's close to 1-2-1 gaussian. However internal blur() always suffered from limited precision, at least in Avisynth 2.x age, don't know if this was improved in Avisynth+ et.al. Using a resizer that does appropriate blurring surely is a good way, too.
"UserDefined2Resize" or "SincLin2Resize" ... I've never heard of them!
UserDefined2 kernel is sum of sinc(x) shifted to +-N sampling steps with 2 and 3 side members weighted by b and c params -
https://github.com/AviSynth/AviSynthPlus/blob/66f0d678d95789d8fae552f0aa6ba2c58bc91342/avs_core/filters/resample_functions.cpp#L321
With some adjusted b/c members it have kernel also significantly limited in real space without long ringing (the sum of sincs also always hard limited in Fourier space) and can give control of overshoots if require for higher sharpness/acutance for downsampling. And it looks like the donsampled result start to inherit the properties of resampling kernel (it is easily visible with high downsampling ratios like 10:1 or higher but also works good too at 2:1 or even lower).
And SincLin2 is close to standard weighted sinc-based (like Lanczos and Blackman) with more simple trapesoidal weighting to fix end of kernel computing issues of too narrow truncated sinc kernel. It is only a bit more sharp in comparison with Lanczos/Blackman with same number of taps and have default taps=15 to skip this typing in scripts. Old Lanczos/Blackman have taps=4 from the old ages of slower CPUs and for better quality require to set higher number of taps in each script.
Some department of our company with many regular transcodings uses FFasTrans. But I do rare and not standard transcodings from random incoming low quality sources and to make things faster tri to use single ffmpeg executable to get output files (if folder with many files processing required - I use FAR user menu with shell script for proceess all files in a folder).
Test settings for UHD 50p to FHD 25i:
ffmpeg.exe -i UHD_50.MOV -vf scale=w=1920:h=1080,tinterlace=mode=interleave_top:flags=cvlpf -c:v libx264 -pix_fmt yuv420p -x264opts tff=1:crf=18 out.mp4
Also possible without flags=cvlpf with 1/4 intermediate height
ffmpeg.exe -i UHD_50.MOV -vf scale=w=1920:h=540,scale=w=1920:h=1080,tinterlace=mode=interleave_top -c:v libx264 -pix_fmt yuv420p -x264opts tff=1:crf=18 out.mp4
But all available vlpf (simple and complex) for tinterlace filter still leave some visible aliasing at some angles of straight lines and also all downsample kernels in ffmpeg (like default bicubic) looks like do not limit specturum enough and also leave some aliasing even with 1/4 height intermediate. Users can ask ffmpeg developers to add kernels like SinPow and UserDefined2 to ffmpeg and it can be used as same engine to transcode files without AVS scripting in between (though for convert to 4:2:2/4:2:0 also better to add downscale kernel setup as we have in AVS now).
Each method (with AVS SinPow of UserDefined) or ffmpeg (vlpf/cvlpf/half_height) make different output look/makeup with also different set of details/sharpness/aliasing/flickering. May be different programm directors can select different output results as best match some artistic intent.
FFASTrans developers may include many different presets for UHDp to FHDi conversion so users can check different results.
One more idea on chroma downsampling for current non-linear chroma subsampled solutions:
The best result for chroma downsampling currently only can be achieved with manual samples adjusting for each transient (in the simple case of ColourBars 4:4:4 'studio/production' source converting into 'distribution' 4:2:0 or semi-distribution 4:2:2 formats). It is too slow to do by manual each colour transient grading and we currently do not have any AI-robot solution to do this.
Some idea how to make it in software:
1. Make some reference quality 4:2:0 to 4:4:4 decoder engine (some example was shown at this forum already). Is it the end of line or intermediate ?
2. Make some randomizer engine to add some 'noise' to chroma planes.
3. Use some video quality estimation engine to make a simple score of quality (like PSNR/SSIM/VIF/etc).
4. Make an iterative loop cycle of:
Input first estimation - simple ConvertToYUV420() using some linear downscale filter.
At each loop spin:
4.1 Feed current result to video quality metric estimation engine in decoded to 4:4:4 format and save its value as current best VQM value.
4.2. Add some noise (small random values to UV planes)
4.3. Process result with 4:2:0 to 4:4:4 decoder and feed to comparison engine with 4:4:4 input source to calculate video quality metric.
4.4. Compare quality metric with best VQM. If current VQM is better - replace 4:2:0 source with current modified source and update best VQM value.
4.5. go to 4.2 (while max allowed spin loop counts not reached ?)
For full frame processing it may be too slow. But we can try to do small blocks processing and use weighted overlapping as we have in mvtools to save from block artifacts. Typical transient encoded values take about 8 samples in size so blocks like 8x8 (or even 8x1 ?) may be tested.
This is not very AI-driven algorithm but may be easy to put on SIMD and into shader-based data compute accelerator (process each block in separate shader) and may already give some better quality in comparison with linear 4:4:4 to 4:2:0 conversion at the end of production chain with 4:4:4 (or at least 4:2:2) quality and making final distribution packed content in 4:2:0 format. The possible quality/performance balance may be adjusted by simply setting the number of iterative (best UV samples combination search) spin loops.
Different search/interpolation methods may be used instead of simply adding random noise values. Another possible method to test - adjust each sample of the block (one by one) with +-1 value and measure if it makes a better VQM or not. Though it can miss some more best combinations in theory. If the processing host is good enough in computing power - several methods may run in parallel and the final output selection engine may take the result with best VQM.
vBulletin® v3.8.11, Copyright ©2000-2026, vBulletin Solutions Inc.