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.

 

Go Back   Doom9's Forum > Announcements and Chat > General Discussion

Reply
 
Thread Tools Search this Thread Display Modes
Old 5th February 2025, 22:40   #1  |  Link
manolito
Registered User
 
manolito's Avatar
 
Join Date: Sep 2003
Location: Berlin, Germany
Posts: 3,113
Hardware challenged HDR to SDR conversions

Being the retro person I am, my computer hardware is a little bit autdated, but I like it and I have no desire to upgrade my stuff anytime soon. I still want to watch some downloaded HDR sources (PQ and HLG) which my display hardware cannot handle, so I have to convert them to standard SDR. The target format is somewhat restricted by my hardware, though...

For watching the stuff on my old CRT TV through my Xtreamer streaming box it needs to be in the H264 format. Full HD would be possible, but I prefer Half HD because none of my hardware devices can go higher than Half HD.

My computer is an older Thinkpad T530, it does not support Vulkan GPU converting. I also refuse to deal with LUTs for the tone mapping, this would be overkill for my needs.


Conclusion:
For these HDRtoSDR conversions there are only three practical methods:
1. DGHDRtoSDR running under AviSynth+
2. FFmpeg based low quality method by GeoffreyA
3. FFmpeg based high quality method by Gyan (found it at Reddit)


Quality ranking:

1. The DG method has the highest quality and is still fast. It has separate parameters for PQ and HLG sources which I find very helpful.

2. The FFmpeg high method by Gyan has the same high quality, but is painfully slow (only half the speed of the DG method).

3. The FFmpeg low method has a tiny little bit lower quality, but it is fast, and for most sources the lower quality will not be visible.


NOTE:
The link for the test conversions has expired. The conversion scripts can be found here:
https://forum.doom9.org/showthread.p...17#post2014617


Cheers
manolito

Last edited by manolito; 19th February 2025 at 16:35.
manolito is offline   Reply With Quote
Old 6th February 2025, 08:11   #2  |  Link
GeoffreyA
Registered User
 
Join Date: Jun 2024
Location: South Africa
Posts: 293
Hi, Manolito.

Great test. I downloaded the files, and you're right, Gyan's method is a big improvement over my earlier one. For my part, I put that version aside when I found that libplacebo gave superior results. One of its key strengths is the frame-by-frame peak detection, and there are various tone- and gamut-mapping operators to choose from. From my testing, it rivals "official" SDR versions, perhaps even beating them, and I use nothing else when converting HDR to SDR. Last December, I got great results with the 4K version of The Lord of the Rings, and more recently, The Substance. It does require a GPU with Vulkan support.

I'm curious to give these videos a go with libplacebo. Have you got the links to them?
GeoffreyA is offline   Reply With Quote
Old 6th February 2025, 15:09   #3  |  Link
manolito
Registered User
 
manolito's Avatar
 
Join Date: Sep 2003
Location: Berlin, Germany
Posts: 3,113
Quote:
Originally Posted by GeoffreyA View Post
Hi, Manolito.
I'm curious to give these videos a go with libplacebo. Have you got the links to them?
Thanks for your reply. But you probably misunderstood my post. Libplacebo does not work on my computer, no chance to test it.
Quote:
My computer is an older Thinkpad T530, it does not support Vulkan GPU converting
Cheers
manolito

Last edited by manolito; 6th February 2025 at 15:15.
manolito is offline   Reply With Quote
Old 6th February 2025, 15:24   #4  |  Link
GeoffreyA
Registered User
 
Join Date: Jun 2024
Location: South Africa
Posts: 293
Quote:
Originally Posted by manolito View Post
Thanks for your reply. But you probably misunderstood my post. Libplacebo does not work on my computer, no chance to test it.


Cheers
manolito
I meant I'd like to try converting them myself, with libplacebo, for the sake of comparison.
GeoffreyA is offline   Reply With Quote
Old 6th February 2025, 18:25   #5  |  Link
Z2697
Registered User
 
Join Date: Aug 2024
Posts: 447
I don't think the "high" samples are higher quality than "low" samples.
I mean, some of them do, some of them don't.

Last edited by Z2697; 6th February 2025 at 18:32.
Z2697 is online now   Reply With Quote
Old 6th February 2025, 18:42   #6  |  Link
Z2697
Registered User
 
Join Date: Aug 2024
Posts: 447
The reason of the high quality command being painfuilly slow is probably that it has many extra filtering.

