深入理解 YUV 颜色空间:从原理到 Android 视频渲染

news2025/4/2 16:01:47

在视频处理和图像渲染领域,YUV 颜色空间被广泛用于压缩和传输视频数据。然而,在实际开发过程中,很多开发者会遇到 YUV 颜色偏色 的问题,例如 画面整体偏绿。这通常与 U、V 分量的取值有关。那么,YUV 颜色是如何转换为 RGB 的?为什么 U/V 分量的错误会导致颜色偏移?在 Android 设备上如何正确渲染 YUV?

本文将带你深入理解 YUV 颜色空间,并解析其在 Android 开发中的应用。


1. 为什么要使用 YUV?

在计算机图像处理中,我们最常见的颜色空间是 RGB(红、绿、蓝),但 YUV 颜色空间更适用于视频压缩和传输,原因包括:

  • 人眼对亮度 (Luminance) 更敏感,对色度 (Chrominance) 较不敏感
  • YUV 颜色空间允许色度子采样(Chroma Subsampling),减少数据量,提高压缩效率
  • 视频格式(如 MPEG、H.264、H.265)广泛使用 YUV 以优化带宽和存储成本

2. YUV 颜色空间基础

YUV 颜色空间主要由三个分量组成:

  • Y(亮度,Luminance):表示像素的亮度信息,取值范围通常是 [0, 255]
  • U(蓝色色度,Chrominance blue):表示颜色的蓝色分量偏移,取值范围通常是 [0, 255],中性值为 128
  • V(红色色度,Chrominance red):表示颜色的红色分量偏移,取值范围通常是 [0, 255],中性值为 128

重要概念:

  • 当 U = 128, V = 128 时,表示没有色彩偏移,即灰度图像。
  • U、V 偏离 128 时,画面会偏向不同颜色。

常见的 YUV 采样格式

YUV 数据通常以不同的方式存储,常见的格式包括:

  • YUV420 (NV12, NV21, I420):色度分量的分辨率是亮度分量的一半。
  • YUV422:色度水平采样减半,但垂直方向采样保持完整。
  • YUV444:无色度子采样,U/V 和 Y 具有相同分辨率。

YUV420 是 Android 设备摄像头常见的输出格式,如 NV21。


3. YUV 到 RGB 颜色转换

在渲染 YUV 图像时,我们需要将其转换为 RGB 格式,以便在屏幕上正确显示。

YUV 转 RGB 公式(BT.601 标准):

R = Y + 1.402 × (V - 128)
G = Y - 0.344 × (U - 128) - 0.714 × (V - 128)
B = Y + 1.772 × (U - 128)

分析:

  • U - 128V - 128 决定了颜色偏移方向。
  • U = 0, V = 0 时,转换公式导致 G 分量大幅提升,而 R、B 下降,画面会出现 明显的绿色偏色
  • 正确的 U/V 取值应在 128 附近,否则颜色失真。

4. 为什么摄像头渲染 YUV 会出现绿色?

如果你在 Android 设备上直接渲染摄像头的 YUV 视频流,可能会发现画面出现明显的绿色偏色。常见原因包括:

  1. U/V 分量错误:

    • 摄像头数据可能没有正确初始化 U/V 分量,导致它们被误设为 0。
    • 在 NV21/NV12 这种格式中,U/V 是交错存储的,解析错误可能导致 UV 变为 0。
  2. 颜色转换问题:

    • 颜色转换时如果 UV 偏离 128,可能会导致 G 颜色过度增强。
    • 计算公式中 U = 0, V = 0 计算出的 G 值较大,使画面偏绿。

解决方案:

  • 确保 UV 分量初始化正确,避免 U/V 直接被置 0。
  • 检查 YUV 转 RGB 公式,确保转换时正确解析了 U/V 分量。

5. 如何在 Android 上正确渲染 YUV?

在 Android 中,我们通常使用 SurfaceView + MediaCodecOpenGL ES 来渲染 YUV 视频。

方案 1:使用 YUV to RGB 直接转换

可以使用 ScriptIntrinsicYuvToRGB 进行快速转换:

val rs = RenderScript.create(context)
val script = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs))
script.setInput(yuvAllocation)
script.forEach(rgbAllocation)

方案 2:使用 OpenGL ES 进行 YUV 渲染

