基于深度学习的车牌识别项目的APP部分之图像预处理(一):C语言读取bmp图像信息

车牌识别项目之图像预处理一:C语言读取bmp图像信息

    • 一、什么是bmp文件
    • 二、BMP格式结构
      • 1、文件信息头
      • 2、图像描述信息块
      • 3、BMP调色板
      • 4、BMP图像数据区
        • ①像素存储
        • ②像素数据
        • ③位图像素格式
    • 三、原理实现:
    • 1、打开一张bmp文件
    • 2、查看图像属性
    • 3、用ULtraEdit打开bmp文件。显示的是16进制的代码。
    • 四、完整代码实现

本文是将用SoC FPGA 板子实现的车牌识别项目,用的改写的LeNet网络。主要实现分俩个大的部分:1、是用fpga实现的卷积、偏置激活、池化和全连接操作。2、是在ARM端实现摄像头读进来的图像数据的预处理操作。我们先介绍的是在ARM对图像的操作,后续继续更新。

一、什么是bmp文件

BMP是bitmap的缩写形式,bitmap顾名思义,就是位图也即Windows位图。它一般由4部分组成:文件头信息块、图像描述信息块、颜色表(在真彩色模式无颜色表)和图像数据区组成。在系统中以BMP为扩展名保存。

图像通常保存的颜色深度有2(1位)、16(4位)、256(8位)、65536(16位)和1670万(24位)种颜色(其中位是表示每点所用的数据位)。8位图像可以是索引彩色图像外,也可以是灰阶图像。表示透明的alpha通道也可以保存在一个类似于灰阶图像的独立文件中。带有集成的alpha通道的32位版本已经随着Windows XP出现,它在Windows系统的登录界面和系统主题中都有使用。

关键:
在读取bmp图片的时候,一定要注意内存对齐的问题,譬如文件头,否则无法读取出正确结果。

关于图片的像素数据,每一行的像素的字节数必须是4的整数倍。如果不是,则需要补齐。一般来说,bmp图像文件的数据是从下到上,从左到右的。即从文件中最先读到的是图像最下面一行的左边第一个像素,然后是坐标第二个…接下来是倒数第二行的第一个像素。

二、BMP格式结构

BMP文件的数据按照从文件头开始的先后顺序分为四个部分:
◆ 位图文件头(bmp file header): 提供文件的格式、大小等信息
◆ 位图信息头(bitmap information):提供图像数据的尺寸、位平面数、压缩方式、颜色索引等信息
◆ 调色板(color palette):可选,如使用索引来表示图像,调色板就是索引与其对应的颜色的映射表
◆ 位图数据(bitmap data):图像数据区

BMP图片文件数据表如下:
基于深度学习的车牌识别项目的APP部分之图像预处理(一):C语言读取bmp图像信息_第1张图片

1、文件信息头

文件信息头 (14字节)存储着文件类型,文件大小等信息。

// 文件信息头结构体
typedef struct tagBITMAPFILEHEADER
{
   /unsigned short bfType;        // 19778,必须是BM字符串,对应的十六进制为0x4d42,十进制为19778,否则不是bmp格式文件
    unsigned int   bfSize;        // 文件大小 以字节为单位(2-5字节)
    unsigned short bfReserved1;   // 保留,必须设置为0 (6-7字节)
    unsigned short bfReserved2;   // 保留,必须设置为0 (8-9字节)
    unsigned int   bfOffBits;     // 从文件头到像素数据的偏移  (10-13字节)
} BITMAPFILEHEADER;

2、图像描述信息块

图片信息头 (40字节)存储着图像的尺寸,颜色索引,位平面数等信息

