1. 程式人生 > >【數字影象處理】C++讀取、旋轉和儲存bmp影象檔案程式設計實現

【數字影象處理】C++讀取、旋轉和儲存bmp影象檔案程式設計實現

通過我這些天用C++讀寫bmp影象的經歷,摸索再摸索,終於對bmp檔案的結構、操作有了一定的瞭解,下面就大概介紹bmp圖片純C++的讀取、旋轉和儲存的實現過程。

有幾點需要注意的是:

在讀取bmp圖片的時候,一定要注意記憶體對齊的問題,譬如檔案頭,否則無法讀取出正確結果。

關於圖片的畫素資料,每一行的畫素的位元組數必須是4的整數倍。如果不是,則需要補齊。一般來說,bmp影象檔案的資料是從下到上,從左到右的。即從檔案中最先讀到的是影象最下面一行的左邊第一個畫素,然後是座標第二個.....接下來是倒數第二行的第一個畫素。

採用的編譯環境是VS2008。

關於影象旋轉,並不難。只需要搞清楚畫素座標變換公式就行。我以影象的中心點為座標原點。先把畫素在目標影象中的位置變化為座標系中的位置,做旋轉變換求出變換之前的在座標系中的座標,再變換為在圖片中的位置。

公式:(x1,y1)是變換之前的座標系中的座標,(x2,y2)是變換之後的座標系中的座標。angle為逆時針旋轉的角度數。

x1 = cos(angle)*x2-sin(angle)*y2;
y1 = sin(angle)*x2-cos(angle)*y2;

我的程式碼分為兩個版本:灰度圖的和彩色圖的。

灰度圖:

灰度圖是隻含亮度資訊,不含彩色資訊的影象。bmp格式檔案中並沒有灰度圖這個概念,但是我們很容易地用bmp檔案來表示灰度圖。方法是用256色的調色盤,只不過這個調色盤有點特殊,每一項的RGB值都是相同的,從(0,0,0),(1,1,1),...,一直到(255,255,255)。這樣,灰度圖就可以用256色圖來表示了。其影象資料就是調色盤索引值,也就是實際的RGB的亮度值。

另外因為是256色的調色盤,所以影象資料中的一個位元組代表一個畫素。如果是彩色的256色圖,影象處理後可能會產生不屬於這256色的顏色,所以,影象處理一般採用灰度圖。這也可以更好地將重點放在演算法上。

下面是灰度圖旋轉程式碼,能處理任意尺寸的bmp灰度圖,以及旋轉任意角度(逆時針)。

程式碼包括兩個檔案:BmpRot.h和BmpRot.cpp

BmpRot.h:

typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef unsigned int DWORD;
typedef long LONG;

//點陣圖檔案頭定義;
//其中不包含檔案型別資訊(由於結構體的記憶體結構決定,
//要是加了的話將不能正確讀取檔案資訊)
typedef struct  tagBITMAPFILEHEADER{
	//WORD bfType;//檔案型別,必須是0x424D,即字元“BM”
	DWORD bfSize;//檔案大小
	WORD bfReserved1;//保留字
	WORD bfReserved2;//保留字
	DWORD bfOffBits;//從檔案頭到實際點陣圖資料的偏移位元組數
}BITMAPFILEHEADER;

typedef struct tagBITMAPINFOHEADER{
	DWORD biSize;//資訊頭大小
	LONG biWidth;//影象寬度
	LONG biHeight;//影象高度
	WORD biPlanes;//位平面數,必須為1
	WORD biBitCount;//每畫素位數
	DWORD  biCompression; //壓縮型別
	DWORD  biSizeImage; //壓縮影象大小位元組數
	LONG  biXPelsPerMeter; //水平解析度
	LONG  biYPelsPerMeter; //垂直解析度
	DWORD  biClrUsed; //點陣圖實際用到的色彩數
	DWORD  biClrImportant; //本點陣圖中重要的色彩數
}BITMAPINFOHEADER; //點陣圖資訊頭定義