All the extra color adjusments aside, the reason of the "low quality" command feels bad is probably the desat is too high.

For record this is the description found in the archive
Quote:
AviSynth + (Script by Donald Graft. Separate parameters for PQ and HLG source files)

ConvertBits(16)
DGHDRToSDR(mode="pq", white=1460, black=0, gamma=0.42, hue=0.00, r=1.00, g=1.00, b=1.15, tm=0.90, roll=0.70, fulldepth=false, impl="255")
# DGHDRtoSDR(mode="hlg", white=2000, black=0, gamma=0.42, hue=0.15, r=1.06, g=1.00, b=1.15, tm=0.90, roll=0.70, fulldepth=false, impl="255")


______________________________________________________________________________________

FFmpeg Fast (Slightly edited script by GeoffreyA)

Half HD target (1280x720):
--------------------------
zscale=1280:-2,zscale=m=gbr:t=linear:dither=none:npl=225,format=gbrpf32le,tonemap=reinhard:desat=2.5,zscale=m=709:t=709=709:r=limited:dither=error_diffusion,format=yuv420p,sidedata=delete


_______________________________________________________________________________________

FFmpeg High Quality (Script by Gyan I found at Reddit. Edited for more natural colors)

Half HD target (1280x720):
--------------------------
zscale=1280:-2,zscale=t=linear:npl=100,format=gbrpf32le,tonemap=tonemap=gammaaram=1.2:desat=0eak=15,zscale=p=709:t=709:m=709:r=full:d=error_diffusion,noise=alls=3:allf=t+u,eq=saturation=1.2:brightness=0.155:contrast=1.15:gamma=0.85,huesaturation=colors=y:saturation=-0.5:intensity=0.40,curves=all=0.05/0 0.35/0.5 1/1,curves=all=0/0 0.75/0.76 0.9/0.94 1/1,deband=1thr=0.015:2thr=0.015:3thr=0.015:4thr=0.015:range=16:blur=true:coupling=true,noise=alls=2:allf=p+t,colorspace=iall=bt709:all=bt709:range=tv:format=yuv420p:dither=fsb

Last edited by Z2697; 6th February 2025 at 18:48.
Z2697 is online now   Reply With Quote
Old 6th February 2025, 19:19   #7  |  Link
manolito
Registered User
 
manolito's Avatar
 
Join Date: Sep 2003
Location: Berlin, Germany
Posts: 3,113
Quote:
Originally Posted by Z2697 View Post
I don't think the "high" samples are higher quality than "low" samples.
I mean, some of them do, some of them don't.
Thanks for your comments...

You can mainly see the reduced quality of "low" samples when the source has lots of bright sky and white clouds, and it is very visible in this file: "Ostuni in 4k UHD - HLG_FFmpeg Low.mkv". But if you know where to look you can also see the reduced resolution in such bright areas in the other samples. Mainly the transitions from the blue sky to the white clouds are very abrupt and not smooth, very low effective resolution.

Whatever, I picked very demanding sources, in many cases you will not notice this reduced quality.


