Opened 5 years ago

Last modified 9 days ago

#7978 open defect

inaccurate conversion from YCbCr Narrow range to RGB Full range

Reported by: cepesh Owned by:
Priority: normal Component: undetermined
Version: unspecified Keywords: scale
Cc: Blocked By:
Blocking: Reproduced by developer: no
Analyzed by developer: no

Description

Summary of the bug: inaccurate conversion from YCbCr Narrow range to RGB Full range
How to reproduce:

Command line:
"C:\\tools\\ffmpeg-4.1.3\\bin\\ffmpeg.exe" -loglevel debug -report -s 3840x2160 -vcodec v210 -i YUV_pattern-2fr.v210 -vf "scale=in_color_matrix=bt2020:in_range=tv" -vframes 1 tiff_bug_00.tiff -y
ffmpeg version 4.1.3 Copyright (c) 2000-2019 the FFmpeg developers
  built with gcc 8.3.1 (GCC) 20190414
  configuration: --enable-gpl --enable-version3 --enable-sdl2 --enable-fontconfig --enable-gnutls --enable-iconv --enable-libass --enable-libbluray --enable-libfreetype --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libopus --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libtheora --enable-libtwolame --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libzimg --enable-lzma --enable-zlib --enable-gmp --enable-libvidstab --enable-libvorbis --enable-libvo-amrwbenc --enable-libmysofa --enable-libspeex --enable-libxvid --enable-libaom --enable-libmfx --enable-amf --enable-ffnvcodec --enable-cuvid --enable-d3d11va --enable-nvenc --enable-nvdec --enable-dxva2 --enable-avisynth
  libavutil      56. 22.100 / 56. 22.100
  libavcodec     58. 35.100 / 58. 35.100
  libavformat    58. 20.100 / 58. 20.100
  libavdevice    58.  5.100 / 58.  5.100
  libavfilter     7. 40.101 /  7. 40.101
  libswscale      5.  3.100 /  5.  3.100
  libswresample   3.  3.100 /  3.  3.100
  libpostproc    55.  3.100 / 55.  3.100

100% White
Source YCbCr value = 0x03ac, 0x0200, 0x0200 (940, 512, 512)
Observed RGB value = 65283, 65283, 65283
Expected RGB value = 65535, 65535, 65535

58% White
Source YCbCr value = 0x023c, 0x0200, 0x0200 (572, 512, 512)
Observed RGB value = 37858, 37858, 37858
Expected RGB value = 38004, 38004, 38004

100% Magenta
Source YCbCr value = 0x015a, 0x343, 0x039c
Observed RGB value = 65280, 0, 65289
Expected RGB value = 65533, 0, 65535

Attachments (1)

20190629-ffmpeg-bug.zip (144.1 KB ) - added by cepesh 5 years ago.
Zip archive includes both the source V210 file (2 frames inside. resolution 3840x2160) and TIFF output

Download all attachments as: .zip

Change History (15)

by cepesh, 5 years ago

Attachment: 20190629-ffmpeg-bug.zip added

Zip archive includes both the source V210 file (2 frames inside. resolution 3840x2160) and TIFF output

comment:1 by Carl Eugen Hoyos, 5 years ago

Priority: importantnormal
Version: 4.1unspecified

Please test current FFmpeg git head and provide the command line together with the complete, uncut console output to make this a valid ticket.

in reply to:  1 comment:2 by cepesh, 5 years ago

Replying to cehoyos:

Please test current FFmpeg git head and provide the command line together with the complete, uncut console output to make this a valid ticket.

Sure, no problem. Retested on N-94137-g89b96900fa