typedef struct tagRGBQUAD{
	BYTE rgbBlue; //該顏色的藍色分量
	BYTE rgbGreen; //該顏色的綠色分量
	BYTE rgbRed; //該顏色的紅色分量
	BYTE rgbReserved; //保留值
}RGBQUAD;//調色盤定義

//畫素資訊
typedef struct tagIMAGEDATA
{
	BYTE blue;
	//BYTE green;
	//BYTE red;
}IMAGEDATA;
BmpRot.cpp:
#include <stdio.h>
#include "BmpRot.h"
#include "stdlib.h"
#include "math.h"
#include <iostream>

#define PI 3.14159//圓周率巨集定義
#define LENGTH_NAME_BMP 30//bmp圖片檔名的最大長度

using namespace std;

//變數定義
BITMAPFILEHEADER strHead;
RGBQUAD strPla[256];//256色調色盤
BITMAPINFOHEADER strInfo;

//顯示點陣圖檔案頭資訊
void showBmpHead(BITMAPFILEHEADER pBmpHead){
	cout<<"點陣圖檔案頭:"<<endl;
	cout<<"檔案大小:"<<pBmpHead.bfSize<<endl;
	cout<<"保留字_1:"<<pBmpHead.bfReserved1<<endl;
	cout<<"保留字_2:"<<pBmpHead.bfReserved2<<endl;
	cout<<"實際點陣圖資料的偏移位元組數:"<<pBmpHead.bfOffBits<<endl<<endl;
}

void showBmpInforHead(tagBITMAPINFOHEADER pBmpInforHead){
	cout<<"點陣圖資訊頭:"<<endl;
	cout<<"結構體的長度:"<<pBmpInforHead.biSize<<endl;
	cout<<"點陣圖寬:"<<pBmpInforHead.biWidth<<endl;
	cout<<"點陣圖高:"<<pBmpInforHead.biHeight<<endl;
	cout<<"biPlanes平面數:"<<pBmpInforHead.biPlanes<<endl;
	cout<<"biBitCount採用顏色位數:"<<pBmpInforHead.biBitCount<<endl;
	cout<<"壓縮方式:"<<pBmpInforHead.biCompression<<endl;
	cout<<"biSizeImage實際點陣圖資料佔用的位元組數:"<<pBmpInforHead.biSizeImage<<endl;
	cout<<"X方向解析度:"<<pBmpInforHead.biXPelsPerMeter<<endl;
	cout<<"Y方向解析度:"<<pBmpInforHead.biYPelsPerMeter<<endl;
	cout<<"使用的顏色數:"<<pBmpInforHead.biClrUsed<<endl;
	cout<<"重要顏色數:"<<pBmpInforHead.biClrImportant<<endl;
}


