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 ビット色)、圧縮、オフセットなどの情報が含まれています。

Portable Pixel Map (PPM) 形式は、比較的単純な形式のカラー イメージです。 PPM には 2つの変種があります。1つは P3 で、もう 1つは P6 です。

P3 は ASCII 形式ですが、P6 はバイナリ形式です。 通常、ASCII 形式はバイナリ形式よりも多くのスペースを必要とします。 ほとんどの画像には大量のデータがあります。 したがって、スペースは非常に重要です。

たとえば、100x100 の小さな画像には 10000 ピクセルがあります。 24 ビットのカラー イメージの場合、各ピクセルは 3 バイトを使用します。 したがって、イメージを格納するには 30000 バイトが必要になります。

このチュートリアルでは、P6 形式について説明します。 P3 は比較的読みやすく理解しやすいです。 ただし、P6 形式を学習すると、より簡単になります。

P6 は 24 ビットのカラー イメージです。 P6 のヘッダーは比較的単純です。 BMP 形式と比較できます。

PPM ファイル形式には 3つのヘッダー行があり、15 バイトのスペースが必要です。 最初の行には、PPM の識別子/署名があります (P3 または P6)。

2 行目には画像の幅とスペース (スペースで区切られた) の情報があり、3 行目には最大カラー値の情報 (例: 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-

前に説明したように、最初の 3 行にはヘッダー情報があります。 最初の行の P6 は、画像の種類を識別します。

プログラマーは、識別子を確認し、フォーマットに従ってイメージを読み取ることができます。

2行目は、幅を111、高さを132と記述しています。 3 行目は、最大色の値として 255 を示しています。

4 行目からは、バイナリ形式で保存された画像データがあります (文字の形で画面に表示され、一部は認識可能ですが、すべてではありません)。

C++ で PPM ファイルを読み取る

PPM ファイルを読み取るには、まずヘッダーを読み取って、幅と高さに関する情報を取得する必要があります。 それに応じて動的メモリを宣言し、画像のデータを読み取る必要があるため、これは重要です。

P6 はバイナリ形式です。 したがって、画像をバイナリ形式で開き、それに応じて読み取る必要があります。

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

1行目でFILEポインタを宣言し、2行目でファイルのハンドラを格納しています。 fopen 関数の 2 番目のパラメーターは rb であることに注意してください。ここで、r は読み取り用であり、b はバイナリー用です (つまり、ファイルのバイナリー読み取りモード)。

バイナリ ストレージに慣れていない場合は、バイナリ ファイル に関するこの記事をお読みください。 ファイルを開いたら、ファイルのヘッダーを読み取る必要があります。

そのためのコードは次のとおりです。

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

fread の詳細については、こちら を参照してください。

ヘッダーが正常に読み取られたら、次は幅と高さの情報を抽出して先に進みます。 この情報は文字形式で保存されるため、文字情報を 1つずつ読み取り、次のコードを使用して整数値に変換できます (代わりに atoi 関数を使用することもできます)。

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

前に説明したように、ヘッダーの最初の 2 バイトには P6 値が含まれ、3 番目は次の行の区切り文字です。 したがって、インデックス 3 から始まる 4 番目の要素から開始します。

1 文字ずつ読み取り、整数値を完成させます (スペース文字または改行文字で区切ります)。 各文字について、数字の ASCII 値は 48 または 32 hex (0 の ASCII 値) から始まるため、0 の ASCII 値を減算する必要があります。

たとえば、6 の ASCII 値は 10 進数で 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 の代わりに unsigned char を使用しているのは、単純な char 型では最初のビットが符号に使用されるためです (正の数の場合は 0、負の数の場合は 1)。

char の場合、正の値の最大範囲は 0 から 127 ですが、0 から 255 までの情報を読み取る必要があります。したがって、unsigned char が必要です。

最後のステップは、画像を読み取ることです。これは非常に簡単です。 fread 関数を次のように呼び出すだけです。

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

繰り返しますが、最初のパラメーターは画像データを格納するための char 配列であり、size については既に十分に説明されています。

変数 image には、すべてのピクセル (赤、緑、青の値) があります。 幅 x 高さ の 3つの個別の配列を宣言するか、最初の値が赤、2 番目が緑、3 番目が青であることを覚えて操作を処理することができます。

たとえば、画像から赤の成分を取り除きたい場合は、すべてのピクセルの最初のインデックスに 0 を割り当てる必要があります。

次のように実行できます。

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

for ループのインクリメント ステップに注意してください。ここでは、i1 の代わりに 3 で増やしています。 これは、2 番目と 3 番目の値が緑と青の値であり、緑と青の値を変更したくないためです。

下の例の画像を参照して、画像から赤のコンポーネントを削除した後の違いを確認してください。 左が元の画像で、右が赤成分を除いた修正後の画像です。

オリジナル 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;
}

このプログラムでは、1つの PPM ファイルを読み取り、変更後に 3つの PPM ファイルを書き込みました。

出力画像を見てみましょう。

赤なし VS 緑なし VS 青なし

一番左の写真では赤い色が取り除かれています。 中央のものは緑なしで、3 番目のものは青成分がありません。