[C/C++]Bitmap Image Crop & Rotate 함수 작성

허재윤·2023년 2월 20일
1

DIP/CV

목록 보기
2/2

Abstract

이미지 데이터를 다루는 연습을 하기 위해서 crop 함수를 직접 구현하는 과정입니다. C로 작성되었습니다.

이전에 받은 과제와 이어지는 내용으로 읽어온 bmp 파일을 crop 및 rotate 하는 프로그램을 작성한다.

Crop

Crop이란 전체 이미지에서 내가 원하는 부분을 그대로 가져오는 과정이다. 이미지 데이터는 다른 텍스트나 정수형 데이터에 비해 상대적으로 큰 데이터를 가지는데, 모든 픽셀 값에 대해서 연산을 수행하려고 하면 그 만큼의 자원 소모를 감당해야 한다. 쓸모없는 부분은 버리고 최소한의 관심 영역 픽셀만을 연산하기 위해서 Crop을 사용할 수 있다.
이미지를 crop하면 가로, 세로 길이가 줄어들고, 이에 따라 전체 이미지 크기도 감소하게 된다. bmp 파일로 출력하기 위해서는 반드시 biWidth, biHeight, biSizeImage 등 변화되는 값을 직접 입력해줘야 한다.

Crop Method

프로그램을 작성하기 전에 대략적인 함수의 흐름을 짜놓는다. 이미지와 그에 대한 정보는 입력되어 있으므로 함수는 Crop 하고자 하는 영역에 대한 정보를 입력받는다.
입력받은 영역과 파일 정보를 참고해서 새로운 이미지 정보를 써서 입히고, 빈 배열에 Crop한 이미지의 데이터를 집어넣는다.
기존에 영상의 모든 픽셀을 참조하기 위해서 이미지 데이터 배열의 모든 가로 세로 곱만큼의 데이터를 참조했다면, Image Crop에서는 잘라내고자 하는 영역의 가로 세로 곱 만큼만 참조해도 된다.
만약 내가 5x5 영역에서 가운데 3x3 크기만 Crop 하고 싶다면 아래와 같이 코드를 작성할 수 있다.

int x1 = 1, x2 = 3, y1 = 1, y2 = 3;

for (int col = 0; col <= y2-y1>; col++)
{
    for (int row = 0; row <= x2-x1; row++)
    {
        output[col][row] = input[y1+col][x1+row];
    }
}

이 과정을 모두 코드로 옮겨적으면 다음과 같다.

BYTE* cropBMP(BITMAPFILEHEADER* hf, BITMAPINFOHEADER* hinfo, BYTE* InputImg, int x1, int y1, int x2, int y2)
{
	int crop_width = x2 - x1, crop_height = y2 - y1;
	int crop_size = crop_width * crop_height * 3;
	int dif_size = hf->bfSize - hinfo->biSizeImage;
	int width = hinfo->biWidth;

	hinfo->biWidth = crop_width;
	hinfo->biHeight = crop_height;
	hinfo->biSizeImage = crop_size;
	hf->bfSize = crop_size + hf->bfOffBits;

	BYTE* OutputImg = NULL;
	OutputImg = (BYTE*)malloc(sizeof(BYTE) * crop_size);

	for (int i = 0; i < crop_width; i++)
	{
		for (int j = 0; j < crop_height; j++)
		{
            // RGB 3 채널 반복
			for (int k = 0; k < 3; k++)
			{
				OutputImg[(3 * crop_width * j) + (3 * i) + k] = InputImg[(3 * width * (y1 + j)) + (3 * (x1 + i)) + k];
			}
		}
	}

	return OutputImg;
}

before crop
이미지의 붉은 영역(50, 50 ~ 150, 150)을 crop하면 아래와 같은 이미지가 생성된다.
after crop

Rotate

Rotate는 원하는 각도만큼 이미지를 회전하는 함수로 행렬의 회전 연산을 기반으로 작성한다.
원점을 기준으로 특정 각도만큼 회전하는 행렬 연산은 다음과 같다.

(xy)(sinθcosθcosθsinθ)=(xy)\begin{pmatrix}x\\y\end{pmatrix}\begin{pmatrix}\sin\theta&\cos\theta\\\cos\theta&-\sin\theta\end{pmatrix}=\begin{pmatrix}x'\\y'\end{pmatrix}

방정식 형태로 표현하면 다음과 같다.

x=xsinθ+ycosθx'=x\sin\theta + y\cos\theta
y=xcosθysinθy'=x\cos\theta-y\sin\theta

이동한 픽셀의 좌표가 기존 이미지 크기를 벗어나는 경우에는 표시하지 않고, 연산 전 값이 이미지 바깥에 있다면 회전된 이미지에는 0을 입력해서 검은색 픽셀을 출력한다.

C언어 삼각함수는 인수로 라디안 값을 입력받기 때문에 θ\theta값을 라디안으로 바꿔줘야 한다.

rad=θ180×πrad = {\theta\over 180}\times\pi

pi 값은 define을 이용해서 상수로 정의한다.

BYTE* rotateBMP(BITMAPINFOHEADER* hinfo, BYTE* InputImg, double degree)
{
	int width = hinfo->biWidth, height = hinfo->biHeight;
	int x = 0, y = 0;
	int dif = 0;
	double radian = degree * pi / 180.0;
	double cos_seta = cos(radian), sin_seta = sin(-radian);
	double center_x = (double)width / 2.0, center_y = (double)height / 2.0;

	BYTE* OutputImg = NULL;
	OutputImg = (BYTE*)malloc(sizeof(BYTE) * hinfo->biSizeImage);

	for (int i = 0; i < width; i++)
	{
		for (int j = 0; j < height; j++)
		{
			for (int k = 0; k < 3; k++)
			{
				x = (int)(center_x + ((double)j - center_y) * sin_seta + ((double)i - center_x) * cos_seta);
				y = (int)(center_y + ((double)j - center_y) * cos_seta - ((double)i - center_x) * sin_seta);
				
				dif = 0;

				if ((y >= 0 && y < height) && (x >= 0 && x < width))
				{
					dif = InputImg[(3 * width * y) + (3 * x) + k];
				}

				OutputImg[(3 * width * j) + (3 * i) + k] = dif;
			}
		}
	}

	return OutputImg;
}

식을 그대로 구현하기만 하면 동작시킬 수 있다.
유의할 점은 회전 입력 시 사용하는 좌표는 중앙 기준이기 때문에 그 값을 맞춰줘야 한다. 위 코드에서는 center_x, center_y를 구했다.
120degree rotated
위 프로그램을 실행해서 120도 회전한 이미지를 획득할 수 있었다.

profile
Junior CV Developer / AI Researcher

0개의 댓글