int main(){
	char strFile[LENGTH_NAME_BMP];//bmp檔名
	IMAGEDATA *imagedata = NULL;//動態分配儲存原圖片的畫素資訊的二維陣列
	IMAGEDATA *imagedataRot = NULL;//動態分配儲存旋轉後的圖片的畫素資訊的二維陣列
	int width,height;//圖片的寬度和高度
	cout<<"請輸入所要讀取的檔名:"<<endl;
	cin>>strFile;
	FILE *fpi,*fpw;
	fpi=fopen(strFile,"rb");
	if(fpi != NULL){
		//先讀取檔案型別
		WORD bfType;
		fread(&bfType,1,sizeof(WORD),fpi);
		if(0x4d42!=bfType)
		{
			cout<<"the file is not a bmp file!"<<endl;
			return NULL;
		}
		//讀取bmp檔案的檔案頭和資訊頭
		fread(&strHead,1,sizeof(tagBITMAPFILEHEADER),fpi);
		//showBmpHead(strHead);//顯示檔案頭
		fread(&strInfo,1,sizeof(tagBITMAPINFOHEADER),fpi);
		//showBmpInforHead(strInfo);//顯示檔案資訊頭

		//讀取調色盤
		for(unsigned int nCounti=0;nCounti<strInfo.biClrUsed;nCounti++)
		{
			fread((char *)&(strPla[nCounti].rgbBlue),1,sizeof(BYTE),fpi);
			fread((char *)&(strPla[nCounti].rgbGreen),1,sizeof(BYTE),fpi);
			fread((char *)&(strPla[nCounti].rgbRed),1,sizeof(BYTE),fpi);
			fread((char *)&(strPla[nCounti].rgbReserved),1,sizeof(BYTE),fpi);
		}

		width = strInfo.biWidth;
		height = strInfo.biHeight;
		//影象每一行的位元組數必須是4的整數倍
		width = (width * sizeof(IMAGEDATA) + 3) / 4 * 4;
		//imagedata = (IMAGEDATA*)malloc(width * height * sizeof(IMAGEDATA));
		imagedata = (IMAGEDATA*)malloc(width * height);
		imagedataRot = (IMAGEDATA*)malloc(2 * width * 2 * height * sizeof(IMAGEDATA));
		//初始化原始圖片的畫素陣列
		for(int i = 0;i < height;++i)
		{
			for(int j = 0;j < width;++j)
			{
				(*(imagedata + i * width + j)).blue = 0;
				//(*(imagedata + i * width + j)).green = 0;
				//(*(imagedata + i *  width + j)).red = 0;
			}
		}
		//初始化旋轉後圖片的畫素陣列
		for(int i = 0;i < 2 * height;++i)
		{
			for(int j = 0;j < 2 * width;++j)
			{
				(*(imagedataRot + i * 2 * width + j)).blue = 0;
				//(*(imagedataRot + i * 2 * width + j)).green = 0;
				//(*(imagedataRot + i * 2 * width + j)).red = 0;
			}
		}
		//fseek(fpi,54,SEEK_SET);
		//讀出圖片的畫素資料
		fread(imagedata,sizeof(struct tagIMAGEDATA) * width,height,fpi);
		fclose(fpi);
	}
	else
	{
		cout<<"file open error!"<<endl;
		return NULL;
	}

	//圖片旋轉處理
	int RotateAngle;//要旋轉的角度數
	double angle;//要旋轉的弧度數
	int midX_pre,midY_pre,midX_aft,midY_aft;//旋轉所圍繞的中心點的座標
	midX_pre = width / 2;
	midY_pre = height / 2;
	midX_aft = width;
	midY_aft = height;
	int pre_i,pre_j,after_i,after_j;//旋轉前後對應的畫素點座標
	cout<<"輸入要旋轉的角度(0度到360度,逆時針旋轉):"<<endl;
	cin>>RotateAngle;
	angle = 1.0 * RotateAngle * PI / 180;
	for(int i = 0;i < 2 * height;++i)
	{
		for(int j = 0;j < 2 * width;++j)
		{
			after_i = i - midX_aft;//座標變換
			after_j = j - midY_aft;
			pre_i = (int)(cos((double)angle) * after_i - sin((double)angle) * after_j) + midX_pre;
			pre_j = (int)(sin((double)angle) * after_i + cos((double)angle) * after_j) + midY_pre;
			if(pre_i >= 0 && pre_i < height && pre_j >= 0 && pre_j < width)//在原圖範圍內
				*(imagedataRot + i * 2 * width + j) = *(imagedata + pre_i * width + pre_j);
		}
	}

	//儲存bmp圖片
	if((fpw=fopen("b.bmp","wb"))==NULL)
	{
		cout<<"create the bmp file error!"<<endl;
		return NULL;
	}
	WORD bfType_w=0x4d42;
	fwrite(&bfType_w,1,sizeof(WORD),fpw);
	//fpw +=2;
	fwrite(&strHead,1,sizeof(tagBITMAPFILEHEADER),fpw);
	strInfo.biWidth = 2 * width;
	strInfo.biHeight = 2 * height;
	fwrite(&strInfo,1,sizeof(tagBITMAPINFOHEADER),fpw);
	//儲存調色盤資料
	for(unsigned int nCounti=0;nCounti<strInfo.biClrUsed;nCounti++)
	{
		fwrite(&strPla[nCounti].rgbBlue,1,sizeof(BYTE),fpw);
		fwrite(&strPla[nCounti].rgbGreen,1,sizeof(BYTE),fpw);
		fwrite(&strPla[nCounti].rgbRed,1,sizeof(BYTE),fpw);
		fwrite(&strPla[nCounti].rgbReserved,1,sizeof(BYTE),fpw);
	}
	//儲存畫素資料
	for(int i =0;i < 2 * height;++i)
	{
		for(int j = 0;j < 2 * width;++j)
		{
			fwrite( &((*(imagedataRot + i * 2 * width + j)).blue),1,sizeof(BYTE),fpw);
			//fwrite( &((*(imagedataRot + i * 2 * width + j)).green),1,sizeof(BYTE),fpw);
			//fwrite( &((*(imagedataRot + i * 2 * width + j)).red),1,sizeof(BYTE),fpw);
		}
	}
	fclose(fpw);

	//釋放記憶體
	delete[] imagedata;
	delete[] imagedataRot;
}

