图像文件的读写和转换:BMPtoYUV

2023-11-05 12:50

本文主要是介绍图像文件的读写和转换:BMPtoYUV,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

实验目的

1.理解图像文件的基本组成。
2.掌握结构体作为复杂数据对象的用法。进一步熟悉由问题到程序的解决方案,并掌握编程细节:如内存分配、倒序读写、字节序、文件读写过程等。

实验原理

BMP文件的组成结构

        BMP(全称Bitmap)是Windows操作系统中的标准图像文件格式,可以分成两类:设备相关位图(DDB)和设备无关位图(DIB),使用广泛。它采用位映射存储格式,除了图像深度可选以外,在绝大多数应用中不采用其他任何压缩,因此,BMP文件所占用的空间很大。BMP文件的图像深度可选lbit、4bit、8bit、16bit及24bit。BMP文件存储数据时,图像的扫描方式是按从左到右、从下到上的顺序。由于BMP文件格式是Windows环境中交换与图有关的数据的一种标准,因此在Windows环境中运行的图形图像软件都支持BMP图像格式。

典型的BMP图像文件由四部分组成:
(1)位图头文件数据结构,它包含BMP图像文件的类型、显示内容等信息;
(2)位图信息数据结构,它包含有BMP图像的宽、高、压缩方法,以及定义颜色等信
息;
(3)调色板,这个部分是可选的,有些位图需要调色板,有些位图,比如真彩色图(24
位的BMP)就不需要调色板;
(4)位图数据,这部分的内容根据BMP位图使用的位数不同而不同,在24位图中直接
使用RGB,而其他的小于24位的使用调色板中颜色索引值。
相应的数据结构可表示如下:


位图文件头(定义BMP文件结构体)主要包括:

