YUV数据采集存储及部分转换原理

最近利用虹软的算法做了人脸识别项目自然就接触到比较多的关于摄像头数据的问题。
今天主要理解一下YUV数据的原理以及nv21数据和yv12的数据互转的原理。
首先感谢以下作者
https://www.cnblogs.com/cumtchw/p/10224329.html
https://blog.csdn.net/u010842019/article/details/52086103

YUV简介

YUV是一种普遍的编码方式,Y表示亮度(Luminance、Luma),U代表色度(Chrominance)、V代表饱和度(Chroma);YUV格式的编码的诞生有效地兼容了黑白电视和彩色电视。相对于较为平常的RGB三通道图像,YUV格式编码的图像视频文件在传输中占据较小的频宽
YUV的采样方式

YUV采样方式

主要描述像素Y、U、V分量采样比例,即表达每个像素时,Y、U、V分量的数目,通常有三种方式:YUV4:4:4,YUV4:2:2,YUV4:2:0。

YUV4:4:4采样,每一个Y对应一组UV分量。

YUV4:2:2采样,每两个Y共用一组UV分量。

YUV4:2:0采样,每四个Y共用一组UV分量。

用图直观地表示采集的方式,以黑点表示采样该像点的Y分量,以空心圆圈表示采用该像素点的UV分量。


image.png

这里重点讲一下yuv420的格式的码流存储方式
首先举一个例子:将摄像头传过来的原始数据 yv12转nv21数据。
先看下网上的例子

class CameraUtil {
    companion object {
        val intance by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
            CameraUtil()
        }
    }
    // YV12 To NV21
    fun YV12toNV21(yv12: ByteArray, nv21: ByteArray, width: Int, height: Int) {
        val frameSize = width * height
        val qFrameSize = frameSize / 4
        val tempFrameSize = frameSize * 5 / 4

        System.arraycopy(yv12, 0, nv21, 0, frameSize) // Y

        for (i in 0 until qFrameSize) {
            nv21[frameSize + i * 2] = yv12[frameSize + i] // Cb (U)
            nv21[frameSize + i * 2 + 1] = yv12[tempFrameSize + i] // Cr (V)
        }
    }
    
}

看完一脸懵逼,这什么 /4,什么又*5/4。底下这公式又是怎么来的。
好吧不管三七二十一能用就好,于是发挥了你强大的CV大法完成功能需求。
然而作为程序员的我们千万不能对算法知其然而不知其所以然

我们好好总结下原理,首先通过yuv420的采集方式我们知道一个uv数据对应了四个y
然后我们看一下存储的几个概念
打包格式(packed)和平面格式(planar)

打包格式是指将YUV保存在一个数组里面,然后YUV交叉存放.

平面格式是指将YUV分量分别保存在三个不同的数组中.

对于yuv420 它是平面模式和半平面模式(可以归为平面模式)所以我们可以看到2个名词
yuv420p和yuv420sp就是分别对应了2种不同的存储方式
其中半平面模式是先保存Y分量,然后UV交叉保存。

yuv420p

image.png

yuv420sp

image.png

图片转至:https://www.cnblogs.com/cumtchw/p/10224329.html

yv12属于yuv420p,nv21属于yuv420sp
有存储规则可以得出 y分量就是width*height,重点是u和v的转换,其实本质就是将原来的数组某些位置转换成其他值而已

 // YV12 To NV21
    fun YV12toNV21(yv12: ByteArray, nv21: ByteArray, width: Int, height: Int) {
        val frameSize = width * height
        val qFrameSize = frameSize / 4
        val tempFrameSize = frameSize * 5 / 4

        System.arraycopy(yv12, 0, nv21, 0, frameSize) // Y

        for (i in 0 until qFrameSize) {
            nv21[frameSize + i * 2] = yv12[frameSize + i] // Cb (U)
            nv21[frameSize + i * 2 + 1] = yv12[tempFrameSize + i] // Cr (V)
        }
    }
    

u分量
首先y保存完后指针的位置已经是指向了width * height 即 frameSize的位置,而U和V的分量的总量是y的四分之一(YUV420采样方式是四个Y对应一个UV)即qFrameSize = frameSize / 4 ,其次要从420p中提取u分量存储到420sp中的相应位置。由于420p的u分量是连续存储的 所以就是[frameSize+i],而420sp是UV交叉存储的,u的位置是0,2,4.....所以就是[frameSize+2*i]
即 nv21[frameSize + i * 2] = yv12[frameSize + i]

v分量
理解了u分量的存储,v分量其实也是差不多的道理。由于存储好了u分量,指针的位置起始位置就是width * height + width * height/4即tempFrameSize=frameSize * 5 / 4。所以420p提取的位置是[tempFrameSize + i],420sp的v分量是1,3,5,7....即[frameSize + i * 2 + 1]
即 nv21[frameSize + i * 2 + 1] = yv12[tempFrameSize + i]

弄懂了概念,我们也就很容易的将nv21数据转换成yv12了

  fun NV21toYV12(input: ByteArray, output: ByteArray, width: Int, height: Int) {
        val frameSize = width * height
        val qFrameSize = frameSize / 4
        val tempFrameSize = frameSize * 5 / 4
        System.arraycopy(input, 0, output, 0, frameSize) // Y
        for (i in 0 until qFrameSize) {
            output[frameSize + i] = input[frameSize + i * 2]// Cb (U)
            output[tempFrameSize + i] = input[frameSize + i * 2 + 1]// Cr (V)
        }
    }

验证一下
创建一个页面左半边是正常的surfaceview,右半边是nv21数据转成yv12然后通过纹理渲染(OpenGL)的方式将yv12数据渲染到jfGLSurfaceView上的view.

override fun onPreview(data: ByteArray?, cameraSize: Point?) {
                    val outData = ByteArray(data!!.size)

                    CameraUtil.intance.NV21toYV12(data, outData, previewWidth, previewHeight)


                    val frameSize = previewWidth * previewHeight
                    val qFrameSize = frameSize / 4
                    val tempFrameSize = frameSize * 5 / 4

                    val y = ByteArray(frameSize)
                    val u = ByteArray(qFrameSize)
                    val v = ByteArray(qFrameSize)
                    System.arraycopy(outData, 0, y, 0, frameSize) // Y
                    System.arraycopy(outData, frameSize, u, 0, qFrameSize) // u
                    System.arraycopy(outData, frameSize+qFrameSize, v, 0, qFrameSize) // v
                    
                    jfGLSurfaceView.feedYuvData(previewWidth,previewHeight,y,v,u)
                }
device-2020-05-14-182403.png

可以看到两个view上都有相同的预览界面(因为同一个通道无法同时打开两个摄像头),说明转换成功。
如果你修改了u、v分量的数据可能还有意想不到的效果


device-2020-05-14-184753.png

不仅如此,通过弄懂原理我们可以弄懂很多问题,比如 一帧分辨率是1280 * 720的nv21数据他的存储大小是多少呢?
大小 = 三个分量的大小,及y = width * height, u = v =y/4 。y+u+v = width*height *1.5 = 1382400.

以上只是yuv数据几个概念的理解,真正的知识远不止这些。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 227,123评论 6 528
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 97,850评论 3 412
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 174,839评论 0 373
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 62,389评论 1 308
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 71,183评论 6 405
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 54,717评论 1 320
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 42,797评论 3 436
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 41,951评论 0 285
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 48,459评论 1 330
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 40,431评论 3 354
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 42,573评论 1 365
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 38,123评论 5 355
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 43,823评论 3 344
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,220评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 35,475评论 1 281
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 51,155评论 3 387
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 47,560评论 2 370