PPMフォーマット

  • 2020.06.25 Thursday
  • 13:57

●PPMフォーマットとは

 8bit以上の階調が扱え、最も簡単でプログラミングし易い非圧縮の画像フォーマットとして、一般にはあまり知られていませんがPPMフォーマットという画像フォーマットがあります。このPPMフォーマットを使って16bit階調の画像データを画像ファイルに出力すれば、あとは前項で説明したconvertを使ってフォーマット変換すれば、下図にように非圧縮の16bit階調で様々な画像フォーマットに変換することが出来、とても便利です。画像処理のプログラミングで様々なフォーマットに対応させるのはとても面倒なのですが、PPMフォーマットにさえ対応すれば、後はconvertで変換出来るのです。

    多階調画像の出入口PPMフォーマット

 PPMフォーマットは、PNM (PPM / PGM / PBM)画像フォーマットの中の一つで、全体を総称して、PNM (Portable aNyMap)フォーマットと呼ばれるものですが、格納するフォーマットによって、呼び名が異なっています。モノクロ(白黒2色)の画像を格納する PBM (Portable BitMap)フォーマット、グレースケールの画像を格納するPGM (Portable GrayMap)フォーマット、フルカラーの画像を格納するPPM (Portable PixMap)フォーマット、という区別になっています。なお、アルファチャンネルには対応していません。それぞれは別の画像フォーマットというよりは、同一の画像フォーマットのモードが違うもの、といった方がしっくり来るのですが、拡張子もそれぞれ.ppm/.pgm/.pbmと分かれているのが通常です。全部を合わせた、pnmという拡張子も定義されていますが、あまり見かけたことはありません。また、このPNMを拡張したフォーマットで、アルファチャンネルにも対応したPAM (Portable Arbitrary Map)というフォーマットもあります。

 

●PPMフォーマット:ヘッダー

 まずは、このヘッダー領域のフォーマットについて説明します。

 ヘッダーは以下の様な構造になっています。

P<x>

# comment

<width> <height>

<max>

 

 はじめのPxと書いているところは、ファイル形式の種別を示すマジックナンバーとなっています。

        PNMフォーマットのマジックナンバー

マジックナンバー

フォーマット

拡張子

画像の種類

データ形式

P1

PBM

.pbm

モノクロ(白黒2色)

テキスト

P2

PGM

.pgm

グレースケール

テキスト

P3

PBM

.ppm

フルカラー

テキスト

P4

PBM

.pbm

モノクロ(白黒2色)

バイナリ-

P5

PGM

.pgm

グレースケール

バイナリ-

P6

PBM

.ppm

フルカラー

バイナリ-

 

 テキスト形式は、ASCII形式、バイナリー形式は、RAW形式と呼ばれることもあります。 テキストで記述された、ある意味で制約のゆるいフォーマットですが、このマジックナンバーだけは必ずファイルの先頭に記述する必要があります。

 続いて、マジックナンバーの次の行に書いている#から始まる行はコメントです。コメントは何もここにだけ現れるとは限らず、テキストの領域であればどこに現れてもよいことになっています。 極端な話では、ヘッダーだけでなく、テキスト形式の場合は画像データ中に現れても仕様違反ではありません。コメントの範囲を厳密に言うと、#で始まり、次の最初の改行、つまり¥nか、¥rが現れるまで、がコメントになります。コメントには画像を出力したツールの名前などが書かれていたりしています。

 コメントの次の行には画像の幅と高さを10進数で記述されています。多くの場合、幅と高さが1行の中に書かれ、間にスペースが挟まれていますが、前述のとおり、幅と高さの間に改行を入れたり、マジックナンバーと同じ行にあっても間違いではありません。

 そして最後に、輝度の最大値を10進数で記述します。この行は2値しか取らないPBM形式には不要なため存在しませんが、PGMフォーマットとPPMフォーマットは指定する必要があります。一般に利用されるRGB値などの最大値は、8bitの最大値である255ですが、この画像フォーマットでは任意の数字を指定出来ます。例えば9と書けば0910階調で表現することも出来ます。もともとは上限255、つまり8bitまでの対応となる仕様でしたが、現在、仕様が拡張され上限65535で、16bit階調を扱える仕様になっています。

 