資料測試:

旋轉前和旋轉後的對比(45度):




彩色圖:

彩色圖的處理和灰度圖略有不一樣。主要是畫素資料不同。由於每行資料的位元組數必須是4的整數倍,這個地方處理起來要比灰度圖麻煩很多,多以暫時還 沒做好。本程式的侷限性就是隻能處理尺寸是4的整數倍的圖片,可以旋轉任意角度(逆時針)。

參考程式碼:分兩個檔案:BmpRot.h和BmpRot.cpp

BmpRot.h:

typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef unsigned int DWORD;
typedef long LONG;

//點陣圖檔案頭定義;
//其中不包含檔案型別資訊(由於結構體的記憶體結構決定,
//要是加了的話將不能正確讀取檔案資訊)
typedef struct  tagBITMAPFILEHEADER{
	//WORD bfType;//檔案型別,必須是0x424D,即字元“BM”
	DWORD bfSize;//檔案大小
	WORD bfReserved1;//保留字
	WORD bfReserved2;//保留字
	DWORD bfOffBits;//從檔案頭到實際點陣圖資料的偏移位元組數
}BITMAPFILEHEADER;

typedef struct tagBITMAPINFOHEADER{
	DWORD biSize;//資訊頭大小
	LONG biWidth;//影象寬度
	LONG biHeight;//影象高度
	WORD biPlanes;//位平面數,必須為1
	WORD biBitCount;//每畫素位數
	DWORD  biCompression; //壓縮型別
	DWORD  biSizeImage; //壓縮影象大小位元組數
	LONG  biXPelsPerMeter; //水平解析度
	LONG  biYPelsPerMeter; //垂直解析度
	DWORD  biClrUsed; //點陣圖實際用到的色彩數
	DWORD  biClrImportant; //本點陣圖中重要的色彩數
}BITMAPINFOHEADER; //點陣圖資訊頭定義

typedef struct tagRGBQUAD{
	BYTE rgbBlue; //該顏色的藍色分量
	BYTE rgbGreen; //該顏色的綠色分量
	BYTE rgbRed; //該顏色的紅色分量
	BYTE rgbReserved; //保留值
}RGBQUAD;//調色盤定義

//畫素資訊
typedef struct tagIMAGEDATA
{
	BYTE red;
	BYTE green;
	BYTE blue;
}IMAGEDATA;
BmpRot.cpp:
#include <stdio.h>
#include "BmpRot.h"
#include "stdlib.h"
#include "math.h"
#include <iostream>

#define PI 3.14159//圓周率巨集定義
#define LENGTH_NAME_BMP 30//bmp圖片檔名的最大長度

using namespace std;

//變數定義
BITMAPFILEHEADER strHead;
RGBQUAD strPla[256];//256色調色盤
BITMAPINFOHEADER strInfo;

//顯示點陣圖檔案頭資訊
void showBmpHead(BITMAPFILEHEADER pBmpHead){
	cout<<"點陣圖檔案頭:"<<endl;
	cout<<"檔案大小:"<<pBmpHead.bfSize<<endl;
	cout<<"保留字_1:"<<pBmpHead.bfReserved1<<endl;
	cout<<"保留字_2:"<<pBmpHead.bfReserved2<<endl;
	cout<<"實際點陣圖資料的偏移位元組數:"<<pBmpHead.bfOffBits<<endl<<endl;
}

