基于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自己的其他优点,例如可以根据需要,采用特定的YUV存储格式,以降低祼码流的空间占用。
YUV存储格式
YUV存储格式有两大类:planar和packed。
对于planar的YUV格式,先连续存储所有像素点的Y,紧接着存储所有像素点的U,随后是所有像素点的V。相当于将YUV拆分成三个平面(plane)存储。
对于packed的YUV格式,每个像素点的Y,U,V是连续交替存储的。
YUV码流又根据不同的采样方式分为YUV4:4:4、YUV4:2:2、YUV4:2:0、YUV4:1:1等存储格式,其中前3种较常见。所谓采样意思就是根据一定的间隔取值。其中的比例是指Y、U、V表示的像素,三者分别占的比值。可以按照如下方式理解,实现存储和扫描与DVD的扫描线有关。
例如:
YUV4:4:4是指每个像素分别有一个Y、一个U和一个V组成,即每4个Y采样,就对应4个Cb和4个Cr采样,也就是一个像素占用8+8+8=24位,这种存储方式图像质量最高,但空间占用也最大,空间占用与RGB存储时一样。对于一个M*N分辨率的图像,该模式下存储空间占用字节数为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个字节。
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。节省较多存储空间,该存储格式也最常用。
YUV4:1:1是指每4个Y采样,对应1个U采样和一个V采样。平均每个像素占用空间为8+2+2=12位。图像空间占用情况同上。这种存储格式实际使用的非常少。
对于packed存储格式,略。
YV12/I420/YU12/NV12/NV21
YV12/I420/YU12/NV12/NV21都属于YUV4:2:0。YU12就是I420,YV12/I420也称为YUV420P(即平面格式,planar),YV12与标准模式I420的区别是UV顺序不同。
YV12取名来源是Y后面紧跟V(然后是U),12表示它位深为12,也就是一个像素占用空间为12位。
在I420(YU12)格式中,U平面紧跟在Y平面之后,然后才是V平面(即:YUV);但YV12则是相反(即:YVU)。大部分视频解码器的输出的原始图像都是I420格式(例如安卓下的图像通常都是I420或NV21),而多数硬解码器中使用的都是NV12格式(例如IntelMSDK、NVIDIA的cuvid、IOS硬解码)。
另一类YUV420SP,Y分量平面格式,UV打包格式,即NV12。NV12与NV21类似,U和V交错排列,不同在于UV顺序。
可理解如下:
I420:YYYYYYYYUUVV=>YUV420P
YV12:YYYYYYYYVVUU=>YUV420P
NV12:YYYYYYYYUVUV=>YUV420SP
NV21:YYYYYYYYVUVU=>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
fromPILimportImage defyuv420_to_rgb888(width,height,yuv): #functionrequiresbothwidthandheighttobemultiplesof4 if(width%4)or(height%4): raiseException("widthandheightmustbemultiplesof4") rgb_bytes=bytearray(width*height*3) red_index=0 green_index=1 blue_index=2 y_index=0 forrowinrange(0,height): u_index=width*height+(row//2)*(width//2) v_index=u_index+(width*height)//4 forcolumninrange(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=255if(R>255)else(0if(R<0)elseR) G=255if(G>255)else(0if(G<0)elseG) B=255if(B>255)else(0if(B<0)elseB) 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 returnrgb_bytes deftestConversion(source,dest): print("openingfile") f=open(source,"rb") yuv=f.read() f.close() print("readfile") 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("finishedconversion.Creatingimageobject") img=Image.frombytes("RGB",(4208,3120),bytes(rgb_bytes)) print("Imageobjectcreated.Startingtosave") img.save(dest,"JPEG") img.close() print("Savecompleted") 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
fromPILimportImage defyuv420_to_rgb888(width,height,yuv): #functionrequiresbothwidthandheighttobemultiplesof4 if(width%4)or(height%4): raiseException("widthandheightmustbemultiplesof4") rgb_bytes=bytearray(width*height*3) red_index=0 green_index=1 blue_index=2 y_index=0 v_index=width*height forrowinrange(0,height): v_index=width*height+(row//2)*width u_index=v_index+1 forcolumninrange(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=255if(R>255)else(0if(R<0)elseR) G=255if(G>255)else(0if(G<0)elseG) B=255if(B>255)else(0if(B<0)elseB) rgb_bytes[red_index]=R rgb_bytes[green_index]=G rgb_bytes[blue_index]=B ifcolumn==0: v_index=v_index elifcolumn%2==0: v_index=v_index+2 u_index=v_index+1 y_index+=1 red_index+=3 green_index+=3 blue_index+=3 returnrgb_bytes deftestConversion(source,dest): print("openingfile") f=open(source,"rb") yuv=f.read() f.close() print("readfile") 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("finishedconversion.Creatingimageobject") img=Image.frombytes("RGB",(1280,720),bytes(rgb_bytes)) print("Imageobjectcreated.Startingtosave") img.save(dest,"JPEG") img.close() print("Savecompleted") testConversion("./test/4.yuv","4.jpg")
以上这篇基于YUV数据格式详解及python实现方式就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。