In your other post you say that the "desat" value is probably too high. I don`t think so, there was a reason I changed it from the original value of 0 to 2.5. A value of 0 means that the parameter is disabled, the default is 2.0. With a value of 0 I get very annoying halos around the bright sun, the default of 2.0 causes colors which are a little too pale. A value of 2.5 was the best compromise for me.


Cheers
manolito
manolito is offline   Reply With Quote
Old 6th February 2025, 20:41   #8  |  Link
GeoffreyA
Registered User
 
Join Date: Jun 2024
Location: South Africa
Posts: 293
I've found that desat seems to be problematic, if not broken. It's also better, but slower, to perform the tone mapping on the 2160p image before downscaling. That eliminates artefacts around bright text, for example, a sort of halo.

Then, zscale is using bilinear by default, causing a loss of sharpness. Changing to a better scaler on both sides of the chain (because of chroma) would be more ideal. Gyan's version is also using the bilinear default but perhaps those extra filters are sharpening along the way.

And it's subjective, but Mobius has a better range and is less "flat" than Reinhard.

Code:
zscale=m=gbr:t=linear:dither=none:npl=203:f=spline36,format=gbrpf32le,tonemap=reinhard:desat=0,zscale=1280:-2:f=spline36,zscale=m=709:t=709:p=709:r=limited:dither=error_diffusion:f=spline36,format=yuv420p,sidedata=delete

Last edited by GeoffreyA; 6th February 2025 at 21:01.
GeoffreyA is offline   Reply With Quote
Old 6th February 2025, 22:45   #9  |  Link
huhn
Registered User
 
Join Date: Oct 2012
Posts: 8,335
try a downscaler that does barely if any ringing.

downscaling first should be relative save.
huhn is offline   Reply With Quote
Old 7th February 2025, 02:16   #10  |  Link
Z2697
Registered User
 
Join Date: Aug 2024
Posts: 447
The default bilinear should have no ringing though...
Z2697 is online now   Reply With Quote
Old 7th February 2025, 11:51   #11  |  Link
GeoffreyA
Registered User
 
Join Date: Jun 2024
Location: South Africa
Posts: 293
Here's another test. I copied Manolito's Low and Gyan commands verbatim. In addition, I made encodes with libplacebo and a better FFmpeg command. The second video, in particular, demonstrates that FFmpeg's desat implementation is either too aggressive or broken. As Z2697 noted, desat seems largely the culprit in the low-quality version.

The sources were Samsung's "Travel with My Pet," and the UHD theatrical version of The Fellowship of the Ring.

Code:
Low
zscale=1280:-2,zscale=m=gbr:t=linear:dither=none:npl=225,format=gbrpf32le,tonemap=reinhard:desat=2.5,
zscale=m=709:t=709:p=709:r=limited:dither=error_diffusion,format=yuv420p,sidedata=delete


Gyan
zscale=1280:-2,zscale=t=linear:npl=100,format=gbrpf32le,tonemap=tonemap=gamma:param=1.2:desat=0:peak=15,
zscale=p=709:t=709:m=709:r=full:d=error_diffusion,
noise=alls=3:allf=t+u,eq=saturation=1.2:brightness=0.155:contrast=1.15:gamma=0.85,huesaturation=colors=y:saturation=-0.5:intensity=0.40,
curves=all=0.05/0 0.35/0.5 1/1,curves=all=0/0 0.75/0.76 0.9/0.94 1/1,
deband=1thr=0.015:2thr=0.015:3thr=0.015:4thr=0.015:range=16:blur=true:coupling=true,noise=alls=2:allf=p+t,
colorspace=iall=bt709:all=bt709:range=tv:format=yuv420p:dither=fsb


GeoffreyA
zscale=m=gbr:t=linear:dither=none:npl=203:f=spline36,format=gbrpf32le,tonemap=reinhard:desat=0,zscale=1280:-1:f=spline36,
zscale=m=709:t=709:p=709:r=limited:c=left:dither=error_diffusion:f=spline36,format=yuv420p,sidedata=delete


libplacebo
libplacebo=upscaler=spline36:downscaler=spline36:w=-1:h=720:peak_detect=true:tonemapping=spline:gamut_mode=perceptual:
colorspace=bt709:color_trc=bt709:color_primaries=bt709:range=limited:dithering=blue:format=yuv420p,sidedata=delete

Last edited by GeoffreyA; 7th February 2025 at 11:59.
GeoffreyA is offline   Reply With Quote
Old 7th February 2025, 16:02   #12  |  Link
Z2697
Registered User
 
Join Date: Aug 2024
Posts: 447
In my opinion the Mobius method is overall a better choice in a lot of case, when you are limited to the "traditional" tonemap.

I don't think the LUT methods will be much slower (if that's your concern), because the underlying mechanisms of tonemapping should be similarily complex, I assume.

By the way sorry for not reading the docs. The desat parameter described by "ffmpeg -h" is "desaturation strength" which I guess most will assume the bigger the value, the stronger the desaturation.
While in reality the doc says it's a "light level threshold" and bigger the values means less desaturation... yeah. Which also corresponds to a quick test.
(value 0, is a special case as it disables the desaturation)

Maybe it's FFmpeg, but libplacebo has an option to use d3d11 instead of vulkan, at least it does in mpv.
And... mpv does even come with video encoding support... yeah... so you can technically use libplacebo as well even when no vulkan... haha.
(sorry again, there seems no way to use the "render" output in the encoding)

Last edited by Z2697; 7th February 2025 at 16:39.
Z2697 is online now   Reply With Quote
Old 7th February 2025, 22:33   #13  |  Link
huhn
Registered User
 
Join Date: Oct 2012
Posts: 8,335
Quote:
Originally Posted by Z2697 View Post
The default bilinear should have no ringing though...
true but what else then ringing should result in haloing when the difference is a scaler...
huhn is offline   Reply With Quote
Old 9th February 2025, 11:17   #14  |  Link
GeoffreyA
Registered User
 
Join Date: Jun 2024
Location: South Africa
Posts: 293
Quote:
Originally Posted by Z2697 View Post
In my opinion the Mobius method is overall a better choice in a lot of case, when you are limited to the "traditional" tonemap.

I don't think the LUT methods will be much slower (if that's your concern), because the underlying mechanisms of tonemapping should be similarily complex, I assume.

By the way sorry for not reading the docs. The desat parameter described by "ffmpeg -h" is "desaturation strength" which I guess most will assume the bigger the value, the stronger the desaturation.
While in reality the doc says it's a "light level threshold" and bigger the values means less desaturation... yeah. Which also corresponds to a quick test.
(value 0, is a special case as it disables the desaturation)

Maybe it's FFmpeg, but libplacebo has an option to use d3d11 instead of vulkan, at least it does in mpv.
And... mpv does even come with video encoding support... yeah... so you can technically use libplacebo as well even when no vulkan... haha.
(sorry again, there seems no way to use the "render" output in the encoding)
I did find that Mobius gives a better range than Reinhard. Reinhard seems to compress too much, and there's a faint sepia-like tone to the picture.
GeoffreyA is offline   Reply With Quote
Old 11th February 2025, 22:48   #15  |  Link
manolito
Registered User
 
manolito's Avatar
 
Join Date: Sep 2003
Location: Berlin, Germany
Posts: 3,113
Sorry for the late reply, I was busy updating the FFmpeg Low Quality method, and this required a lot of test encodes. But I believe I got excellent results now from this method...

It turned out that I had to create two separate commands for PQ and HLG sources (just like for the DG method). Those input formats need a couple of different parameters for good results, and it took a lot of test encodes until I was happy with the results.

This means that before starting the conversion you have to determine the source format. MediaInfo will tell you if the transfer characteristics of your source are PQ or HLG, and then you can select the correct command.

Code:
AviSynth + (Script by Donald Graft. Separate parameters for PQ and HLG source files)

ConvertBits(16)
DGHDRToSDR(mode="pq", white=1460, black=0, gamma=0.42, hue=0.00, r=1.00, g=1.00, b=1.15, tm=0.90, roll=0.70, fulldepth=false, impl="255")
# DGHDRtoSDR(mode="hlg", white=2000, gamma=0.50, b=1.15)

______________________________________________________________________________________

FFmpeg Fast (Slightly edited script by GeoffreyA)

Half HD target (1280x720):
--------------------------
PQ:
===
zscale=1280:-2,zscale=m=gbr:t=linear:dither=none:npl=225,format=gbrpf32le,tonemap=mobius:desat=3.0,zscale=m=709:t=709:p=709:r=limited:c=left:dither=error_diffusion,format=yuv420p,sidedata=delete

HLG:
====
zscale=1280:-2,zscale=m=gbr:t=linear:dither=none:npl=203,format=gbrpf32le,tonemap=mobius:desat=0,zscale=m=709:t=709:p=709:r=limited:c=left:dither=error_diffusion,format=yuv420p,sidedata=delete


_______________________________________________________________________________________

FFmpeg High Quality (Script by Gyan I found at Reddit. Edited for more natural colors)

Half HD target (1280x720):
--------------------------
zscale=1280:-2,zscale=t=linear:npl=100,format=gbrpf32le,tonemap=tonemap=gamma:param=1.2:desat=0:peak=15,zscale=p=709:t=709:m=709:r=full:d=error_diffusion,noise=alls=3:allf=t+u,eq=saturation=1.2:brightness=0.155:contrast=1.15:gamma=0.85,huesaturation=colors=y:saturation=-0.5:intensity=0.40,curves=all=0.05/0 0.35/0.5 1/1,curves=all=0/0 0.75/0.76 0.9/0.94 1/1,deband=1thr=0.015:2thr=0.015:3thr=0.015:4thr=0.015:range=16:blur=true:coupling=true,noise=alls=2:allf=p+t,colorspace=iall=bt709:all=bt709:range=tv:format=yuv420p:dither=fsb

Cheers
manolito

Last edited by manolito; 4th March 2025 at 02:40.
manolito is offline   Reply With Quote
Old 13th February 2025, 08:23   #16  |  Link
Z2697
Registered User
 
Join Date: Aug 2024
Posts: 447
Why use 2 separate zscale filter in the beginning?
Z2697 is online now   Reply With Quote
Old 13th February 2025, 16:30   #17  |  Link
manolito
Registered User
 
manolito's Avatar
 
Join Date: Sep 2003
Location: Berlin, Germany
Posts: 3,113
Well, I modified GeoffreyA's original method a little bit, and I moved the downscaling command from its original position to the very beginning, so it is executed before the tonemapping (in the original version all scaling is done after the tonemapping resulting in a much lower speed).

Using a separate zscale command makes sure that zscale does not change the execution order of the resizing command. (I believe that a while ago I found it in a FranceBB post that z-lib has its own mind about the execution order of the paramters whithin one block)

Whatever, I am not the only one who thinks that downscaling before applying tonemapping does not hurt quality, and it takes only about half of the time. (Ask Donald Graft and huhn, and I agree with them)


Cheers
manolito
manolito is offline   Reply With Quote
Old 13th February 2025, 17:23   #18  |  Link
FranceBB
Broadcast Encoder
 
FranceBB's Avatar
 
Join Date: Nov 2013
Location: Royal Borough of Kensington & Chelsea, UK
Posts: 3,210
Quote:
Originally Posted by manolito View Post
I believe that a while ago I found it in a FranceBB post that z-lib has its own mind about the execution order of the parameters within one block
Hey Manolito, I'm actually glad to see that you're ok and that you're still around.
And yes, you're right, I did post about this, but there was a reason behind this. Essentially zlib performs the conversion by making an RGB roundtrip, which means that - unless you're starting with a 4:4:4 source (or indeed from RGB) - it will first upscale the chroma resolution to make it match the one of the luma, then convert to RGB, then apply the tonemapping and then convert back to YUV to whatever output you want.

In other words, supposing your input is a 3840x2160 4:2:0 HDR PQ source you have 3840x2160 luma and 1920x1080 chroma, which means that it will first upscale the chroma to 3840x2160, then bring everything to RGB, then tonemap, then convert back to YUV and downscale the chroma back to 4:2:0.

This means that if you start with the actual resolution, you're gonna be processing everything while preserving the information until the very end when you're gonna be downscaling. By downscaling first the process becomes faster, however you're losing information 'cause you're first going to 1920x1080 luma and 960x540 chroma, then you're upscaling the already downscaled chroma to 1920x1080, converting to RGB, applying the tonemapping, converting back and then downscaling again, which is a "no no".

What you could be doing to still save CPU cycles while avoiding compromising quality, however, is downscale the luma while preserving the chroma of the original UHD source, in other words going from 3840x2160 4:2:0 to 1920x1080 4:4:4 and from there run the tonemapping process before finally outputting 1920x1080 4:2:0 with the last chroma downscaling.
FranceBB is offline   Reply With Quote
Old 13th February 2025, 18:24   #19  |  Link
manolito
Registered User
 
manolito's Avatar
 
Join Date: Sep 2003
Location: Berlin, Germany
Posts: 3,113
Hey FranceBB,

good to see you after a long time, thanks for your reply. Even though I do not really understand this stuff any more. I am just result oriented these days.

This is from the DG readme:

Quote:
Alternatively, the cropping and resizing can be done in Avisynth. Do it before
calling dghdrtosdr() for best performance. Here is a script corresponding
to the one above:

dgsource("hdr sample.dgi")
crop(0,276,-0,-276)
spline36resize(1920,804)
dghdrtosdr(...)
For me this means that resizing to the target size before applying tonemapping is the way to go. And it does work for me, the results look very nice, and speed is very good.


Cheers
manolito
manolito is offline   Reply With Quote
Old 13th February 2025, 18:48   #20  |  Link
FranceBB
Broadcast Encoder
 
FranceBB's Avatar
 
Join Date: Nov 2013
Location: Royal Borough of Kensington & Chelsea, UK
Posts: 3,210
Ah, right, what I said about the RGB roundtrip actually applies to zlib, so essentially z_ConvertFormat(), and it's also true if you use my LinearTransformation() and indeed for Jean Philippe's Hable/Mobius/Reinhard tonemapping in HDRTools (although the last one also supports going to XYZ instead of RGB). I don't actually have any idea of what DGHDRtoSDR() does internally, but if Donald said that you can crop and downscale before using it, then I guess you're good to go
FranceBB is offline   Reply With Quote
Reply

Thread Tools Search this Thread
Search this Thread:

Advanced Search
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT +1. The time now is 04:14.


Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2025, vBulletin Solutions Inc.