平台:STM32F1+FATFS+SD卡


基于STM32的BMP图片处理

  • 处理结果
  • 原图 test.bmp
  • 灰度图 gray.bmp
  • 缩放 zoom.bmp
  • 代码实现
  • 调试过程遇到的几个问题
  • 双线性插值
  • 字节对齐
  • 图片读写
  • 参考文档


处理结果

原图 test.bmp

stm32图像分类 stm32处理图片_图像处理


stm32图像分类 stm32处理图片_stm32图像分类_02


stm32图像分类 stm32处理图片_stm32图像分类_03

灰度图 gray.bmp

stm32图像分类 stm32处理图片_c语言_04


stm32图像分类 stm32处理图片_bmp_05


stm32图像分类 stm32处理图片_stm32图像分类_06

缩放 zoom.bmp

失真不明显哈,估计是我缩放的比例太小 795 * 457 => 1280 * 720

插值采用的是双线性插值法

stm32图像分类 stm32处理图片_bmp_07


stm32图像分类 stm32处理图片_c语言_08


stm32图像分类 stm32处理图片_stm32_09

代码实现

FATFS myfs;                 	// Work area (file system object) for logical drive
 
BITMAPFILEHEADER bitHead;		//原图头信息
BITMAPINFOHEADER bitInfoHead;	
BITMAPFILEHEADER grayHead;		//灰度化文件头信息
BITMAPINFOHEADER grayInfoHead;	
BITMAPFILEHEADER zoomHead;		//缩放图头信息
BITMAPINFOHEADER zoomInfoHead;	
RGBQUAD pColorTable[256];		//调色板

u8  bmpfile[5000];			//读取数据缓存
u8  newfile[5000];			//待写入数据缓存
 
/* 打印BMP文件的头信息,用于调试 */
static void showBmpHead(BITMAPFILEHEADER* pBmpHead)
{
    printf("位图文件头:\r\n");
    printf("文件大小:%d\r\n",(*pBmpHead).bfSize);
    printf("保留字:%d\r\n",(*pBmpHead).bfReserved1);
    printf("保留字:%d\r\n",(*pBmpHead).bfReserved2);
    printf("实际位图数据的偏移字节数:%d\r\n",(*pBmpHead).bfOffBits);    
}

