YUV:
YUV,是一种颜色编码方法。常使用在各个影像处理组件中。 YUV在对照片或影片编码时,考虑到人类的感知能力,允许降低色度的带宽。
YUV是编译true-color颜色空间(color space)的种类,Y'UV, YUV, YCbCr,YPbPr等专有名词都可以称为YUV,彼此有重叠。“Y”表示明亮度(Luminance、Luma),“U”和“V”则是色度、浓度(Chrominance、Chroma),
Y′UV, YUV, YCbCr, YPbPr所指涉的范围,常有混淆或重叠的情况。从历史的演变来说,其中YUV和Y'UV通常用来编码电视的模拟信号,而YCbCr则是用来描述数字的影像信号,适合影片与图片压缩以及传输,例如MPEG、JPEG。 但在现今,YUV通常已经在电脑系统上广泛使用。
与RGB格式(红-绿-蓝)不同,YUV格式用一个称为Y(相当于灰度)的“亮度”分量和两个“色度”分量表示,分别称为U(蓝色投影)和V(红色投影)。
YUV Formats分成两个格式:
紧缩格式(packed formats):将Y、U、V值存储成Macro Pixels数组,和RGB的存放方式类似。
平面格式(planar formats):将Y、U、V的三个分量分别存放在不同的矩阵中。
紧缩格式:
紧缩格式(packed format)中的YUV是混合在一起的,对于YUV4:2:2格式而言,用紧缩格式很合适的,因此就有了UYVY、YUYV等。
UYVY(YUV422)
平面格式:
平面格式(planar formats)是指每Y分量,U分量和V分量都是以独立的平面组织的,也就是说所有的U分量必须在Y分量后面,而V分量在所有的U分量后面,此一格式适用于采样(subsample)。平面格式(planar format)有I420(4:2:0)、YV12、IYUV、 NV21、 NV12等。平面格式较为常用
NV21
NV12
与RGB转换关系
对于同一个观测像素点,在RGB空间和YUV空间可进行了不同角度的表示,两者满足单射线性变换,两者可以通过线性变换公式进行转换,如图1所示
转换公式如下:
Y = 0.299R + 0.587G + 0.114B
U= -0.169R - 0.331G + 0.500B + 128
V= 0.500R - 0.419G - 0.081B + 128
反向从YUV转换到RGB的公式为:
R= Y + 1.402 (V − 128 )
G= Y − 0.344136 (U − 128)− 0.714136 (V − 128)
B= Y + 1.772(U − 128)
YUV转RGB C语音代码:
#include <stdio.h>
#include <stdlib.h>
// YUV到RGB的转换公式
void YUV422_to_RGB888(int width, int height, unsigned char *yuv_buffer, unsigned char *rgb_buffer) {
int y, u, v, r, g, b;
for (int i = 0; i < width * height * 2; i += 4) {
// 获取YUV分量
u = yuv_buffer[i] - 128;
y = yuv_buffer[i + 1];
v = yuv_buffer[i + 2] - 128;
// 第一个像素的RGB转换
r = y + (1.370705 * v);
g = y - (0.698001 * v) - (0.337633 * u);
b = y + (1.732446 * u);
// 确保RGB值在0-255范围内
r = r < 0 ? 0 : r > 255 ? 255 : r;
g = g < 0 ? 0 : g > 255 ? 255 : g;
b = b < 0 ? 0 : b > 255 ? 255 : b;
// 将RGB值写入缓冲区
rgb_buffer[(i / 2) * 3] = r;
rgb_buffer[(i / 2) * 3 + 1] = g;
rgb_buffer[(i / 2) * 3 + 2] = b;
// 获取下一个Y分量
y = yuv_buffer[i + 3];
// 第二个像素的RGB转换
r = y + (1.370705 * v);
g = y - (0.698001 * v) - (0.337633 * u);
b = y + (1.732446 * u);
// 确保RGB值在0-255范围内
r = r < 0 ? 0 : r > 255 ? 255 : r;
g = g < 0 ? 0 : g > 255 ? 255 : g;
b = b < 0 ? 0 : b > 255 ? 255 : b;
// 将RGB值写入缓冲区
rgb_buffer[(i / 2) * 3 + 3] = r;
rgb_buffer[(i / 2) * 3 + 4] = g;
rgb_buffer[(i / 2) * 3 + 5] = b;
}
}
void RGB888_to_YUV422(int width, int height, unsigned char *rgb_buffer, unsigned char *yuv_buffer) {
int y0, u, y1, v;
for (int i = 0; i < width * height * 3; i += 6) {
// 获取RGB分量
unsigned char r0 = rgb_buffer[i];
unsigned char g0 = rgb_buffer[i + 1];
unsigned char b0 = rgb_buffer[i + 2];
unsigned char r1 = rgb_buffer[i + 3];
unsigned char g1 = rgb_buffer[i + 4];
unsigned char b1 = rgb_buffer[i + 5];
// 转换第一个像素的Y分量
y0 = (77 * r0 + 150 * g0 + 29 * b0) >> 8;
// 转换U分量
u = ((-43 * r0 - 85 * g0 + 128 * b0) >> 8) + 128;
// 转换第二个像素的Y分量
y1 = (77 * r1 + 150 * g1 + 29 * b1) >> 8;
// 转换V分量
v = ((128 * r0 - 107 * g0 - 21 * b0) >> 8) + 128;
// 将YUV分量写入缓冲区
yuv_buffer[i / 3 * 2] = u;
yuv_buffer[i / 3 * 2 + 1] = y0;
yuv_buffer[i / 3 * 2 + 2] = v;
yuv_buffer[i / 3 * 2 + 3] = y1;
}
}
int main() {
// 假设yuv_buffer是包含YUV数据的缓冲区
unsigned char *yuv_buffer = malloc(1920 * 1080 * 2); // YUV 4:2:2
// 假设rgb_buffer是用于存储RGB数据的缓冲区
unsigned char *rgb_buffer = malloc(1920 * 1080 * 3); // RGB 8:8:8
// 填充yuv_buffer数据...
// ...
// 转换YUV到RGB
YUV422_to_RGB888(1920, 1080, yuv_buffer, rgb_buffer);
//RGB888_to_YUV422(1920, 1080, rgb_buffer, yuv_buffer);
// 使用rgb_buffer数据...
// ...
// 释放内存
free(yuv_buffer);
free(rgb_buffer);
return 0;
}
yuv422缩放:
void RGB888_to_YUV422(int width, int height, unsigned char *rgb_buffer, unsigned char *yuv_buffer) {
int y0, u, y1, v;
for (int i = 0; i < width * height * 3; i += 6) {
// 获取RGB分量
unsigned char r0 = rgb_buffer[i];
unsigned char g0 = rgb_buffer[i + 1];
unsigned char b0 = rgb_buffer[i + 2];
unsigned char r1 = rgb_buffer[i + 3];
unsigned char g1 = rgb_buffer[i + 4];
unsigned char b1 = rgb_buffer[i + 5];
// 转换第一个像素的Y分量
y0 = (77 * r0 + 150 * g0 + 29 * b0) >> 8;
// 转换U分量
u = ((-43 * r0 - 85 * g0 + 128 * b0) >> 8) + 128;
// 转换第二个像素的Y分量
y1 = (77 * r1 + 150 * g1 + 29 * b1) >> 8;
// 转换V分量
v = ((128 * r0 - 107 * g0 - 21 * b0) >> 8) + 128;
// 将YUV分量写入缓冲区
yuv_buffer[i / 3 * 2] = u;
yuv_buffer[i / 3 * 2 + 1] = y0;
yuv_buffer[i / 3 * 2 + 2] = v;
yuv_buffer[i / 3 * 2 + 3] = y1;
}
}
图像压缩:
一般可以使用libyuv进行压缩,如果项目上存储容量不够可以使用下面比较简单的转换做压缩处理
yuv422(UYVY)
1760*1280转1280*720 由于每两个y分量共用一组uv色差值,所以视为宽880 高1280 转换宽比值为:11:9 高比值:16:9
C:
#include <stdio.h>
#include <cstring>
int readFile(unsigned char* buf, int len,char*name){
FILE* fp = NULL;
fp = fopen(name, "rb+");
if (fp != NULL) {
fread((char*)buf, 1, len, fp);
fclose(fp);
}
}
int writeFile(unsigned char* buf, int len,char*name){
FILE* fp = NULL;
fp = fopen(name, "wb+");
if (fp != NULL) {
fwrite((char*)buf, 1, len, fp);
fclose(fp);
}
}
void RGB888_to_YUV422(int width, int height, unsigned char *rgb_buffer, unsigned char *yuv_buffer) {
int y0, u, y1, v;
for (int i = 0; i < width * height * 3; i += 6) {
// 获取RGB分量
unsigned char r0 = rgb_buffer[i];
unsigned char g0 = rgb_buffer[i + 1];
unsigned char b0 = rgb_buffer[i + 2];
unsigned char r1 = rgb_buffer[i + 3];
unsigned char g1 = rgb_buffer[i + 4];
unsigned char b1 = rgb_buffer[i + 5];
// 转换第一个像素的Y分量
y0 = (77 * r0 + 150 * g0 + 29 * b0) >> 8;
// 转换U分量
u = ((-43 * r0 - 85 * g0 + 128 * b0) >> 8) + 128;
// 转换第二个像素的Y分量
y1 = (77 * r1 + 150 * g1 + 29 * b1) >> 8;
// 转换V分量
v = ((128 * r0 - 107 * g0 - 21 * b0) >> 8) + 128;
// 将YUV分量写入缓冲区
yuv_buffer[i / 3 * 2] = u;
yuv_buffer[i / 3 * 2 + 1] = y0;
yuv_buffer[i / 3 * 2 + 2] = v;
yuv_buffer[i / 3 * 2 + 3] = y1;
}
}
int main(void){
int width = 1706;
int heigh = 1280;
//===========1706 *1279 --> 1706 *1280 ===============
unsigned char *rgbBuf = new unsigned char[1760*1280*3];
unsigned char *yuvBuf = new unsigned char[1760*1280*2];
readFile(rgbBuf,width * 1279 *3,"20240308094827.rgb");
memcpy(rgbBuf+width * 1279 *3,rgbBuf+width * 1278 *3,width*3);
writeFile(rgbBuf,width * heigh *3,"1706x1280.rgb");
//=============================================================
//===========1706 *1280 --> 1760 *1280 ===============
unsigned char *dstBuf = new unsigned char[1760*1280*3];
int dstH = 0;
int dstW = 0;
for(int h=0;h<1280;h++){
for(int w=0;w<1706;w++){
dstBuf[((dstW + dstH*1760)*3)]= rgbBuf[((w+h*width)*3)] ;//r
dstBuf[((dstW + dstH*1760)*3)+1] = rgbBuf[((w+h*width)*3)+1] ;//g
dstBuf[((dstW + dstH*1760)*3)+2] = rgbBuf[((w+h*width)*3)+2] ;//b
dstW++;
if((dstW % 32 == 0) && dstW < 1760){
dstBuf[((dstW + dstH*1760)*3)]= rgbBuf[((w+h*width)*3)] ;//r
dstBuf[((dstW + dstH*1760)*3)+1] = rgbBuf[((w+h*width)*3)+1] ;//g
dstBuf[((dstW + dstH*1760)*3)+2] = rgbBuf[((w+h*width)*3)+2] ;//b
dstW++;
}
}
dstH++;
dstW= 0;
}
writeFile(dstBuf,1760*1280*3,"1760x1280.rgb");
memcpy(rgbBuf,dstBuf,1760*1280*3);
dstW = 0;
RGB888_to_YUV422(1760, 1280, dstBuf, yuvBuf);
writeFile(yuvBuf,1760 *1280 *2,"1760x1280.uyuv");
//===============1760*1280 UYUV --> 1280*720 UYUV=============
dstH = 0;
unsigned char u,y,v,r,g,b;
//高取一半数据
for(int h=0;h<1280;h+=2){
//由于y和y1共用一组uv数据,所以宽为2个y数据为一组像素 ,1760/2 ==880
for(int w=0;w<880;w++){//880 //1280
//判断宽数据比例11取8,进行数据填充
if(w % 11 == 0 || w % 11 == 2 || w % 11 == 3 || w % 11 == 4
|| w % 11 == 6 || w % 11 == 7 || w % 11 == 8 || w % 11 == 10
){
dstBuf[(dstW + dstH*640)*4+0] = yuvBuf[(w + h*880)*4+0];//u数据填充
dstBuf[(dstW + dstH*640)*4+1] = yuvBuf[(w + h*880)*4+1];//y数据填充
dstBuf[(dstW + dstH*640)*4+2] = yuvBuf[(w + h*880)*4+2];//v数据填充
dstBuf[(dstW + dstH*640)*4+3] = yuvBuf[(w + h*880)*4+3];//y1数据填充
dstW++;
}
}
dstH++;
dstW= 0;
//判断高数据比例16比9 ,额外补多一行进行数据填充
if(h % 16 == 14){
for(int w=0;w<880;w++){
//判断宽数据比例11取8,进行数据填充
if(w % 11 == 0 || w % 11 == 2 || w % 11 == 3 || w % 11 == 4
|| w % 11 == 6 || w % 11 == 7 || w % 11 == 8 || w % 11 == 10
){
dstBuf[(dstW + dstH*640)*4+0] = yuvBuf[(w + h*880)*4+0];//u数据填充
dstBuf[(dstW + dstH*640)*4+1] = yuvBuf[(w + h*880)*4+1];//y数据填充
dstBuf[(dstW + dstH*640)*4+2] = yuvBuf[(w + h*880)*4+2];//v数据填充
dstBuf[(dstW + dstH*640)*4+3] = yuvBuf[(w + h*880)*4+3];//y1数据填充
dstW++;
}
}
dstH++;
}
dstW= 0;
}
writeFile(dstBuf,1280 *720 *2,"1280x720.uyuv");
}
图像效果:
测试代码在附近上