
カラースペース
はsRGB IEC61966-2.1
で統一しています。なお、縦横比を補正する前のため、ドクターイエローの顔が長く伸びています。
TrainScannerで作成したドクターイエローの編成写真をSNSでシェアするためにPNGファイルから動画ファイルを生成してみたら、VLCなどの動画プレーヤーで見たときに、なんとなく赤っぽい黄色になってしまいました。FFmpegで動画を作ったときに色化けしたようです。
FFmpeg colorspace
などのキーワードでネット検索して、FFmpegでRGB画像からYUV形式の動画を作るときに色空間の変換が必要になることが分かりました(詳細はこちら)が、実際にどれくらい違って見えるのか比較してみました。
動作環境と動画データの詳細
動作環境は以下のとおりです。
- 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フィルターあり・なしで再生動画を比較すると
VLC media playerで再生した場合
上から順に元となるPNG画像(Photoshop)、colorspace
フィルターなしの動画、colorspace
フィルターありの動画です。
元々PNG画像の黄色は少し赤みを帯びていましたが、colorspace
フィルターなしの動画は赤みが増して見えます。MacBook Proのディスプレイではあまり気にならなかったのですが、Windows 11 PCで使っている32インチ外部ディスプレイで見たときに赤みが増していることに気付きました。ヒストグラムにおいても赤と緑の山が分離して赤が強くなっています。
colorspace
フィルターありの動画はヒストグラムの赤と緑の山が少し分離しているものの、PNG画像の色合いに近づいて改善できているように思います。



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)
が一番良い感じです。


QuickTime Playerで再生した場合
次はQuickTime Playerで動画を再生した場合です。上から順にcolorspace
フィルターなしの動画、colorspace
フィルターありの動画です。


QuickTime Playerで再生した場合はcolorspace
フィルターなしの動画でも赤みが気にならないように思いました。ヒストグラムはcolorspace
フィルターありの動画をVLCで再生した場合に近い感じです。
colorspace
フィルターありの動画はPNG画像(Photoshop)のヒストグラムとほぼ同じで、見た目もPNG画像とほぼ同じような感じになりました。Colorspace support in FFmpegで示されているcolorspace
フィルターが効いているのが分かります。
Pythonで動画を作るときに便利なパッケージ
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
)
fffio
ffmpeg-pythonはFFmpegの機能を網羅的に使うことができて非常に強力なPythonパッケージなのですが、シンプルに動画データの読み書きだけで使おうと思うと、上記のサンプルスクリプトは手間がかかります。
そこで、テキストファイルの読み書きと同じくらい簡単に動画データを読み書きできる fffio
というPythonパッケージを作りました。pipを使ってインストールできます。
pip install fffio
動画データからフレームを読み出す場合は、以下のように書くことができます。
from fffio import FrameReader
import 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 FrameWriter
import cv2
from 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です。