ffmpeg started on 2019-06-30 at 11:10:10
Report written to "ffmpeg-20190630-111010.log"
Command line:
"C:\\tools\\ffmpeg-20190629-89b9690-win64-static\\bin\\ffmpeg.exe" -loglevel debug -report -s 3840x2160 -vcodec v210 -i YUV_pattern-2fr.v210 -vf "scale=in_color_matrix=bt2020:in_range=tv" -vframes 1 tiff_bug_00.tiff -y
ffmpeg version N-94137-g89b96900fa Copyright (c) 2000-2019 the FFmpeg developers
  built with gcc 9.1.1 (GCC) 20190621
  configuration: --enable-gpl --enable-version3 --enable-sdl2 --enable-fontconfig --enable-gnutls --enable-iconv --enable-libass --enable-libdav1d --enable-libbluray --enable-libfreetype --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libopus --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libtheora --enable-libtwolame --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libzimg --enable-lzma --enable-zlib --enable-gmp --enable-libvidstab --enable-libvorbis --enable-libvo-amrwbenc --enable-libmysofa --enable-libspeex --enable-libxvid --enable-libaom --enable-libmfx --enable-amf --enable-ffnvcodec --enable-cuvid --enable-d3d11va --enable-nvenc --enable-nvdec --enable-dxva2 --enable-avisynth --enable-libopenmpt
  libavutil      56. 30.100 / 56. 30.100
  libavcodec     58. 53.100 / 58. 53.100
  libavformat    58. 28.101 / 58. 28.101
  libavdevice    58.  7.100 / 58.  7.100
  libavfilter     7. 55.100 /  7. 55.100
  libswscale      5.  4.101 /  5.  4.101
  libswresample   3.  4.100 /  3.  4.100
  libpostproc    55.  4.100 / 55.  4.100
Splitting the commandline.
Reading option '-loglevel' ... matched as option 'loglevel' (set logging level) with argument 'debug'.
Reading option '-report' ... matched as option 'report' (generate a report) with argument '1'.
Reading option '-s' ... matched as option 's' (set frame size (WxH or abbreviation)) with argument '3840x2160'.
Reading option '-vcodec' ... matched as option 'vcodec' (force video codec ('copy' to copy stream)) with argument 'v210'.
Reading option '-i' ... matched as input url with argument 'YUV_pattern-2fr.v210'.
Reading option '-vf' ... matched as option 'vf' (set video filters) with argument 'scale=in_color_matrix=bt2020:in_range=tv'.
Reading option '-vframes' ... matched as option 'vframes' (set the number of video frames to output) with argument '1'.
Reading option 'tiff_bug_00.tiff' ... matched as output url.
Reading option '-y' ... matched as option 'y' (overwrite output files) with argument '1'.
Finished splitting the commandline.
Parsing a group of options: global .
Applying option loglevel (set logging level) with argument debug.
Applying option report (generate a report) with argument 1.
Applying option y (overwrite output files) with argument 1.
Successfully parsed a group of options.
Parsing a group of options: input url YUV_pattern-2fr.v210.
Applying option s (set frame size (WxH or abbreviation)) with argument 3840x2160.
Applying option vcodec (force video codec ('copy' to copy stream)) with argument v210.
Successfully parsed a group of options.
Opening an input file: YUV_pattern-2fr.v210.
[NULL @ 000002c7c2a2b680] Opening 'YUV_pattern-2fr.v210' for reading
[file @ 000002c7c2a2c700] Setting default whitelist 'file,crypto'
[v210 @ 000002c7c2a2b680] Format v210 probed with size=2048 and score=50
[v210 @ 000002c7c2a2b680] Before avformat_find_stream_info() pos: 0 bytes read:32768 seeks:0 nb_streams:1
[v210 @ 000002c7c2a2b680] All info found
[v210 @ 000002c7c2a2b680] Estimating duration from bitrate, this may be inaccurate
[v210 @ 000002c7c2a2b680] After avformat_find_stream_info() pos: 22118400 bytes read:22118400 seeks:0 frames:1
Input #0, v210, from 'YUV_pattern-2fr.v210':
  Duration: 00:00:00.08, start: 0.000000, bitrate: 4423680 kb/s
    Stream #0:0, 1, 1/25: Video: v210, 1 reference frame, yuv422p10le, 3840x2160, 0/1, 4423680 kb/s, 25 tbr, 25 tbn, 25 tbc
