기본적인 영상처리
by DINHO
지난번 C언어로 Raw파일의 입출력 코드를 살펴 봤었는데요. 오늘은 Raw 파일을 읽고 이미지의 색 반전, 밝기 조절, 이미지 회전 등의 기본적인 영상 신호 처리 과정을 함께 해보도록 하겠습니다. 관련 이론을 먼저 소개해 드리고 이후에 C코드를 살펴보겠습니다.
지난번 색 공간 에서 한 픽셀에 0~255 사이의 값으로 정보를 가지고 있다는 말 기억하시나요? 그때 0은 검정색이고 255로 갈수록 흰 색이 된다고 했었는데요. 원래 0~255 의미는 각각의 색의 농도를 의미하는 것이로 255에 가까워질수록 농도가 진해진다는 의미입니다. 흑백 이미지에서는 흰색의 농도, 컬러 이미지의 경우는 빨강, 초록, 파랑의 농도를 의미합니다.
색 반전
이번 실습 코드에서는 지난번과 같이 흑백 코드를 사용합니다. 흑백 이미지에서 색을 반전시킨다는 것은 흑은 백으로, 백은 흑으로 바뀐다는 것을 의미합니다. 이를 구현하기 위해 각각의 픽셀 값을 255에서 빼주면 됩니다!! 굉장히 간단한 아이디어입니다.
밝기 조절
흑백 이미지에서는 밝기 조절 역시 픽셀 수의 값들을 이용합니다. 픽셀 값들을 증가시키거나 감소시킴으로써 구현됩니다. 이번 코드에서는 전체적인 밝기 조절을 구현하였으며, 원하는 부분만 조절하는 것도 크게 어렵진 않습니다!!
회전
Raw 파일의 데이터 저장 순서는 이미지를 화면을 보았을 떄 좌측 상단의 위치한 픽셀로부터 우측 상단으로 저장되고, 다음 열을 저장할 때도 좌측부터 저장합니다. 우리가 흔히 알고 있는 행렬에서 1행 1열부터 1행 n열까지 채우고 그 다음에 2행 1열부터 2행 n열까지 채우는 순서입니다. (그러나 컴퓨터에서는 0행 0열부터 시작합니다!!)
이미지를 회전시키는 것은 저장 순서를 바꿔주면 회전이 되겠죠? 이 부분은 코드로 보시는 게 이해하기 쉬울 겁니다.
그렇다면 코드로 확인해보겠습니다.
아래 C코드에는 파일 메모리를 할당하고 위에서 소개한 이미지 처리를 하는 과정을 담아뒀습니다. 결과는 생략하겠습니다😁
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h> // 인풋 아웃풋 동작을 위한 함수 라이브러리
#include <stdlib.h> // 메모리 할당 및 프로레스 제어를 위한 라이브러리
#include <string.h> // 메모리 조작을 위한 문자열 라이브러리
#define VER 512 // 이미지의 수직 크기에 대한 상수 VER 정의
#define HOR 512 // 이미지의 수평 크기에 대한 상수 HOR 정의
#define F_SIZE (VER*HOR) // 이미지의 정체 사이즈에 대한 상수 F_SIZE 정의(VER x HOR)
/* 함수 선언 */
unsigned char** alloc_pic_2d(int width, int height); // 2D 사진 배열에 메모리를 할당하는 함수 선언
int main() // 메인 함수
{
int i, j; // 루프 카운터 변수 선언
unsigned char** ori_pic_2d = alloc_pic_2d(HOR, VER); // 2D 배열의 원본 영상에 대한 메모리 할당
unsigned char** inverted_pic_2d = alloc_pic_2d(HOR, VER); // 2D 배열의 색 반전 영상에 대한 메모리 할당
unsigned char** bright_pic_2d = alloc_pic_2d(HOR, VER); // 2D 배열의 밝기 조절 영상에 대한 메모리 할당
unsigned char** rotate90_pic_2d = alloc_pic_2d(HOR, VER); // 2D 배열의 90도 회전 영상에 대한 메모리 할당
unsigned char** rotate180_pic_2d = alloc_pic_2d(HOR, VER); // 2D 배열의 180도 회전 영상에 대한 메모리 할당
unsigned char** rotate270_pic_2d = alloc_pic_2d(HOR, VER); // 2D 배열의 270도 영상에 대한 메모리 할당
unsigned char** rotateup_pic_2d = alloc_pic_2d(HOR, VER);
unsigned char** rotateleft_pic_2d = alloc_pic_2d(HOR, VER);
FILE * fin2, * fout2, * fout3, * fout4, * fout5, * fout6, *fout7, * fout8; // 파일 포인터 선언
fin2 = fopen("Lena_512.raw", "rb"); // 이진 모드로 읽기 위한 입력 파일 열기
fout2 = fopen("Lena_512_inverted.raw", "wb"); // 이진 모드로 쓰기 위한 색 반전 출력 파일 열기
fout3 = fopen("Lena_512_brighten.raw", "wb"); // 이진 모드로 쓰기 위한 밝기 조절 출력 파일 열기
fout4 = fopen("Lena_512_rotate90.raw", "wb"); // 이진 모드로 쓰기 위한 90도 회전 출력 파일 열기
fout5 = fopen("Lena_512_rotate180.raw", "wb"); // 이진 모드로 쓰기 위한 180도 회전 출력 파일 열기
fout6 = fopen("Lena_512_rotate270.raw", "wb"); // 이진 모드로 쓰기 위한 270도 회전 출력 파일 열기
fout7 = fopen("Lena_512_rotateup.raw", "wb"); // 이진 모드로 쓰기 위한 상하반전 출력 파일 열기
fout8 = fopen("Lena_512_rotateleft.raw", "wb"); // 이진 모드로 쓰기 위한 좌우반전 출력 파일 열기
/* 입력 파일이 성공적으로 열렸는 지와 출력 파일이 성공적으로 저장되었는지 확인하는 과정 */
if (!fin2) // Check if the second input file was not opened successfully
{
printf("ERROR :: File Can't Read\n"); // Print error message
exit(1); // Exit the program with an error code
}
if (!fout2) // Check if the second output file was not opened successfully
{
printf("ERROR :: File Can't Save\n"); // Print error message
exit(1); // Exit the program with an error code
}
if (!fout3) // Check if the third output file was not opened successfully
{
printf("ERROR :: File Can't Save\n"); // Print error message
exit(1); // Exit the program with an error code
}
if (!fout4) // Check if the fourth output file was not opened successfully
{
printf("ERROR :: File Can't Save\n"); // Print error message
exit(1); // Exit the program with an error code
}if (!fout5) // Check if the fifth output file was not opened successfully
{
printf("ERROR :: File Can't Save\n"); // Print error message
exit(1); // Exit the program with an error code
}if (!fout6) // Check if the sixth output file was not opened successfully
{
printf("ERROR :: File Can't Save\n"); // Print error message
exit(1); // Exit the program with an error code
}
/* 색 반전 함수, 픽셀마다 0~255 값을 가지고 있기 때문에 최대값인 255에서 현재 value 값을 빼주면 반전된 색 획득 가능 */
for (j = 0; j < VER; j++) {
fread(ori_pic_2d[j], sizeof(unsigned char), HOR, fin2);
for (i = 0; i < HOR; i++) {
inverted_pic_2d[j][i] = 255 - ori_pic_2d[j][i]; // Invert the color
}
}
/* 색 밝기 조절 함수, 0 ~ 255 값 사이에서 원본 픽셀의 value에 값을 더해주거나 빼주면 명암의 변화가 생김 */
for (j = 0; j < VER; j++)
{
fread(ori_pic_2d[i], sizeof(unsigned char), HOR, fin2);
for (i = 0; i < HOR; i++)
{
int new_val = 50 + ori_pic_2d[j][i]; // 50만큼 밝게 조절
new_val = new_val > 255 ? 255 : (new_val < 0) ? 0 : new_val; //unsigned char 자료형은 0~255 값만 가지므로 255의 크면 255로, 0보다 작으면 0으로 고정
bright_pic_2d[j][i] = new_val;
}
}
/* 회전 함수, j는 row, i는 colum의 index를 의미 */
for (j = 0; j < VER; j++)
{
fread(ori_pic_2d[i], sizeof(unsigned char), HOR, fin2);
for (i = 0; i < HOR; i++)
{
rotate90_pic_2d[j][i] = ori_pic_2d[(VER - 1) - i][j]; // 90도 변환
rotate180_pic_2d[j][i] = ori_pic_2d[(VER - 1) - j][(HOR - 1) - i]; // 180도 변환
rotate270_pic_2d[j][i] = ori_pic_2d[i][(VER - 1) - j]; // 270도 변환
rotateup_pic_2d[j][i] = ori_pic_2d[(VER - 1) - j][i]; // 상하 반전
rotateleft_pic_2d[j][i] = ori_pic_2d[j][(HOR - 1) - i]; // 좌우 반전
}
}
/* raw_image 배열로부터 이미지 데이터 쓰기 */
for (i = 0; i < VER; i++) {
fwrite(inverted_pic_2d[i], sizeof(unsigned char), HOR, fout2);
fwrite(bright_pic_2d[i], sizeof(unsigned char), HOR, fout3);
fwrite(rotate90_pic_2d[i], sizeof(unsigned char), HOR, fout4);
fwrite(rotate180_pic_2d[i], sizeof(unsigned char), HOR, fout5);
fwrite(rotate270_pic_2d[i], sizeof(unsigned char), HOR, fout6);
fwrite(rotateup_pic_2d[i], sizeof(unsigned char), HOR, fout7);
fwrite(rotateleft_pic_2d[i], sizeof(unsigned char), HOR, fout8);
}
/* 이진모드로 연 파일들을 닫아줌 */
fclose(fin2);
fclose(fout2);
fclose(fout3);
fclose(fout4);
fclose(fout5);
fclose(fout6);
fclose(fout7);
fclose(fout8);
/* 메모리 해제 함수, 할당 메모리는 반드시 free 함수를 통해 메모리를 해제 해야 함 */
for (i = 0; i < VER; i++) {
free(ori_pic_2d[i]);
free(inverted_pic_2d[i]);
free(bright_pic_2d[i]);
free(rotate90_pic_2d[i]);
free(rotate180_pic_2d[i]);
free(rotate270_pic_2d[i]);
free(rotateup_pic_2d[i]);
free(rotateleft_pic_2d[i]);
}
free(ori_pic_2d); // 원본 이미지 어레이의 포인터 메모리 해제
free(inverted_pic_2d); // 색 반전 이미지 어레이의 포인터 메모리 해제
free(bright_pic_2d); // 밝기 이미지 어레이의 포인터 메모리 해제
free(rotate90_pic_2d); // 90도 회전 이미지 어레이의 포인터 메모리 해제
free(rotate180_pic_2d); // 180도 회전 이미지 어레이의 포인터 메모리 해제
free(rotate270_pic_2d); // 270도 회전 이미지 어레이의 포인터 메모리 해제
free(rotateup_pic_2d); // 상하반전 이미지 어레이의 포인터 메모리 해제
free(rotateleft_pic_2d); // 좌우반전 이미지 어레이의 포인터 메모리 해제
return 0; // Return success
}
// 위에서 정의한 함수는 다음과 같음
unsigned char** alloc_pic_2d(int width, int height) // 2차원 배열 메모리 할당 함수
{
unsigned char** pic;
int i;
if ((pic = (unsigned char**)calloc(width, sizeof(unsigned char*))) == NULL)
{
printf("\n malloc_picture : Picture structure \n");
exit(1);
}
for (i = 0; i < height; i++)
{
if ((pic[i] = (unsigned char*)calloc(height, sizeof(unsigned char))) == NULL)
{
printf("\n malloc_picture : Picture structure \n");
exit(1);
}
}
return pic;
}
Follow my github