●PPMフォーマット:画像データ

 ヘッダー情報に続いて配置されるのが画像データです。詳細は各画像フォーマット毎に説明して行きますが、共通しているのは、データの格納順は横書き文章と同一で、左上から右側へ走査しながら下に向かう形で格納されています。また、テキスト形式は1行の長さは70文字以下であることが推奨されています。ですが、実際には70文字以上あっても読み込んでくれるツールがほとんどです。

P1PBMフォーマット)

 白黒2色のテキスト形式。0が白、1が黒として、順にテキストで格納されます。 このフォーマットで混乱しやすいのが、ピクセルの表現に使用される文字は012種類しか無いため、区切り文字しての空白は必ずしも必要ありません。隙間なく01が連なっていてもよいし、間に空白があっても良いことになっています。読み込む側を作ることを考えると、どちらでも良いという曖昧な仕様はかえって面倒になります。

P2PGMフォーマット)

 グレースケールのテキスト形式。指定した最大値までの数値を10進数のテキストで順に格納します。数値の桁数は決まっていないため、数値と数値の間には区切り文字が必要になります。

P3PPMフォーマット)

フルカラーのテキスト形式。指定した最大値までの数値で、RGBの順に10進数のテキストで順に格納します。数値の桁数は決まっていないため、数値と数値の間には区切り文字が必要になります。

P4PBMフォーマット)

 白黒2値のバイナリー形式。0が白、1が黒とするのはテキスト形式と同じですが、1bitで表現可能なので、上位ビットから順に、1byte8ピクセル分格納します。つまり、左から「白白白白黒黒黒黒」であれば、0x0Fと格納されます。また、画像の幅が8の倍数で無い場合は、最後の1byteに余地が残りますが、次の行のデータは詰め込みません。つまり、幅4の画像で「白白白白」、次の行が「黒黒黒黒」であれば0x0f1byteにまとめてしまうのではなく、0x00,0xf0と行ごとに分離します。

P5PGMフォーマット)

 グレースケールのバイナリー形式、指定した最大値までの数値を1ピクセル1byteで順に格納します。例えば最大値が15だとすると、1ピクセルを表現するのに4bitあればよく、 1byte2ピクセル分格納出来ますが、そのようなことは行わず、必ず1byteを使用します。 また、最大値が256以上の場合2byte必要になりますが、その場合はビッグエンディアンで格納されます。

P6PPMフォーマット)

 フルカラーのバイナリー形式、指定した最大値までの数値で、RGBの順に1byteずつ、1ピクセル3byteで順に格納します。P5と同じで3byte以下で表現出来る場合もまとめるようなことは行いません。また、最大値が256以上の場合も同様に、ビッグエンディアンの2byteで格納されます。

 

●PPMフォーマットの関数プログラム

 ここからはプログラム演習の始まりです。実際にプログラムを書いて見ましょう。ここでは、ppmフォーマットで入力してゲインx30して、ppmフォーマットに出力するプログラムを書いてみます。プログラムはMakefileを使う方法とVisual Studioを使う方法とどちらでもコンパイル出来るような書き方をしています。後で使い回しし易い様に関数(サブルーチン)で記述します。

 ただ、この関数は、ファイル操作の関数などを多く使っていて、C/C++として面倒な操作を沢山行っているためにC/C++の初心者には少し難しいと思います。C/C++を使った画像処理でハードルが高く、難しくしているのはこの画像ファイルの入出力部分で、多くの人が挫折しています。この画像処理アルゴリズムの開発とあまり関係のないこの部分の関数はブラックボックスにしてライブラリーとして扱えば良く、後で興味があったら勉強してみる程度で良いと思います。

ppmフォーマットの関数(サブルーチン)の記述

 ppmフォーマットの関数は、マジックナンバーP3P6ppmフォーマットなので、P3P68bit16bitまで対応出来るように記述しました。次にサンプルプログラム(関数)と、そのヘッダープログラムを載せます。このプログラムは演習のprogram110の全てに使います。拡張子は〜.cC言語の拡張子ですが、C++の拡張子〜.cppにした方が、Visual Studioの併用やC++のライブラリーなども使えるので後で便利です。なので実際はCで記述しているのですが〜.cppというC++の拡張子を使います。また、ヘッダーファイルの拡張子は、〜.hになります。

      PPMフォーマットの関数(ppm.cpp)

