基于YUV 数据格式详解及python实现方式


Posted in Python onDecember 09, 2019

YUV 数据格式概览

YUV 的原理是把亮度与色度分离,使用 Y、U、V 分别表示亮度,以及蓝色通道与亮度的差值和红色通道与亮度的差值。其中 Y 信号分量除了表示亮度 (luma) 信号外,还含有较多的绿色通道量,单纯的 Y 分量可以显示出完整的黑白图像。U、V 分量分别表示蓝 (blue)、红 (red) 分量信号,它们只含有色彩 (chrominance/color) 信息,所以 YUV 也称为 YCbCr,C 意思可以理解为 (component 或者 color)。

维基百科上的 RGB 转 YUV 的公式能更好的反应 YUV 与 RGB 的关系,以及为什么称为 YCbCr:

基于YUV 数据格式详解及python实现方式

Y 中含有三元色色信息,且有较多的 G,所以他们一起可以显示出全彩的图像。

很显然我们可以想到是不是会有 YCgCb、YCgCr 等,针对不同的应用场景,也确实有相关应用研究。

如下图,一张从上到下分别为原图、Y、U 和 V:

基于YUV 数据格式详解及python实现方式

采用 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 方式):

基于YUV 数据格式详解及python实现方式

这幅图的上面一幅可以看出 Y1、Y2、Y7、Y8 共用 U1 和 V1。后面的线性数组为其存储顺序,可以看出 Y、U 和 V 都是顺序存储的,往外写的时候,先按顺序将 Y 分量写出,然后再根据 U、V 分别将它们依次写出即可。

NV12 的单帧结构示意图如下(Planar 方式):

基于YUV 数据格式详解及python实现方式

可以看出与 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 相关文章推荐
一个计算身份证号码校验位的Python小程序
Aug 15 Python
详解C++编程中一元运算符的重载
Jan 19 Python
python运行时间的几种方法
Jun 17 Python
Python最火、R极具潜力 2017机器学习调查报告
Dec 11 Python
python中itertools模块zip_longest函数详解
Jun 12 Python
python爬虫 urllib模块反爬虫机制UA详解
Aug 20 Python
树莓派极简安装OpenCv的方法步骤
Oct 10 Python
Python aiohttp百万并发极限测试实例分析
Oct 26 Python
python将时分秒转换成秒的实例
Dec 07 Python
Pytorch .pth权重文件的使用解析
Feb 14 Python
Python用摘要算法生成token及检验token的示例代码
Dec 01 Python
Python爬虫教程之利用正则表达式匹配网页内容
Dec 08 Python
Python编写一个验证码图片数据标注GUI程序附源码
Dec 09 #Python
Python内置方法实现字符串的秘钥加解密(推荐)
Dec 09 #Python
opencv-python 读取图像并转换颜色空间实例
Dec 09 #Python
opencv-python 提取sift特征并匹配的实例
Dec 09 #Python
python 多维高斯分布数据生成方式
Dec 09 #Python
使用python模拟高斯分布例子
Dec 09 #Python
使用python+whoosh实现全文检索
Dec 09 #Python
You might like
一些星际专用术语解释
2020/03/04 星际争霸
php Try Catch异常测试
2009/03/01 PHP
php与php MySQL 之间的关系
2009/07/17 PHP
使用PHP实现微信摇一摇周边红包
2016/01/04 PHP
教你在header中隐藏php的版本信息
2016/08/10 PHP
Laravel使用消息队列需要注意的一些问题
2017/12/13 PHP
php记录搜索引擎爬行记录的实现代码
2018/03/02 PHP
PHP实现基于PDO扩展连接PostgreSQL对象关系数据库示例
2018/03/31 PHP
laravel http 自定义公共验证和响应的方法
2019/09/29 PHP
Laravel框架之解决前端显示图片问题
2019/10/24 PHP
a标签的css样式四个状态
2021/03/09 HTML / CSS
取得父标签
2006/11/14 Javascript
默认让页面的第一个控件选中的javascript代码
2009/12/26 Javascript
jquery对表单操作2
2011/04/06 Javascript
jquery解决客户端跨域访问问题
2015/01/06 Javascript
学习JavaScript编程语言的8张思维导图分享
2015/03/27 Javascript
codeMirror插件使用讲解
2017/01/16 Javascript
d3.js实现立体柱图的方法详解
2017/04/28 Javascript
jQuery实现获取及设置CSS样式操作详解
2018/09/05 jQuery
详解webpack打包时排除其中一个css、js文件或单独打包一个css、js文件(两种方法)
2018/10/26 Javascript
微信小程序实现蓝牙打印
2019/09/23 Javascript
基于Vue+Webpack拆分路由文件实现管理
2020/11/16 Javascript
python中pycurl库的用法实例
2014/09/30 Python
以Flask为例讲解Python的框架的使用方法
2015/04/29 Python
python清除指定目录内所有文件中script的方法
2015/06/30 Python
浅析python中SQLAlchemy排序的一个坑
2017/02/24 Python
Python解压 rar、zip、tar文件的方法
2019/11/19 Python
让你的Python代码实现类型提示功能
2019/11/19 Python
基于python爬取有道翻译过程图解
2020/03/31 Python
安装Anaconda3及使用Jupyter的方法
2020/10/27 Python
美国综合购物商城:UnbeatableSale.com
2018/11/28 全球购物
OLEDBConnection和SQLConnection有什么区别
2013/05/31 面试题
说一下Linux下有关用户和组管理的命令
2014/08/18 面试题
函授药学自我鉴定
2014/02/07 职场文书
实习证明格式范文
2014/10/14 职场文书
《莫泊桑拜师》教学反思
2016/02/22 职场文书