When I encode a particular sequence of BMP image files to AVC/H.264, the colors get shifted. Why does this happen?

But if I convert the BMPs to PNGs before running ffmpeg, the video conversion appears correct. My source material is in BMPs, and would prefer not to manually convert everything to PNGs to perform the workaround.

For the purpose of this question, I took the sequence of images and summarized them into these main colors being used:

enter image description here

First attempt at encoding (the pure white becomes noticeably green): ffmpeg -i 000.bmp -pix_fmt yuv420p -vcodec libx264 -profile:v main -crf 16 first.mp4

enter image description here

Second attempt (even worse): ffmpeg -i 000.bmp -vf "colormatrix=bt601:bt709" -pix_fmt yuv420p -vcodec libx264 -profile:v main -crf 16 second.mp4

enter image description here

Third attempt (too bright): ffmpeg -i 000.bmp -vf "scale=in_range=full:in_color_matrix=bt709:out_range=full:out_color_matrix=bt709" -pix_fmt yuv420p -vcodec libx264 -profile:v main -crf 16 third.mp4

enter image description here

However, using a PNG version of the same input image works great: ffmpeg -i 000.png -pix_fmt yuv420p -vcodec libx264 -profile:v main -crf 16 fourth.mp4

enter image description here

I am using the latest FFmpeg Windows x86-64 build (2016-11-22 Git d316b21):

C:\Temp> ffmpeg.exe -version
ffmpeg version N-82597-gd316b21 Copyright (c) 2000-2016 the FFmpeg developers
built with gcc 5.4.0 (GCC)
configuration: --enable-gpl --enable-version3 --disable-w32threads --enable-dxva2
    --enable-libmfx --enable-nvenc --enable-avisynth --enable-bzlib --enable-fontconfig
    --enable-frei0r --enable-gnutls --enable-iconv --enable-libass --enable-libbluray
    --enable-libbs2b --enable-libcaca --enable-libfreetype --enable-libgme
    --enable-libgsm --enable-libilbc --enable-libmodplug --enable-libmp3lame
    --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenh264
    --enable-libopenjpeg --enable-libopus --enable-librtmp --enable-libschroedinger
    --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libtheora
    --enable-libtwolame --enable-libvidstab --enable-libvo-amrwbenc --enable-libvorbis
    --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx264
    --enable-libx265 --enable-libxavs --enable-libxvid --enable-libzimg --enable-lzma
    --enable-decklink --enable-zlib
libavutil      55. 40.100 / 55. 40.100
libavcodec     57. 66.106 / 57. 66.106
libavformat    57. 58.100 / 57. 58.100
libavdevice    57.  2.100 / 57.  2.100
libavfilter     6. 67.100 /  6. 67.100
libswscale      4.  3.101 /  4.  3.101
libswresample   2.  4.100 /  2.  4.100
libpostproc    54.  2.100 / 54.  2.100

To rephrase my question, how can I convert the sequence of BMP images to an H.264 video without unwanted color shifts?

  • I'm note sure, so please confirm before I consider this as an answer. Please try with '-vcodec libx264rgb' instead of '-vcodec libx264' – RawBean Nov 25 '16 at 5:25
  • 1
    Given that the title of the question is about a color shift when encoding to YUV format, how will encoding to RGB address the issue. The issue here is to correctly and fully specify the attributes of the source, so that swscale can correctly transform it. Also needed is to flag the output stream for lazy/sloppy players. – Gyan Nov 25 '16 at 6:08
  • To the OP. how did you convert BMPs to PNG? And how were the BMPs generated? – Gyan Nov 25 '16 at 6:09
  • @Mulvya For my MCVE in this question, I generated the BMPs by hand in MS Paint. I converted to PNG using IrfanView. Looking at the files in a hex editor, the BMP has no color profile or related metadata. The PNG appears to have a gAMA chunk and sRGB chunk added by default. – Nayuki Nov 25 '16 at 7:03
  • @RawBean Interesting suggestion. Using -vcodec libx264rgb -profile:v high444 (minus -pix_fmt), I get a video with perfect colors with zero deviation. However it is only playable in MPC-HC, not Windows Explorer or Mozilla Firefox. I think it would be good to stay within H.264 main profile. – Nayuki Nov 25 '16 at 7:11

Apparently FFmpeg doesn't set input color attributes correctly for BMP input (probably because the file doesn't contain that metadata) and the BMP decoder doesn't check for manually flagged attributes. However, we can force it using the format filter.

ffmpeg -i 000.bmp -vf format=rgb24 -pix_fmt yuv420p -vcodec libx264 -profile:v main -crf 16 first.mp4
  • This works perfectly! It gets the same result as using a PNG input. Thank you so much! Still, this is kinda crazy because BMP files can only be RGB24, nothing else... – Nayuki Nov 25 '16 at 15:32
  • 1
    According to the decoder, BMPs can have pixel format of an 8-bit palette or 5-bit RGB or 6-bit G..etc (also, can have alpha). But yes, colorspace is RGB. The decoder is originally from 2005 and its the decoder's job to populate the AVFrame struct passed on to filters with relevant attributes. Since BMP hasn't changed much & isn't used much, no one's bothered to retrofit the code with new flags added since. – Gyan Nov 25 '16 at 15:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.