Successfully opened the file.
Parsing a group of options: output url tiff_bug_00.tiff.
Applying option vf (set video filters) with argument scale=in_color_matrix=bt2020:in_range=tv.
Applying option vframes (set the number of video frames to output) with argument 1.
Successfully parsed a group of options.
Opening an output file: tiff_bug_00.tiff.
Successfully opened the file.
Stream mapping:
  Stream #0:0 -> #0:0 (v210 (native) -> tiff (native))
Press [q] to stop, [?] for help
cur_dts is invalid st:0 (0) [init:0 i_done:0 finish:0] (this is harmless if it occurs once at the start per stream)
detected 32 logical cores
[Parsed_scale_0 @ 000002c7c2a34700] Setting 'in_color_matrix' to value 'bt2020'
[Parsed_scale_0 @ 000002c7c2a34700] Setting 'in_range' to value 'tv'
[Parsed_scale_0 @ 000002c7c2a34700] Setting 'flags' to value 'bicubic'
[Parsed_scale_0 @ 000002c7c2a34700] w:iw h:ih flags:'bicubic' interl:0
[graph 0 input from stream 0:0 @ 000002c7c2a32d40] Setting 'video_size' to value '3840x2160'
[graph 0 input from stream 0:0 @ 000002c7c2a32d40] Setting 'pix_fmt' to value '66'
[graph 0 input from stream 0:0 @ 000002c7c2a32d40] Setting 'time_base' to value '1/25'
[graph 0 input from stream 0:0 @ 000002c7c2a32d40] Setting 'pixel_aspect' to value '0/1'
[graph 0 input from stream 0:0 @ 000002c7c2a32d40] Setting 'sws_param' to value 'flags=2'
[graph 0 input from stream 0:0 @ 000002c7c2a32d40] Setting 'frame_rate' to value '25/1'
[graph 0 input from stream 0:0 @ 000002c7c2a32d40] w:3840 h:2160 pixfmt:yuv422p10le tb:1/25 fr:25/1 sar:0/1 sws_param:flags=2
[format @ 000002c7c2a47880] Setting 'pix_fmts' to value 'rgb24|rgb48le|pal8|rgba|rgba64le|gray|ya8|gray16le|ya16le|monob|monow|yuv420p|yuv422p|yuv440p|yuv444p|yuv410p|yuv411p'
[AVFilterGraph @ 000002c7c2a322c0] query_formats: 4 queried, 3 merged, 0 already done, 0 delayed
[Parsed_scale_0 @ 000002c7c2a34700] picking rgb48le out of 17 ref:yuv422p10le alpha:0
[Parsed_scale_0 @ 000002c7c2a34700] w:3840 h:2160 fmt:yuv422p10le sar:0/1 -> w:3840 h:2160 fmt:rgb48le sar:0/1 flags:0x4
Output #0, image2, to 'tiff_bug_00.tiff':
  Metadata:
    encoder         : Lavf58.28.101
    Stream #0:0, 0, 1/25: Video: tiff, 1 reference frame, rgb48le, 3840x2160, 0/1, q=2-31, 200 kb/s, 25 fps, 25 tbn, 25 tbc
    Metadata:
      encoder         : Lavc58.53.100 tiff
Clipping frame in rate conversion by 0.000008
No more output streams to write to, finishing.
[image2 @ 000002c7c2a3e280] Opening 'tiff_bug_00.tiff' for writing
[file @ 000002c7c2e6c880] Setting default whitelist 'file,crypto'
[AVIOContext @ 000002c7c2a83580] Statistics: 0 seeks, 21 writeouts
frame=    1 fps=0.0 q=-0.0 Lsize=N/A time=00:00:00.04 bitrate=N/A speed=0.103x    
video:5147kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown
Input file #0 (YUV_pattern-2fr.v210):
  Input stream #0:0 (video): 1 packets read (22118400 bytes); 1 frames decoded; 
  Total: 1 packets (22118400 bytes) demuxed
