基于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字符串切片操作知识详解
Mar 28 Python
Python实现利用最大公约数求三个正整数的最小公倍数示例
Sep 30 Python
Python字符串拼接六种方法介绍
Dec 18 Python
python网络爬虫学习笔记(1)
Apr 09 Python
python3利用venv配置虚拟环境及过程中的小问题小结
Aug 01 Python
Python爬虫框架scrapy实现downloader_middleware设置proxy代理功能示例
Aug 04 Python
Python将主机名转换为IP地址的方法
Aug 14 Python
python基于FTP实现文件传输相关功能代码实例
Sep 28 Python
使用python的turtle函数绘制一个滑稽表情
Feb 28 Python
JAVA SWT事件四种写法实例解析
Jun 05 Python
Python爬虫如何应对Cloudflare邮箱加密
Jun 24 Python
使用SimpleITK读取和保存NIfTI/DICOM文件实例
Jul 01 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
PHP的Yii框架中使用数据库的配置和SQL操作实例教程
2016/03/17 PHP
解决yii2左侧菜单子级无法高亮问题的方法
2016/05/08 PHP
Yii框架日志操作图文与实例详解
2019/09/09 PHP
获取URL地址中的文件名和参数的javascript代码
2009/09/02 Javascript
javascript关于继承的用法汇总
2014/12/20 Javascript
js控制输入框获得和失去焦点时状态显示的方法
2015/01/30 Javascript
jQuery实现的经典竖向伸缩菜单效果代码
2015/09/24 Javascript
jQuery增加与删除table列的方法
2016/03/01 Javascript
javascript创建对象的几种模式介绍
2016/05/06 Javascript
JS遍历对象属性的方法示例
2017/01/10 Javascript
详解微信开发中snsapi_base和snsapi_userinfo及静默授权的实现
2017/03/11 Javascript
webstorm添加vue.js支持的方法教程
2017/07/05 Javascript
vue使用axios实现文件上传进度的实时更新详解
2017/12/20 Javascript
vue中typescript装饰器的使用方法超实用教程
2019/06/17 Javascript
微信小程序wx.request的简单封装
2019/11/13 Javascript
解决VUE项目localhost端口服务器拒绝连接,只能用127.0.0.1的问题
2020/08/14 Javascript
Vue实现图书管理小案例
2020/12/03 Vue.js
[01:03:56]Mineski vs TNC 2018国际邀请赛淘汰赛BO1 8.21
2018/08/22 DOTA
11个并不被常用但对开发非常有帮助的Python库
2015/03/31 Python
使用Python程序抓取新浪在国内的所有IP的教程
2015/05/04 Python
Django框架中处理URLconf中特定的URL的方法
2015/07/20 Python
Flask框架的学习指南之开发环境搭建
2016/11/20 Python
Python中datetime模块参考手册
2017/01/13 Python
Python实现简易版的Web服务器(推荐)
2018/01/29 Python
运用TensorFlow进行简单实现线性回归、梯度下降示例
2018/03/05 Python
ActiveMQ:使用Python访问ActiveMQ的方法
2019/01/30 Python
python机器学习实现决策树
2019/11/11 Python
Django REST framwork的权限验证实例
2020/04/02 Python
CSS3实现大小不一的粒子旋转加载动画
2016/04/21 HTML / CSS
纯CSS3+DIV实现小三角形边框效果的示例代码
2020/08/03 HTML / CSS
一套带网友答案的.NET笔试题
2016/12/06 面试题
Final类有什么特点
2012/04/25 面试题
四群教育工作实施方案
2014/03/26 职场文书
小学安全教育月活动总结
2014/07/07 职场文书
新郎接新娘保证书
2015/05/08 职场文书
elementui的el-popover修改样式不生效的解决
2021/06/30 Javascript