AV1で採用されているChroma from Luma Prediction (CfL)を使ってイントラ予測
Chroma from Luma Prediction (CfL) はAlliance for Open Mediaが開発したオープンかつロイヤリティフリーな動画圧縮コーデックAV1などで採用されているイントラ予測手法です。イントラ予測というのは動画圧縮で他のフレームを参照しないフレーム符号化のことを言います。今回は、Juliaを用いてその効果を検証していきます。
Chroma(彩度)とLuma(輝度)の関係
CfLはLumaからChromaを推定する手法です。これにより保持すべきデータ容量が削減できます。ここでは、レナの画像を使ってChromaとLumaの関係を可視化します。
まず必要なパッケージを使ってレナの画像を準備します。
using Images, ImageView, TestImages using Plots lena = testimage("lena_color_256") plot(lena)
次にYCbCr色空間に変換します。
lena = YCbCr.(lena) lena_arr = channelview(lena) p_y = heatmap(reverse(lena_arr[1,:,:], dims=1), title="Y(Luma)", color=:grays) p_cb = heatmap(reverse(lena_arr[2,:,:], dims=1), title="Cb", color=:grays) p_cr = heatmap(reverse(lena_arr[3,:,:], dims=1), title="Cr", color=:grays) plot(p_y, p_cb, p_cr, layout=(1, 3), size=(1200, 300), fmt=:png)
左上の32x32のタイル上における、ビットごとのそれぞれChromaとLumaの関係を見ます。横軸にLuma、縦軸にChromaの散布図を作成します。
bs = 32 # block size s = 1 e = s + bs - 1 plot(lena[s:e,s:e], fmt=:png) x_min = minimum(lena_arr[1,s:e,s:e]) x_max = maximum(lena_arr[1,s:e,s:e]) # Cb x = vec(lena_arr[1,s:e,s:e]) y = vec(lena_arr[2,s:e,s:e]) scatter(x, y, label="Cb", xlabel="Luma", ylabel="Chroma") cb_a = (bs*bs*sum(x.*y) - sum(x)*sum(y)) / (sum(bs*bs*(x.^2)) - sum(x)^2) cb_b = (sum(y) - cb_a * sum(x)) / (bs*bs) x = range(x_min, x_max, length=1000) y = cb_a .* x .+ cb_b plot!(x, y, label="Cb prediction") # Cr x = vec(lena_arr[1,s:e,s:e]) y = vec(lena_arr[3,s:e,s:e]) scatter!(x, y, label="Cr") cr_a = (bs*bs*sum(x.*y) - sum(x)*sum(y)) / (sum(bs*bs*(x.^2)) - sum(x)^2) cr_b = (sum(y) - cr_a * sum(x)) / (bs*bs) x = range(x_min, x_max, length=1000) y = cr_a .* x .+ cr_b plot!(x, y, label="Cr prediction", fmt=:png)
上記の結果から、ChromaのそれぞれのCr, CbはLumaに対してのある程度線形な関係にあることが分かりました。つまりCr, Cbはそれぞれ傾きと切片が分かっているならLumaからある程度推定可能ということです。この関係を用いてCfLを実装します。
Chroma from Luma Prediction (CfL)
先程の例でChromaとLumaの関係が分かっているのであとは定式化してみましょう。 Predicting Chroma from Luma in AV1 から式を引用するとCfLは以下のようなパラメータの線形モデルを使って推定します。 はChroma, Lumaです。
パラメータは次のように最小二乗法で求めます。
最後に、8, 16, 32のブロックサイズごとにCfLをした画像とオリジナル画像を比較してみましょう。
function cfl(img_arr, bs=8) nc, h, w = size(img_arr) img_arr_est = zeros((nc, h, w)) for i in 1:Int(h/bs) si = (i-1)*bs + 1 ei = i*bs for j in 1:Int(w/bs) sj = (j-1)*bs + 1 ej = j*bs img_arr_tmp = img_arr[:, si:ei, sj:ej] # Cb x = vec(img_arr_tmp[1, :, :]) y = vec(img_arr_tmp[2, :, :]) cb_a = (bs*bs*sum(x.*y) - sum(x)*sum(y)) / (sum(bs*bs*(x.^2)) - sum(x)^2) cb_b = (sum(y) - cb_a * sum(x)) / (bs*bs) # Cr x = vec(img_arr_tmp[1, :, :]) y = vec(img_arr_tmp[3, :, :]) cr_a = (bs*bs*sum(x.*y) - sum(x)*sum(y)) / (sum(bs*bs*(x.^2)) - sum(x)^2) cr_b = (sum(y) - cr_a * sum(x)) / (bs*bs) x = img_arr_tmp[1, :, :] cb_est = cb_a .* x .+ cb_b cr_est = cr_a .* x .+ cr_b img_arr_est_tmp = zeros((3, bs, bs)) img_arr_est_tmp[1,:,:] .= x img_arr_est_tmp[2,:,:] .= cb_est img_arr_est_tmp[3,:,:] .= cr_est img_arr_est[:, si:ei, sj:ej] .= img_arr_est_tmp end end img_arr_est end p_org = plot(colorview(YCbCr, lena_arr), title="origin") p8 = plot(colorview(YCbCr, cfl(lena_arr, 8)), title="CfL(block size 8)") p16 = plot(colorview(YCbCr, cfl(lena_arr, 16)), title="CfL(block size 16)") p32 = plot(colorview(YCbCr, cfl(lena_arr, 32)), title="CfL(block size 32)") plot(p_org, p8, p16, p32, layout = (1, 4), size = (1200, 300), fmt=:png)
上記の結果からオリジナル画像に対して、CfLでかなりきれいに推定できることが分かりました。 AV1では、上記のような基本的なCfLが用いられているわけではないのですが原理は同じです。 さらに今後の取り組みとして非線形モデルを使って推定するなども考えられているらしいです。