FFmpegで画像から動画を作る(RGB→YUV)と色が異なってしまうときの具体例と対処
ドクターイエローの編成写真から動画を生成してみたら、なんとなく赤っぽい黄色になってしまいました。FFmpegでcolorspaceを指定せずに動画を作ったときに色化けしたようです。VLCとQuickTimeで見え方を調べてみました。さらに、色化けを解消した動画を作成するPythonパッケージを公開しています。
オリジナルの写真をPhotoshopで開いたところ。PhotoshopのカラースペースはsRGB IEC61966-2.1で統一しています。
なお、縦横比を補正する前のため、ドクターイエローの顔が長く伸びています。
TrainScannerで作成したドクターイエローの編成写真をSNSでシェアするためにPNGファイルから動画ファイルを生成してみたら、VLCなどの動画プレーヤーで見たときに、なんとなく赤っぽい黄色になってしまいました。FFmpegで動画を作ったときに色化けしたようです。
FFmpeg colorspace などのキーワードでネット検索して、FFmpegでRGB画像からYUV形式の動画を作るときに色空間の変換が必要になることが分かりました(詳細はこちら)が、実際にどれくらい違って見えるのか比較してみました。
動作環境と動画データの詳細
Section titled “動作環境と動画データの詳細”動作環境は以下のとおりです。
- MacBook Pro (16インチ, 2024)
- Liquid Retina XDRディスプレイ搭載、ディスプレイのプリセットはApple XDR Display (P3-1600 nits)を選択している状態
- macOS Sequoia 15.6.1
- 動画プレーヤーのスクリーンショットはOSの機能(⌘+Shift+5)を使ってPNGファイルに保存
- QuickTime Player バージョン10.5
- VLC media player Version 3.0.21 Vetinari (Apple Silicon)
- Photoshop 26.11.0
- Liquid Retina XDRディスプレイに表示している動画プレーヤーのスクリーンショットはPNG形式(カラープロファイルはカラーLCDとなっている)だが、Photoshopで開いたときに編集メニューのプロファイル変換を使ってsRGB IEC61966-2.1に変換している
- ffmpeg version 8.0 built with Apple clang version 17.0.0 (clang-1700.0.13.3)
- Homebrewを使ってインストールしたFFmpegを使用
PNGファイルから作成した動画データは、colorspaceフィルターなしで作った動画とcolorspaceフィルターありで作った動画の2種類を準備しました。
- colorspaceフィルターなしの場合:
ffmpeg -f rawvideo -pix_fmt rgb24 -i pipe: \ -pix_fmt yuv420p output.mp4- colorspaceフィルターありの場合 (詳細は Colorspace support in FFmpeg を参照):
ffmpeg -f rawvideo -pix_fmt rgb24 -i pipe: \ -filter_complex "[0]colorspace=bt709:fast=1:iall=bt601-6-625[s0]" -map "[s0]" \ -color_primaries 1 -color_range 1 -color_trc 1 -colorspace 1 \ -sws_flags spline+accurate_rnd+full_chroma_int \ -pix_fmt yuv420p output.mp4実際にはPythonを使ってPillowでPNGファイルを読み込み、numpyでndarray((高さ, 幅, 3), dtype=np.uint8)に変換し、ndarray.tobytes()で得たRGB画像のバイトデータを-i pipe:で標準入力からFFmpegに送り込んでいます。
colorspaceフィルターあり・なしで再生動画を比較すると
Section titled “colorspaceフィルターあり・なしで再生動画を比較すると”(1) VLC media playerで再生した場合
Section titled “(1) VLC media playerで再生した場合”上から順に、元となるPNG画像(Photoshop)、colorspaceフィルターなしの動画、colorspaceフィルターありの動画です。
元々PNG画像の黄色は少し赤みを帯びていましたが、colorspaceフィルターなしの動画は赤みが増して見えます。MacBook Proのディスプレイではあまり気にならなかったのですが、Windows 11 PCで使っている32インチ外部ディスプレイで見たときに赤みが増していることに気付きました。ヒストグラムにおいても赤と緑の山が分離して赤が強くなっています。
colorspaceフィルターありの動画はヒストグラムの赤と緑の山が少し分離しているものの、PNG画像の色合いに近づいて改善できているように思います。
(2) VLC media player(macOS)の表示設定を変えて再生した場合
Section titled “(2) VLC media player(macOS)の表示設定を変えて再生した場合”macOS用VLC media playerの場合、設定画面→すべてを表示→ビデオ→Mac OS Xを開くと、Colorspace conversionという設定項目があります。
Display primariesの初期状態はUnknown primariesが選択されていますが、これを**Adobe RGB (1998)に変更してVLCを起動しなおすと、オリジナルのPNG画像に近い色合いで表示されるようになりました。他の設定値も試してみましたが赤または緑がかった色合いになってしまうので、今回の動画データでは少なくともAdobe RGB (1998)**が一番良い感じです。
(3) QuickTime Playerで再生した場合
Section titled “(3) QuickTime Playerで再生した場合”次はQuickTime Playerで動画を再生した場合です。上から順に、元となるPNG画像(Photoshop)、colorspaceフィルターなしの動画、colorspaceフィルターありの動画です。
QuickTime Playerで再生した場合はcolorspaceフィルターなしの動画でも赤みが気にならないように思いました。ヒストグラムはcolorspaceフィルターありの動画をVLCで再生した場合に近い感じです。
colorspaceフィルターありの動画はPNG画像(Photoshop)のヒストグラムとほぼ同じで、見た目もPNG画像とほぼ同じような感じになりました。Colorspace support in FFmpegで示されているcolorspaceフィルターが効いているのが分かります。
Pythonで動画を作るときに便利なパッケージ
Section titled “Pythonで動画を作るときに便利なパッケージ”(1) ffmpeg-python
Section titled “(1) ffmpeg-python”Pythonで動画を生成するスクリプトを作るときにとても便利なのが ffmpeg-python というパッケージです。
FFmpegの-filter_complexパラメータはとても複雑なフィルターグラフを作って高度な動画処理を行うことができますが、コマンドラインでそれを手作業で作るのはとても難しいと思います。
しかし、ffmpeg-pythonを使えば複雑なフィルターグラフもメソッドチェーンの形で読みやすく記述できます。GitHubのExamplesに分かりやすい例が多数掲載されています。
上で示したcolorspaceフィルターありのコマンドラインはffmpeg-pythonを使って生成しています。
# FFmpegの入力・フィルター・出力をメソッドチェーンで定義するcommand = ( ffmpeg .input('pipe:', format='rawvideo', pix_fmt='rgb24', r=fps, s=f'{video_width}x{video_height}') .filter_('colorspace', 'bt709', iall='bt601-6-625', fast='1') .output('output.mp4', sws_flags='spline+accurate_rnd+full_chroma_int', color_range=1, colorspace=1, color_primaries=1, color_trc=1, pix_fmt=output_pix_fmt, video_bitrate=bitrate, qmin=qmin, qmax=qmax) .overwrite_output() .compile())# print(command)で生成されたコマンドラインを表示できる
# subprocessでFFmpegを実行するprocess = subprocess.Popen( command, stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL )(2) fffio
Section titled “(2) fffio”ffmpeg-pythonはFFmpegの機能を網羅的に使うことができて非常に強力なPythonパッケージなのですが、シンプルに動画データの読み書きだけで使おうと思うと、上記のサンプルスクリプトは手間がかかります。
そこで、テキストファイルの読み書きと同じくらい簡単に動画データを読み書きできる fffio というPythonパッケージを作りました。pipを使ってインストールできます。
pip install fffio動画データからフレームを読み出す場合は、以下のように書くことができます。
from fffio import FrameReaderimport cv2
with FrameReader('sample.mp4') as reader: for i, frame in enumerate(reader.frames(), 1): # frame is a numpy.ndarray(shape=(height, width, 3), dtype=np.uint8). _ = cv2.imwrite( f'sample{i:05d}.jpg', cv2.cvtColor(frame, cv2.COLOR_RGB2BGR) )画像を動画データに書き込む場合は、以下のように書くことができます。
from fffio import FrameWriterimport cv2from pathlib import Path
size=(1920, 1080)with FrameWriter('sample.mp4', size=size) as writer: for file in sorted(Path('.').glob('*.jpg')): frame = cv2.cvtColor(cv2.imread(str(file)), cv2.COLOR_BGR2RGB) frame = cv2.resize(frame, size, interpolation=cv2.INTER_LANCZOS4) writer.write(frame)FrameWriterの中ではFFmpegのcolorspaceフィルターを実装しているので、色合いを心配しないで動画を作れるようになっています。colorspaceフィルターを外す場合はFrameWriterの引数にcolorspace=Falseを指定すればOKです。