基于YUV 数据格式详解及python实现方式
作者:angus_17 发布时间:2021-12-03 07:28:59
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 都属于 YUV 4: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 格式(例如 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")
来源:https://blog.csdn.net/angus_17/article/details/84581757
猜你喜欢
- 实例1:获取星期字符串程序读入一个表示星期几的数字(1~7),输出对应的星期字符串名称。例如,输入 3,返回“星期三&a
- 有的时候,我们为了保持网页的美观,需要将较长的文字在一定长度时截断。比如我们希望在列表中显示文章标题的前15个字,那么一个这样的标题:“rs
- 这篇文章主要介绍了通过实例学习Python Excel操作,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要
- object.OpenTextFile(filename[, iomode[, create[, format]]]) 参数 object
- 学习复杂代码的最好方法是简化:(function(win, undefined) { var jQuery = f
- 1.ResNet的创新现在重新稍微系统的介绍一下ResNet网络结构。 ResNet结构首先通过一个卷积层然后有一个池化层,然后通过一系列的
- 他们是如何不让我的Teleport和Webzip工作的?你也可以做得到哦: <%dim UserAgentUser
- ADB是Android SDK中的一个工具, 使用ADB可以直接操作管理Android模拟器或者真实的Andriod设备。ADB主要功能有:
- 首先水仙花数是什么?水仙花数(Narcissistic number)也被称为超完全数字不变数(pluperfect digital inv
- 说到排序,我想起一个故事,大意是说唐僧师徒西游美利坚,孙悟空买了本词典,开始逐条背诵单词。他们第一次下美国馆子的时候,不管服务员推荐什么,孙
- 位运算,赋值状态时异或对应位数1的整形,判断状态则与运算对应位数1的整形。最大用处就是同时判断32位状态,节省存储空间,便于扩展, 
- 最近在学一些基础的算法,发现我的数学功底太差劲了,特别是大学的这一部分,概率论、线性代数、高数等等,这些大学学的我是忘得一干二净(我当时学的
- 使用Python的pillow模块 random 模块随机生成验证码图片,并应用到Django项目中安装pillow$ pip3 insta
- 写一个爬虫首先就是学会设置请求头header,这样才可以伪装成浏览器。下面小编我就来给大家简单分析一下python3怎样构建一个爬虫的请求头
- 井字棋,英文名叫Tic-Tac-Toe,是一种在3*3格子上进行的连珠游戏,和五子棋类似,由于棋盘一般不画边框,格线排成井字故得名。游戏需要
- 一、简介我们在这里采用Python中的matplotlib来实现曲线图形的绘制。matplotlib是著名的python绘图库,它提供了一整
- 前言最近重新再看python的基础知识,感觉自己还是对于这些知识很陌生,需要用的时候还是需要翻书查阅,还是先注重基础吧——我要重新把pyth
- 内容摘要:一堆数据摆面前,数据背后有什么样的事情在发生,这些数据里面暗藏着什么样的用户需求,什么样的商业机会?看懂这些,将为未来产品设计的方
- 使用循环神经网络(RNN)实现影评情感分类作为对循环神经网络的实践,我用循环神经网络做了个影评情感的分类,即判断影评的感 * 彩是正面的,还是
- 在写完前面“模块化”相关的文章后,感觉试图用“模块化”本身去讲什么是“模块化”真是不容易讲得清。相信大家都多多少少能理解什么是“模块化”,但