Linux应用开发第二章图像处理应用开发
Posted 韦东山
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux应用开发第二章图像处理应用开发相关的知识,希望对你有一定的参考价值。
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.
2.2 JPEG图像处理
2.2.1 JPEG文件格式和libjpeg编译
JPEG的后缀名为.jpg的图像文件。对于图像内容和信息相同的JPEG文件和BMP文件,JPEG格式的文件要比BMP格式的文件小得多,这是因为JPEG文件是经过JPEG压缩算法后得到的一种文件格式。
相对于BMP格式的文件,JPEG由于压缩算法的关系,其文件解析较为复杂,我们可以利用Linux系统开源的优点,使用开源工具对jpeg文件进行格式的解析和转换。
我们可以使用libjpeg库来对jpeg文件进行格式的解析和转换。libjpeg支持X86,ARM等架构。libjpeg是开源工具,所以可以在网上免费下载。
在使用libjpeg之前,我们先要交叉编译libjpeg的库文件和头文件并存到开发板的文件系统中。以下是libjpeg的编译过程:
- 解压并进入文件目录
tar xzf libjpeg-turbo-1.2.1.tar.gz
cd libjpeg-turbo-1.2.1/
- 交叉编译
tar xzf libjpeg-turbo-1.2.1.tar.gz
./configure --prefix=/work/projects/libjpeg-turbo-1.2.1/tmp/ --host=arm-linux
make
make install
- 将编译出来的头文件和库文件拷贝到交叉编译器的相应目录下
cd /work/projects/libjpeg-turbo-1.2.1/tmp/include
cp * /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include
cd /work/projects/libjpeg-turbo-1.2.1/tmp/lib
cp *so* -d /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/armv4t/lib
- 将编译出来的头文件和库文件拷贝到开发板文件系统的相应目录下
cd /work/projects/libjpeg-turbo-1.2.1/tmp/lib
cp *.so* /work/nfs_root/fs_mini_mdev_new/lib/ -d
2.2.2 libjpeg接口函数的解析和使用
libjpeg的使用方法可以参考解压包中的使用说明libjpeg.txt和例程example.c。libjpeg的使用步骤简单总结如下:
1. 分配和初始化一个jpeg_compress_struct结构体
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_decompress(&cinfo);
2. 指定源文件
jpeg_stdio_src(&cinfo, infile);
参数1是步骤1中分配的jpeg_compress_struct类型的结构体
参数2是要解析的JPEG文件的文件句柄。
3. 获得jpg信息头并设置解压参数
jpeg_read_header(&cinfo, TRUE);
当调用完这个参数之后,我们就可以通过cinfo中的image_width,image_height等成员来获得图像的信息了。此外我们还可以设置cinfo中的scale_num和scale_denom等成员变量来设置解压参数。
4. 启动解压
jpeg_start_decompress(&cinfo);
调用这个函数后,就可以对cinfo所指定的源文件进行解压,并将解压后的数据存到cinfo结构体的成员变量中。
5. 读取解压后数据
jpeg_read_scanlines(&cinfo, buffer, 1);
调用这个函数后,可以读取RGB数据到buffer中,参数3能指定读取多少行
6. 完成读取
jpeg_finish_decompress(&cinfo);
7. 释放jpeg_compress_struct结构体
jpeg_destroy_decompress(&cinfo);
完成读取后释放结构体
2.2.3 使用libjpeg把JPEG文件解析为RGB格式,在LCD上显示
根据上节的解析,利用上述的库函数将JPEG文件解析为RGB格式了。
代码清单2.2
1. /**********************************************************************
2. * 函数名称: IsJpg
3. * 功能描述:判断是否为Jpg文件
4. * 输入参数: ptData - 内含图像信息
5. strFileName - 文件名
6. * 返 回 值:0 - 不是JPG格式 其他-是JPG格式
7. ***********************************************************************/
8. static int IsJpg(PT_PictureData ptData, const char *strFileName)
9.
10. int iRet;
11.
12. jpeg_stdio_src(&ptData->tInfo, ptData->ptFp);
13.
14. /* 用jpeg_read_header获得jpeg信息*/
15. iRet = jpeg_read_header(&ptData->tInfo, TRUE);
16.
17. return (iRet == JPEG_HEADER_OK);
18.
19.
20. /**********************************************************************
21. * 函数名称: DecodeJpg2Rgb
22. * 功能描述:把JPG文件解析为RGB888格式
23. * 输入参数: ptData - 内含文件信息
24. * strFileName - 文件名
25. * 输出参数:PT_PictureData->pucRgbData - 内含rgb数据
26. * 返 回 值:0 - 成功 其他-失败
27. ***********************************************************************/
28. static int DecodeJpg2Rgb(const char *strFileName, PT_PictureData ptData)
29. int iRowSize;
30. unsigned char *pucbuffer;
31. unsigned char *pucHelp;//辅助拷贝变量
32.
33. /* 1.分配和初始化一个jpeg_compress_struct结构体 */
34. ptData->tInfo.err = jpeg_std_error(&ptData->tJerr);
35. jpeg_create_decompress(&ptData->tInfo);
36.
37.
38. /* 2.指定源文件*/
39. if ((ptData->ptFp= fopen(strFileName, "rb")) == NULL)
40. fprintf(stderr, "can't open %s\\n", strFileName);
41. return -1;
42.
43.
44. /* 3.获得jpg信息头并设置解压参数并判断是否为JPEG格式文件 */
45. if (!IsJpg(ptData, strFileName))
46. printf("file is not jpg ...\\n");
47. return -1;
48.
49.
50.
51.
52. /* 默认尺寸为原尺寸 */
53. ptData->tInfo.scale_num = 1;
54. ptData->tInfo.scale_denom = 1;
55. /* 4. 启动解压:jpeg_start_decompress */
56. jpeg_start_decompress(&ptData->tInfo);
57.
58.
59. /* 解压完成后可以通过tInfo中的成员获得图像的某些信息 */
60. ptData->iWidth= ptData->tInfo.output_width;
61. ptData->iHeight = ptData->tInfo.output_height;
62. ptData->iBpp = ptData->tInfo.output_components*8;
63. /* 计算一行的数据长度 */
64. iRowSize = ptData->iWidth * ptData->tInfo.output_components;
65. pucbuffer = malloc(iRowSize);
66. ptData->iRgbSize= iRowSize * ptData->iHeight;
67. ptData->pucRgbData = malloc(ptData->iRgbSize);
68.
69. /* pucHelp指向ptData->pucRgbData首地址 */
70. pucHelp = ptData->pucRgbData;
71. /* 5.循环调用jpeg_read_scanlines来一行一行地获得解压的数据 */
72. while (ptData->tInfo.output_scanline < ptData->tInfo.output_height)
73.
74. /* 调用jpeg_read_scanlines得到的时候会存到pucbuffer中 */
75. jpeg_read_scanlines(&ptData->tInfo, &pucbuffer, 1);
76. /* 将数据一行行读到缓冲区中 */
77. memcpy(pucHelp,pucbuffer,iRowSize);
78. pucHelp += iRowSize;
79.
80. free(pucbuffer);
81. /* 6.完成读取 */
82. jpeg_finish_decompress(&ptData->tInfo);
83. /* 7.释放jpeg_compress_struct结构体 */
84. jpeg_destroy_decompress(&ptData->tInfo);
85. return 0;
86.
2.3 PNG图像处理
2.3.1 PNG文件格式和libpng编译
跟JPEG文件格式一样,PNG也是一种使用了算法压缩后的图像格式,与JPEG不同,PNG使用从LZ77派生的无损数据压缩算法。对于PNG文件格式,也有相应的开源工具libpng。
libpng库可从官网上下载最新的源代码:
http://www.libpng.org/pub/png/libpng.html
在使用libpng之前,我们先要交叉编译libpng的库文件和头文件并存到开发板的文件系统中。以下是libpng的编译过程:
- 解压并进入文件目录
tar xzf libpng-1.6.37.tar.gz
cd libpng-1.6.37/
- 交叉编译
./configure --prefix=/work/projects/libpng-1.6.37/tmp/ --host=arm-linux
make
make install
- 将编译出来的头文件和库文件拷贝到交叉编译器的相应目录下
cd /work/projects/libpng-1.6.37/tmp/include
cp * /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include
cd /work/projects/libpng-1.6.37/tmp/lib
cp *so* -d /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/armv4t/lib
- 将编译出来的头文件和库文件拷贝到开发板文件系统的相应目录下
cd 分享前端开发常用代码片段
如何在Android中加载带有动画的cardview GridView?