yuv與rgb的轉換矩陣推導過程

yuv與rgb的轉換矩陣推導過程

  • 在網上經常看到一些shader裏用了矩陣就能把yuv轉爲rgb了,但那個矩陣究竟是如何來的,爲何每一個人寫的矩陣都不太一樣,卻最終都能完美呈現圖像
  • 帶着這些疑惑,決定自己推導一下轉換矩陣,在推導之前,必須拿到一個可靠的轉換公式,但一搜的話發現網上的公式也是多種多樣,最後選擇了一個看起來還靠譜一點的公式,進行推導驗證
  • 查看本文之前需要具有線性代數的關於矩陣的基礎能力以及瞭解平移的變換矩陣

yuv與rgb互轉公式

  • 首先以下公式是從網上獲取的,這個公式的好處就是各分量的範圍都在[0-255],剛好符合yuv420p和rgb888,即每個分量用一個字節存儲
Y     = 0.257R + 0.504G + 0.098B + 16
Cr    = 0.439R – 0.368G – 0.071B + 128
Cb    =0.148R – 0.291G + 0.439B + 128

R     = 1.164(Y – 16) + 1.596(Cr – 128)
G     = 1.164(Y – 16)0.813(Cr – 128)0.391(Cb – 128)
B     = 1.164(Y – 16) + 2.018(Cb – 128)
  • 上面的公式我也說不準是否正確,因此需要寫代碼驗證一下

yuv420p轉rgb888矩陣

  • 運用上面的公式,在此嘗試一下yuv420p轉rgb888能否正常使用
  • 在shader編程中,rgb都是歸一化的,也就是rgb各分量的範圍都在[0,1],因此上面的公式中我們需要將16和128也歸一化:16/256=0.065,128/256=0.5,即
R     = 1.164(Y – 0.065) + 1.596(Cr – 0.5)
G     = 1.164(Y – 0.065)0.813(Cr – 0.5)0.391(Cb – 0.5)
B     = 1.164(Y – 0.065) + 2.018(Cb – 0.5)
  • 以上的公式RGB和YCrCb的範圍都是[0,1]了
  • 運用數學中的矩陣,上面的公式可以等價於如下矩陣公式
[ R ]     [ 1.164  1.5960  0.0000 ]      [ Y  ]    [ 0.065 ]
| G |  =  | 1.164  -0.813  -0.391 | * (  | Cr | -  | 0.500 | )
[ B ]     [ 1.164  0.0000  2.0180 ]      [ Cb ]    [ 0.500 ]
  • 這樣子中間那塊就是我們需要的變換矩陣了,根據這個矩陣去編寫shader驗證之後我發現人物的皮膚是藍色的
  • 在這裏插入圖片描述
  • 這說明uv這兩個應該有點問題,上面我是默認把Cr當做U,把Cb當做V來使用的,難道這兩個順序反了,於是我調整順序,把Cr當做V,把Cb當做U來使用時,發現竟然正常了,雖然正常了,但我依舊不清楚爲何要做這種調整,難道是公式寫錯了?望大神告知
  • 調整後的矩陣如下,就是調換了第2,3列的順序而已
 [ 1.164   0.0000  1.5960 ]  
 | 1.164   -0.391  -0.813 | 
 [ 1.164   2.0180  0.0000 ]  
  • 根據上面的矩陣推導我們就可以開始片元着色的器的編程了,以webgl爲例
precision highp float;
varying lowp vec2 vTextureCoord;
uniform sampler2D YTexture;
uniform sampler2D UTexture;
uniform sampler2D VTexture;
const mat3 yuv2rgb = mat3(//由於是左乘,因此這裏需要以數學中矩陣的列開始順序放入數組
1.164 , 1.164,  1.164,
0.0000, -0.391, 2.0180,
1.5960,-0.813 , 0.0000
);
void main(){
vec3 yuv=vec3(texture2D(YTexture,vTextureCoord).x-0.065,texture2D(UTexture,vTextureCoord).x-0.5,texture2D(VTexture,vTextureCoord).x-0.5);
gl_FragColor=vec4(yuv2rgb*yuv,1.0);
}
  • 頂點着色器也提供下
attribute highp vec4 aVertexPosition; //頂點
attribute vec2 aTextureCoord; //頂點紋理座標
varying highp vec2 vTextureCoord;
void main(void) {
  gl_Position = aVertexPosition;
  vTextureCoord = aTextureCoord;
}
  • shader裏需要的uniform參數 YTexture,UTexture,VTexture就是yuv420p各個分量的值,需要通過gl.texImage2D將數據設置進去
  • 上面的shader親測通過,能正常渲染,也不會出現藍皮膚

優化一下

  • 上面的公式中YCrCb在使用之前其實還需要先減去一個矩陣,那麼能不能優化這一步,直接變成 rgb = yuv * 矩陣 這種格式的
  • 上面的想法是可以實現的,就是要引入齊次座標的概念,將三維變爲四維,上面的公式展開後可以變成如下形式
    R     = 1.164Y + 1.596Cr +  0*Cb 	– 0.87075
    G     = 1.164Y – 0.813Cr –  0.391Cb + 0.52925
    B     = 1.164Y + 0*Cr    +  2.018Cb – 1.08175
    1     = 0*Y    + 0*Cr    +  0*Cb    + 1
    
  • 如果你已經掌握了平移的矩陣變換,那麼看到上面的形式應該就可以馬上寫出轉換矩陣了,這裏由於前面已經說了,需要把Cr當做V,Cb當做U,因此最終矩陣爲
 [ 1.164   0.0000  1.5960  -0.87075 ]  
 | 1.164   -0.391  -0.813  0.529250 | 
 | 1.164   2.0180  0.0000  -1.08175 |
 [ 0.000   0.0000  0.0000  1.000000 ]  
  • 那麼最終的片元shader就如下了
precision highp float;
varying lowp vec2 vTextureCoord;
uniform sampler2D YTexture;
uniform sampler2D UTexture;
uniform sampler2D VTexture;
const mat4 yuv2rgb = mat4(
1.164 , 0.000,  1.596, -0.87075,
1.164, -0.391, -0.813, 0.529250,
1.164, 2.0180 , 0.000, -1.08175,
0.000, 0.0000 , 0.000, 1.000000
);
void main(){
gl_FragColor = vec4( texture2D(YTexture, vTextureCoord).x, texture2D(UTexture, vTextureCoord).x, texture2D(VTexture, vTextureCoord).x, 1) * yuv2rgb;
}
  • 最後跟網絡上其他人使用的矩陣對比了一下,基本是一致的,這說明文章開頭yuv轉rgb的公式基本是正確的,就是精度的差異而已
  • 在這裏插入圖片描述

參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章