YUV、RGB、YcbCr是色彩空间的模型,而平常听到的BMP、PNG、JPEG、GIF是文件储存的形式。
提出YUV格式的原因,是为了解决彩色电视和黑白电视兼容性问题,因此从rgb的颜色空间,转换为yuv的颜色空间,其中y代表亮度,u和v代表色度。
YUV种类分为很多,可以理解是一个二维的,即空间间,和空间内,这样的表述,借鉴了h264中的帧间和帧内的思想。
空间-间:不同空间,即描述一个像素的bit数不同,比如yuv444,yuv422,yuv411,yuv420
空间-内:相同空间,即描述一个像素的bit数相同,但是存储方式不同,比如对于yuv420而言,又可细分为yuv420p,yuv420sp,nv21,nv12,yv12,yu12,I420
为什么yuv444,yuv420,yuv422,yuv411都是用的4呢?
因为用到了共享的思想,这是yuv和rgb的本质区别
RGB与YUV的转换公式:
Y= 0.299*R+0.587*G+0.114*B
U=-0.147*R-0.289*G+0.463*B
V= 0.615*R-0.515*G-0.100*B
rgb是一个像素是一个家庭,家庭成员是r,g,b,但是yuv是若干像素是一个家庭,不同像素的y共享同一个u和v,这样,引入了共享的思想,虽然最小单位是一个字节,但实际上描述一个像素点的字节,不一定是字节的整数倍。
1、YUV444
[ y u v ] [ y u v ] [ y u v ] [ y u v ] [ y u v ] [ y u v ] [ y u v ] [ y u v ] [ y u v ] [ y u v ] [ y u v ] [ y u v ] [ y u v ] [ y u v ] [ y u v ] [ y u v ]
1920*1080文件的大小:1920*1080*3(B)
2、YUV422
[ y u ] [ y v ] [ y u ] [ y v ] [ y v ] [ y u ] [ y v ] [ y u ] [ y u ] [ y v ] [ y u ] [ y v ] [ y v ] [ y u ] [ y v ] [ y u ]
显然一个家庭的成员为
[ y u ] [ y v ]
1920*1080文件的大小:1920*1080+1920*1080*0.5+1920*1080*0.5(B)因为UV的数量减少了一半,相对于YUV444空间节省了1/3
3、 YUV420
yuv420的意思似乎是在yuv422的基础上,再拿掉两个v,这样不就没有v了吗?其实yuv420的取名方式不是很高明,更确切的命名为yuv420yuv402,也就是第一行只有U,而第二行只有V
[ y u ] [ y ] [ y u ] [ y ] [ y v ] [ y ] [ y v ] [ y ] [ y u ] [ y ] [ y u ] [ y ]
对于yuv420而言,这个家庭的成员为
[ y u ] [ y ] [ y v ] [ y ]
1920*1080文件的大小:1920*1080+1920*1080*0.25+1920*1080*0.25(B)相对于YUV444空间节省1/2,因此也是比较主流的采样方式。
三种格式packet,planar,semi-plane
packet是打包格式,即存储yuv,然后再存储下一个yuv。
planar是平面格式,即先存储y平面,再存储u平面,最后存储v平面。
semi-planar即先存储y平面,再存储uv平面。
1、yuv422
yuyv(yuy2)
[ y u ] [ y v ] [ y u ] [ y v ] [ y u ] [ y v ] [ y u ] [ y v ] [ y u ] [ y v ] [ y u ] [ y v ] [ y u ] [ y v ] [ y u ] [ y v ]
uyvy
[ u y ] [ v y ] [ u y ] [ v y ] [ u y ] [ v y ] [ u y ] [ v y ] [ u y ] [ v y ] [ u y ] [ v y ] [ u y ] [ v y ] [ u y ] [ v y ]
yuv422p(yu16)或(yv16)
[ y y y y ] [ y y y y ] [ y y y y ] [ y y y y ] [ u u u u ] [ u u u u ] [ v v v v ] [ v v v v ] -------------- [ y y y y ] [ y y y y ] [ y y y y ] [ y y y y ] [ v v v v ] [ v v v v ] [ u u u u ] [ u u u u ]
yuv422sp(nv16)或(nv61)
[ y y y y ] [ y y y y ] [ y y y y ] [ y y y y ] [ u v u v ] [ u v u v ] [ u v u v ] [ u v u v ] ------------------ [ y y y y ] [ y y y y ] [ y y y y ] [ y y y y ] [ v u v u ] [ v u v u ] [ v u v u ] [ v u v u ]
2、yuv420
yuv420p(yu12)或(yv12)
[ y y y y ] [ y y y y ] [ y y y y ] [ y y y y ] [ u u ] [ u u ] [ v v ] [ v v ] -------------- [ y y y y ] [ y y y y ] [ y y y y ] [ y y y y ] [ v v ] [ v v ] [ u u ] [ u u ]
yuv420sp(nv12)或(nv21)
[ y y y y ] [ y y y y ] [ y y y y ] [ y y y y ] [ u v u v ] [ u v u v ] --------------- [ y y y y ] [ y y y y ] [ y y y y ] [ y y y y ] [ v u v u ] [ v u v u ]
常见的有nv12,nv21,nv16,nv61等,这里代表什么意思呢?
其实nv系列,都属于semi-plane系列
这里nv12表示正常的顺序,即uv plane,先是u,然后是v
而nv21表示相反的顺序,即uv plane,先是v,然后是u
同样,nv16和nv61的区别也是仅仅是uv的次序而已
这里的12和16又代表什么呢?实际上代表的是一个像素所占的位数!
以nv12为例,表示一个像素占用12bit,其中y是定死的占8bit,也就是u占2bit,v占2bit,实际上就是yuv420格式,具体而言是yuv420sp格式
nv16,则表示一个像素占用16bit,其中y是定死的8bit,也即是u占4bit,v占4bit,实际上就是yuv422格式,具体而言是yuv422sp格式
出处:https://www.jianshu.com/p/6a361e86ccd5
编码测试
将YUV422P中的Y、U、V分别分离出来
int simplest_yuv420_split(char *url, int w, int h, int num) { FILE *fp = fopen(url, "r"); if (fp < 0) cerr << "File doesn't exist."; FILE *fy, *fu, *fv; fy = fopen("output_420_y.y", "w"); fu = fopen("output_420_u.y", "w"); fv = fopen("output_420_v.y", "w"); if (fv < 0 || fy < 0 || fu < 0) cerr << "Cannot create file."; //YUV420:Y->w*h + U->w*h*1/4 + V->w*h*1/4 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); fwrite(pic, 1, w*h, fy); fwrite(pic + w * h, 1, w*h * 1 / 4, fu); fwrite(pic + w * h * 5 / 4, 1, w*h * 1 / 4, fv); } free(pic); fclose(fp); fclose(fy); fclose(fu); fclose(fv); return 0; } w、h分别为图像的宽高(px),那么图像的大小就是w*h*3/2(B),那么Y的数据储存位置便是w*h,紧接着的1/4便是U,最后1/4是V。
将YUV420p转换成灰度图
int simplest_yuv420_gray(char* path, int w, int h) { FILE* fo = fopen(path, "r"); FILE* fw = fopen("out_gray.yuv", "wb+"); unsigned char* picbuf = (unsigned char*)malloc(w*h * 3 / 2); fread(picbuf, 1, w*h * 3 / 2, fo); memset(picbuf + w * h, 128, w*h * 1 / 2); fwrite(picbuf, 1, w* h * 3 / 2, fw); free(picbuf); fclose(fo); fclose(fw); return 0; }
只要将uv设置成120。这是因为U、V是图像中的经过偏置处理的色度分量。
色度分量在偏置处理前的取值范围是-128至127,
这时候的无色对应的是“0”值。
经过偏置后色度分量取值变成了0至255,
因而此时的无色对应的就是128了。
int simplest_yuv420_halfy(char* path, int w, int h) { FILE* fo = fopen(path, "r"); FILE* fw = fopen("out_halfy.yuv", "wb+"); unsigned char* picbuf = (unsigned char*)malloc(w*h * 3 / 2); fread(picbuf, 1, w * h * 3 / 2, fo); for (int i = 0; i < (w * h); i++) { unsigned char temp = picbuf[i] / 2; printf("%d ", temp); picbuf[i] = temp; } fwrite(picbuf, 1, w*h * 3 / 2, fw); free(picbuf); fclose(fo); fclose(fw); return 0; } int simplest_yuv420_stready(char* path, int w, int h) { FILE* fo = fopen(path, "r"); FILE* fw = fopen("out_stready.yuv", "wb+"); unsigned char* picbuf = (unsigned char*)malloc(w*h * 3 / 2); fread(picbuf, 1, w * h * 3 / 2, fo); for (int i = 0; i < (w * h); i++) { unsigned char temp = picbuf[i] * 2; printf("%d ", temp); picbuf[i] = temp; } fwrite(picbuf, 1, w*h * 3 / 2, fw); free(picbuf); fclose(fo); fclose(fw); return 0; }
如果打算将图像的亮度减半(增强),
只要将图像的每个像素的Y值取出来分别进行除以2(乘以2)的工作就可以了。
图像的每个Y值占用1 Byte,
取值范围是0至255。
YUV420图像增加边框
int simplest_yuv420_border(char *url, int w, int h, int border) { FILE *fp = fopen(url, "rb+"); FILE *fp1 = fopen("output_border.yuv", "wb+"); unsigned char *pic = (unsigned char *)malloc(w*h * 3 / 2); fread(pic, 1, w*h * 3 / 2, fp); for (int j = 0; j < h; j++) { for (int k = 0; k < w; k++) { if (k<border || k>(w - border) || j<border || j>(h - border)) { pic[j*w + k] = 0; } } } fwrite(pic, 1, w*h * 3 / 2, fp1); free(pic); fclose(fp); fclose(fp1); return 0; }
border的像素的Y分量调整为0
计算两个YUV420P像素数据的PSNR
PSNR(峰值信噪比)解释: https://www.cnblogs.com/seniusen/p/10012656.html
int simplest_yuv420_psnr(char *url1, char *url2, int w, int h) { FILE *fp1 = fopen(url1, "rb+"); FILE *fp2 = fopen(url2, "rb+"); unsigned char *pic1 = (unsigned char *)malloc(w*h); unsigned char *pic2 = (unsigned char *)malloc(w*h); fread(pic1, 1, w*h, fp1); fread(pic2, 1, w*h, fp2); double mse_sum = 0, mse = 0, psnr = 0; for (int j = 0; j < w*h; j++) { mse_sum += pow((double)(pic1[j] - pic2[j]), 2); } mse = mse_sum / (w*h); psnr = 10 * log10(255.0*255.0 / mse); printf("%f ", psnr); fseek(fp1, w*h / 2, SEEK_CUR); fseek(fp2, w*h / 2, SEEK_CUR); free(pic1); free(pic2); fclose(fp1); fclose(fp2); return 0; }
RGB保存成bmp
int simplest_rgb24_to_bmp(const char *rgb24path, int width, int height, const char *bmppath) { typedef struct { long imageSize; long blank; long startPosition; }BmpHead; typedef struct { long Length; long width; long height; unsigned short colorPlane; unsigned short bitColor; long zipFormat; long realSize; long xPels; long yPels; long colorUse; long colorImportant; }InfoHead; int i = 0, j = 0; BmpHead m_BMPHeader = { 0 }; InfoHead m_BMPInfoHeader = { 0 }; char bfType[2] = { 'B','M' }; int header_size = sizeof(bfType) + sizeof(BmpHead) + sizeof(InfoHead); unsigned char *rgb24_buffer = NULL; FILE *fp_rgb24 = NULL, *fp_bmp = NULL; if ((fp_rgb24 = fopen(rgb24path, "rb")) == NULL) { printf("Error: Cannot open input RGB24 file. "); return -1; } if ((fp_bmp = fopen(bmppath, "wb")) == NULL) { printf("Error: Cannot open output BMP file. "); return -1; } rgb24_buffer = (unsigned char *)malloc(width*height * 3); fread(rgb24_buffer, 1, width*height * 3, fp_rgb24); m_BMPHeader.imageSize = 3 * width*height + header_size; m_BMPHeader.startPosition = header_size; m_BMPInfoHeader.Length = sizeof(InfoHead); m_BMPInfoHeader.width = width; //BMP storage pixel data in opposite direction of Y-axis (from bottom to top). m_BMPInfoHeader.height = -height; m_BMPInfoHeader.colorPlane = 1; m_BMPInfoHeader.bitColor = 24; m_BMPInfoHeader.realSize = 3 * width*height; fwrite(bfType, 1, sizeof(bfType), fp_bmp); fwrite(&m_BMPHeader, 1, sizeof(m_BMPHeader), fp_bmp); fwrite(&m_BMPInfoHeader, 1, sizeof(m_BMPInfoHeader), fp_bmp); //BMP save R1|G1|B1,R2|G2|B2 as B1|G1|R1,B2|G2|R2 //It saves pixel data in Little Endian //So we change 'R' and 'B' for (j = 0; j < height; j++) { for (i = 0; i < width; i++) { char temp = rgb24_buffer[(j*width + i) * 3 + 2]; rgb24_buffer[(j*width + i) * 3 + 2] = rgb24_buffer[(j*width + i) * 3 + 0]; rgb24_buffer[(j*width + i) * 3 + 0] = temp; } } fwrite(rgb24_buffer, 3 * width*height, 1, fp_bmp); fclose(fp_rgb24); fclose(fp_bmp); free(rgb24_buffer); printf("Finish generate %s! ", bmppath); return 0; return 0; }
BMP文件格式:
BITMAPFILEHEADER
BITMAPINFOHEADER
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;
RGB24彩色色条图
int simplest_rgb24_colorbar(int width, int height, char *url_out) { unsigned char *data = NULL; int barwidth; char filename[100] = { 0 }; FILE *fp = NULL; int i = 0, j = 0; data = (unsigned char *)malloc(width*height * 3); barwidth = width / 8; if ((fp = fopen(url_out, "wb+")) == NULL) { printf("Error: Cannot create file!"); return -1; } for (j = 0; j < height; j++) { for (i = 0; i < width; i++) { int barnum = i / barwidth; switch (barnum) { case 0: { data[(j*width + i) * 3 + 0] = 255; data[(j*width + i) * 3 + 1] = 255; data[(j*width + i) * 3 + 2] = 255; break; } case 1: { data[(j*width + i) * 3 + 0] = 255; data[(j*width + i) * 3 + 1] = 255; data[(j*width + i) * 3 + 2] = 0; break; } case 2: { data[(j*width + i) * 3 + 0] = 0; data[(j*width + i) * 3 + 1] = 255; data[(j*width + i) * 3 + 2] = 255; break; } case 3: { data[(j*width + i) * 3 + 0] = 0; data[(j*width + i) * 3 + 1] = 255; data[(j*width + i) * 3 + 2] = 0; break; } case 4: { data[(j*width + i) * 3 + 0] = 255; data[(j*width + i) * 3 + 1] = 0; data[(j*width + i) * 3 + 2] = 255; break; } case 5: { data[(j*width + i) * 3 + 0] = 255; data[(j*width + i) * 3 + 1] = 0; data[(j*width + i) * 3 + 2] = 0; break; } case 6: { data[(j*width + i) * 3 + 0] = 0; data[(j*width + i) * 3 + 1] = 0; data[(j*width + i) * 3 + 2] = 255; break; } case 7: { data[(j*width + i) * 3 + 0] = 0; data[(j*width + i) * 3 + 1] = 0; data[(j*width + i) * 3 + 2] = 0; break; } } } } fwrite(data, width*height * 3, 1, fp); fclose(fp); free(data); return 0; }
转自:https://blog.csdn.net/leixiaohua1020/article/details/50534150(雷霄骅CSDN)