Vmaf とは
簡単にいうと Netflix が開発したライブラリで、二つの動画を比較して片方を参照してもう片方がどの程度劣化したかをスコアとして表現するもの。
疑問
vmaf って要は動画のフレームとフレームをいっこずつ比較しているんだから、フレーム間引いても統計的には問題ないんじゃないの?って思ったので、そうなのかどうなのか調査。
ここを理解することで fps を変更した時の vmaf の使い方もわかるはず
結論
vmaf は単純に一個ずつフレームを比較するわけではない。例えば motion features とかだと前のフレームの情報も参照して feature を求めている。なのでフレームが一緒でも前のフレームとの関係によってスコアが変わる。
つまり以下のようなことが言える
- 元の動画と fps から fps を減らした場合、元の fps に合わせて score を求めるべき
- 元の動画からなんらかの補完手法で fps を増やした場合、それがスコアにプラスとしてに評価されることはない(と思ってる)
- 計算速度のために frame を間引く意図で fps を下げて score を求めるとスコアは意図しないものとなる
以下、メモ
ここからは読まなくてもいい
まず Vmaf とは
まず、データセットは
- 1080p のディスプレイに Netflix 10 秒程度の video clip を表示
- 実際にユーザに bad, poor, fair, good, excellent の 5 段階評価してもらう
- bad = 20 〜 excellent = 100 とスコア化してデータセットにする
そんで、そのデータセットの video clip 動画から以下のような手法で各種 features を取得して、その features から Score を予測する SVM モデルを作るという手法
Feature name | Identifier | Core feature? | Individual Metrics |
---|---|---|---|
VIF | vif |
Yes |
vif_scale0 , vif_scale1 , vif_scale2 , vif_scale3
|
Motion2 | motion |
Yes |
motion , motion2
|
ADM | adm |
Yes |
adm2 , adm_scale0 , adm_scale1 , adm_scale2 , adm_scale3
|
CAMBI | cambi |
No | cambi |
CIEDE2000 | ciede |
No | ciede2000 |
MS-SSIM | float_ms_ssim |
No | |
PSNR | psnr |
No |
psnr_y , psnr_cb , psnr_cr
|
PSNR-HVS | psnr_hvs |
No |
psnr_hvs , psnr_hvs_y , psnr_hvs_cb , psnr_hvs_cr
|
SSIM | float_ssim |
No |
ほんで
libvmaf というライブラリになってて
(めちゃくちゃ雑にかいているので動くかは謎だけど)大体以下のようなコードを書く
// context の初期化(各動画フレームの feature のデータパイプラインのようなものを内部に持っている)
VmafContext *vmaf;
vmaf_init(&vmaf, cfg);
// model のロード
VmafCudaConfiguration cfg = { 0 };
VmafModel *model;
vmaf_model_load(&model, &cfg, "vmaf_b_v0.6.3")
vmaf_use_features_from_model(vmaf, model);
// 動画の 1 フレームごとに以下のような処理をする。
// raw data を yuv 形式で vmaf の VmafPicture 構造体に入れる(本当は ffmpeg とか gst とか使う)
VmafPicture ref_pic;
vmaf_picture_alloc(&ref_pic, VMAF_PIX_FMT_YUV420P, 8, 1920, 1080); // 1080p yuv420 の場合
memcpy(ref_pic.data[0], ref_frame.y_data, 1920 * 1080);
memcpy(ref_pic.data[1], ref_frame.y_data, 1920 * 1080 / 4);
memcpy(ref_pic.data[2], ref_frame.y_data, 1920 * 1080 / 4);
// raw data を yuv 形式で vmaf の VmafPicture 構造体に入れる(本当は ffmpeg とか gst とか使う)
VmafPicture dist_pic;
vmaf_picture_alloc(&dist_pic, VMAF_PIX_FMT_YUV420P, 8, 1920, 1080); // 1080p yuv420 の場合
memcpy(dist_pic.data[0], dist_frame.y_data, 1920 * 1080);
memcpy(dist_pic.data[1], dist_frame.y_data, 1920 * 1080 / 4);
memcpy(dist_pic.data[2], dist_frame.y_data, 1920 * 1080 / 4);
// vmaf context に frame の index と一緒に登録、 feature extract と svm prediction が行われる
vmaf_read_pictures(vmaf, &ref_pic, &dist_pic frame_index);
// 計算が全部終わるまで待つ
vmaf_read_pictures(vmaf, NULL, NULL, 0);
// 結果を得る
double vmaf_score;
vmaf_score_pooled(vmaf, model, VMAF_POOL_METHOD_MEAN, &vmaf_score, first_frame_index, last_frame_index);
printf("Score: %f", vmaf_score);
で本題
vmaf_score_pooled で取得されるスコアって単純に一個一個のフレームのスコアを平均するんだっけ?ってこと
コードを読んでみたらそういう実装になっている。
でも
ただ、経験的にフレームを間引くとスコアが大きく変わることを経験的に知っていて、それがなぜ発生するかを調べる必要が出てきた。
で、以下のあたりのコードを読んでみたところ
motion feature など VMAF_FEATURE_EXTRACTOR_TEMPORAL が設定されているものは single thread で feature extraction が順番に行われており、以下のコードのように二つ前までのフレームが参照されていることがわかった。
なるほどですね。