如果你需要高效渲染 YUV 数据,可以使用 OpenGL ES 纹理,将 Y、U、V 分量分别绑定到 GL_LUMINANCE 纹理。

示例代码:

vec3 yuv;
yuv.x = texture2D(y_texture, texCoord).r;
yuv.y = texture2D(u_texture, texCoord).r - 0.5;
yuv.z = texture2D(v_texture, texCoord).r - 0.5;

vec3 rgb = mat3( 1,       1,       1,
                 0,  -0.344,  1.772,
                 1.402, -0.714,  0) * yuv;

6. 总结

  • YUV 颜色空间适用于视频压缩,U/V 分量的正确取值对颜色显示至关重要。
  • YUV 转 RGB 需要正确的转换公式,否则可能导致画面偏色(如绿色)。
  • 在 Android 开发中,可使用 RenderScriptOpenGL ES 进行 YUV 渲染。
  • 调试 YUV 渲染时,务必检查 U/V 分量,确保它们不是 0,而是接近 128

如果你遇到 Android 设备摄像头输出偏绿色的问题,可以先检查 YUV 数据的 UV 分量,并尝试调整颜色转换公式。

7. 附录:理解YUV转RGB举例

我们用一个 简单的 YUV 420 示例,手动计算 YUV → RGB,然后用 Kotlin 代码 还原它。

7.1 假设我们有一个 2x2 的 YUV 420 数据

我们先定义一个 简单的 2×2 像素的 YUV 420 数据

Y  Y  
Y  Y  
U  V  

每个像素都有 Y(亮度),但所有像素 共享 U/V(色度)

假设:

Y1 = 100, Y2 = 150
Y3 = 200, Y4 = 250
U = 128, V = 128  (中性颜色)

这个数据对应的 YUV 420 格式 字节数组

val yuv420 = byteArrayOf(
    100, 150,   // Y 分量
    200, 250,   // Y 分量
    128, 128    // UV 分量(2x2 像素共用)
)

7.2 使用 YUV 转换公式 计算 RGB

YUV 420 转 RGB 的标准公式:
[
R = Y + 1.402 \times (V - 128)
]
[
G = Y - 0.344 \times (U - 128) - 0.714 \times (V - 128)
]
[
B = Y + 1.772 \times (U - 128)
]

因为 U = 128, V = 128,所以:
[
(V - 128) = 0, \quad (U - 128) = 0
]
代入公式:
[
R = Y
]
[
G = Y
]
[
B = Y
]
所以,这个 YUV 数据转换后的 RGB 颜色是:

(Y1=100) → (100,100,100)  灰色
(Y2=150) → (150,150,150)  灰色
(Y3=200) → (200,200,200)  灰色
(Y4=250) → (250,250,250)  灰色

结论:当 U=128, V=128 时,所有颜色都是 灰色(R=G=B)。


7.3 用 Kotlin 代码 实现 YUV 420 转 RGB

fun yuvToRgb(y: Int, u: Int, v: Int): Triple<Int, Int, Int> {
    val c = y
    val d = u - 128
    val e = v - 128

    val r = (c + 1.402 * e).toInt().coerceIn(0, 255)
    val g = (c - 0.344 * d - 0.714 * e).toInt().coerceIn(0, 255)
    val b = (c + 1.772 * d).toInt().coerceIn(0, 255)

    return Triple(r, g, b)
}

fun main() {
    val yuv420 = byteArrayOf(
        100, 150,  
        200, 250,  
        128, 128   
    )

    val y1 = yuv420[0].toInt() and 0xFF
    val y2 = yuv420[1].toInt() and 0xFF
    val y3 = yuv420[2].toInt() and 0xFF
    val y4 = yuv420[3].toInt() and 0xFF
    val u = yuv420[4].toInt() and 0xFF
    val v = yuv420[5].toInt() and 0xFF

    println("Pixel 1 (Y1=100): ${yuvToRgb(y1, u, v)}")
    println("Pixel 2 (Y2=150): ${yuvToRgb(y2, u, v)}")
    println("Pixel 3 (Y3=200): ${yuvToRgb(y3, u, v)}")
    println("Pixel 4 (Y4=250): ${yuvToRgb(y4, u, v)}")
}

运行结果