//图像信息头结构体
typedef struct tagBITMAPINFOHEADER
{
    unsigned int    biSize;          // 此结构体的大小 (14-17字节)
    long            biWidth;         // 图像的宽  (18-21字节)
    long            biHeight;        // 图像的高  (22-25字节)
    unsigned short  biPlanes;        // 表示bmp图片的平面属,显然显示器只有一个平面,所以恒等于1 (26-27字节)
    unsigned short  biBitCount;      // 一像素所占的位数,一般为24   (28-29字节)
    unsigned int    biCompression;   // 说明图象数据压缩的类型,0为不压缩。 (30-33字节)
    unsigned int    biSizeImage;     // 像素数据所占大小, 这个值应该等于上面文件头结构中bfSize-bfOffBits (34-37字节)
    long            biXPelsPerMeter; // 说明水平分辨率,用象素/米表示。一般为0 (38-41字节)
    long            biYPelsPerMeter; // 说明垂直分辨率,用象素/米表示。一般为0 (42-45字节)
    unsigned int    biClrUsed;       // 说明位图实际使用的彩色表中的颜色索引数(设为0的话,则说明使用所有调色板项)。 (46-49字节)
    unsigned int    biClrImportant;  // 说明对图象显示有重要影响的颜色索引的数目,如果是0,表示都重要。(50-53字节)
} BITMAPINFOHEADER;

3、BMP调色板

调色板 (由颜色索引数决定)【可以没有此信息】这部分定义了图像中所用的颜色。如上所述,位图图像一个像素接着一个像素储存,每个像素使用一个或者多个字节的值表示,所以调色板的目的就是要告诉应用程序这些值所对应的实际颜色。

典型的位图文件使用RGB彩色模型。在这种模型中,每种颜色都是由不同强度(从0到最大强度)的红色(R)、绿色(G)和蓝色(B)组成的,也就是说,每种颜色都可以使用红色、绿色和蓝色的值所定义。

在位图文件的实现中,调色板可以包含很多条目,条目个数就是图像中所使用的颜色的个数。

//24位图像素信息结构体,即调色板
typedef struct tagRGBQUAD{
    unsigned char rgbBlue;   //该颜色的蓝色分量  (值范围为0-255)
    unsigned char rgbGreen;  //该颜色的绿色分量  (值范围为0-255)
    unsigned char rgbRed;    //该颜色的红色分量  (值范围为0-255)
    unsigned char rgbReserved;// 保留,必须为0
} RGBQUAD;

每个条目用来描述一种颜色,包含4个字节,其中三个表示蓝色、绿色和红色,第四个字节没有使用(大多数应用程序将它设为0);对于每个字节,数值0表示该颜色分量在当前的颜色中没有使用,而数值255表示这种颜色分量使用最大的强度。

注意

  • 1,4,8位图像才会使用调色板数据,16,24,32位图像不需要调色板数据,即调色板最多只需要256项(索引0 - 255)。
  • 颜色表的大小根据所使用的颜色模式而定:2色图像为8字节;16色图像位64字节;256色图像为1024字节。其中,每4字节表示一种颜色,并以B(蓝色)、G(绿色)、R(红色)、alpha(32位位图的透明度值,一般不需要)。即首先4字节表示颜色号1的颜色,接下来表示颜色号2的颜色,依此类推。
  • 颜色表中RGBQUAD结构数据的个数有biBitCount来确定,当biBitCount=1,4,8时,分别有2,16,256个表项。
  • 当biBitCount=1时,为2色图像,BMP位图中有2个数据结构RGBQUAD,一个调色板占用4字节数据,所以2色图像的调色板长度为2*4为8字节。
  • 当biBitCount=4时,为16色图像,BMP位图中有16个数据结构RGBQUAD,一个调色板占用4字节数据,所以16像的调色板长度为16*4为64字节。
  • 当biBitCount=8时,为256色图像,BMP位图中有256个数据结构RGBQUAD,一个调色板占用4字节数据,所以256色图像的调色板长度为256*4为1024字节。
  • 当biBitCount=16,24或32时,没有颜色表。

4、BMP图像数据区

①像素存储

表示位图中像素的比特是以行为单位对齐存储的,每一行的大小都向上取整为4字节(32位DWORD)的倍数。如果图像的高度大于1,多个经过填充实现对齐的行就形成了像素数组。

完整存储的一行像素所需的字节数可以通过这个公式计算:
在这里插入图片描述
Windows规定一个扫描行所占的字节数必须是4的倍数(即以long为单位),不足的以0填充,

//一个扫描行所占的字节数计算方法:
DataSizePerLine= (biWidth* biBitCount+31)/8;
//一个扫描行所占的字节数
DataSizePerLine= DataSizePerLine/4*4; // 字节数必须是4的倍数
//位图数据的大小(不压缩情况下):
DataSize= DataSizePerLine* biHeight;

