基于YUV 數據格式詳解及python實現方式
YUV 數據格式概覽
YUV 的原理是把亮度與色度分離,使用 Y、U、V 分別表示亮度,以及藍色通道與亮度的差值和紅色通道與亮度的差值。其中 Y 信號分量除了表示亮度 (luma) 信號外,還含有較多的綠色通道量,單純的 Y 分量可以顯示出完整的黑白圖像。U、V 分量分別表示藍 (blue)、紅 (red) 分量信號,它們只含有色彩 (chrominance/color) 信息,所以 YUV 也稱為 YCbCr,C 意思可以理解為 (component 或者 color)。
維基百科上的 RGB 轉 YUV 的公式能更好的反應 YUV 與 RGB 的關系,以及為什么稱為 YCbCr:
Y 中含有三元色色信息,且有較多的 G,所以他們一起可以顯示出全彩的圖像。
很顯然我們可以想到是不是會有 YCgCb、YCgCr 等,針對不同的應用場景,也確實有相關應用研究。
如下圖,一張從上到下分別為原圖、Y、U 和 V:
采用 YUV 而不是使用 RGB,既有歷史原因:為了兼容老式黑白電視,因為 YUV 如果只輸出 Y 就成了黑白圖像了。也有 YUV 自己的其他優(yōu)點,例如可以根據需要,采用特定的 YUV 存儲格式,以降低祼碼流的空間占用。
YUV 存儲格式
YUV 存儲格式有兩大類:planar 和 packed。
對于 planar 的 YUV 格式,先連續(xù)存儲所有像素點的 Y,緊接著存儲所有像素點的 U,隨后是所有像素點的 V。相當于將 YUV 拆分成三個平面 (plane) 存儲。
對于 packed 的 YUV 格式,每個像素點的 Y,U,V 是連續(xù)交替存儲的。
YUV 碼流又根據不同的采樣方式分為 YUV4:4:4、YUV4:2:2、YUV4:2:0、YUV4:1:1 等存儲格式,其中前 3 種較常見。所謂采樣意思就是根據一定的間隔取值。其中的比例是指 Y、U、V 表示的像素,三者分別占的比值??梢园凑杖缦路绞嚼斫猓瑢崿F存儲和掃描與 DVD 的掃描線有關。
例如:
YUV4:4:4 是指每個像素分別有一個 Y、一個 U 和一個 V 組成,即每 4 個 Y 采樣,就對應 4 個 Cb 和 4 個 Cr 采樣,也就是一個像素占用 8+8+8=24 位,這種存儲方式圖像質量最高,但空間占用也最大,空間占用與 RGB 存儲時一樣。對于一個 M*N分辨率的圖像,該模式下存儲空間占用字節(jié)數為 M*N*3。
YUV4:2:2 是指每 4 個 Y 采樣,對應 2 個 Cb 和 2 個 Cr 采樣,這樣在解析時就會有一些像素點只有亮度信息而沒有色度信息,缺失的色度信息就需要在解析時由相鄰的其他色度信息根據一定的算法填充。這種方式下平均一個像素占用空間為 8+4+4=16 位。對于一個 M*N 分辨率的圖像,空間占用 16/24,即 M*N*3*(16/24) = M*n*2 個字節(jié)。
YUV4:2:0 是指每 4 個 4 采樣,對應 2 個 U 采樣或者 2 個 V 采樣,注意其中并不是表示 2 個 U 和 0 個 V,而是指無論水平下采樣還是垂直下采樣,色度采樣都只有亮度的一半。該存儲格式下,平均每個像素占用空間為 8+4+0=12 位。對于一個 M*N 分辨率的圖像來說,空間占用為原來的 12/24,即 M*N*3*(12/24)=M*N*3/2。節(jié)省較多存儲空間,該存儲格式也最常用。
YUV4:1:1 是指每 4 個 Y 采樣,對應 1 個 U 采樣和一個 V 采樣。平均每個像素占用空間為 8+2+2=12 位。圖像空間占用情況同上。這種存儲格式實際使用的非常少。
對于 packed 存儲格式,略。
YV12/I420/YU12/NV12/NV21
YV12/I420/YU12/NV12/NV21 都屬于 YUV 4:2:0。YU12 就是 I420,YV12/I420 也稱為 YUV420P(即平面格式,planar),YV12 與標準模式 I420 的區(qū)別是 UV 順序不同。
YV12 取名來源是 Y 后面緊跟 V(然后是 U),12 表示它位深為 12,也就是一個像素占用空間為 12 位。
在 I420(YU12) 格式中,U 平面緊跟在 Y 平面之后,然后才是 V 平面(即:YUV);但 YV12 則是相反(即:YVU)。大部分視頻解碼器的輸出的原始圖像都是 I420 格式(例如安卓下的圖像通常都是 I420 或 NV21),而多數硬解碼器中使用的都是 NV12 格式(例如 Intel MSDK、NVIDIA 的 cuvid、IOS 硬解碼)。
另一類 YUV420SP, Y 分量平面格式,UV 打包格式,即 NV12。 NV12 與 NV21 類似,U 和 V 交錯排列,不同在于 UV 順序。
可理解如下:
I420: YYYYYYYY UU VV => YUV420P
YV12: YYYYYYYY VV UU => YUV420P
NV12: YYYYYYYY UVUV => YUV420SP
NV21: YYYYYYYY VUVU => YUV420SP
維基百科上有兩張 I420 和 NV12 的兩張圖非常好:
I420 的單幀結構示意圖如下(Planar 方式):
這幅圖的上面一幅可以看出 Y1、Y2、Y7、Y8 共用 U1 和 V1。后面的線性數組為其存儲順序,可以看出 Y、U 和 V 都是順序存儲的,往外寫的時候,先按順序將 Y 分量寫出,然后再根據 U、V 分別將它們依次寫出即可。
NV12 的單幀結構示意圖如下(Planar 方式):
可以看出與 YV12 不同的時,它的 Y 雖然也是順序存儲,但 U、V 卻是交錯存儲的,這種方式存儲在往外寫出時則先直接順序寫出 Y,然后對 UV 分別依次寫出。
Python的實現:將420P轉為jpg
from PIL import Image def yuv420_to_rgb888(width, height, yuv): # function requires both width and height to be multiples of 4 if (width % 4) or (height % 4): raise Exception("width and height must be multiples of 4") rgb_bytes = bytearray(width*height*3) red_index = 0 green_index = 1 blue_index = 2 y_index = 0 for row in range(0,height): u_index = width * height + (row//2)*(width//2) v_index = u_index + (width*height)//4 for column in range(0,width): Y = yuv[y_index] U = yuv[u_index] V = yuv[v_index] C = (Y - 16) * 298 D = U - 128 E = V - 128 R = (C + 409*E + 128) // 256 G = (C - 100*D - 208*E + 128) // 256 B = (C + 516 * D + 128) // 256 R = 255 if (R > 255) else (0 if (R < 0) else R) G = 255 if (G > 255) else (0 if (G < 0) else G) B = 255 if (B > 255) else (0 if (B < 0) else B) rgb_bytes[red_index] = R rgb_bytes[green_index] = G rgb_bytes[blue_index] = B u_index += (column % 2) v_index += (column % 2) y_index += 1 red_index += 3 green_index += 3 blue_index += 3 return rgb_bytes def testConversion(source, dest): print("opening file") f = open(source, "rb") yuv = f.read() f.close() print("read file") rgb_bytes = yuv420_to_rgb888(4208,3120, yuv) # cProfile.runctx('yuv420_to_rgb888(1920,1088, yuv)', {'yuv420_to_rgb888':yuv420_to_rgb888}, {'yuv':yuv}) print("finished conversion. Creating image object") img = Image.frombytes("RGB", (4208,3120), bytes(rgb_bytes)) print("Image object created. Starting to save") img.save(dest, "JPEG") img.close() print("Save completed") testConversion("C:/adb1031/yuveffectout/MV_F_Cap1.yuv", "C:/adb1031/yuveffectout/MV_F_Cap1.jpg") testConversion("C:/adb1031/yuveffectout/MV_F_Cap2.yuv", "C:/adb1031/yuveffectout/MV_F_Cap2.jpg")
Python的實現:將NV21轉為jpg
from PIL import Image def yuv420_to_rgb888(width, height, yuv): # function requires both width and height to be multiples of 4 if (width % 4) or (height % 4): raise Exception("width and height must be multiples of 4") rgb_bytes = bytearray(width*height*3) red_index = 0 green_index = 1 blue_index = 2 y_index = 0 v_index = width * height for row in range(0,height): v_index = width * height + (row//2)*width u_index = v_index + 1 for column in range(0,width): Y = yuv[y_index] #print(y_index) U = yuv[u_index] V = yuv[v_index] C = (Y - 16) * 298 D = U - 128 E = V - 128 R = (C + 409*E + 128) // 256 G = (C - 100*D - 208*E + 128) // 256 B = (C + 516 * D + 128) // 256 R = 255 if (R > 255) else (0 if (R < 0) else R) G = 255 if (G > 255) else (0 if (G < 0) else G) B = 255 if (B > 255) else (0 if (B < 0) else B) rgb_bytes[red_index] = R rgb_bytes[green_index] = G rgb_bytes[blue_index] = B if column==0: v_index = v_index elif column%2==0: v_index = v_index + 2 u_index = v_index + 1 y_index += 1 red_index += 3 green_index += 3 blue_index += 3 return rgb_bytes def testConversion(source, dest): print("opening file") f = open(source, "rb") yuv = f.read() f.close() print("read file") rgb_bytes = yuv420_to_rgb888(1280,720, yuv) # cProfile.runctx('yuv420_to_rgb888(1920,1088, yuv)', {'yuv420_to_rgb888':yuv420_to_rgb888}, {'yuv':yuv}) print("finished conversion. Creating image object") img = Image.frombytes("RGB", (1280,720), bytes(rgb_bytes)) print("Image object created. Starting to save") img.save(dest, "JPEG") img.close() print("Save completed") testConversion("./test/4.yuv", "4.jpg")
以上這篇基于YUV 數據格式詳解及python實現方式就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
python numpy 部分排序 尋找最大的前幾個數的方法
今天小編就為大家分享一篇python numpy 部分排序 尋找最大的前幾個數,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-06-06解決編碼問題:UnicodeDecodeError: 'utf-8' codec
這篇文章主要介紹了快速解決編碼問題:UnicodeDecodeError: 'utf-8' codec can't decod,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-05-05