/* 打印BMP文件的头信息,用于调试 */
static void showBmpInforHead(tagBITMAPINFOHEADER* pBmpInforHead)
{
    printf("位图信息头:\r\n");
    printf("结构体的长度:%d\r\n",(*pBmpInforHead).biSize);
    printf("位图宽:%d\r\n",(*pBmpInforHead).biWidth);
    printf("位图高:%d\r\n",(*pBmpInforHead).biHeight);
    printf("biPlanes平面数:%d\r\n",(*pBmpInforHead).biPlanes);
    printf("biBitCount采用颜色位数:%d\r\n",(*pBmpInforHead).biBitCount);
    printf("压缩方式:%d\r\n",(*pBmpInforHead).biCompression);
    printf("biSizeImage实际位图数据占用的字节数:%d\r\n",(*pBmpInforHead).biSizeImage);
    printf("X方向分辨率:%d\r\n",(*pBmpInforHead).biXPelsPerMeter);
    printf("Y方向分辨率:%d\r\n",(*pBmpInforHead).biYPelsPerMeter);
    printf("使用的颜色数:%d\r\n",(*pBmpInforHead).biClrUsed);
    printf("重要颜色数:%d\r\n",(*pBmpInforHead).biClrImportant);
}
int main(void)
{
	FIL file;
	UINT bw;
	FRESULT fres;
	
	WORD fileType;		//文件类型
	
	int bmpWidth;		//原图宽度
	int bmpHeight;		//原图高度
	int bmpBitCount;	//颜色位数 24色位图即为24
	int bmpLinelen;		//原图单行数据长度
	int grayLinelen;	//灰度图单行数据长度
	
	int zoomWidth;		//目标宽度
	int zoomHeight;		//目标高度
	int zoomLinelen;	
	
	int tmp;
	unsigned char *pb1;	//数据读取指针

	double r1,r2;		//宽和高的缩放率

	int x1, x2, y1, y2;	//原图点坐标
	unsigned char Fq11, Fq12, Fq21, Fq22;//原图点的灰度值
	double x, y, Fr1, Fr2, Fp;//目标点坐标及目标点灰度值
	
	SystemInit();   
	USART1_Init();	
	while(1)
	{ 
		f_mount(&myfs,"0:",0);
 		//将原图test.bmp灰度化,保存为gray.bmp
		fres = f_open(&file , (char *)"test.bmp", FA_OPEN_EXISTING | FA_READ);
		if ( fres == FR_OK )
		{
	        	printf("Open file success\r\n");
	        
		        //读取位图文件头信息        
		        f_read(&file,&fileType,sizeof(WORD),&bw);     
		        
		        if( fileType != 0x4d42)
		        {
		            printf("file is not .bmp file!\r\n"); 
		        }
		        else
		        {
		            printf("Ok this is .bmp file\r\n");	
		        }        
				f_read(&file,&bitHead,sizeof(tagBITMAPFILEHEADER),&bw); 
		        showBmpHead(&bitHead); //显示头文件
		        printf("\r\n");
		        
		        //读取位图信息头信息
		        f_read(&file,&bitInfoHead,sizeof(BITMAPINFOHEADER),&bw);        
		        showBmpInforHead(&bitInfoHead);//打印头信息
		        printf("\r\n");			
	
			bmpWidth = bitInfoHead.biWidth;
			bmpHeight = bitInfoHead.biHeight;
			bmpBitCount = bitInfoHead.biBitCount;
			bmpLinelen = (bmpWidth * bmpBitCount / 8 + 3) / 4 * 4;//每行字节数
			printf("bmpLinelen %d\r\n",bmpLinelen);
			f_close(&file); //关闭原图
			//==========================================
			//==================生成灰度图===================
			//==========================================
			//现将24位真彩图灰度化
			fres = f_open(&file , (char *)"gray.bmp", FA_OPEN_ALWAYS | FA_WRITE);	//新建灰度图文件
			grayLinelen = (bmpWidth * 8 / 8 + 3) / 4 * 4;//由于灰度化后每像素位数变为8,所以每行字节数发生改变,但仍要求为4的整数倍
			fileType = 0x4D42;
			f_write(&file,(char *)&fileType, 2, &bw);    //将修改后的文件头存入gray.bmp;	
			grayHead.bfSize = 14 + 40 + 256 * sizeof(RGBQUAD)+grayLinelen*bmpHeight;//修改文件大小
			grayHead.bfReserved1 = 0;
			grayHead.bfReserved2 = 0;
			grayHead.bfOffBits = 14 + 40 + 256 * sizeof(RGBQUAD);//修改偏移字节数,24位bmp无调色板,原图的便宜字节数为(14+40)
			f_write(&file,(char *)&grayHead, 12, &bw);    //将修改后的文件头存入gray.bmp;				
							
			//修改信息头,其中有两项需要修改,1个位biBitCount:真彩图为24 ,应改成8;另一个是biSizeImage:由于每像素所占位数的变化
			grayInfoHead.biBitCount = 8;    //将每像素的位数改为8
			grayInfoHead.biClrImportant = 0;
			grayInfoHead.biCompression = 0;
			grayInfoHead.biClrUsed = 0;
			grayInfoHead.biHeight = bmpHeight;
			grayInfoHead.biWidth = bmpWidth;
			grayInfoHead.biPlanes = 1;
			grayInfoHead.biSize = 40;
			grayInfoHead.biSizeImage = grayLinelen*bmpHeight;//修改位图数据的大小
			grayInfoHead.biXPelsPerMeter = 0;
			grayInfoHead.biYPelsPerMeter = 0;
			f_write(&file,(char *)&grayInfoHead, 40,&bw);  //将修改后的信息头存入fp1;
			
			//调色板
			for (int i = 0; i < 256; i++){
				pColorTable[i].rgbRed = i;
				pColorTable[i].rgbGreen = i;
				pColorTable[i].rgbBlue = i; //是颜色表里的B、G、R分量都相等,且等于索引值
			}
			f_write(&file,(char *)pColorTable, sizeof(RGBQUAD)*256,&bw);  //将修改后的信息头存入gray.bmp;
		
			//由于RAM不足,不能直接读取全部原图RGB数据,只能读一行,写一行。
			f_close(&file); //关闭灰度图
			for (int i = 0; i < bmpHeight; i++)
			{
				//读取第i行原图RNG数据
				f_open(&file , (char *)"test.bmp", FA_OPEN_EXISTING | FA_READ);
				f_lseek(&file, 14 + 40 + i*bmpLinelen);
				f_read(&file,&bmpfile,bmpLinelen,&bw);  
				f_close(&file); 
				//将RGB数据转化为灰度值
				for (int j = 0; j<bmpWidth; j++){			
					pb1 = bmpfile + j * 3;
					tmp = *(pb1)*0.299 + *(pb1 + 1)*0.587 + *(pb1 + 2)*0.114; //将每一个像素都按公式y=B*0.299+G*0.587+R*0.114进行转化
					*(newfile + j) = tmp;
				}
				//写入第i行灰度值
				f_open(&file , (char *)"gray.bmp", FA_OPEN_ALWAYS | FA_WRITE);
				f_lseek(&file, 14 + 40  + 256 * sizeof(RGBQUAD) + i*grayLinelen);
				f_write(&file,(char *)newfile, grayLinelen,&bw);  //将修改后的信息头存入fp1;
				f_close(&file); 
			}		
		}
		//==========================================
		//==================生成缩放图===================
		//==========================================
		//将图片规格缩放为1280*720
		zoomWidth = 1280;
		zoomHeight = 720;
		fres = f_open(&file , (char *)"zoom.bmp", FA_OPEN_ALWAYS | FA_WRITE);
		zoomLinelen = (zoomWidth * 8 / 8 + 3) / 4 * 4;
		fileType = 0x4D42;
		f_write(&file,(char *)&fileType, 2, &bw);    //将修改后的文件头存入fp1; 
		zoomHead.bfSize = 14 + 40 + 256 * sizeof(RGBQUAD)+zoomLinelen*zoomHeight;//修改文件大小
		zoomHead.bfReserved1 = 0;
		zoomHead.bfReserved2 = 0;
		zoomHead.bfOffBits = 14 + 40 + 256 * sizeof(RGBQUAD);//修改偏移字节数
		f_write(&file,(char *)&zoomHead, 12, &bw);    //将修改后的文件头存入fp1;    
		 
		//修改信息头,其中有两项需要修改,1个位biBitCount:真彩图为24 ,应改成8;
		//另一个是biSizeImage:由于每像素所占位数的变化,所以位图数据的大小发生变化
		zoomInfoHead.biBitCount = 8;    //将每像素的位数改为8
		zoomInfoHead.biClrImportant = 0;
		zoomInfoHead.biCompression = 0;
		zoomInfoHead.biClrUsed = 0;
		zoomInfoHead.biHeight = zoomHeight;
		zoomInfoHead.biWidth = zoomWidth;
		zoomInfoHead.biPlanes = 1;
		zoomInfoHead.biSize = 40;
		zoomInfoHead.biSizeImage = zoomLinelen*zoomHeight;//修改位图数据的大小
		zoomInfoHead.biXPelsPerMeter = 0;
		zoomInfoHead.biYPelsPerMeter = 0;
		f_write(&file,(char *)&zoomInfoHead, 40,&bw);  //将修改后的信息头存入fp1;
		
		for (int i = 0; i < 256; i++){
			pColorTable[i].rgbRed = i;
			pColorTable[i].rgbGreen = i;
			pColorTable[i].rgbBlue = i; //是颜色表里的B、G、R分量都相等,且等于索引值
		}
		f_write(&file,(char *)pColorTable, sizeof(RGBQUAD)*256,&bw);  //将修改后的信息头存入fp1;
		
		f_close(&file); 
			
		r1 = (double)bmpHeight / zoomHeight;
		r2 = (double)bmpWidth / zoomWidth;
		
		for (int i = 0; i < zoomHeight; ++i)
		{
			
			y = (double)r1*i;
			y1 = (int)(y);  
			if(y1 == bmpHeight-1) y1 = bmpHeight-2;
			y2 = y1 + 1;
			
			f_open(&file , (char *)"gray.bmp", FA_OPEN_EXISTING | FA_READ);
			f_lseek(&file, 14 + 40 + 256 * sizeof(RGBQUAD)+ y1*grayLinelen);
			f_read(&file,&bmpfile,grayLinelen*2,&bw);  
			f_close(&file); 
			for (int j = 0; j < zoomWidth; ++j)
			{
				
				x = (double)r2*j; // 原图像坐标
				
				// 四个坐标值
				x1 = (int)(x);  
				if(x1 == zoomWidth-1)x1 = zoomWidth-2;
				x2 = x1 + 1;
				
				// 四个坐标对应的灰度值
				Fq11 = *(bmpfile  + x1);
				Fq12 = *(bmpfile + grayLinelen + x1);
				Fq21 = *(bmpfile  + x2);
				Fq22 = *(bmpfile + grayLinelen + x2);
				// x方向插值和y方向插值
				Fr1 = 0; Fr2 = 0;
				Fr1 = (double)(x2 - x)*Fq11/ (x2 - x1) + (double)(x - x1)*Fq21/ (x2 - x1);
				Fr2 = (double)(x2 - x)*Fq12 / (x2 - x1) + (double)(x - x1)*Fq22/ (x2 - x1);
				Fp = (double)(y2 - y)*Fr1 / (y2 - y1) + (double)(y - y1)*Fr2/ (y2 - y1);

				// 新图像灰度值赋值
				//if (Fp >= 0 && Fp <= 255){
					pb1 = (unsigned char *)(newfile + j); // 新图像
					(*pb1 ) = (char)(Fp);
				//}
			}
			printf("%d\r\n",i+1);
			f_open(&file , (char *)"zoom.bmp", FA_OPEN_ALWAYS | FA_WRITE);
			f_lseek(&file, 14 + 40  + 256 * sizeof(RGBQUAD) + i*zoomLinelen);
			f_write(&file,(char *)newfile, zoomLinelen,&bw);  //将修改后的信息头存入fp1;
			f_close(&file); 					

			
		}
			
				
	}

}