typedef struct tagBITMAPFILEHEADER {WORD bfType; //说明文件的类型DWORD bfSize; // 说明文件的大小(包括头文件的14字节),用字节为单位WORD bfReserved1; //保留,设置为0 WORD bfReserved2; // 保留,设置为0 DWORD bfOffBits; //说明从BITMAPFILEHEADER结构开始到实际的图像数据之间的字节偏移量 } BITMAPFILEHEADER;

位图信息头主要包括:

typedef struct tagBITMAPINFOHEADER{DWORD      biSize;			//说明结构体所需字节数LONG       biWidth;			//以像素为单位说明图像的宽度LONG       biHeight;		//以像素为单位说明图像的高度WORD       biPlanes;		//说明位面数,必须为1WORD       biBitCount;		//说明位数像素,1表示黑白图、 4表示16色图、//8表示256色图、24表示真彩色图DWORD      biCompression;	//说明图像是否压缩及压缩类型BI_RGB、BI_RLE8、BI_RLE4、BI_BITFIELDSDWORD      biSizeImage;		//以字节为单位说明图像大小,必须是4的整数倍LONG       biXPelsPerMeter;	//目标设备的水平分辨率,像素/米LONG       biYPelsPerMeter;	//目标设备的垂直分辨率,像素/米DWORD      biClrUsed;		//说明图像实际用到的颜色数,如果为0则颜色数为2的 //biBitCount次方DWORD      biClrImportant;	//说明对图像显示有重要影响的颜色索引的数目,//如果是0,表示都重要
} BITMAPINFOHEADER;

调色板

        调色板实际上是一个数组,它所包含的元素与位图所具有的颜色数相同,决定于biClrUsed和biBitCount字段。数组中每个元素的类型是一个RGBQUAD结构。真彩色无调色板部分。

typedef struct tagRGBQUAD {BYTE    rgbBlue;	//指定蓝色分量BYTE    rgbGreen;	//指定绿色分量BYTE    rgbRed;		//指定红色分量BYTE    rgbReserved;//指定保留,指定为0
} RGBQUAD

图像数据字节阵列

        紧跟在调色板之后的是图像数据字节阵列。对于用到调色板的位图,图像数据就是该像素颜色在调色板中的索引值(逻辑色)。对于真彩色图,图像数据就是实际的R、G、B值。图像的每一扫描行由表示图像像素的连续的字节组成,每一行的字节数取决于图像的颜色数目和用像素表示的图像宽度。规定每一扫描行的字节数必须是4的整倍数,也就是DWORD对齐的。扫描行是由底向上存储的,这就是说,阵列中的第一个字节表示位图左下角的像素,而最后一个字节表示位图右上角的像素。

字节序

        不同的计算机系统采用不同的字节序存储数据,同样一个4字节的32位整数,在内存中存储的方式不同。字节序分为小尾字节序(Little Endian)和大尾字节序(Big Endian)。Intel处理器大多数使用小尾字节序,Motorola处理器大多数使用大尾(Big Endian)字节序。

        小尾就是低位字节排放在内存的低端,高位字节排放在内存的高端,即所谓的“低位在前,高位在后”。大尾就是高位字节排放在内存的低端,低位字节排放在内存的高端,即所谓的“高位在前,低位在后”。TCP/IP各层协议将字节序定义为大尾,因此TCP/IP协议中使用的字节序通常称之为网络字节序。

        在实现BMP文件头信息的写入和读出时,需要注意整数保存时的字节序。例如:文件大小是以Intel序保存的。在编程前先用二进制打开方式观察BMP文件各个部分的数据存储格式。

实验流程

(1)在图像处理软件中自行生成多个BMP文件,至少含5个不同的场景画面,要求带含有班级、学号后四位和本人姓名(缩写或昵称均可)的logo。(基本要求为24bit的BMP,进阶要求为支持小于24bit的BMP)
(2)编写将第一步所生成的多个BMP文件转化为YUV文件,要求可在命令行中设置每个画面出现的帧数。最后形成的YUV文件应至少包含200帧。重点掌握函数定义、缓冲区分配、倒序读写、结构体的操作。
(3)对整个程序进行调试,并将生成的YUV文件用播放软件观看,验证是否正确。

实验内容

1.生成五个不同场景的BMP文件

2.新建工程并传入参数

 头文件rgb2yuv.h

//头文件
//类的定义
//外部变量、函数的声明
//只能写声明,不能定义
//可以定义常量
//头文件保护
#ifndef RGB2YUV_H
#define	RGB2YUV_H
#define _CRT_SECURE_NO_DEPRECATE
#include<iostream>
#include<Windows.h>
using namespace std;void readrgb(FILE* bmpFile, BITMAPFILEHEADER& File_header, BITMAPINFOHEADER& Info_header,unsigned char* rgbBuf, unsigned long width, unsigned long height);void rgb2yuv(unsigned char* rgbBuf, unsigned char* yBuf, unsigned char* uBuf, unsigned char* vBuf,unsigned long width, unsigned long height);void outyuv(unsigned char* yBuf, unsigned char* uBuf, unsigned char* vBuf, unsigned long size, FILE* yuvFile);#endif

readrgb.cpp

#include"rgb2yuv.h"
//规定每一扫描行的字节数必须是4的整倍数,也就是DWORD对齐的。
//扫描行是由底向上存储的,这就是说,阵列中的第一个字节表示位图左下角的像素,而最后一个字节表示位图右上角的像素。
void readrgb(FILE* bmpFile, BITMAPFILEHEADER& File_header, BITMAPINFOHEADER& Info_header,unsigned char* rgbBuf, unsigned long width, unsigned long height)
{//判断是否有调色盘if (Info_header.biBitCount == 24)cout << "真彩无调色盘" << endl;//读rgb,24bit深度则位图数据部分就是真实的rgb直接输出即可unsigned char* bmp_rgb = (unsigned char*)malloc(sizeof(unsigned char) * width * height * 3);unsigned char* rgb = (unsigned char*)malloc(sizeof(unsigned char) * width * height * 3);//读取bmp文件fread(bmp_rgb, width * height * 3, 1, bmpFile);//倒序转正序for (int i = 0; i < int(height); i++) {for (int j = 0; j < int(width*3); j++) {*(rgbBuf+i * width*3 + j) = *(bmp_rgb+(height - i - 1) * width*3 + j);}}//memcpy(rgbBuf, rgb, height * width*3);}

rgb2yuv.cpp

#include"rgb2yuv.h"//Y=0.2990R+0.5870G+0.1140B
//U = -0.1684R-0.3316G + 0.5000B
//V = 0.5000R-0.4187G-0.0813B
//RGB排列顺序为BGRBGRBGR
void rgb2yuv(unsigned char* rgbBuf, unsigned char* yBuf, unsigned char* uBuf, unsigned char* vBuf,unsigned long width, unsigned long height)
{//将rgb取出提前处理参数unsigned char* u= (unsigned char*)malloc(width * height);unsigned char* v= (unsigned char*)malloc(width * height);int tmp = 0;for (int i = 0; i < int(width * height) * 3; i += 3) {*(yBuf + tmp) = (unsigned char)((float)0.2990 * (*(rgbBuf+i+2))+(float)0.5870 * (*(rgbBuf + i + 1))+ (float)0.1140* (*(rgbBuf + i )));*(u + tmp) = (unsigned char)((float)(-0.1684) * (*(rgbBuf + i + 2)) + (float)(-0.3316) * (*(rgbBuf + i + 1)) + (float)0.5 * (*(rgbBuf + i))+128);*(v + tmp) = (unsigned char)((float)0.5 * (*(rgbBuf + i + 2)) + (float)(-0.4187)* (*(rgbBuf + i + 1)) + (float)(-0.0813)* (*(rgbBuf + i))+ 128);tmp++;}tmp = 0;//将uv放入缓存,注意4:2:0for (int i = 0; i <int(height-1); i+=2) {for (int j = 0; j < int(width-1); j += 2) {*(uBuf + tmp) = (*(u + i * width + j) + *(u + (i+1) * width + j)+ *(u + i * width + j+1) + *(u + (i+1) * width + j+1))/4.0;*(vBuf + tmp)= (*(v + i * width + j) + *(v + (i + 1) * width + j)+ *(v + i * width + j + 1) + *(v + (i + 1) * width + j + 1)) / 4.0;tmp++;}}//对yuv信号进行限量化电平处理for (int i = 0; i <int( width * height); i++){if (*(yBuf + i) < 16)*(yBuf + i) = 16;if (*(yBuf + i) > 235)*(yBuf + i) = 235;}for (int i = 0; i < int(width * height) / 4; i++){if (*(uBuf + i) < 16)*(uBuf + i) = 16;if (*(vBuf + i) < 16)*(vBuf + i) = 16;if (*(uBuf + i) > 240)*(uBuf + i) = 240;if (*(vBuf + i) > 240)*(vBuf + i) = 240;}free(u);free(v);}

outyuv.cpp

#include"rgb2yuv.h"//Y=0.2990R+0.5870G+0.1140B
//U = -0.1684R-0.3316G + 0.5000B
//V = 0.5000R-0.4187G-0.0813B
//RGB排列顺序为BGRBGRBGR
void rgb2yuv(unsigned char* rgbBuf, unsigned char* yBuf, unsigned char* uBuf, unsigned char* vBuf,unsigned long width, unsigned long height)
{//将rgb取出提前处理参数unsigned char* u= (unsigned char*)malloc(width * height);unsigned char* v= (unsigned char*)malloc(width * height);int tmp = 0;for (int i = 0; i < int(width * height) * 3; i += 3) {*(yBuf + tmp) = (unsigned char)((float)0.2990 * (*(rgbBuf+i+2))+(float)0.5870 * (*(rgbBuf + i + 1))+ (float)0.1140* (*(rgbBuf + i )));*(u + tmp) = (unsigned char)((float)(-0.1684) * (*(rgbBuf + i + 2)) + (float)(-0.3316) * (*(rgbBuf + i + 1)) + (float)0.5 * (*(rgbBuf + i))+128);*(v + tmp) = (unsigned char)((float)0.5 * (*(rgbBuf + i + 2)) + (float)(-0.4187)* (*(rgbBuf + i + 1)) + (float)(-0.0813)* (*(rgbBuf + i))+ 128);tmp++;}tmp = 0;//将uv放入缓存,注意4:2:0for (int i = 0; i <int(height-1); i+=2) {for (int j = 0; j < int(width-1); j += 2) {*(uBuf + tmp) = (*(u + i * width + j) + *(u + (i+1) * width + j)+ *(u + i * width + j+1) + *(u + (i+1) * width + j+1))/4.0;*(vBuf + tmp)= (*(v + i * width + j) + *(v + (i + 1) * width + j)+ *(v + i * width + j + 1) + *(v + (i + 1) * width + j + 1)) / 4.0;tmp++;}}//对yuv信号进行限量化电平处理for (int i = 0; i <int( width * height); i++){if (*(yBuf + i) < 16)*(yBuf + i) = 16;if (*(yBuf + i) > 235)*(yBuf + i) = 235;}for (int i = 0; i < int(width * height) / 4; i++){if (*(uBuf + i) < 16)*(uBuf + i) = 16;if (*(vBuf + i) < 16)*(vBuf + i) = 16;if (*(uBuf + i) > 240)*(uBuf + i) = 240;if (*(vBuf + i) > 240)*(vBuf + i) = 240;}free(u);free(v);}

main.cpp

#include"rgb2yuv.h"int main(int argc, char* argv[])
{BITMAPFILEHEADER File_header; //位图文件头BITMAPINFOHEADER Info_header; //位图信息头FILE* bmpFile = NULL;         //指向输入bmp文件指针FILE* yuvFile = NULL;         //指向输出yuv文件指针unsigned char* rgbBuf = NULL; //rgb缓存区unsigned char* yBuf = NULL;   //y缓存区unsigned char* uBuf = NULL;   //u缓存区unsigned char* vBuf = NULL;   //v缓存区int bmpwidth, bmpheight,width,height;  //宽和高的处理变量//定义输入输出文件const char* bmp[5] = { argv[1],argv[2],argv[3],argv[4],argv[5] };const char* yuv = argv[6];//二进制方式打开yuv文件,只写yuvFile = fopen(yuv, "wb");      if (yuvFile == NULL){//找不到bmp文件cout << "无法打开YUV"<< endl;}//二进制方式打开bmp文件,只读for (int i = 0; i < 5;i++){bmpFile = fopen(bmp[i], "rb");if (bmpFile == NULL)                  //找不到bmp文件{cout<<"无法打开bmp"<<i+1<<endl;}//读取bmp文件头if (fread(&File_header, sizeof(BITMAPFILEHEADER), 1, bmpFile) != 1){cout << "bmp"<<i+1<<"的文件头无法读取" << endl;}else if (File_header.bfType != 0x4D42) {cout << "bmp" << i + 1 << "不是bmp文件" << endl;}else {cout << "bmp" << i + 1 << "文件头读取成功并且是bmp文件" << endl;}//读取bmp信息头if (fread(&Info_header, sizeof(BITMAPINFOHEADER), 1, bmpFile) != 1){cout << "bmp" << i + 1 << "的信息头无法读取" << endl;}else {cout << "bmp" << i + 1 << "信息头读取成功" << endl;}//通过结构体实例的成员函数 来调用参数bmpwidth = Info_header.biWidth;bmpheight = Info_header.biHeight;//宽是4的倍数if ((Info_header.biWidth % 4) == 0) {width = Info_header.biWidth;}else {width = Info_header.biWidth+4- Info_header.biWidth % 4;}//高是偶数if ((Info_header.biHeight % 2) == 0) {height = Info_header.biHeight;}else {height = Info_header.biHeight + 1;}cout << width << " " << height << endl;//分配缓存区//分配空间rgbBuf = (unsigned char*)malloc(width * height * 3);yBuf = (unsigned char*)malloc(width * height);uBuf = (unsigned char*)malloc(width * height / 4);vBuf = (unsigned char*)malloc(width * height / 4);//读取BMP文件的RGBreadrgb(bmpFile, File_header, Info_header, rgbBuf, width ,height);//rgb2yuvrgb2yuv(rgbBuf, yBuf, uBuf, vBuf, width, height);//yuv写入输出文件outyuv(yBuf, uBuf, vBuf, width * height, yuvFile);fclose(bmpFile);}free(rgbBuf);free(yBuf);free(uBuf);free(vBuf);fclose(yuvFile);return 0;
}

实验结果

yuv

这篇关于图像文件的读写和转换:BMPtoYUV的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/349998

相关文章

Java数字转换工具类NumberUtil的使用

《Java数字转换工具类NumberUtil的使用》NumberUtil是一个功能强大的Java工具类,用于处理数字的各种操作,包括数值运算、格式化、随机数生成和数值判断,下面就来介绍一下Number... 目录一、NumberUtil类概述二、主要功能介绍1. 数值运算2. 格式化3. 数值判断4. 随机

C语言中自动与强制转换全解析

《C语言中自动与强制转换全解析》在编写C程序时,类型转换是确保数据正确性和一致性的关键环节,无论是隐式转换还是显式转换,都各有特点和应用场景,本文将详细探讨C语言中的类型转换机制,帮助您更好地理解并在... 目录类型转换的重要性自动类型转换(隐式转换)强制类型转换(显式转换)常见错误与注意事项总结与建议类型

Python实现视频转换为音频的方法详解

《Python实现视频转换为音频的方法详解》这篇文章主要为大家详细Python如何将视频转换为音频并将音频文件保存到特定文件夹下,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. python需求的任务2. Python代码的实现3. 代码修改的位置4. 运行结果5. 注意事项

使用Python实现图片和base64转换工具

《使用Python实现图片和base64转换工具》这篇文章主要为大家详细介绍了如何使用Python中的base64模块编写一个工具,可以实现图片和Base64编码之间的转换,感兴趣的小伙伴可以了解下... 简介使用python的base64模块来实现图片和Base64编码之间的转换。可以将图片转换为Bas

Linux使用dd命令来复制和转换数据的操作方法

《Linux使用dd命令来复制和转换数据的操作方法》Linux中的dd命令是一个功能强大的数据复制和转换实用程序,它以较低级别运行,通常用于创建可启动的USB驱动器、克隆磁盘和生成随机数据等任务,本文... 目录简介功能和能力语法常用选项示例用法基础用法创建可启动www.chinasem.cn的 USB 驱动

Python实现高效地读写大型文件

《Python实现高效地读写大型文件》Python如何读写的是大型文件,有没有什么方法来提高效率呢,这篇文章就来和大家聊聊如何在Python中高效地读写大型文件,需要的可以了解下... 目录一、逐行读取大型文件二、分块读取大型文件三、使用 mmap 模块进行内存映射文件操作(适用于大文件)四、使用 pand

C# 读写ini文件操作实现

《C#读写ini文件操作实现》本文主要介绍了C#读写ini文件操作实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录一、INI文件结构二、读取INI文件中的数据在C#应用程序中,常将INI文件作为配置文件,用于存储应用程序的

Python 标准库time时间的访问和转换问题小结

《Python标准库time时间的访问和转换问题小结》time模块为Python提供了处理时间和日期的多种功能,适用于多种与时间相关的场景,包括获取当前时间、格式化时间、暂停程序执行、计算程序运行时... 目录模块介绍使用场景主要类主要函数 - time()- sleep()- localtime()- g

JAVA中整型数组、字符串数组、整型数和字符串 的创建与转换的方法

《JAVA中整型数组、字符串数组、整型数和字符串的创建与转换的方法》本文介绍了Java中字符串、字符数组和整型数组的创建方法,以及它们之间的转换方法,还详细讲解了字符串中的一些常用方法,如index... 目录一、字符串、字符数组和整型数组的创建1、字符串的创建方法1.1 通过引用字符数组来创建字符串1.2

C#实现文件读写到SQLite数据库

《C#实现文件读写到SQLite数据库》这篇文章主要为大家详细介绍了使用C#将文件读写到SQLite数据库的几种方法,文中的示例代码讲解详细,感兴趣的小伙伴可以参考一下... 目录1. 使用 BLOB 存储文件2. 存储文件路径3. 分块存储文件《文件读写到SQLite数据库China编程的方法》博客中,介绍了文