前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >高清YUV极速转码,了解一下?

高清YUV极速转码,了解一下?

作者头像
用户2617681
发布2019-08-08 14:53:15
1.2K0
发布2019-08-08 14:53:15
举报
文章被收录于专栏:秘籍酷秘籍酷

市面上主流摄像头的图像封装格式一般逃不过这三种:JPEG、MJPG和YUV。其中YUV编码既可以与灰度图像兼容,又利用了人眼对亮度和色度的定量优化,使其可以直接跟三原色RGB进行直接互换而到广泛青睐。但YUV与RGB的转码涉及大量浮点运算,对于高分辨率高速摄像头而言,转码对CPU的负担很重,本文来看看如何巧妙化解这个难点。

首先,来引用一段我在GPLE中的叙述:

由于人类的眼睛对亮度的敏感度比颜色要高许多,而且在RGB三原色中对绿色有尤为敏感,利用这个原理,可以把色度信息减少一点,人眼也无法察觉。YUV三个字母中,其中"Y"表示明亮度(Lumina nce或Luma),也就是灰阶值,而"U"和"V"表示的则是色度(Chrominance或Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。我们可以通过减少图像中的红色和蓝色分量来达到减少图像尺寸的目的。在很多技术文档中YUV还会写成YCbCr,Y指的是绿色和亮度,C是Component的首字母,b和r分别是blue和red,从这个角度出发可以认为YUV是RGB的变种。

——《Linux环境编程图文指南》

可见,YUV可以看做是RGB的优化变种。并且更进一步,既然U和V对人类的眼睛不敏感,我们可以针对它们做各种变化,来减少整体图像的尺寸。具体情况我们来一个个看。

第一种:YUV422。后面的数字可以理解为代表YUV分量的比例是4:2:2,其原理是在每个像素中删去一个U或者V分量,然后再在还原的时候用相邻的像素的UV分量填充,一图顶千言:

这样做虽然损失了原先的一部分UV信息,但是人眼对此部分信息不敏感,还原就可以得到很好的效果,照这样的思路,可以对YUV进行更激进的裁剪,比如下面这种压缩率大一点的YUV411:

还有这种更没人性的YUV420:

以上各种YUV格式思路大同小异,都是通过裁剪UV信息来达到缩小图像尺寸的目的,因此不再一个个赘述了。我们以最常见的YUV422为例,来看看从摄像头中捕获这种数据之后,怎么极速转化为RGB。

首先,你得知道YUV与RGB存在以下互换公式:

R = Y + 1.042*(V-128); G = Y - 0.344*(U-128)-0.714*(V-128); B = Y + 1.772*(U-128);

有了以上公式,我们自然可以写出如下代码,来计算每一个YUV像素点的等价RGB值,比如我们有原始YUV422的两个像素为:

那根据公式我们很容易写出对应的两个 RGB 像素点的数值为:

R0 = *Y0 + 1.042*(*V1-128); G0 = *Y0 - 0.344*(*U0-128) - 0.714*(*V1-128); B0 = *Y0 + 1.772*(*U0-128); R1 = *Y1 + 1.042*(*V1-128); G1 = *Y1 - 0.344*(*U0-128) - 0.714*(*V1-128); B1 = *Y1 + 1.772*(*U0-128);

以上算式只是其中两个像素点的计算量,假如摄像头的分辨率是1280×720,那么一帧这样的YUV数据就得进行好几百万次浮点运算,而最普通的摄像头一秒可以产生25-30帧数据,高速摄像头每秒可以产生几百到几千帧数据(激光扑捉器每秒200亿帧了解一下),这还不算转换后的RGB写入显存的时间,这种运算量对于嵌入式系统而言简直就是噩梦,如果CPU不支持浮点运算的话还得转化成整型运算,最终的结果是:

牛逼闪闪的摄像头

烂成渣渣的视频流

这个问题的解决,可以通过优化以上公式来苟延残喘,比如可以将浮点运算变成整数运算:

R0 = *Y0 + 1042*(*V1-128) / 1000; G0 = *Y0 - (344*(*U0-128) - 714*(*V1-128)) / 1000; B0 = *Y0 + 1772*(*U0-128) / 1000; R1 = *Y1 + 1042*(*V1-128) / 1000; G1 = *Y1 - (344*(*U0-128) - 714*(*V1-128)) / 1000; B1 = *Y1 + 1772*(*U0-128) / 1000;

当然这不够刺激,可以进一步将除法变成右移:

R0 = *Y0 + 4268*(*V1-128) >> 12; G0 = *Y0 - (1409*(*U0-128) - 2924*(*V1-128)) >> 12; B0 = *Y0 + 7258*(*U0-128) >> 12; R1 = *Y1 + 4268*(*V1-128) >> 12; G1 = *Y1 - (1409*(*U0-128) - 2924*(*V1-128)) >> 12; B1 = *Y1 + 7258*(*U0-128) >> 12;

虽然这已经大大提高了运算速度,但公式中依然残留了部分挥之不去的乘法运算,踌躇间,想起一句算法届至圣名言:如果要取得时间,就必须要牺牲空间。时间就是执行效率,空间就是运算内存。这条 IT 公理可简称为算法的时空守恒。

怎么个时空互换?简单讲就是作弊:既然那么难算,我就把答案先算好写纸条里,不仅要写纸条里,为了方便作弊还要画个表格(其实就是数组),一五一十白纸黑字写死,要算某个数的时候直接像查字典那样查就行了。请注意,是要把所有的答案全部算出来,作弊就要做得彻底。 比如这两公式:

R = Y + 1.042*(V-128); B = Y + 1.772*(U-128);

其中Y、U、V是从摄像头获取的图像数值,分别保存在一个字节中,他们的取值无非就是 0 - 255之间,不可能有别的取值,这么一来R、B的取值就从他们中产生,总共256×256种可能。将所有的这些可能统统暴力计算出来之后保存在一个叫 R、B 的数组中:

for(int i=0; i<256; i++) { for(int j=0; j<256; j++) { R[i][j] = i + 1.042 * (j-128); R[i][i] = R[i][j] < 0 ? 0 : R[i][j]; R[i][j] = R[i][j] > 255 ? 255 : R[i][j]; B[i][j] = i + 1.772 * (j-128); B[i][i] = B[i][j] < 0 ? 0 : B[i][j]; B[i][j] = B[i][j] > 255 ? 255 : B[i][j]; } }

注意以上代码是在摄像头开启之前就做好了,因此丝毫不需要对浮点公式本身做任何优化,红色部分是保证RGB取值正确。使用完全一样的算法,将具有三个变量的G也暴力计算一下:

for(int i=0; i<256; i++) { for(int j=0; j<256; j++) { for(int k=0; k<256; k++) { G[i][j] = i - 0.344*(j-128) - 0.714*(k-128); G[i][i] = G[i][j] < 0 ? 0 : G[i][j]; G[i][j] = G[i][j] > 255 ? 255 : G[i][j]; } } }

有了这杨的金光闪闪的数组 R、G、B,进行 YUV 转码 RGB 的时候就易如反掌、快如闪电了:

R = R[*Y][*V]; G = G[*Y][*U][*V]; B = B[*Y][*U];

以上操作就是取数组元素,所需时间几乎为零。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-06-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 秘籍酷 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档