/***************************************************
 * ppm
 * @file    ppm.cpp
 * @author    Akihiro Okumura
 * @date    2020.03.04
 ***************************************************/

#include "ppm.h"

/*****************************************************************
 * read_ppm_header
 *****************************************************************/
void read_ppm_header(char *file, int *width, int *height, int *ppm_pn, int *bits)
{
    FILE *fp;

    if ( (fp = fopen( file, "rb" )) == NULL ){
        fprintf(stderr, "¥nCan't open inputfile %s¥n", file);
        fclose(fp);
        exit(0);
    }
    fprintf(stderr, "input file (¥"%s¥")¥n", file);

    char s[1024];
    int w, h;

    // P3・P6(ppm)のみ対応
    do {
        if (fgets(s, 1023, fp) == NULL){
            fclose(fp);
            exit(-1);
        }
    } while ( s[0] == '#' );

    if ( s[0] == 'P' && (s[1] == '3' || s[1] == '6') ){
        if ( s[1] == '3' ) *ppm_pn = 3;
        if ( s[1] == '6' ) *ppm_pn = 6;
    } else {
        return;
    }

    // サイズ(width, height)
    do {
        if (fgets(s, 1023, fp) == NULL){
            fclose(fp);
            exit(-1);
        }
    } while (s[0] == '#');
    sscanf( s, "%d %d", &w, &h);
    // 頭出し
    for ( int line=1; line <3; line++ ){
        if ( w == 0 && h == 0 ){
            do {
                if (fgets(s, 1023, fp) == NULL){
                    fclose(fp);
                    exit(-1);
                }
            } while (s[0] == '#');
            sscanf( s, "%d %d", &w, &h);
        }
    }
    *width = w;
    *height = h;

    // レベル最大値
    int data = 0;
    do {
        if (fgets(s, 1023, fp) == NULL){
            fclose(fp);
            exit(-1);
        }
    } while (s[0] == '#');
    sscanf( s, "%d", &data );
    *bits = 0;
    for (int i = 1; i <= 16; i++){
        if ((data >= (0x01 << (i-1))) && (data < (0x01 << i))){
            *bits = i;
        }
    }
    if (*bits == 0){
        fprintf( stderr, "warning : unknown level = %d¥n", data );
        fclose(fp);
        exit(0);
    }
    fprintf(stderr, "Get PPM file infomation(width=%d, heigt=%d, bits=%d)¥n", *width, *height, *bits);

    fclose(fp);
    return;
}

/*****************************************************************
 * read_ppm
 *****************************************************************/