BMP位图要求一行的字节数为4的整数倍,4字节就是32位,根据整数除法的规则,所以加31(这其实就是数学上的取整运算的计算机实现)!

 (biWidth*biBitCount+31)/32*4 公式算出来的是一行的字节数。

②像素数据

位图数据记录了位图的每一个像素值,记录顺序是在扫描行内是从左到右,扫描行之间是从下到上。
基于深度学习的车牌识别项目的APP部分之图像预处理(一):C语言读取bmp图像信息_第2张图片
通常,像素是从下到上、从左到右保存的。但如果使用的不是BITMAPCOREHEADER,那么未压缩的Windows位图还可以从上到下存储,此时图像高度为负值。

每一行的末尾通过填充若干个字节的数据(并不一定为0)使该行的长度为4字节的倍数。像素数组读入内存后,每一行的起始地址必须为4的倍数。这个限制仅针对内存中的像素数组,针对存储时,仅要求每一行的大小为4字节的倍数,对文件的偏移没有限制。

例如:对于24位色的位图,如果它的宽度为1像素,那么除了每一行的数据(蓝、绿、红)需要占3字节外,还会填充1字节;而如果宽为2像素,则需要2字节的填充;宽为3像素时,需要3字节填充;宽为4像素时则不需要填充。

图像相同的条件下,位图图像文件通常比使用其它压缩算法的图像文件大很多。

③位图像素格式

无论是磁盘上的位图文件还是内存中的位图图像,像素都由一组位(英语:bit)表示。

  • 每像素占1位(色深为1位,1bpp)的格式支持2种不同颜色。像素值直接对应一个位的值,最左像素对应第一个字节的最高位。使用该位的值用来对色表的索引:为0表示色表中的第一项,为1表示色表中的第二项(即最后一项)。
  • 每像素占2位(色深为2位,2bpp)的格式支持4种不同颜色。每个字节对应4个像素,最左像素为最高的两位(仅在WindowsCE中有效)。需要使用像素值来对一张含有4个颜色值的色表进行索引。
  • 每像素占4位(色深为4位,4bpp)的格式支持16种不同的颜色。每个字节对应2个像素,最左像素为最高的四位。需要使用像素值来对一张含有16个颜色值的色表进行索引。
  • 每像素占8位(色深为8位,8bpp)的格式支持256种不同的颜色。每个字节对应1个像素。需要使用像素值来对一张含有256个颜色值的色表进行索引。
  • 每像素占16位(色深为16位,16bpp)的格式支持65536种不同的颜色,每2个字节(byte)对应一个像素。该像素的不透明度(英语:alpha)、红、绿、蓝采样值即存储在该2个字节中。
  • 每像素占24位(色深为24位,24bpp)的格式支持16777216种不同的颜色,每3个字节对应一个像素。
  • 每像素占32位(色深为32位,32bpp)的格式支持4294967296种不同的颜色,每4个字节对应一个像素。

简单来说就是如下:
位图的一个像素值所占的字节数:
当biBitCount=1时,8个像素占1个字节;
当biBitCount=4时,2个像素占1个字节;
当biBitCount=8时,1个像素占1个字节;
当biBitCount=16时,1个像素占2个字节;
当biBitCount=24时,1个像素占3个字节;
当biBitCount=32时,1个像素占4个字节;

三、原理实现:

这里我们先用Lena.bmp图像实现,后续便于我们给你更好的理解车牌。

1、打开一张bmp文件

基于深度学习的车牌识别项目的APP部分之图像预处理(一):C语言读取bmp图像信息_第3张图片

2、查看图像属性

基于深度学习的车牌识别项目的APP部分之图像预处理(一):C语言读取bmp图像信息_第4张图片

3、用ULtraEdit打开bmp文件。显示的是16进制的代码。