void showBmpInforHead(tagBITMAPINFOHEADER pBmpInforHead){
	cout<<"點陣圖資訊頭:"<<endl;
	cout<<"結構體的長度:"<<pBmpInforHead.biSize<<endl;
	cout<<"點陣圖寬:"<<pBmpInforHead.biWidth<<endl;
	cout<<"點陣圖高:"<<pBmpInforHead.biHeight<<endl;
	cout<<"biPlanes平面數:"<<pBmpInforHead.biPlanes<<endl;
	cout<<"biBitCount採用顏色位數:"<<pBmpInforHead.biBitCount<<endl;
	cout<<"壓縮方式:"<<pBmpInforHead.biCompression<<endl;
	cout<<"biSizeImage實際點陣圖資料佔用的位元組數:"<<pBmpInforHead.biSizeImage<<endl;
	cout<<"X方向解析度:"<<pBmpInforHead.biXPelsPerMeter<<endl;
	cout<<"Y方向解析度:"<<pBmpInforHead.biYPelsPerMeter<<endl;
	cout<<"使用的顏色數:"<<pBmpInforHead.biClrUsed<<endl;
	cout<<"重要顏色數:"<<pBmpInforHead.biClrImportant<<endl;
}


int main(){
	char strFile[LENGTH_NAME_BMP];//bmp檔名
	IMAGEDATA *imagedata = NULL;//動態分配儲存原圖片的畫素資訊的二維陣列
	IMAGEDATA *imagedataRot = NULL;//動態分配儲存旋轉後的圖片的畫素資訊的二維陣列
	int width,height;//圖片的寬度和高度
	cout<<"請輸入所要讀取的檔名:"<<endl;
	cin>>strFile;
	FILE *fpi,*fpw;
	fpi=fopen(strFile,"rb");
	if(fpi != NULL){
		//先讀取檔案型別
		WORD bfType;
		fread(&bfType,1,sizeof(WORD),fpi);
		if(0x4d42!=bfType)
		{
			cout<<"the file is not a bmp file!"<<endl;
			return NULL;
		}
		//讀取bmp檔案的檔案頭和資訊頭
		fread(&strHead,1,sizeof(tagBITMAPFILEHEADER),fpi);
		//showBmpHead(strHead);//顯示檔案頭
		fread(&strInfo,1,sizeof(tagBITMAPINFOHEADER),fpi);
		//showBmpInforHead(strInfo);//顯示檔案資訊頭

		//讀取調色盤
		for(unsigned int nCounti=0;nCounti<strInfo.biClrUsed;nCounti++)
		{
			//儲存的時候,一般去掉保留字rgbReserved
			fread((char *)&strPla[nCounti].rgbBlue,1,sizeof(BYTE),fpi);
			fread((char *)&strPla[nCounti].rgbGreen,1,sizeof(BYTE),fpi);
			fread((char *)&strPla[nCounti].rgbRed,1,sizeof(BYTE),fpi);
			cout<<"strPla[nCounti].rgbBlue"<<strPla[nCounti].rgbBlue<<endl;
			cout<<"strPla[nCounti].rgbGreen"<<strPla[nCounti].rgbGreen<<endl;
			cout<<"strPla[nCounti].rgbRed"<<strPla[nCounti].rgbRed<<endl;
		}

		width = strInfo.biWidth;
		height = strInfo.biHeight;
		imagedata = (IMAGEDATA*)malloc(width * height * sizeof(IMAGEDATA));
		imagedataRot = (IMAGEDATA*)malloc(2 * width * 2 * height * sizeof(IMAGEDATA));
		//初始化原始圖片的畫素陣列
		for(int i = 0;i < height;++i)
		{
			for(int j = 0;j < width;++j)
			{
				(*(imagedata + i * width + j)).blue = 0;
				(*(imagedata + i * width + j)).green = 0;
				(*(imagedata + i *  width + j)).red = 0;
			}
		}
		//初始化旋轉後圖片的畫素陣列
		for(int i = 0;i < 2 * height;++i)
		{
			for(int j = 0;j < 2 * width;++j)
			{
				(*(imagedataRot + i * 2 * width + j)).blue = 0;
				(*(imagedataRot + i * 2 * width + j)).green = 0;
				(*(imagedataRot + i * 2 * width + j)).red = 0;
			}
		}
		//fseek(fpi,54,SEEK_SET);
		//讀出圖片的畫素資料
		fread(imagedata,sizeof(struct tagIMAGEDATA) * width,height,fpi);
		/*
		for(int i = 0;i < height;++i)
		{
			fread(imagedata + i * width * sizeof(IMAGEDATA),sizeof(struct tagIMAGEDATA) * width,height,fpi);
		}*/
		fclose(fpi);
	}
	else
	{
		cout<<"file open error!"<<endl;
		return NULL;
	}

	//圖片旋轉處理
	int RotateAngle;//要旋轉的角度數
	double angle;//要旋轉的弧度數
	int midX_pre,midY_pre,midX_aft,midY_aft;//旋轉所圍繞的中心點的座標
	midX_pre = width / 2;
	midY_pre = height / 2;
	midX_aft = width;
	midY_aft = height;
	int pre_i,pre_j,after_i,after_j;//旋轉前後對應的畫素點座標
	cout<<"輸入要旋轉的角度(0度到360度,逆時針旋轉):"<<endl;
	cin>>RotateAngle;
	angle = 1.0 * RotateAngle * PI / 180;
	for(int i = 0;i < 2 * height;++i)
	{
		for(int j = 0;j < 2 * width;++j)
		{
			after_i = i - midY_aft;//座標變換
			after_j = j - midX_aft;
			pre_i = (int)(cos((double)angle) * after_i - sin((double)angle) * after_j) + midY_pre;
			pre_j = (int)(sin((double)angle) * after_i + cos((double)angle) * after_j) + midX_pre;
			if(pre_i >= 0 && pre_i < height && pre_j >= 0 && pre_j < width)//在原圖範圍內
				*(imagedataRot + i * 2 * width + j) = *(imagedata + pre_i * width + pre_j);
		}
	}

	//儲存bmp圖片
	if((fpw=fopen("b.bmp","wb"))==NULL)
	{
		cout<<"create the bmp file error!"<<endl;
		return NULL;
	}
	WORD bfType_w=0x4d42;
	fwrite(&bfType_w,1,sizeof(WORD),fpw);
	//fpw +=2;
	fwrite(&strHead,1,sizeof(tagBITMAPFILEHEADER),fpw);
	strInfo.biWidth = 2 * width;
	strInfo.biHeight = 2 * height;
	fwrite(&strInfo,1,sizeof(tagBITMAPINFOHEADER),fpw);
	//儲存調色盤資料
	for(unsigned int nCounti=0;nCounti<strInfo.biClrUsed;nCounti++)
	{
		fwrite(&strPla[nCounti].rgbBlue,1,sizeof(BYTE),fpw);
		fwrite(&strPla[nCounti].rgbGreen,1,sizeof(BYTE),fpw);
		fwrite(&strPla[nCounti].rgbRed,1,sizeof(BYTE),fpw);
	}
	//儲存畫素資料
	for(int i =0;i < 2 * height;++i)
	{
		for(int j = 0;j < 2 * width;++j)
		{
			fwrite( &(*(imagedataRot + i * 2 * width + j)).red,1,sizeof(BYTE),fpw);//注意三條語句的順序:否則顏色會發生變化
			fwrite( &(*(imagedataRot + i * 2 * width + j)).green,1,sizeof(BYTE),fpw);
			fwrite( &(*(imagedataRot + i * 2 * width + j)).blue,1,sizeof(BYTE),fpw);
		}
	}
	fclose(fpw);

	//釋放記憶體
	delete[] imagedata;
	delete[] imagedataRot;
}

資料測試:(旋轉10°)





注意:顏色問題已解決。請看程式碼中註釋部分。