void read_ppm(char *file, unsigned short *r_data, unsigned short *g_data, unsigned short *b_data, int width, int height)
{
    FILE *fp;

    if ( (fp = fopen( file, "rb" )) == NULL ){
        perror( file );
        exit(0);
    }

    char s[1024];
    int ppm_pn = 0;
    int w = 0;
    int h = 0;
    int bits = 0;

    // P3・P6(ppm)のみ対応
    do {
        if (fgets(s, 1023, fp) == NULL){
            fclose(fp);
            exit(-1);
        }
    } while (s[0] == '#');

    if ( s[0] == 'P' && (s[1] == '3' || s[1] == '6') ){
        if ( s[1] == '3' ) ppm_pn = 3;
        if ( s[1] == '6' ) ppm_pn = 6;
    } else {
        return;
    }
    // 頭出し
    for ( int line=1; line <3; line++ ){
        if ( w == 0 && h == 0 ){
            do {
                if (fgets(s, 1023, fp) == NULL){
                    fclose(fp);
                    exit(-1);
                }
            } while (s[0] == '#');
            sscanf( s, "%d %d", &w, &h);
        }
    }
    // レベル最大値
    int data = 0;
    do {
        if (fgets(s, 1023, fp) == NULL){
            fclose(fp);
            exit(-1);
        }
    } while (s[0] == '#');
    sscanf( s, "%d", &data );
    bits = 0;
    for (int i = 1; i <= 16; i++){
        if ((data >= (0x01 << (i - 1))) && (data < (0x01 << i))){
            bits = i;
        }
    }
    if (bits == 0){
        fprintf(stderr, "warning : unknown level = %d¥n", data);
        fclose(fp);
        exit(0);
    }

    int dno = width * height;
    int words;
    int frame_size;

    if ( ppm_pn == 3 ){
        unsigned short *r, *g, *b;
        int data[3];

        r = r_data;
        g = g_data;
        b = b_data;

        // フレーム読みだし
        for ( int i = 0; i < dno; i++ ){
            for ( int j = 0; j < 3; j++ ){
                if (fscanf(fp, "%s", s) == EOF){
                    fclose(fp);
                    exit(-1);
                }
                    
                //コメント行をスキップ
                while(s[0] == '#'){
                    if (fgets(s, sizeof(s), fp) == NULL){
                        fclose(fp);
                        exit(-1);
                    }
                    if (fscanf(fp, "%s", s) == EOF){
                        fclose(fp);
                        exit(-1);
                    }
                }
                data[j] = atoi(s);
            }

            //        fprintf( stderr, "%d %d %d¥n", data[0], data[1], data[2] );

            *r = (unsigned short)data[0];
            *g = (unsigned short)data[1];
            *b = (unsigned short)data[2];

            r++;
            g++;
            b++;
        }
    }
    if ( ppm_pn == 6 ){
        // フレーム読出し
        words = word_size( bits );
        frame_size = dno * 3 * words;
        if ( words == 1 ){
            unsigned short *ip;
            if ( libuf_init(frame_size) ){
                fclose(fp);
                exit(-1);
            }
            ip = libufp;
            if ((int)fread(ip, sizeof(unsigned char), frame_size, fp) != frame_size) {
                 fprintf(stderr, " ERROR2 (readf_ppm) : read file %s ¥n", file);
                fclose(fp);
                exit(-1);
            }
            unsigned short *r, *g, *b;
            unsigned char *d;

            r = r_data;
            g = g_data;
            b = b_data;
            d = (unsigned char *)libufp;

            // フレーム読みだし
            for ( int i=0; i<dno; i++ ){
                *r = (unsigned short)*d++;
                *g = (unsigned short)*d++;
                *b = (unsigned short)*d++;

                r++;
                g++;
                b++;
            }
        } else {
            unsigned short *ip;
            if ( libuf_init(frame_size) ){
                fclose(fp);
                exit(-1);
            }
            ip = libufp;
            if ((int)fread(ip, sizeof(unsigned short), frame_size/2, fp) != frame_size/2) {
                 fprintf(stderr, " ERROR2 (readf_ppm) : read file %s ¥n", file);
                fclose(fp);
                exit(-1);
            }

            unsigned short *r, *g, *b;
            unsigned short *d;

            r = r_data;
            g = g_data;
            b = b_data;
            d = libufp;

            // フレーム読みだし
            for ( int i=0; i<dno; i++ ){
                *r = endian(*d++);
                *g = endian(*d++);
                *b = endian(*d++);

                r++;
                g++;
                b++;
            }
        }
    }

    fclose(fp);
    return;
}

/*****************************************************************
 * write_ppm
 *****************************************************************/
