[C/C++]Bitmap 파일 구조와 bmp 파일 읽기/쓰기

허재윤·2023년 2월 17일
0

DIP/CV

목록 보기
1/2

Abstract

Digital Image Processing의 기초가 되는 비트맵에 대한 개요를 파악하고, 직접 bmp 파일을 읽어오는 프로그램을 작성해봅니다. 코드는 C와 부분적인 C++로 작성되었습니다.

입사 기본 교육 후 처음으로 과제를 받았다..!
bmp 파일을 읽고, 이미지를 crop 하거나 rotate 하는 프로그램 작성을 C 또는 C++을 이용해서 구현하는 과제다. 프로그램을 작성하기에 앞서 bmp 파일의 구조를 살펴보자.

Bitmap

Bitmap은 컴퓨터 분야에서 디지털 이미지를 저장하는데 쓰이는 이미지 파일 포맷이다. 모든 픽셀 정보를 저장해야 하기 때문에 기본적으로 물체를 방정식으로 표현하는 벡터 방식에 비해 용량이 크고 느리다는 단점이 있다. 이를 보완하기 위해 다양한 파일 형식이 개발되었으며, 우리는 주로 Bitmap 방식으로 화면을 출력하는 모니터를 사용하기 때문에 벡터 방식의 파일을 가지고 있더라도 Bitmap 형식으로 변환 후 출력된다.

Bitmap File Structure

Bitmap 파일의 확장자는 bmp로 File Header와 Image Data 영역으로 구분된다. File header는 파일과 이미지에 대한 정보를 가지고 있으며 Image Data는 픽셀 값 정보를 가지고 있다.

.bmp 포맷의 헤더는 BITMAPFILEHEADER, BITMAPINFOHEADER, RGBQUAD로 분류된다.

BITMAPFILEHEADER Bitmap file header struct

BITMAPFILEHEADER는 가장 선두에 있는 구조체이며 파일에 대한 전반적인 정보를 가진다. bfreserved의 경우 설계 당시에는 목적을 가지고 설계되었지만 실제로 사용하지 않는다고 한다.

BITMAPINFOHEADER Bitmap information header struct

BITMAPINFOHEADER는 이미지에 관한 정보와 기타 장치로부터 독립적인 변수들을 저장한다. bitplane의 경우 몇 개의 bitplane으로 나누는가에 대한 정보인 것 같다...(혹시 아니라면 알려주세요 ㅎㅎ..ㅜ)

RGBQUAD RGBQUAD struct

RGBQUAD는 256 이상의 색상을 사용하는 비트맵은 사용하지 않는다. R, G, B의 강도로 표시되며 사용하는 색상 수만큼 배열으로 사용한다.

bmp file reader

대략적인 header 파일 정보를 알아보았으니 bmp 파일을 읽어오는 프로그램을 작성해보자!
어렵게 생각할 것 없이 header를 순서대로 읽어오고 데이터를 읽으면 된다. 다만 유의해야 하는 점은 bmp 파일의 경우 픽셀 데이터가 수직 반전된 형태로 읽어진다는 것. 따라서 영상을 해석하고 싶다면 column 정보를 뒤집어서 사용하자.

파일은 쓰여있는 순서대로 읽어야 한다.
BITMAPFILEHEADER → BITMAPINFOHEADER → RGBQUAD → Image data (pixel value)

#include <stdio.h>
#include <Windows.h>

BYTE* readBMP(BITMAPFILEHEADER* hf, BITMAPINFOHEADER* hinfo, char filename[])
{
	int width = 0, height = 0;
	FILE* f = NULL;
	f = fopen(filename, "rb");
	if (f == NULL)
	{
		printf("fail to read file");
		return NULL; 
	}
	fread(hf, sizeof(BITMAPFILEHEADER), 1, f);
	fread(hinfo, sizeof(BITMAPINFOHEADER), 1, f);
	//fread(hRGB, sizeof(RGBQUAD), 256, f);

	width = hinfo->biWidth;
	height = hinfo->biHeight;

	printf("Width :%d \t Height :%d\t Image Size :%d\n", width, height, hinfo->biSizeImage);

	BYTE* InputImg = NULL;

	InputImg = (BYTE*)malloc(sizeof(BYTE) * hinfo->biSizeImage);
	fread(InputImg, sizeof(BYTE), hinfo->biSizeImage, f);

	fclose(f);

	if (hf->bfType == 0x4D42)
	{
		printf("bmp format image\n");
	}
	else
	{
		printf("wrong file type input\n");
	}

	return InputImg;
}

BYTE는 unsigned char 자료형인데, pixel 값은 0~255의 8 bit (= 1 byte)값을 가진다는 사실을 기억한다면 왜 unsigned char를 사용하는지 알 수 있다.
result

읽어온 bmp 파일 정보를 출력해보면 여러 정보를 알 수 있다.
biSizeImage는 400 x 225 x 3 = 270000로 총 270000 byte의 pixel data를 가지고 있음을 알 수 있다.

bmp file write

파일 쓰기는 읽은 순서대로 진행한다.
1. 파일스트림 객체를 생성하고
2. 쓰기 모드로 열어준 후
3. file header, info header, image data 순서대로 fwrite 하면 된다.

int width = hinfo.biWidth, height = hinfo.biHeight;
FILE* f;

if ((f = fopen(filename, "wb")) == NULL)
{
	printf("write file error");
	return 0;
}
fwrite(&hf, sizeof(BITMAPFILEHEADER), 1, f);
fwrite(&hinfo, sizeof(BITMAPINFOHEADER), 1, f);

printf("File.bfType = %hx\n", hf.bfType);
printf("File.bfSize = %d\n", hf.bfSize);
printf("File.bfOffBits = %d\n", hf.bfOffBits);
printf("Info.biSize = %d\n", hinfo.biSize);
printf("Info.biWidth = %d\n", hinfo.biWidth);
printf("Info.biHeight = %d\n", hinfo.biHeight);
printf("Info.biPlanes = %d\n", hinfo.biPlanes);
printf("Info.biSizeImage = %d\n", info.biSizeImage);
printf("Info.biClrUsed = %d\n", hinfo.biClrUsed);
	

fwrite(OutputImg, sizeof(BYTE), hinfo.biSizeImage, f);
fclose(f);


제대로 result.bmp파일이 생성된 것을 확인할 수 있다.

profile
Junior CV Developer / AI Researcher

0개의 댓글