Summary. With 10-bit input, cambi with full_ref=true and a source (src_width / src_height) larger than the encoded picture scores the distorted plane with its rows at the wrong offsets, so the reported cambi is wrong.
Where. libvmaf/src/feature/cambi.c, decimate_generic_uint16_and_convert_to_10b() (master 0497a0f29, around line 751). When the input and output sizes are equal and bpc == 10, the plane is copied with one call:
memcpy(out_data, data, stride * pic->h[0] * sizeof(uint16_t));
This assumes out_stride == stride. With full_ref=true the working picture is allocated at the larger of the source and encode sizes, so out_stride is wider than the input stride and every row after the first lands at the wrong place. The 12- and 16-bit branch next to it already copies row by row with both strides, and 8-bit input takes a different path, so only 10-bit input is affected.
How to reproduce. Any 10-bit 4:2:0 pair, scored once without and once with a larger source, for example with a 480x270 pair:
vmaf -r ref_480x270_10b.yuv -d dis_480x270_10b.yuv -w 480 -h 270 -p 420 -b 10 \
--feature cambi --no_prediction --precision max
vmaf -r ref_480x270_10b.yuv -d dis_480x270_10b.yuv -w 480 -h 270 -p 420 -b 10 \
--feature cambi=full_ref=true:src_width=960:src_height=540 --no_prediction --precision max
The distorted-side cambi of the second run should equal the first (it is the distorted score at the encode size), but it does not. On one 10-bit clip we measured frame 0 at 0.0048362395598900215 with full_ref against 0.3734397949735747 without; 0 of 5 frames matched.
Suggested fix. Copy row by row with both strides, as the other branch does:
for (unsigned i = 0; i < out_h; i++)
memcpy(out_data + i * out_stride, data + i * stride, out_w * sizeof(uint16_t));
With that change the full_ref run equals the no-reference run on every frame we tried, and 8-, 12- and 16-bit results are unchanged.
Found while testing the VMAFx fork (https://github.com/VMAFx/vmafx), where the fix and a regression test are in VMAFx/vmafx#2111.
Summary. With 10-bit input,
cambiwithfull_ref=trueand a source (src_width/src_height) larger than the encoded picture scores the distorted plane with its rows at the wrong offsets, so the reportedcambiis wrong.Where.
libvmaf/src/feature/cambi.c,decimate_generic_uint16_and_convert_to_10b()(master0497a0f29, around line 751). When the input and output sizes are equal andbpc == 10, the plane is copied with one call:This assumes
out_stride == stride. Withfull_ref=truethe working picture is allocated at the larger of the source and encode sizes, soout_strideis wider than the input stride and every row after the first lands at the wrong place. The 12- and 16-bit branch next to it already copies row by row with both strides, and 8-bit input takes a different path, so only 10-bit input is affected.How to reproduce. Any 10-bit 4:2:0 pair, scored once without and once with a larger source, for example with a 480x270 pair:
vmaf -r ref_480x270_10b.yuv -d dis_480x270_10b.yuv -w 480 -h 270 -p 420 -b 10 \ --feature cambi --no_prediction --precision max vmaf -r ref_480x270_10b.yuv -d dis_480x270_10b.yuv -w 480 -h 270 -p 420 -b 10 \ --feature cambi=full_ref=true:src_width=960:src_height=540 --no_prediction --precision maxThe distorted-side
cambiof the second run should equal the first (it is the distorted score at the encode size), but it does not. On one 10-bit clip we measured frame 0 at 0.0048362395598900215 withfull_refagainst 0.3734397949735747 without; 0 of 5 frames matched.Suggested fix. Copy row by row with both strides, as the other branch does:
With that change the
full_refrun equals the no-reference run on every frame we tried, and 8-, 12- and 16-bit results are unchanged.Found while testing the VMAFx fork (https://github.com/VMAFx/vmafx), where the fix and a regression test are in VMAFx/vmafx#2111.