Output file #0 (tiff_bug_00.tiff):
  Output stream #0:0 (video): 1 frames encoded; 1 packets muxed (5270238 bytes); 
  Total: 1 packets (5270238 bytes) muxed
1 frames successfully decoded, 0 decoding errors
[AVIOContext @ 000002c7c2a34940] Statistics: 22118400 bytes read, 0 seeks

comment:3 by cepesh, 5 years ago

Hi there. Is there any more information required from my side for this to go through your initial registration?

comment:4 by Balling, 3 years ago

Resolution: invalid
Status: newclosed

See downstream: https://github.com/sekrit-twc/zimg/issues/109

"found that you must add 'format=gbrp16le' after 'zscale'. Otherwise, FFmpeg instructs z.lib to perform a no-op conversion from YUV to YUV, and instead uses its internal code to convert to RGB. After I did this, I verified that I got '38004' when converting from '572'. This bug(?) exists in FFmpeg, not z.lib."

Last edited 3 years ago by Balling (previous) (diff)

comment:5 by Balling, 3 years ago

Okay, actually the real bug here is that the values inside YUV_pattern-2fr.v210 are not what you said they should be.

Check out: ffmpeg -video_size 3840x2160 -vcodec v210 -i YUV_pattern-2fr.v210 -vf extractplanes=y crazystuff.png

yuv422p10le that is used in v210 file are also a problem.

comment:6 by Balling, 3 years ago

Resolution: invalid
Status: closedreopened

Okay, I was wrong, I used yuvview (hex view in settings) and your file is indeed using in hex 0x3ac 0x200 0x200. So the bug here is not about the scale: even normal production of a png will produce 65283 Y only png. I suppose the idea that 10 bit value in png 16 bit per component will be just multiply with 64 is wrong. Sigh.

Last edited 3 years ago by Balling (previous) (diff)

comment:7 by pdr0, 3 years ago

Use zscale instead

ffmpeg -s 3840x2160 -vcodec v210 -i YUV_pattern-2fr.v210 -vf zscale=matrixin=2020_ncl,format=gbrp16le -an -frames:v 1 out_zscale.png

100% white
65535, 65535, 65535

58% white
38004, 38004, 38004

100% Magenta
65533, 0, 65535

comment:8 by Balling, 3 years ago

So, yes. I was able to confirm the bug:

ffmpeg.exe -video_size 3840x2160 -vcodec v210 -i YUV_pattern-2fr.v210 -vf scale=in_color_matrix=bt2020:in_range=tv,format=gbrp10le -f rawvideo rawle.rgb

(Do not use gbrp10be, since YUVview does not support BE.)

Open in YUVview, first select it being GBR (not RGB, select it is planar, select it is 10 bit) then select it is 3840x2160, it will crash at least once. Then if you will activate hex viewer in settings and zoom in you will get the hex values. Indeed white is 1020 10 bit, just like in 16 bit png (65283 / 64 =1020). Now, as described in ITU docs it is normal to use 1020 instead of 1023 since that is what naïve implementation will give. WOW.

Last edited 3 years ago by Balling (previous) (diff)

comment:9 by Balling, 3 years ago

Same happens with BT.601 matrix too with white color that is of course not caring about matrix. And BT.709.

comment:10 by François-Xavier Thomas, 21 months ago

I've hit the same issue in https://stackoverflow.com/questions/75260266, and found this ticket while trying to answer it, and I was wondering is there were anybody currently working on updating this inside ffmpeg? Is there any way I can help?

I suppose the idea that 10 bit value in png 16 bit per component will be just multiply with 64 is wrong.

At least in my mind, all standards I know of assume 255 (8-bit) = 1023 (10-bit) = 65535 (16-bit) = 100% = 1.0 (float)...