void write_ppm(char *file, unsigned short *r_data, unsigned short *g_data, unsigned short *b_data,
    int width, int height, int ppm_pn, int bits, const char *program_name)
{
    FILE *fp;

    char header[21];
    char comment[1024];
    int max_code  = (0x01 << bits) - 1;
      
//    Open output Ppm File
    if (( fp = fopen( file, "wb" )) == NULL ){
        printf( "PPM File cannot not make¥n" );
        exit(1);
    }

//    PPM_FILE_HEADER
    snprintf( header, sizeof(header), "P%1d¥n", ppm_pn );
    fputs( header, fp );
    snprintf( comment, sizeof(comment), "# used: %s¥n", program_name );
    fputs( comment, fp );
    snprintf( comment, sizeof(comment), "# %s¥n", file );
    fputs( comment, fp );
    snprintf( header, sizeof(header), "%d %d¥n%d¥n", width, height, max_code );
    fputs( header, fp );

    int dno = width * height;
    int words;
    int frame_size;

    if ( ppm_pn == 3 ){
        unsigned short *r, *g, *b;
        int data[3];

        r = r_data;
        g = g_data;
        b = b_data;

        // フレーム書き込み
        for ( int i = 0; i < dno; i++ ){
            data[0] = (int)*r;
            data[1] = (int)*g;
            data[2] = (int)*b;

            fprintf( fp, "%d %d %d¥n", data[0], data[1], data[2] );

            r++;
            g++;
            b++;
        }
    }
    if ( ppm_pn == 6 ){
        words = word_size( bits );
        frame_size = dno * 3 * words;
        // フレーム書き込み
        if ( words == 1 ){
            unsigned short *r, *g, *b;

            r = r_data;
            g = g_data;
            b = b_data;

            unsigned char *ip;
            if ( libuf_init(frame_size) ){
                fclose(fp);
                exit(-1);
            }
            ip = (unsigned char *)libufp;    
            for ( int i = 0; i < dno; i++ ){
                *ip++ = (unsigned char)*r;
                *ip++ = (unsigned char)*g;
                *ip++ = (unsigned char)*b;

                r++;
                g++;
                b++;
            }
            ip = (unsigned char *)libufp;
            if ((int)fwrite(ip, sizeof(unsigned char), frame_size, fp) != frame_size) {
                 fprintf(stderr, " ERROR2 (writef_ppm) : write file %s ¥n", file);
                fclose(fp);
                exit(-1);
            }
        } else {
            unsigned short *r, *g, *b;

            r = r_data;
            g = g_data;
            b = b_data;

            unsigned short *ip;
            if ( libuf_init(frame_size) ){
                fclose(fp);
                exit(-1);
            }
            ip = libufp;    
            for ( int i = 0; i < dno; i++ ){
                *ip++ = endian(*r);
                *ip++ = endian(*g);
                *ip++ = endian(*b);

                r++;
                g++;
                b++;
            }
            ip = libufp;
            if ((int)fwrite(ip, sizeof(unsigned short), frame_size/2, fp) != frame_size/2) {
                 fprintf(stderr, " ERROR2 (writef_ppm) : write file %s ¥n", file);
                fclose(fp);
                exit(-1);
            }
        }
    }

    fclose(fp);
    return;
}

unsigned short endian( unsigned short d )
{
    unsigned short data = d;
    data = ((data << 8) | (data >> 8 ));
    return data;
}

int    word_size( int ws )
{
    int d;

    if ( ws <= 5 )
        d = ws;
    else 
        d = (ws+7)/8;     /* 8=1, 10=2, 16=2 */

    return d;
}

int libuf_init( int size )
{
    if (( libufp = (unsigned short *)realloc( libufp, size )) == NULL ){
        perror( "libuf_init" );
        return -1;
    }
    return 0;
}

/***** End of File (ppm.cpp) *****/

 

   PPMフォーマットのヘッダーファイル(ppm.h)

/***************************************************
 * Calculate ppm(header file)
 * @file    ppm.h
 * @brief    in ppm -> ppm -> out ppm
 * @author    Akuhiro Okumura
 * @date    2020.03.04
 ***************************************************/

#ifdef LINUX
#else
#pragma warning(disable:4996)    //    fopen
#define snprintf    _snprintf
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

/***** struct *****/
unsigned short endian( unsigned short d );
int    word_size( int ws );
static unsigned short *libufp;
int libuf_init( int size );

/***** End of File (ppm.h) *****/

 このppm.cppとppm.hはこの後のprogram1〜5全てに用います。

 

 この内容は、「Interface誌2020年7月号特集 AI時代の画像処理教科書 第4の記事を、読者の便宜をはかるために加筆修正したものです。(同一内容ではない)、また、ここでのプログラムは、CQ出版社さんのダウンロードページの[7月号 AI時代の画像処理教科書][特集 第4章,第5章 画像処理プログラミング] [関連ファイル一式]からダウンロードできます。

コメント
コメントする








    

calendar

S M T W T F S
   1234
567891011
12131415161718
19202122232425
262728293031 
<< July 2020 >>

selected entries

categories

archives

links

profile

search this site.

others

mobile

qrcode

powered

無料ブログ作成サービス JUGEM