调试过程遇到的几个问题

双线性插值

stm32图像分类 stm32处理图片_stm32图像分类_10


stm32图像分类 stm32处理图片_stm32_11

  • x、y是图片的坐标参数
  • f(Q11)、f(Q12)、f(R1)、f( P)、··· 分别是Q11、Q12、R1、P、···点的灰度值

字节对齐

typedef struct tagBITMAPFILEHEADER { 
//WORD    bfType; 
DWORD   bfSize; 
WORD    bfReserved1; 
WORD    bfReserved2; 
DWORD   bfOffBits; 
} BITMAPFILEHEADER
BITMAPFILEHEADER bitHead;
f_read(&file,&fileType,sizeof(WORD),&bw);     
if( fileType != 0x4d42)
{
   printf("file is not .bmp file!\r\n"); 
}
else
{
   printf("Ok this is .bmp file\r\n"); 
}        
f_read(&file,&bitHead,sizeof(tagBITMAPFILEHEADER),&bw); 
showBmpHead(&bitHead); //显示头文件
printf("\r\n");

需要将 BITMAPFILEHEADER 结构体中的 参数bfType 屏蔽,文件类型不放在结构体中,单独读写。

typedef unsinged short WORD
typedef unsinged long DWORD
  • WORD为2个字节
  • DWORD为4个字节

所以 BITMAPFILEHEADER 是4字节对齐

链接: C语言struct字节对齐问题

不屏蔽参数bfType时
- sizeof(BITMAPFILEHEADER) = 16;
- &(bitHead.bfSize) = &bitHead + 4;
屏蔽参数bfType时
- sizeof(BITMAPFILEHEADER) = 12;
- &(bitHead.bfSize) = &bitHead;

图片读写

  • 跟参考链接的平台不同,MCU资源有限,不能一次全部读出,需要读一行、处理一行、写一行。
  • 双线性插值时,y值最大为bmpHeight-1,注意读取y2 = y1 + 1坐标灰度时,内存溢出。
    y1 = (int)(y);
    if(y1 == bmpHeight-1) y1 = bmpHeight-2;
    y2 = y1 + 1;
  • 注意强制类型转换。我没仔细区分,为了省事除法都强制转换为(double)。
  • 还有就是单片机进行浮点型运算是真的慢。。。

参考文档

  • 链接: C语言实现BMP图像处理(彩色图转灰度图)
  • 链接: C语言实现BMP图像处理(缩放)