用其他软件打开也是可以的
基于深度学习的车牌识别项目的APP部分之图像预处理(一):C语言读取bmp图像信息_第5张图片
现在我们来读取这些代码,看看他们到底保存了一些啥东西。 在这里要注意的是Windows的数据是倒着念的,(小端存储:低地址存低数据 。如果不理解的可以参考此文连接,请点击:)这是PC电脑的特色。如果一段数据为42 4D,倒着念就是4D 42,即0x4D42。 因此,如果bfSize的数据为A2 1E 04 00,实际上就成了0x00041EA2,也就是0x41EA2。
基于深度学习的车牌识别项目的APP部分之图像预处理(一):C语言读取bmp图像信息_第6张图片
基于深度学习的车牌识别项目的APP部分之图像预处理(一):C语言读取bmp图像信息_第7张图片
上述圈起来的信息用代码读出来发现一一对应:

unsigned short bfType          = 0x4D42     = 19778
unsigned int   bfSize          = 0xC0036    = 786486字节=786486/1024=768kb
unsigned short bfReserved1     = 00 00
unsigned short bfReserved2     = 00 00
unsigned int   bfOffBits       = 0X0000036 = 0x36 = 54字节

unsigned int   biSize          = 0x00000028 = 0x28  = 40字节
int            biWidth         = 0x00000200 = 0x200 = 512像素;
int            biHeight        = 0x00000200 = 0x200 = 512像素 ;
unsigned short biPlanes        = 0x0001 =0x1 = 1;
unsigned short biBitCount      = 0x0018     = 0x18  = 24;
unsigned int   biCompression   = 0x00000000 = 0;
unsigned int   biSizeImage     = 0x000C0000 = 0xC0000=786,432;
int            biXPelsPerMeter = 0x00000000 = 0;
int            biYPelsPerMeter = 0x00000000 = 0;  
unsigned int   biClrUsed       = 0x00000000 = 0;
unsigned int   biClrImportant  = 0x00000000 = 0;

unsigned char rgbBlue  = 0x9E5137 其中R:0X37 G:0X51 B:0X9E ;   //该颜色的蓝色分量  (值范围为0-255)
unsigned char rgbGreen = 0xA5583E 其中R:0X3E G:0X58 B:0XA5 ;   //该颜色的绿色分量  (值范围为0-255)
unsigned char rgbRed   = 0x7A5A3E 其中R:0X3E G:0X5A B:0X7A ;   //该颜色的红色分量  (值范围为0-255)
unsigned char rgbReserved;  // 保留,必须为0

四、完整代码实现

BmpFormat.h

# ifndef BMP_H
# define BMP_H
/*
BMP格式
这种格式内的数据分为三到四个部分,依次是:
文件信息头 (14字节)存储着文件类型,文件大小等信息
图片信息头 (40字节)存储着图像的尺寸,颜色索引,位平面数等信息
调色板 (由颜色索引数决定)【可以没有此信息】
位图数据 (由图像尺寸决定)每一个像素的信息在这里存储

一般的bmp图像都是24位,也就是真彩。每8位为一字节,24位也就是使用三字节来存储每一个像素的信息,三个字节对应存放r,g,b三原色的数据,
每个字节的存贮范围都是0-255。那么以此类推,32位图即每像素存储r,g,b,a(Alpha通道,存储透明度)四种数据。8位图就是只有灰度这一种信息,
还有二值图,它只有两种颜色,黑或者白。
*/
// 文件信息头结构体
typedef struct tagBITMAPFILEHEADER
{
    //unsigned short bfType;        // 19778,必须是BM字符串,对应的十六进制为0x4d42,十进制为19778,否则不是bmp格式文件
    unsigned int   bfSize;        // 文件大小 以字节为单位(2-5字节)
    unsigned short bfReserved1;   // 保留,必须设置为0 (6-7字节)
    unsigned short bfReserved2;   // 保留,必须设置为0 (8-9字节)
    unsigned int   bfOffBits;     // 从文件头到像素数据的偏移  (10-13字节)
} BITMAPFILEHEADER;

