2 图像处理
前言:所有的图像文件,都是一种二进制格式文件,每一个图像文件,都可以通过解析文件中的每一组二进制数的含义来获得文件中的各种信息,如图像高度,宽度,像素位数等等。只是不同的文件格式所代表的二进制数含义不一样罢了。我们可以通过UltraEdit软件打开图像文件并查看里面的二进制数排列。
2.1 BMP图像处理
2.1.1 BMP文件格式解析
BMP是一种常见的图像格式,BMP文件可看成由4个部分组成:位图文件头(bitmap-file header)、位图信息头(bitmap-information header)、调色板(color palette)和定义位图的字节阵列。以最简单的24位真彩色BMP文件作例子讲解:
- 位图文件头(bitmap-file header)
这部分可以理解为是一个结构体,里面的每一个成员都表示一个属性
位数文件头由以下信息组成:
名称 | 字节数 | 含义 |
---|---|---|
bfType | 2字节 | 表明它是BMP格式的文件, 内容固定为0x42,0x4D, 即ASCII字符中的“B”“M” |
bfSize | 4字节 | BMP文件的大小,单位为字节 |
bfReserved1 | 2字节 | 保留 |
bfReserved2 | 2字节 | 保留 |
我们用UltraEdit打开一个BMP文件,可以看到如下信息
这是该BMP文件前32字节的数据,可以看到,前两个字节分别为0x42,0x4D;
接着后面4个字节依次是0x36,0xF9,0x15,0x00。
在BMP格式中,文件的存储方式是小端模式,即如果一个数据需要用几个字节来表示的话,那么,低位数据存在低位地址上,高位数据存在高位地址上。类似的,还有大端模式,即:如果一个数据需要用几个字节来表示的话,那么,低位数据存在高位地址上,高位数据存在低位地址上。
所以0x36,0xF9,0x15,0x00四个数据拼接方法应该是:0x0015F936(在数字中个位即最右边才是最低位),它正好就是这个文件的大小:
紧接着是4个保留位字节,其数据必须为0x00。
最后是4个字节的便宜位,可以看到位图文件头+位图信息头+调色板的大小应该是0x36。
- 位图信息头(bitmap-information header)
位图信息头也可以理解为是一个结构体,其成员有:
名称 | 字节数 | 含义 |
---|---|---|
biSize | 4 | 整个位图信息头结构体的大小 |
biWidth | 4 | 图像宽度,单位为像素 |
biHeight | 4 | 图像高度,单位为像素。 此外,这个数的 正负可以判断图像是正向还是倒向的,若为 正,则表示是正向;若为负,则表示反向。 其实根本不同就是坐标系的建立方法不一样。 后面写代码时会讲。 |
biPlanes | 2 | 颜色平面书,其值总为1 |
biBitCount | 2 | 即1个像素用多少位的数据来表示,其值可 能为1,4,8,16,24,32。我们是以24位 真彩色为例子讲解的 |
biCompression | 4 | 数据的压缩类型 |
biSizeImage | 4 | 图像数据的大小,单位为字节 |
biXPelsPerMeter | 4 | 水平分辨率,单位是像素/米 |
biYPelsPerMeter | 4 | 垂直分辨率,单位是像素/米 |
biClrUsed | 4 | 调色板中的颜色索引数 |
biClrImportant | 4 | 说明有对图像有重要影响的颜色索引的数 目,若为0,表示都重要 |
对照源文件数据:
0E-11:00000028h = 40,表示这个结构体大小是40字节。
12-15:00000320h = 800,图像宽为800像素。
16-19:00000258h = 600,图像高为600像素,与文件属性一致。这是一个正数,说明图像是正向的,数据是以图像左下角为原点,以水平向右为X轴正方向,以垂直向上为Y轴正方向排列的。若为负,则说明图像是反向的,数据是以图像左上角角为原点,以水平向右为X轴正方向,以垂直向下为Y轴正方向排列的。
1A-1B:0001h, 该值总为1。
1C-1D:0018h = 24, 表示每个像素占24个比特,即24位真彩色
上面这几个信息跟文件属性是一致的:
1E-21:00000000h,BI_RGB, 说明本图像不压缩。
22-25:00000000h,图像的大小,因为使用BI_RGB,所以设置为0。
26-29:00000000h,水平分辨率,缺省。
2A-2D:00000000h,垂直分辨率,缺省。
2E-31:00000000h,对于24位真彩色来说,是没有调色板的,所以为0。
32-35:00000000h,对于24位真彩色来说,是没有调色板的,所以为0。
- 调色板(color palette)
24位真彩色没有调色板,这里为了简化不赘诉。
- 定义位图的字节阵列
这一部分就是真正的图像数据了,24位真彩色数据是按按BGR各一字节循环排列而成。
2.1.2 代码实现:将BMP文件解析为RGB格式,在LCD上显示
让BMP文件在开发板的LCD上显示出来,有几个需要注意的点:
- 开发板LCD上的显示格式是RGB格式的,而且有多种表示格式:可能用2字节表示(RGB565格式),可能用3字节表示(RGB888),而原始的24位真彩色BMP文件则是按BGR格式排列的,需要对原始的图像数据进行转化。
- 在转化过程中,LCD上的显存地址固定是以LCD左上角为首地址,而BMP格式中正向图像是以图片的左下角为数据首地址的。因此在进行数据转化时还需要注意坐标的变换。
代码清单2.1实现了将24位真彩色的BMP图像转化为RGB格式
代码清单2.1
1. /**********************************************************************
2. * 函数名称: IsBmp
3. * 功能描述: 判断该文件是否为BMP文件
4. * 输入参数: ptFileMap - 内含文件信息
5. * 输出参数: 无
6. * 返 回 值: 0 - 是BMP格式, -1 -不是BMP格式
7. ***********************************************************************/
8. int IsBmp(FILE **ppFp, const char *strFileName)
9. {
10. char strCheckHeader[2];
11. *ppFp= fopen(strFileName, "rb+");
12. if (*ppFp== NULL) {
13. return -1;
14. }
15. if (fread(strCheckHeader, 1, 2, *ppFp) != 2)
16. return -1;
17.
18. if (strCheckHeader[0] != 0x42 || strCheckHeader[1] != 0x4d)
19. return -1;
20. else
21. return 0;
22. }
23.
24.
25.
26. /**********************************************************************
27. * 函数名称: MapFile
28. * 功能描述: 使用mmap函数映射一个文件到内存,以后就可以直接通过内存来访问文件
29. * 输入参数: PT_PictureData ptData 内含图像数据
30. * 输出参数: ptData->iFileSize : 文件大小
31. * ptData->pucFileData : 映射内存的首地址
32. * 返 回 值: 0 - 成功其他值 - 失败
33. ***********************************************************************/
34. int MapFile(PT_PictureData ptData)
35. {
36. int iFd;
37. struct stat tStat;
38.
39. /* 打开文件 */
40. iFd = fileno(ptData->ptFp);
41. fstat(iFd, &tStat);
42. ptData->iFileSize= tStat.st_size;
43. ptData->pucFileData= (unsigned char *)mmap(NULL , tStat.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, iFd, 0);
44. if (ptData->pucFileData == (unsigned char *)-1)
45. {
46. printf("mmap error!\n");
47. return -1;
48. }
49. return 0;
50. }
51.
52. /**********************************************************************
53. * 函数名称: DecodeBmp2Rgb
54. * 功能描述:把BMP文件转化为rgb格式
55. * 输入参数: strFileName - 文件名
56. * ptData - 内含图像信息
57. * 返 回 值: 0 - 成功其他值 - 失败
58. * -1 - 文件不是BMP格式
59. * -2 - 不支持的bpp
60. * -3 - 图像缓存区分配失败
61. ***********************************************************************/
62. static int DecodeBmp2Rgb(const char *strFileName, PT_PictureData ptData) {
63. int x,y;
64. int iPos = 0;
65. int iLineWidthAlign;
66. BITMAPFILEHEADER *ptBITMAPFILEHEADER;
67. BITMAPINFOHEADER *ptBITMAPINFOHEADER;
68. unsigned char *aFileHead;
69. unsigned char *pucSrc;
70. unsigned char *pucDest;
71. int iLineBytes;
72.
73. /* 判断该文件是否为BMP格式 */
74. if (IsBmp(&ptData->ptFp, strFileName))
75. return -1;
76.
77. /* 将BMP文件映射到内存空间 */
78. MapFile(ptData);
79.
80.
81. aFileHead = ptData->pucFileData;
82.
83. ptBITMAPFILEHEADER = (BITMAPFILEHEADER *)aFileHead;
84. ptBITMAPINFOHEADER = (BITMAPINFOHEADER *)(aFileHead + sizeof(BITMAPFILEHEADER));
85. /* 获取必要的图像信息 */
86. ptData->iWidth = ptBITMAPINFOHEADER->biWidth;
87. ptData->iHeight = ptBITMAPINFOHEADER->biHeight;
88. ptData->iBpp = ptBITMAPINFOHEADER->biBitCount;
89. iLineBytes = ptData->iWidth*ptData->iBpp/8;//一行数据的字节数
90. ptData->iBmpDataSize= ptData->iHeight * iLineBytes;//整个BMP图像的字节数
91. /*暂时只支持24bpp格式*/
92. if (ptData->iBpp != 24)
93. {
94. printf("iBMPBpp = %d\n", ptData->iBpp);
95. printf("sizeof(BITMAPFILEHEADER) = %d\n", sizeof(BITMAPFILEHEADER));
96. return -2;
97. }
98.
99. /* 分配空间 */
100. ptData->pucBmpData = malloc(ptData->iBmpDataSize);
101. ptData->pucRgbData = malloc(ptData->iBmpDataSize);
102.
103. if (NULL == ptData->pucBmpData||NULL == ptData->pucRgbData)
104. return -2;
105.
106. /* 从bmp文件中读取图像信息,24bpp的BMP图像为BGR格式 */
107. pucDest = ptData->pucBmpData;
108. iLineWidthAlign = (iLineBytes + 3) & ~0x3; /* 向4取整 */
109. pucSrc = aFileHead + ptBITMAPFILEHEADER->bfOffBits;
110.
111. pucSrc = pucSrc + (ptData->iHeight - 1) * iLineWidthAlign;
112.
113. /* 对于bmp文件中的源数据,是以左下角为原点计算坐标的,因此拷贝数据时需要转换坐标 */
114. for (y = 0; y < ptData->iHeight; y++)
115. {
116. memcpy(pucDest, pucSrc, ptData->iWidth*3);
117. pucSrc -= iLineWidthAlign;
118. pucDest += iLineBytes;
119. }
120.
121.
122. /* 将得到的BGR数据转化为RGB数据 */
123. for (y = 0; y < ptData->iHeight; y++){
124. for(x = 0;x<ptData->iWidth*3;x+=3){
125. ptData->pucRgbData[iPos++] = ptData->pucBmpData[y*ptData->iWidth*3+x+2];
126. ptData->pucRgbData[iPos++] = ptData->pucBmpData[y*ptData->iWidth*3+x+1];
127. ptData->pucRgbData[iPos++] = ptData->pucBmpData[y*ptData->iWidth*3+x+0];
128. }
129. }
130.
131. return 0;
132.
133. }