C++에서 PPM 파일 읽기

Abdul Mateen 2023년10월12일
  1. PPM 파일
  2. C++에서 PPM 파일 읽기
C++에서 PPM 파일 읽기

이 자습서에서는 PPM 파일에 대해 배우고 C++를 사용하여 읽는 방법을 배웁니다.

먼저 PPM 파일 형식에 대해 논의하고 이해할 것입니다. 나중에 우리는 C++에서 PPM 파일을 읽는 단계별 절차를 배울 것입니다.

PPM 파일

모든 이미지 파일에는 이미지 데이터에 첨부된 헤더/메타데이터가 있습니다. 헤더에는 이미지와 파일에 대한 다양한 정보가 포함되어 있습니다.

모든 이미지 파일의 시작 부분에는 파일 형식의 식별이 있습니다. 예를 들어 BM은 모든 BMP 파일의 시작 부분에 기록됩니다.

헤더의 나머지 부분에는 너비, 높이, 색상 수, 이미지 유형(바이너리, 그레이스케일, 16색, 256색, 24비트 색상), 압축, 오프셋 등과 같은 정보가 포함됩니다.

PPM(Portable Pixel Map) 형식은 비교적 간단한 형식의 컬러 이미지입니다. PPM에는 두 가지 변형이 있습니다. 하나는 P3이고 다른 하나는 P6입니다.

P3은 ASCII 형식인 반면 P6은 이진 형식입니다. ASCII 형식은 일반적으로 이진 형식보다 더 많은 공간을 차지합니다. 대부분의 이미지에는 많은 데이터가 있습니다. 따라서 공간이 중요합니다.

예를 들어 100x100의 작은 이미지에는 10000픽셀이 있습니다. 24비트 컬러 이미지의 경우 각 픽셀은 3바이트를 사용합니다. 따라서 이미지를 저장하려면 30000바이트가 필요합니다.

이 튜토리얼에서는 P6 형식에 대해 설명합니다. 상대적으로 P3을 읽고 이해하기 쉽습니다. 그러나 P6 형식을 배운 후에는 더 쉬워질 것입니다.

P6은 24비트 컬러 이미지입니다. P6의 헤더는 비교적 간단합니다. BMP 형식과 비교할 수 있습니다.

PPM 파일 형식에는 15바이트의 공간을 차지하는 3개의 헤더 줄이 있습니다. 첫 번째 줄에는 PPM의 식별자/서명이 있습니다(P3 또는 P6).

두 번째 줄에는 이미지의 너비 및 공간(공백으로 구분) 정보가 있고 세 번째 줄에는 최대 색상 값 정보(예: 15 또는 255)가 있습니다.

예를 들어 다음 이미지를 보십시오.

하우스 이미지 PPM 파일

집 이미지의 메타 정보를 살펴보겠습니다(데이터가 이 이미지에 속한다고 믿기지 않을 수도 있습니다).