//图像信息头结构体
typedef struct tagBITMAPINFOHEADER
{
    unsigned int    biSize;          // 此结构体的大小 (14-17字节)
    long            biWidth;         // 图像的宽  (18-21字节)
    long            biHeight;        // 图像的高  (22-25字节)
    unsigned short  biPlanes;        // 表示bmp图片的平面属,显然显示器只有一个平面,所以恒等于1 (26-27字节)
    unsigned short  biBitCount;      // 一像素所占的位数,一般为24   (28-29字节)
    unsigned int    biCompression;   // 说明图象数据压缩的类型,0为不压缩。 (30-33字节)
    unsigned int    biSizeImage;     // 像素数据所占大小, 这个值应该等于上面文件头结构中bfSize-bfOffBits (34-37字节)
    long            biXPelsPerMeter; // 说明水平分辨率,用象素/米表示。一般为0 (38-41字节)
    long            biYPelsPerMeter; // 说明垂直分辨率,用象素/米表示。一般为0 (42-45字节)
    unsigned int    biClrUsed;       // 说明位图实际使用的彩色表中的颜色索引数(设为0的话,则说明使用所有调色板项)。 (46-49字节)
    unsigned int    biClrImportant;  // 说明对图象显示有重要影响的颜色索引的数目,如果是0,表示都重要。(50-53字节)
} BITMAPINFOHEADER;

//24位图像素信息结构体,即调色板
typedef struct tagRGBQUAD{
    unsigned char rgbBlue;   //该颜色的蓝色分量  (值范围为0-255)
    unsigned char rgbGreen;  //该颜色的绿色分量  (值范围为0-255)
    unsigned char rgbRed;    //该颜色的红色分量  (值范围为0-255)
    unsigned char rgbReserved;// 保留,必须为0
} RGBQUAD;

#endif

read_bmp.c

#include 
#include 
#include "BmpFormat.h"

BITMAPFILEHEADER fileHeader;
BITMAPINFOHEADER infoHeader;

void showBmpHead(BITMAPFILEHEADER pBmpHead)
{  //定义显示信息的函数,传入文件头结构体
    printf("BMP文件大小:%dkb\n", fileHeader.bfSize/1024);
    printf("保留字必须为0:%d\n",  fileHeader.bfReserved1);
    printf("保留字必须为0:%d\n",  fileHeader.bfReserved2);
    printf("实际位图数据的偏移字节数: %d\n",  fileHeader.bfOffBits);
}
void showBmpInfoHead(BITMAPINFOHEADER pBmpinfoHead)
{//定义显示信息的函数,传入的是信息头结构体
   printf("位图信息头:\n" );
   printf("信息头的大小:%d\n" ,infoHeader.biSize);
   printf("位图宽度:%d\n" ,infoHeader.biWidth);
   printf("位图高度:%d\n" ,infoHeader.biHeight);
   printf("图像的位面数(位面数是调色板的数量,默认为1个调色板):%d\n" ,infoHeader.biPlanes);
   printf("每个像素的位数:%d\n" ,infoHeader.biBitCount);
   printf("压缩方式:%d\n" ,infoHeader.biCompression);
   printf("图像的大小:%d\n" ,infoHeader.biSizeImage);
   printf("水平方向分辨率:%d\n" ,infoHeader.biXPelsPerMeter);
   printf("垂直方向分辨率:%d\n" ,infoHeader.biYPelsPerMeter);
   printf("使用的颜色数:%d\n" ,infoHeader.biClrUsed);
   printf("重要颜色数:%d\n" ,infoHeader.biClrImportant);
}

int main()
{
    FILE* fp;
    fp = fopen("lena.bmp", "rb");//读取同目录下的image.bmp文件。
    if(fp == NULL)
    {
        printf("打开'lena.bmp'失败!\n");
        return -1;
    }
    //如果不先读取bifType,根据C语言结构体Sizeof运算规则——整体大于部分之和,从而导致读文件错位
    unsigned short  fileType;
    fread(&fileType,1,sizeof (unsigned short), fp);  
    if (fileType == 0x4d42)
    {
        printf("文件类型标识正确!" );  
        printf("\n文件标识符:%d\n", fileType);
        fread(&fileHeader, 1, sizeof(BITMAPFILEHEADER), fp);
        showBmpHead(fileHeader);
        fread(&infoHeader, 1, sizeof(BITMAPINFOHEADER), fp);
        showBmpInfoHead(infoHeader);
        fclose(fp);
    }
}

运行结果:
基于深度学习的车牌识别项目的APP部分之图像预处理(一):C语言读取bmp图像信息_第8张图片
原理:参考百度百科和维基百科

完整的车牌识别项目若有需要请私聊博主。

你可能感兴趣的:(C语言,SOC,FPGA,c语言,图像处理,windows,fpga)