雷神系列文章
[总结]视音频编解码技术零基础学习方法
RGB、YUV像素数据处理
YUV格式
YUV图解 (YUV444, YUV422, YUV420, YV12, NV12, NV21)
YUV原始格式
YUV、YCbCr、YPbPr
Y : 表示明亮度(Luminance或Luma),也就是灰度值;
U V: 表示的则是色度(Chrominance或Chroma),作用是描述影像色彩,饱和度,用于指定像素的颜色。
YUV存储格式 planar、Packet
对于planar的YUV格式,即平面模式 先连续存储所有像素点的Y,紧接着存储所有像素点的U,随后是所有像素点的V。形如 YYYY UUUU VVVV 4个像素的YUV444P
对于packed的YUV格式,即数据包模式, 每个像素点的Y,U,V是连续交叉存储的。 形如 YUV YUV YUV YUV 四个像素。
YUV444,YUV422,YUV420,NV12,NV21
YUV444
YUV444,这种格式占用空间最大,每个像素点有一个Y分量+一个U分量+一个V分量所以和rgb一样每个像素点占用3个字节!
但是根据UV存储顺序不一样,又有两种不同的格式。
形如:
YYYY UUUU VVVV
YYYY VVVV UUUU
YUV422
YUV422 每两个Y分量公用一个UV分量,所以一个像素占用两个字节,根据存储顺序不同又分为四种不同的格式。
还有一种变态的V210格式,好像是苹果搞出来的。
YUV420
YUV420 每四个y分量公用一个UV分量,所以每个像素点占用1.5个字节空间,根据存储顺序不一样又分为四个不同的类型。
形如:
I420: YYYYYYYY UU VV =>YUV420P
YV12: YYYYYYYY VV UU =>YUV420P
NV12: YYYYYYYY UVUV =>YUV420SP
NV21: YYYYYYYY VUVU =>YUV420SP
YUV数据常见处理
(1) 分离YUV420P像素数据中的Y、U、V分量
主要思路
就是读取平面格式的yuv数据,
前widthheight 为Y分量数据;
从widthheight,到widthheight5/4,是U分量数据;
从widthheight1/4,到widthheight3/2,是V分量数据;
分别存为三个文件output_420_y,output_420_u,output_420_v文件。
具体实现
int simplest_yuv420_split(char *url, int w, int h,int num){
FILE *fp=fopen(url,"rb+");
FILE *fp1=fopen("output_420_y.y","wb+");
FILE *fp2=fopen("output_420_u.y","wb+");
FILE *fp3=fopen("output_420_v.y","wb+");
unsigned char *pic=(unsigned char *)malloc(w*h*3/2);
for(int i=0;i<num;i++){
fread(pic,1,w*h*3/2,fp);
//Y
fwrite(pic,1,w*h,fp1);
//U
fwrite(pic+w*h,1,w*h/4,fp2);
//V
fwrite(pic+w*h*5/4,1,w*h/4,fp3);
}
free(pic);
fclose(fp);
fclose(fp1);
fclose(fp2);
fclose(fp3);
return 0;
}
(2)分离YUV444P像素数据中的Y、U、V分量
主要思路
同理,yuv444p就是比例是1:1:1;
文件y,u,v区间分别是[0,widhei],[widhei,widhei2],[widhei2,widhei3].
(3) 将YUV420P像素数据去掉颜色(变成灰度图)
主要思路
去掉颜色uv,
如果想把YUV格式像素数据变成灰度图像,只需要将U、V分量设置成128。
(4) 将YUV420P像素数据的亮度减半
主要思路
可以通过将YUV数据中的亮度分量Y的数值减半的方法,降低图像的亮度。
如果打算将图像的亮度减半,只要将图像的每个像素的Y值取出来分别进行除以2的工作就可以了。图像的每个Y值占用1 Byte,取值范围是0至255,对应C语言中的unsigned char数据类型。
(5)将YUV420P像素数据的周围加上边框
主要思路
边框区域 设置为255, w在[0,border] && [w-border,w],h在[0,border]&&[h-border,h]区域为边框区域
(6) 生成YUV420P格式的灰阶测试图
主要思路
根据y的范围,和色带的数量,确定每个色带的亮度值,y = ymin+n*(ymax-ymin)/num
然后重新设置yuv图片值,uv=128,y根据色带设置y值。
(7)计算两个YUV420P像素数据的PSNR
PSNR通常用于质量评价,就是计算受损图像与原始图像之间的差别,以此来评价受损图像的质量。
主要思路
先算出mse sum+= pow(pic1[j]-pic2[j],2), mse = sum/M*N;
对于8bit量化的像素数据来说,PSNR的计算公式如下所示。
上述公式中mse的计算公式如下所示。
PSNR取值通常情况下都在20-50的范围内,取值越高,代表两张图像越接近,反映出受损图像质量越好。
RGB格式
参考 YUV数据格式与RGB数据格式
RGB,其实就是R(red:红色)+G(green:绿色)+B(blue:蓝色);像彩色电视或者计算机显示屏都是通过这三种颜色按照不同比例混合叠加而构成图像而显示的。红色、绿色和蓝色又称为三基色。之所以称为‘基色’是因为这三种颜色按照不同比例混合后几乎可以变成人类示例所能感知的所有颜色。
RGB555、RGB565、RGB24(RGB888)和RGB32
RGB555(高彩色)
RGB555是一种16位的RGB格式,R、G、B分量都用5位来表示,剩下的一位不用,存储格式如下图:
假设计算机中存储某一个像素点的变量为color, 数据类型为short.,那么则有:
R = color & 0x7C00, (获取高字节的5个bit)
G = color & 0x03E0, (获取中间5个bit)
B = color & 0x001F, (获取低字节5个bit)
RGB565
RGB565同样是一种16位的RGB格式,R和B分量用5位来表示,G分量用6位标志。存储格式如下图:
假设计算机中存储某一个像素点的变量为color, 数据类型为short, 那么则有:
R = color & 0xF800, (获取高字节的5个bit)
G = color & 0x07E0, (获取中间6个bit)
B = color & 0x001F, (获取低字节5个bit)
RGB24(真彩色)
RGB24是一种24位的RGB格式,R、G、B分量都用8位来表示,每位取值范围都为0-255。存储格式如下图:
假设计算机中存储某一个像素点的变量为color, 数据类型为int, 那么则有:
R = color & 0x000000FF,
G = color & 0x0000FF00,
B = color & 0x00FF0000,
RGB32(真彩色)
RGB32使用32位来表示一个像素,RGB分量各用去8位,剩下的8位用作Alpha通道或者不用。Alpha通道是一个8位的灰度通道,该通道用256级灰度来记录图像中的透明度信息,定义透明、不透明和半透明区域,其中白表示不透明,黑表示透明,灰表示半透明。其结构如下:
假设计算机中存储某一个像素点的变量为color, 数据类型为int,那么则有:
a、低8位保留
R = color & 0x0000FF00
G = color & 0x00FF0000,
B = color & 0xFF000000,
b、低8位为ALPHA值
R = color & 0x0000FF00,
G = color & 0x00FF0000,
B = color & 0xFF000000,
A = color & 0x000000FF,
RGB数据常见处理
(8) 分离RGB24像素数据中的R、G、B分量
与YUV420P三个分量分开存储不同,RGB24格式的每个像素的三个分量是连续存储的。一帧宽高分别为w、h的RGB24图像一共占用wh3 Byte的存储空间。RGB24格式规定首先存储第一个像素的R、G、B,然后存储第二个像素的R、G、B…以此类推。类似于YUV420P的存储方式称为Planar方式,而类似于RGB24的存储方式称为Packed方式。
思路
RGB24是8,8,8,一个像素3个字节,按BBBBBBBB GGGGGGGG RRRRRRRR 排列的 属于Packet格式。
读取3wh, 3个字节一读取,然后分别写在三个分别存储 R,G,B的文件里。最后用yuvplayer设置500*500分别打开r,g,b文件 会发现
原图
R数据图像如下所示。
G数据图像如下所示。
B数据图像如下所示。
(9) 将RGB24格式像素数据封装为BMP图像
BMP文件组成 |
---|
BITMAP_FILE_HEADER |
BITMAP_INFO_HEADER |
RGB像素数据 |
typedef struct tagBITMAPFILEHEADER
{
unsigned short int bfType; //位图文件的类型,必须为BM
unsigned long bfSize; //文件大小,以字节为单位
unsigned short int bfReserverd1; //位图文件保留字,必须为0
unsigned short int bfReserverd2; //位图文件保留字,必须为0
unsigned long bfbfOffBits; //位图文件头到数据的偏移量,以字节为单位
}BITMAPFILEHEADER;
typedef struct tagBITMAPINFOHEADER
{
long biSize; //该结构大小,字节为单位
long biWidth; //图形宽度以象素为单位
long biHeight; //图形高度以象素为单位
short int biPlanes; //目标设备的级别,必须为1
short int biBitcount; //颜色深度,每个象素所需要的位数
short int biCompression; //位图的压缩类型
long biSizeImage; //位图的大小,以字节为单位
long biXPelsPermeter; //位图水平分辨率,每米像素数
long biYPelsPermeter; //位图垂直分辨率,每米像素数
long biClrUsed; //位图实际使用的颜色表中的颜色数
long biClrImportant; //位图显示过程中重要的颜色数
}BITMAPINFOHEADER;
BMP采用的是小端(Little Endian)存储方式。
这种存储方式中“RGB24”格式的像素的分量存储的先后顺序为B、G、R。由于RGB24格式存储的顺序是R、G、B,所以需要将“R”和“B”顺序作一个调换再进行存储。
思路
- 将RGB数据前面加上文件头
- 将RGB数据中每个像素的“B”和“R”的位置互换。
(10) 将RGB24格式像素数据转换为YUV420P格式像素数据
思路
RGB24是packet格式(连续rgb rgb),yuv420p是planar格式(平面分块yyyy uu v)。故需要连续读写3字节RGB 根据公式算出yuv之后然后分别存y,u,v。
RGB到YUV的转换公式:
Y= 0.299R+0.587G+0.114B
U=-0.147R-0.289G+0.463B
V= 0.615R-0.515G-0.100*B
//RGB to YUV420
bool RGB24_TO_YUV420(unsigned char *RgbBuf,int w,int h,unsigned char *yuvBuf)
{
unsigned char*ptrY, *ptrU, *ptrV, *ptrRGB;
memset(yuvBuf,0,w*h*3/2);
ptrY = yuvBuf;
ptrU = yuvBuf + w*h;
ptrV = ptrU + (w*h*1/4);
unsigned char y, u, v, r, g, b;
for (int j = 0; j<h;j++){
ptrRGB = RgbBuf + w*j*3 ;
for (int i = 0;i<w;i++){
r = *(ptrRGB++);
g = *(ptrRGB++);
b = *(ptrRGB++);
y = (unsigned char)( ( 66 * r + 129 * g + 25 * b + 128) >> 8) + 16 ;
u = (unsigned char)( ( -38 * r - 74 * g + 112 * b + 128) >> 8) + 128 ;
v = (unsigned char)( ( 112 * r - 94 * g - 18 * b + 128) >> 8) + 128 ;
*(ptrY++) = clip_value(y,0,255);
if (j%2==0&&i%2 ==0){
*(ptrU++) =clip_value(u,0,255);
}
else{
if (i%2==0){
*(ptrV++) =clip_value(v,0,255);
}
}
}
}
return true;
}
以上是雷神的转换代码,指出其中
y = (unsigned char)( ( 66 * r + 129 * g + 25 * b + 128) >> 8) + 16 ;
u = (unsigned char)( ( -38 * r - 74 * g + 112 * b + 128) >> 8) + 128 ;
v = (unsigned char)( ( 112 * r - 94 * g - 18 * b + 128) >> 8) + 128 ;
是为了减少浮点运算 从而优化运算时间
以下代码是减少浮点和乘法运算
转自 图像处理-RGB24转YUV420遇到的坑以及执行效率对比
/**********************不包含浮点和乘法运算*****************/
#define RGB2Y_SHIFT(r,g,b) \
((unsigned char)((((r << 6) + (r << 3) + (r << 2) + r + (g << 7) + (g << 4) + (g << 2) + (g << 1) + (b << 4) + (b << 3) + (b << 2) + b) + 128) >> 8) + 16)
#define RGB2U_SHIFT(r,g,b) \
((unsigned char)(((-((r << 5) + (r << 2) + (r << 1)) - ((g << 6) + (g << 3) + (g << 1)) + ((b << 6) + (b << 5) + (b << 4))) + 128) >> 8) + 128)
#define RGB2V_SHIFT(r,g,b) \
((unsigned char)(((r << 7) + (r << 4) + (r << 3) + (r << 2) + (r << 1) - ((g << 7) + (g << 2)) - ((b << 4) + (b << 3) + (b << 1)) + 128) >> 8) + 128)
(11) 生成RGB24格式的彩条测试图
颜色 | (R,G,B) |
---|---|
白 | (255, 255, 255) |
黄 | (255, 255, 0) |
青 | ( 0, 255, 255) |
绿 | ( 0, 255, 0) |
品 | (255, 0, 255) |
红 | (255, 0, 0) |
蓝 | ( 0, 0, 255) |
黑 | ( 0, 0, 0) |
思路
分为8条色带,area = width /8 为一个区域,按如上表设置rgb值
[0, area]为白色
[area,area*2]为黄色
[area*2,area*3]为青色
[area*3,area*4]为绿色
[area*4,area*5]为品色
[area*5,area*6]为红色
[area*6,area*7]为蓝色
[area*7,area*8]为黑色
RGB和YUV转换
关于RGB与YUV之间的转换,有如下一条矩阵公式:
1)、YUV(256 级别) 可以从8位 RGB 直接计算:
Y = 0.299 R + 0.587 G + 0.114 B
U = - 0.1687 R - 0.3313 G + 0.5 B + 128
V = 0.5 R - 0.4187 G - 0.0813 B + 128
2)、反过来,RGB 也可以直接从YUV (256级别) 计算:
R = Y + 1.402 (Cr-128)
G = Y - 0.34414 (Cb-128) - 0.71414 (Cr-128)
B = Y + 1.772 (Cb-128)
注意:浮点数和乘法会比较耗时 转化为定点运算会比较快