P6
111 132
255
U`6Xe8Xk8Ul9Tg:Ve<Wd7Wd5Td5N_0MY/NZ3P^5Ub5Wc4T`2R`4T`4[d6Yd7NY1CM,@J-FQ/O\2Vg8Ra5FW.?O+@M,BQ/:F-0:**5)*1(6@-CQ5=H1;H-

앞에서 설명한 것처럼 처음 세 줄에는 헤더 정보가 있습니다. 첫 번째 줄의 P6은 이미지 유형을 나타냅니다.

프로그래머는 식별자를 확인하고 형식에 따라 이미지를 읽을 수 있습니다.

두 번째 줄은 너비를 111로, 높이를 132로 설명합니다. 세 번째 줄은 최대 색상 값으로 255를 나타냅니다.

네 번째 줄부터 바이너리 형식으로 저장된 이미지 데이터가 있습니다(화면에 문자 형태로 표시되며 일부는 인식할 수 있지만 전부는 아님).

C++에서 PPM 파일 읽기

PPM 파일을 읽으려면 먼저 헤더를 읽어 너비와 높이에 대한 정보를 얻어야 합니다. 이에 따라 동적 메모리를 선언하고 이미지의 데이터를 읽어야 하므로 이는 중요합니다.

‘P6’은 바이너리 형식입니다. 따라서 이진 형식으로 이미지를 열고 그에 따라 읽어야 합니다.

FILE *read;
read = fopen("west_1.ppm", "rb");

첫 번째 줄에서 FILE 포인터는 두 번째 줄에 파일 처리기를 저장하도록 선언됩니다. fopen 함수의 두 번째 매개변수는 rb입니다. 여기서 r은 읽기용이고 b는 이진용입니다(즉, 파일의 이진 읽기 모드).

바이너리 저장소에 익숙하지 않은 경우 바이너리 파일에 대한 이 문서를 읽어보세요. 파일을 연 후에는 파일의 헤더를 읽어야 합니다.

이에 대한 코드는 다음과 같습니다.

unsigned char header[15];
fread(header, 15, 1, read);

fread에 대한 자세한 내용은 여기에서 확인할 수 있습니다.

헤더가 성공적으로 읽히면 다음은 너비 및 높이 정보를 추출하여 계속 진행합니다. 이 정보는 문자 형태로 저장되므로 다음 코드를 사용하여 문자 정보를 하나씩 읽어서 정수 값으로 변환할 수 있습니다(또는 atoi 함수를 사용할 수도 있습니다).

int x = 0;
for (pos = 3; header[pos] != '\n' && header[pos] != ' '; pos++)
  x = x * 10 + (header[pos] - '0');

앞에서 설명한 것처럼 헤더의 처음 두 바이트에는 P6 값이 포함되고 세 번째 바이트는 다음 줄의 구분 기호입니다. 따라서 인덱스 3부터 시작하는 4번째 요소부터 시작하겠습니다.

정수 값을 완성하기 위해 한 문자씩 읽습니다(공백 문자 또는 줄 바꿈 문자로 구분됨). 각 문자에 대해 숫자의 ASCII 값이 48 또는 32 hex(ASCII 값 0)에서 시작하기 때문에 ASCII 값 0을 빼야 합니다.

예를 들어 6의 ASCII 값은 십진수로 54입니다. 따라서 54에서 48을 빼면 6이 됩니다. 이 사실을 사용하여 문자 배열에서 정수 값을 얻을 수 있습니다.

문자 배열 135를 정수로 변환하는 작은 예제를 참조하십시오.

integer x = 0                             // initial integer value
    x = x* 10 + 1 = 1 x = x* 10 + 3 = 13  // find out 10s by
    x = x* 10 + 5 = 135  // find out 100s and x will have final integer value

너비와 높이 정보를 얻은 후 이미지에 “너비 x 높이” 픽셀이 있고 각 이미지가 3바이트(빨강, 녹색 및 파랑, 알려진 RGB로).

unsigned char *image;
image = new unsigned char[width * height * 3];

간단한 char 유형에서 첫 번째 비트가 부호에 사용되기 때문에 char 대신 unsigned char를 사용합니다(양수는 0, 음수는 1).

char의 경우 양수 값의 최대 범위는 0~127이지만 0~255의 정보를 읽어야 합니다. 따라서 unsigned char가 필요합니다.

마지막 단계는 이미지를 읽는 것으로 매우 간단합니다. 다음과 같이 fread 함수를 호출하십시오.

fread(image, size, 1, file);  // where size is width x height x 3

다시 말하지만 첫 번째 매개변수는 이미지 데이터를 저장하는 문자 배열이며 크기는 이미 잘 설명되어 있습니다.

변수 image에는 빨강, 녹색 및 파랑 값의 모든 픽셀이 있습니다. 크기가 너비 x 높이인 세 개의 개별 배열을 선언하거나 첫 번째 값이 빨간색, 두 번째 값이 녹색, 세 번째 값이 파란색임을 기억하여 작업을 처리할 수 있습니다.

예를 들어 이미지에서 빨간색 구성 요소를 제거하려면 모든 픽셀의 첫 번째 인덱스에 0을 할당해야 합니다.

다음과 같이 할 수 있습니다.

for (i = 0; i < size; i = i + 3) {
  image[i] = 0;

for 루프에서 1 대신 3으로 i를 증가시킨 증분 단계에 유의하십시오. 두 번째와 세 번째 값이 녹색과 파란색에 대한 값이고 녹색과 파란색 값을 변경하고 싶지 않기 때문입니다.

이미지에서 빨간색 구성 요소를 제거한 후 차이점을 보려면 아래 예제 이미지를 참조하십시오. 왼쪽에는 원본 이미지가 있고 오른쪽에는 빨간색 구성 요소가 없는 수정된 이미지가 있습니다.

빨간색 구성 요소가 없는 원본 VS

PPM 파일 읽기에 대한 전체적인 아이디어를 얻으셨기를 바랍니다. 마지막으로 PPM 파일을 읽고, 수정하고, 쓰는 완전한 C++ 프로그램을 살펴보겠습니다.

#include <cstdio>
#include <iostream>

using namespace std;

void readPPMHeader(FILE *file, unsigned char *header) {
  fread(header, 15, 1, file);
}
void readPPMImage(FILE *file, unsigned char *image, int size) {
  fread(image, size, 1, file);
}
void writePPM(FILE *file, unsigned char *header, unsigned char *image,
              int size) {
  fwrite(header, 15, 1, file);   // writing header information
  fwrite(image, size, 1, file);  // writing image information
}
void removeRed(unsigned char *image, unsigned char *withoutredimage, int size) {
  int i;
  for (i = 0; i < size; i = i + 3) {
    withoutredimage[i] = 0;  // red component is set to 0
    withoutredimage[i + 1] = image[i + 1];
    withoutredimage[i + 2] = image[i + 2];
  }
}
void removeGreen(unsigned char *image, unsigned char *withoutgreenimage,
                 int size) {
  int i;
  for (i = 0; i < size; i = i + 3) {
    withoutgreenimage[i] = image[i];
    withoutgreenimage[i + 1] = 0;  // green component is set to 0
    withoutgreenimage[i + 2] = image[i + 2];
  }
}
void removeBlue(unsigned char *image, unsigned char *withoutblueimage,
                int size) {
  int i;
  for (i = 0; i < size; i = i + 3) {
    withoutblueimage[i] = image[i];
    withoutblueimage[i + 1] = image[i + 1];
    withoutblueimage[i + 2] = 0;  // blue component is set to 0
  }
}
// To extract width & height from header
int getDimension(unsigned char *header, int &pos) {
  int dim = 0;
  for (; header[pos] != '\n' && header[pos] != ' '; pos++)
    dim = dim * 10 + (header[pos] - '0');
  return dim;
}
int main() {
  FILE *read, *write1, *write2, *write3;
  read = fopen("west_1.ppm", "rb");
  unsigned char header[15], *image;
  readPPMHeader(read, header);
  if (header[0] != 'P' || header[1] != '6') {
    cout << "Wrong file format\n";
    return 0;
  }
  write1 = fopen("west_1_without_red.ppm", "wb");
  write2 = fopen("west_1_without_green.ppm", "wb");
  write3 = fopen("west_1_without_blue.ppm", "wb");
  int width, height, clrs, pos = 3;
  width = getDimension(header, pos);
  pos++;
  height = getDimension(header, pos);
  cout << "Width:" << width << "\tHeight:" << height << '\n';
  image = new unsigned char[width * height * 3];
  unsigned char *withoutredimage, *withoutgreenimage, *withoutblueimage;
  withoutredimage = new unsigned char[width * height * 3];
  withoutgreenimage = new unsigned char[width * height * 3];
  withoutblueimage = new unsigned char[width * height * 3];
  readPPMImage(read, image, width * height * 3);
  removeRed(image, withoutredimage, width * height * 3);
  writePPM(write1, header, withoutredimage, width * height * 3);
  removeGreen(image, withoutgreenimage, width * height * 3);
  writePPM(write2, header, withoutgreenimage, width * height * 3);
  removeBlue(image, withoutblueimage, width * height * 3);
  writePPM(write3, header, withoutblueimage, width * height * 3);
  fclose(read);
  fclose(write1);
  fclose(write2);
  fclose(write3);
  return 0;
}

이 프로그램에서는 하나의 PPM 파일을 읽고 수정 후 세 개의 PPM 파일을 작성했습니다.

출력 이미지를 살펴보겠습니다.

빨간색 없음 VS 녹색 없음 VS 파란색 없음

가장 왼쪽 사진에서 빨간색이 제거되었습니다. 가운데는 녹색이 없고 세 번째는 파란색 구성 요소가 없습니다.