平台:STM32F1+FATFS+SD卡
基于STM32的BMP图片处理
- 处理结果
- 原图 test.bmp
- 灰度图 gray.bmp
- 缩放 zoom.bmp
- 代码实现
- 调试过程遇到的几个问题
- 双线性插值
- 字节对齐
- 图片读写
- 参考文档
处理结果
原图 test.bmp
灰度图 gray.bmp
缩放 zoom.bmp
失真不明显哈,估计是我缩放的比例太小 795 * 457 => 1280 * 720
插值采用的是双线性插值法
代码实现
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);
}
}
}
调试过程遇到的几个问题
双线性插值
- 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图像处理(缩放)