Pixel 1 (Y1=100): (100, 100, 100)
Pixel 2 (Y2=150): (150, 150, 150)
Pixel 3 (Y3=200): (200, 200, 200)
Pixel 4 (Y4=250): (250, 250, 250)

7.4 结论

  1. U = 128, V = 128 代表 无色(中性),转换后 R=G=B=Y,所以变成 灰度图
  2. Y 影响亮度,所以 Y1=100 是深灰色,Y4=250 是浅灰色
  3. 如果 U/V 不是 128,颜色就会偏向某种色彩(如蓝、红、绿等)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2325944.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

全长约8.3公里!宁波象山港跨海大桥南中塔柱云端合龙

快科技3月31日消息&#xff0c;据报道&#xff0c;由中国交建二航局承建的宁波象山港跨海大桥顺利完成南中塔柱合龙施工&#xff0c;标志着这一重大交通工程取得阶段性突破。 这座连接宁波鄞州区与象山县的跨海通道全长8.3公里&#xff0c;其标志性的南主塔采用创新"钻石…

使用 2 端口探头测量 40 uOhm(2000 安培)PDN 的挑战 – 需要多少 CMRR?

部分 1 / 3 本文是 3 部分系列的第一部分&#xff1a; 第 2 部分 - 测量结果&#xff01; 第 3 部分 - 使用另一台 VNA 的测量结果 介绍 我们大多数人都知道 2 端口测量中的接地回路。我们大多数人也都知道&#xff0c;我们需要引入接地回路隔离器来纠正错误。如果没有&…

蓝桥杯——统计子矩阵

解法&#xff1a;二维前缀和双指针 代码&#xff1a; #include <iostream> using namespace std; typedef long long ll; ll prefix[505][505], a[250010]; int main() {ll n, m, k, ans 0; cin >> n >> m >> k;for(int i 1; i < n; i)for(int …

吾爱破解安卓逆向学习笔记(4p)

学习目标&#xff0c;了解安卓四大组件&#xff0c;activity生命周期&#xff0c;同时了解去除部分广告和更新提示。 广告类型 1.启动页广告 2.更新广告 3.横幅广告 安卓四大组件 组件描述Activity(活动)在应用中的一个Activity可以用来表示一个界面&#xff0c;意思可以…

stm32第十天外部中断和NVIC讲解

一&#xff1a;外部中断基础知识 1.STM32外部中断框架 中断的概念&#xff1a;在主程序运行过程中&#xff0c;出现了特点的中断触发条件&#xff0c;使得CPU暂停当前正在运行的程序&#xff0c;转而去处理中断程序&#xff0c;处理完成后又返回原来被暂停的位置继续运行 1&…

26考研——线性表_ 线性表的链式表示_单链表(2)

408答疑 文章目录 三、 线性表的链式表示单链表概念单链表的结构头结点 单链表上基本操作的实现单链表的初始化带头结点和不带头结点的初始化操作注意 求表长操作按序号查找结点按值查找表结点插入结点操作扩展&#xff1a;对某一结点进行前插操作 删除结点操作扩展&#xff1a…

MATLAB 控制系统设计与仿真 - 31

二次型最优控制 考虑到系统如果以状态空间方程的形式给出&#xff0c;其性能指标为&#xff1a; 其中F,Q,R是有设计者事先选定。线性二次最优控制问题简称LQ(Linear Quadractic)问题,就是寻找一个控制,使得系统沿着由指定初态出发的相应轨迹,其性能指标J取得最小值。 LQ问题分…

蓝桥杯15届JAVA_A组

