基于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 相关文章推荐
Python3.x中自定义比较函数
Apr 24 Python
在Python中操作列表之List.append()方法的使用
May 20 Python
Python3中的真除和Floor除法用法分析
Mar 16 Python
python 基础教程之Map使用方法
Jan 17 Python
Python爬虫实现全国失信被执行人名单查询功能示例
May 03 Python
TensorFlow利用saver保存和提取参数的实例
Jul 26 Python
python如何创建TCP服务端和客户端
Aug 26 Python
Python Tkinter模块 GUI 可视化实例
Nov 20 Python
python 工具 字符串转numpy浮点数组的实现
Mar 14 Python
Pycharm常用快捷键总结及配置方法
Nov 14 Python
详解如何使用Pytest进行自动化测试
Jan 14 Python
python画条形图的具体代码
Apr 20 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
关于页面优化和伪静态
2009/10/11 PHP
解决PHP4.0 和 PHP5.0类构造函数的兼容问题
2013/08/01 PHP
PHP超牛逼无限极分类生成树方法
2015/05/11 PHP
php求今天、昨天、明天时间戳的简单实现方法
2016/07/28 PHP
百度留言本js 大家可以参考下
2009/10/13 Javascript
使用AngularJS编写较为优美的JavaScript代码指南
2015/06/19 Javascript
值得分享和收藏的Bootstrap学习教程
2016/05/12 Javascript
JS实现Ajax的方法分析
2016/12/20 Javascript
BootStrap学习笔记之nav导航栏和面包屑导航
2017/01/03 Javascript
js实现PC端和移动端刮卡效果
2020/03/27 Javascript
jQuery给表格添加分页效果
2017/03/02 Javascript
学习使用Bootstrap栅格系统
2017/05/11 Javascript
react-router中的属性详解
2017/06/01 Javascript
微信小程序实现漂亮的弹窗效果
2020/05/26 Javascript
vue中v-for循环给标签属性赋值的方法
2018/10/18 Javascript
vue-cli3.0+element-ui上传组件el-upload的使用
2018/12/03 Javascript
详解Element-UI中上传的文件前端处理
2019/08/07 Javascript
flexible.js实现移动端rem适配方案
2020/04/07 Javascript
js代码实现轮播图
2020/05/04 Javascript
Vue检测屏幕变化来改变不同的charts样式实例
2020/10/26 Javascript
在Python中使用Mako模版库的简单教程
2015/04/08 Python
Python三级菜单的实例
2017/09/13 Python
Python中format()格式输出全解
2019/04/12 Python
Python 抓取数据存储到Redis中的操作
2020/07/16 Python
详解Html5中video标签那些属性和方法
2019/07/01 HTML / CSS
html5移动端价格输入键盘的实现
2019/09/16 HTML / CSS
Oakley西班牙官方商店:太阳眼镜和男女运动服
2019/04/26 全球购物
泰国最新活动和优惠:Megatix
2020/05/07 全球购物
兼职业务员岗位职责
2014/01/01 职场文书
高中生期末评语
2014/01/28 职场文书
中秋节活动总结
2014/08/29 职场文书
党员查摆问题及整改措施
2014/10/10 职场文书
务虚会发言材料
2014/12/25 职场文书
健康教育主题班会
2015/08/14 职场文书
趣味运动会广播稿
2015/08/19 职场文书
在校大学生才艺比赛策划书怎么写?
2019/08/26 职场文书