RGB文件是原始的没有压缩的包含红绿蓝三种颜色的图像文件。
常见的RGB格式例如RGB24,也就是一组RGB像素中的R、G、B各占8比特,即一个字节,一组RGB一共是24个比特。
这里只以RGB24为例。
YUV文件是与RGB文件同样属于原始的没有压缩的图像文件,只是YUV格式存储的是亮度Y、色差信号U和V。
和YUV不同的是,由大面积着色原理,人眼对于亮度信号的敏感度远强于颜色信号的敏感度。因此色度信号可以比亮度信号取得更少,而视觉上差别不大。
因此YUV又有多种取样结构,如4:4:4、4:2:2、4:2:0等。
显然4:4:4的画面质量最好。数字演播室采用的取样结构为4:2:2。
RGB与YUV的转换公式如下:
Y = 0.299 R + 0.587 G + 0.114 B R − Y = 0.701 R − 0.587 G − 0.114 B B − Y = − 0.299 R − 0.587 G + 0.886 B U = 0.713 ∗ ( R − Y ) + 128 V = 0.713 ∗ ( B − Y ) + 128 Y = 0.299R + 0.587G + 0.114B\\ R-Y = 0.701R - 0.587G-0.114B\\ B-Y = -0.299R - 0.587G + 0.886B\\\\ U = 0.713 *(R-Y) + 128\\ V = 0.713 * (B-Y) + 128 Y=0.299R+0.587G+0.114BR−Y=0.701R−0.587G−0.114BB−Y=−0.299R−0.587G+0.886BU=0.713∗(R−Y)+128V=0.713∗(B−Y)+128
YUV的Y、U、V的信号在YUV格式中是分开存放的。
BMP文件有两种颜色表示方式
typedef struct
{
Char Signature[2];
DWORD FileSize;
DWORD _reserved;
DWORD DataOffset;
} BITMAPFILEHEADER;
("BM"标识也应该是BITMAPFILEHEADER的一部分,但是这样声明结构体,在C语言下会引起内存对齐的问题,不方便直接把文件头读进结构体,因此实际编码的时候没有把它放进结构体)
再接下来是文件信息头,包含了图片的元数据信息。
typedef struct
{
DWORD Size;
int Width;
int Height;
WORD Planes;
WORD BitCount;
DWORD Compression;
DWORD ImageSize;
DWORD XpixelsPerM;
DWORD YpixelsPerM;
DWORD CorlorsUsed;
DWORD ColorsImaportant;
} BITMAPINFOHEADER;
对于24bit像素的Bitmap文件,像素以RGB的形式直接存放在文件信息头之后,也就是BITMAPFILEHEADER的DataOffset指示的地方。
需要注意的是,BMP像素排列有两种方式,
判断方法是根据BITMAPINFOHEADER中Height的正负,当Height为正时,从上到下扫描,当Height为负时,从下到上扫描。
因此Height在结构体里应该声明成有符号整型。
思路很简单,首先,无论输入的是什么格式,都一律解码为RGB格式。
RGB格式的数据进行转码比YUV格式更方便。
不同BMP的大小可能不一,因此在转换成YUV前需要根据输出尺寸进行裁剪。
在最终输出阶段,全部转换成YUV格式,然后按照要求的帧数重复写入到文件。
这些定义是放在头文件里的
typedef unsigned char BYTE;
typedef unsigned int DWORD;
typedef unsigned short WORD;
#ifndef _YUV_H_
#define _YUV_H_
typedef struct {
BYTE Y;
BYTE U;
BYTE V;
} YUV;
typedef struct {
BYTE R;
BYTE G;
BYTE B;
} RGB;
typedef struct {
int Y;
int U;
int V;
}YUV_FORMAT;
typedef enum {FILE_BMP, FILE_RGB} INPUT_TYPE;
typedef struct
{
FILE *fp;
const char * file_path;
INPUT_TYPE type;
DWORD height;
DWORD width;
YUV * yuv_raw;
DWORD yuv_size;
RGB * rgb_raw;
DWORD rgb_size;
DWORD start;
int duration;
} ImgFile;
void RGB2YUV(RGB * rgb_img, YUV * yuv_img, int size) {
int i=0;
for(i=0;i<size;i++){
RGB rgb = rgb_img[i];
double dr = rgb.R, dg = rgb.G, db = rgb.B;
double y = 0.299 * dr + .587 * dg + .114 * db;
double u = 0.713 * (dr - y) + 128;
double v = 0.713 * (db - y) + 128;
yuv_img[i].Y = (BYTE) y;
yuv_img[i].U = (BYTE) u;
yuv_img[i].V = (BYTE) v;
}
}
#include "BMP.h"
// #include "YUV.h"
#include "stdlib.h"
#include "string.h"
void printBMPFileHeader(BITMAPFILEHEADER *fhp)
{
printf("Signature: %.2s\r\n"
"FileSize: %d\r\n"
"DataOffset: %d\r\n",
"BM", fhp->FileSize, fhp->DataOffset);
}
void printBMPINFOHeader(BITMAPINFOHEADER *bih)
{
printf("Size: %d\r\n"
"Width: %d\r\n"
"Height: %d\r\n"
"Planes: %d\r\n"
"BitCount: %d\r\n",
bih->Size, bih->Width, bih->Height, bih->Planes, bih->BitCount);
}
void getBMPHeader(FILE *fp, BITMAPFILEHEADER *file_header, BITMAPINFOHEADER *info_header)
{
fread(file_header, 2, 1, fp);
fread(file_header, sizeof(BITMAPFILEHEADER), 1, fp);
printBMPFileHeader(file_header);
fread(info_header, sizeof(BITMAPINFOHEADER), 1, fp);
printBMPINFOHeader(info_header);
// validing header info
fseek(fp, 0, SEEK_END);
int size = ftell(fp);
printf("FS:%d == HD:%d\r\n", size, file_header->FileSize);
fseek(fp, 0, SEEK_SET);
}
int BMP2YUV(FILE *fp, BYTE **yuv_out)
{
return 0;
}
int BMP2RGB(FILE *fp, RGB **rgb_out)
{
fseek(fp, 2, SEEK_SET); // Rollback to start, jump off the signature
BITMAPFILEHEADER file_header; // bitmap file header
BITMAPINFOHEADER info_header; // bitmap info header
fread(&file_header, sizeof(BITMAPFILEHEADER), 1, fp); // read file header
fread(&info_header, sizeof(BITMAPINFOHEADER), 1, fp); // read info header
DWORD data_offset = file_header.DataOffset;
fseek(fp, data_offset, SEEK_SET); // Seek to data
int height = info_header.Height;
int width = info_header.Width;
int pixels = height * width;
RGB * rgbs = (RGB *)malloc(pixels * sizeof(RGB));
* rgb_out = rgbs;
fread(rgbs, sizeof(RGB), pixels, fp);
if(info_header.Height>0) {
// bitmap is bottom-up DIB(Start at lower left) when height>0. rotate it.
for(int i=0;i<info_header.Height/2;i++){
for(int j=0;j<info_header.Width;j++){
RGB temp = rgbs[info_header.Width* (info_header.Height - 1 - i) + j];
rgbs[info_header.Width* (info_header.Height - 1 - i) + j] = rgbs[i * info_header.Width + j];
rgbs[i * info_header.Width + j] = temp;
}
}
}
return pixels;
}
#include
#include "RGB.h"
int RGB2RGB(FILE *fp, RGB**rgb_out_bp)
{
fseek(fp, 0, SEEK_END);
int size = ftell(fp);
fseek(fp, 0, SEEK_SET);
int pixels = size/3; // caculating file size
RGB * rgbs = (RGB *) malloc(size);
fread(rgbs, 1, size, fp);
*rgb_out_bp = rgbs;
return pixels;
}
int save_yuv(YUV * yuv_img, BYTE ** bufp, YUV_FORMAT format, int height, int width)
{
int i;
int size = height * width;
int file_size = (format.Y + format.U + format.V) * size / 4;
int flag = 10*(format.U) + format.V;
int h_inter=0, v_inter=0;
BYTE * output = (BYTE*) malloc(file_size);
*bufp = output;
BYTE * start_addr = output;
for(i=0;i<size;i++){
*output = yuv_img[i].Y;
output++;
}
if(flag==44) {
v_inter = h_inter = 1;
}else if(flag==22){
v_inter = 1;
h_inter = 2;
}else if(flag==20){
v_inter = h_inter = 2;
}else {
return size;
}
for(int y=0;y<height;y++){
for(int x=0;x<width;x++){
i = y * width + x;
if(y%v_inter==0 && x%h_inter ==0){
*output = yuv_img[i].U;
output++;
}
}
}
for(int y=0;y<height;y++){
for(int x=0;x<width;x++){
i = y * width + x;
if(y%v_inter==0 && x%h_inter ==0){
*output = yuv_img[i].V;
output++;
}
}
}
printf("Convert size: %ld\n", output - start_addr);
return file_size;
}
主函数包含了解析命令行参数和主要处理流程
int main(int argc, char const *argv[])
{
if(argc<5){
HelpMessage();
return 0;
}
int input_count = 0;
int argc_temp= argc;
int argn = 1;
int file_count = 0;
INPUT_TYPE TYPE;
ImgFile *input_file_list;
assert(argv[argn++], "-c", " not defined." );
// Read input file count
sscanf(argv[argn++], "%d", &file_count);
// Read file list
input_file_list = (ImgFile *) malloc(file_count * sizeof(ImgFile));
for(int i=0;i<file_count;i++){
assert(argv[argn++], "-i", " not defined");
const char * input_file = argv[argn];
const char * file_path = input_file + 4;
FILE *fp = fopen(file_path, "rb");
if(fp==NULL) printf("\tFile %s point is null\r\n", file_path);
input_file_list[i].fp = fp;
input_file_list[i].file_path = file_path;
input_file_list[i].height = -1;
input_file_list[i].width = -1;
argn++;
if(memcmp(input_file, "RGB", 3)==0){
int h, w;
assert(argv[argn++], "-s", " not defined." );
const char * size_str = argv[argn++];
sscanf(size_str, "%dx%d", &h, &w);
input_file_list[i].height = h;
input_file_list[i].width = w;
input_file_list[i].type = FILE_RGB;
input_file_list[i].start = 0;
}
else if(memcmp(input_file, "BMP", 3)==0){
// read file size;
BITMAPFILEHEADER file_header;
BITMAPINFOHEADER info_header;
getBMPHeader(input_file_list[i].fp, &file_header, &info_header);
input_file_list[i].type = FILE_BMP;
input_file_list[i].height = info_header.Height;
input_file_list[i].width = info_header.Width;
}
assert(argv[argn++], "-d", " not defined." );
int dura;
sscanf(argv[argn++], "%d", &dura);
input_file_list[i].duration = dura;
}
// Read output options
assert(argv[argn++], "-o", ");
FILE *foutp = fopen(argv[argn++], "wb");
if(foutp==NULL) printf("Output file pointer is NULL.\r\n");
assert(argv[argn++], "-s", " not defined." );
int output_height, output_width;
sscanf(argv[argn++], "%dx%d", &output_height, &output_width);
printf("Output\r\n\tOutput \tfile #0\r\n\t\tSize: %dx%d\r\n", output_height, output_width);
// List all file
PrintFileList(input_file_list, file_count);
// Convert to raw rgb
printf("Convert to raw rgb\t\r\n");
for(int i=0;i<file_count;i++){
ImgFile * file = input_file_list + i;
printf("\tConvert %s\r\n", file->file_path);
if(file->type==FILE_BMP){
file->rgb_size = BMP2RGB(file->fp, &(file->rgb_raw));
}
else if(file->type=FILE_RGB)
{
file->rgb_size = RGB2RGB(file->fp, &(file->rgb_raw));
}
}
// crop to certain size
// read
for(int i=0;i<file_count;i++){
resize(&(input_file_list[i].rgb_raw),
input_file_list[i].height,
input_file_list[i].width,
output_height,
output_width,
"C");
input_file_list[i].rgb_size = output_height * output_width;
}
YUV_FORMAT yuv_format;
yuv_format.Y = 4;
yuv_format.U = 2;
yuv_format.V = 0;
assert(argv[argn++], "-f", " not defined." );
const char * format_str = argv[argn++];
yuv_format.Y = format_str[0] - '0';
yuv_format.U = format_str[1] - '0';
yuv_format.V = format_str[2] - '0';
for(int i=0;i<file_count;i++){
// convert to YUV 444
input_file_list[i].yuv_raw = (YUV *) malloc(sizeof(YUV) *( input_file_list[i].rgb_size));
memset(input_file_list[i].yuv_raw, 127, input_file_list[i].rgb_size);
input_file_list[i].yuv_size = input_file_list[i].rgb_size;
RGB2YUV(input_file_list[i].rgb_raw, input_file_list[i].yuv_raw,
input_file_list[i].rgb_size);
}
BYTE *bufp;
for(int i=0;i<file_count;i++){
ImgFile * temp = input_file_list + i;
// convert YUV 444 to YUV 4xx
int output_size = save_yuv(temp->yuv_raw,
&(bufp),
yuv_format,
output_height,
output_width);
printf("Output size: %d\r\n", output_size);
for(int j=0;j<temp->duration;j++)
{
fwrite(bufp, 1, output_size, foutp);
}
}
fclose(foutp);
// List all file
PrintFileList(input_file_list, file_count);
return 0;
}
void HelpMessage() {
printf("Help\r\n");
printf(" -c -i : -d [-s x][] -o -s x -f \r\n" );
printf(" | | | | | | +-> YUV format 420/422/444.\r\n");
printf(" | | | | | +-> Output height and width.\r\n");
printf(" | | | | +-> Output file path.\r\n");
printf(" | | | +-> File size, required when input format is RGB.\r\n");
printf(" | | +-> Duration.\r\n");
printf(" | +-> Type (Support BMP and RGB): FilePath\r\n");
printf(" +-> Input files count\r\n");
}
int assert(const char * left,const char * right, const char *msg){
printf("ASSERT \"%s\"==\"%s\"\r\n", left , right);
if(strcmp((char *) left,(char *) right)!=0){
printf("[Assert Error]: %s\r\n", msg);
exit(1);
return 0;
}else return 1;
}
void PrintImgFile(ImgFile * imgFile) {
printf("\t\tFile path: %s\r\n"
"\t\tType: %s\r\n"
"\t\tHeight: %d\r\n"
"\t\tWidth: %d\r\n"
"\t\tRGB Size: %d\t\n"
"\t\tYUV Size: %d\r\n"
"\t\tDuration: %d\r\n",
imgFile->file_path,
imgFile->type==FILE_BMP?"BMP":"RGB",
imgFile->height,
imgFile->width,
imgFile->rgb_size,
imgFile->yuv_size,
imgFile->duration
);
}
void PrintFileList(ImgFile *imgFiles, int file_count)
{
// List all file
printf("Input %d files\t\r\n", file_count);
for(int i=0;i<file_count;i++){
printf("\tInput file #%d\r\n", i);
PrintImgFile(imgFiles + i);
}
}
// void ReadSizeOption(char * s, )
测试图片:
将5张图片转换成YUV,分别重复50次,再输出到文件中。
./RGB2YUV
Help
-c <Count> -i <Type>:<File> -d <Dura> [-s <Height>x<Width>][] -o <YUVFile> -s <Height>x<Width> -f <Format>
| | | | | | +-> YUV format 420/422/444.
| | | | | +-> Output height and width.
| | | | +-> Output file path.
| | | +-> File size, required when input format is RGB.
| | +-> Duration.
| +-> Type (Support BMP and RGB): FilePath
+-> Input files count
./RGB2YUV -c 5 -i BMP:./imgs/pic1_r.bmp -d 50 -i BMP:./imgs/pic2_r.bmp -d 50 -i BMP:./imgs/pic3_r.bmp -d 50 -i BMP:./imgs/pic4_r.bmp -d 50 -i BMP:./imgs/pic5_r.bmp -d 50 -o ./test2.yuv -s 2048x2048 -f 422
使用ffplay播放yuv文件:
ffplay -f rawvideo -pixel_format yuv422p -i test2.yuv -s 2048x2048