将所有1x1转化为2x2 即1x1的方块➗4 然后计算平方数 记得-1 2 import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter;public class Main{static BufferedReader in new BufferedReader(new In…

deepseek v3 0324实现工作流编辑器

HTML 工作流编辑器 以下是一个简单的工作流编辑器的HTML实现&#xff0c;包含基本的拖拽节点、连接线和可视化编辑功能&#xff1a; <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewp…

20250331-智谱-沉思

背景 收到GLM沉思的消息&#xff0c;立马试用下。感觉真的太及时了。 &#xff08;背景&#xff1a;为了客户的需求“AI辅助写作”实验了2款开源workflow&#xff0c;2款在线workflow&#xff0c;好几款多智能体框架后&#xff0c;心中无底之际。。。&#xff09; 1. GLM(开启…

Java EE(17)——网络原理——IP数据报结构IP协议解析(简述)

一.IP数据报结构 (1)版本&#xff1a;指明协议的版本&#xff0c;IPv4就是4&#xff0c;IPv6就是6 (2)首部长度&#xff1a;单位是4字节&#xff0c;表示IP报头的长度范围是20~60字节 (3)8位区分服务&#xff1a;实际上只有4位TOS有效&#xff0c;分别是最小延时&#xff0c;最…

26考研|高等代数:线性空间

线性空间这一章在整个高等代数学习过程中是非常精华的部分&#xff0c;在学习这一章的过程中会有部分的概念较为抽象&#xff0c;一定要抓紧抓牢对于概念的理解&#xff0c;反复阅读与感受&#xff0c;同时也可以根据已知的解析几何中介绍的二维空间或者三维空间进行类推比较&a…

【Linux】进程间通信(IPC)-- 无名管道、命名管道

IPC机制 实现进程间通信 在多个进程间传输数据或共享信息的机制。 数据交换&#xff0c;共享资源&#xff0c;进程同步&#xff0c;消息传递。 IPC实现原理&#xff1a;通信进程能够访问相同的内存区域。 方法&#xff1a; 管道&#xff1a;无名管道pipe、命名管道FIFO S…

每日一题-力扣-2278. 字母在字符串中的百分比 0331

字母在字符串中的百分比求解方案 | 力扣 2278 题解 问题描述 给定一个字符串 s 和一个字母 letter&#xff0c;我们需要计算 letter 在 s 中出现的百分比&#xff0c;并将结果向下取整。例如&#xff0c;如果字符串是 "foobar"&#xff0c;字母是 "o"&…

关于CodeJava的学习笔记——11

一、GUI 1、最简单的GUI 只有一个按钮的GUI import java.awt.*; import javax.swing.*; public class SimpleGUI{JFrame frame;Button bt;public SimpleGUI(){frame new JFrame("标题栏内容");bt new Button("点我啊");frame.add(bt);frame.setSize(8…

首个物业plus系列展 2025上海国际智慧物业博览会开幕

AI赋能服务升级&#xff01;首个“物业plus”系列展 2025上海国际智慧物业博览会盛大开幕 3月31日&#xff0c;2025上海国际智慧物业博览会&#xff08;简称“上海物博会”&#xff09;在上海新国际博览中心N4馆隆重开幕。本届展会由广州旭杨国际展览有限公司主办&#xff0c…

rk3586开发版新增系统调用(Android13)

一、前言 最近想学一下kernel和hal,所以买了一块板子,带了个摄像头和屏幕,1100,学习投资了。这个Android内核定一个系统调用感觉是真的麻烦&#xff0c;主要是有一层bionic C&#xff0c;一开始不熟悉的时候还是花了点时间去配置。 二、kernel修改 include/uapi/asm-generic…

OCR第三个方案:PP-OCRv4的初步探索

一、PP-OCR历史简要回顾 先请出PP-OCR官网&#xff0c;理解上有出入的&#xff0c;以官网为准。 1.1 PP-OCR系列历史 PP-OCRv1&#xff08;2020&#xff09;&#xff1a;首创3.5M超轻量模型&#xff0c;奠定两阶段架构基础&#xff08;检测方向分类识别&#xff09;PP-OCRv2…

ICLR 2025 Spotlight:让机器人实现「自主进化」,蚂蚁数科、清华提出具身协同框架 BodyGen

最近&#xff0c;全球 AI 和机器学习顶会 ICLR 2025 公布了论文录取结果&#xff1a;由蚂蚁数科与清华大学联合团队提出的全新具身协同框架 BodyGen 成功入选 Spotlight&#xff08;聚光灯/特别关注&#xff09;论文。 论文出自蚂蚁数科与清华大学兴军亮老师团队合作的科研项目…

第十九章:Python-pyttsx3 库实现文本转语音功能

前言 在开发语音交互应用或需要文本转语音功能的项目时&#xff0c;pyttsx3 是一个非常实用的 Python 库。它支持离线语音合成&#xff0c;无需联网即可将文本转换为语音。本文将详细介绍 pyttsx3 的功能、用法以及常见问题的解决方法&#xff0c;并通过示例代码帮助你快速上手…