For 10-bit signals the multiplication factor should therefore be slightly different (65535/1023 = 64.0616...). In binary math I think the closest is (v << 6) + (v >> 4) with v a 10-bit integer).

(Using a factor of 64 is indeed a common misconception: the roundoff error is at most 1 if the final output is downconverted to a smaller integer type, so it's not easily visible, especially combined with lossy encoding and chroma subsampling.)

Now, as described in ITU docs it is normal to use 1020 instead of 1023 since that is what naïve implementation will give.

Could you clarify which ITU recommendation (and which version) you were referring to, if you still have it?

At least in the case of ITU-R BT.2020-2, I believe this is an incorrect interpretation ; this recommendation only provides specifications for narrow-range signals, for which Y'=940 corresponds to 100% signal, and the maximum value of 1019 is still valid but maps to a signal that is much greater than 100%. It does not cover full-range conversions like what PNG images are supposed to contain.

ITU-R BT.2100 does add an explicit definition for full-range signals, where 100% = 1023 (in 10-bit), which seem to correspond to the usual assumption that 100% = maximum of the data type.

comment:11 by Balling, 21 months ago

At least in my mind, all standards I know of assume 255 (8-bit) = 1023 (10-bit) = 65535 (16-bit) = 100% = 1.0 (float)..

That is not the case for limited range YCbCr or limited RGB (only for full RGB and YCBCr), they have values reserved for sync, 0 and 255 for 8 bit and 0, 1, 2, 3 and 1019, 1020, 1021, 1023 for all 3 components. We know from BT.601 the consensus is to use the following: 255.75 means that first 8 bit is before the dot and last two bits are after the dot, after is encoded as follows: 00 is 00, 25 is 01, 50 is 10, 75 is 11. If we omit .00 that means last two bits are 0, which means 8 bit 255 value is 255.00, not 255.75, with 255.75 being all ones. From this we see that if we say black level is at 16.00, it means that it is all ones for 8 bit, but for 10 bit it has last two bits as 0. In fact that means that in bitshift < 4 is used to convert from 8 bit to 10 bit, which is the case for 1.0 float value Y 235 and Cb, Cr 240 too. 240 * 4 is 960.

Ideally 960 was supposed to be 962, but ITU-R/CCIR decided otherwise.

They simply pad the 8bit number with two trailing zeros to convert to 8bit space. It's not perfect, but it's fast, simple and good enough (wasting 0.3% of available steps).

https://web.archive.org/web/20150912033144/http://www.spectracal.com/forum/viewtopic.php?f=51&t=1962#p12485&mobile=on

What also should be considered is that if we convert from RGB to 8 bit or 10 bit (limited) YCBCR it will not be just a bitshift between those, as clarified here: https://en.wikipedia.org/wiki/SMPTE_color_bars

So here we have a bug. We know to decode 10 bit YCbCr we need 12 bit RGB, so for white it should be 4095, 4095, 4095 already, then to convert from these RGB values to 16 bit RGB we need to (4095+1) * 4 -1 and we get 65536.

Last edited 21 months ago by Balling (previous) (diff)

comment:12 by Pierre-Anthony Lemieux, 19 months ago

Possibly related.

(1) create 16-bit PNG filled with peak white

convert -size 3840x2160 xc:white png48:white.png

(2) convert to YUV 10-bit

ffmpeg -i white.png -pix_fmt yuv444p10 -f rawvideo white.yuv

The resulting Y code values are equal to 943. They should be equal to 940 = (219 * 1 + 16) * 4.

comment:13 by Balling, 19 months ago

The resulting Y code values are equal to 943. They should be equal to 940

Yes, you are correct. Indeed, in RGB as it is full range in png, we have the 100% white at 65535 (counting from 0). Though again, the source of the image is using 2.2 gAMA (not even sRGB) and yuv will be assuming BT.709 transfer...

comment:14 by Balling, 9 days ago

Keywords: scale added
Status: reopenedopen
Note: See TracTickets for help on using tickets.