画像ファイルのヘッダ情報をテキストファイルに出力する
図1 1週目の目標
図2 画像データの画素の概念図
コンピュータで処理可能なディジタル画像とは画素 (Pixel) と呼ばれる点の集合である. 画素は図2のように二次元格子状に配置され,さらに各画素の色は,赤(R),緑(G),青(B)の光の三原色の混合比で表す. 例えば,“白”は赤,緑,青を1:1:1の明るさ比で混合したもののうち, 最も明るい色である. 同じ混合比でも,明るさを変えることによって白~灰~黒を表現することができる. 三原色の明るさがすべて0の場合が黒である.(※注1)
図3 画像データのメモリ上の配置図
図4 PPM画像のファイル構造
画像データの画素の集合は三次元的な構造(縦画素数×横画素数×RGB)で解釈できるが, メモリ上ではそれを一次元の構造で表現する必要がある. 多くの画像フォーマットでは,RGBの軸を優先とする順序で図3の通りメモリ上に記録される. このような画素値がそのまま並んだ画像フォーマットは,一般的にraw形式と呼ばれる. raw形式は画素値以外に複雑な情報が含まれない単純な形式だが, このままでは元々の画像の縦画素数と横画素数がわからないため, その情報を別のファイルなどで添付する必要がある.
そこで,本演習課題では,PPM (Portable Pixel Map) と呼ばれる画像フォーマットを使用する. PPM画像は図4の通り,画像の画素データだけでなく,ファイルの最初(ヘッダ)に画像の構造に関する情報を テキスト形式で保持しているため,単一のファイルで元の画像の縦画素数,横画素数,全画素値を把握できる. 図4のPPMのヘッダ情報の意味は以下の通りである. もし#で始まる文字列がある場合,改行まではコメント文と見なされる.
- P6: PPM形式(バイナリ)を表すマジックナンバー.本演習ではP6で固定.
- 256[半角空白]192: 最初の数値が横画素数,次の数値が縦画素数.
- 255: 最大輝度(最大階調).本演習では255で固定.
図5 画像処理プログラムの設計指針
- 入力ファイルからヘッダと画素値を読み込む
- 画素値を処理に応じて適宜変更する
- 変更した画像情報を別ファイルとして出力する
図6 プログラムテンプレートの流れ
ファイルの読み書きは,およそ以下の三つの手順となる.
- ファイルを開いてファイルポインタをセット
- ファイル操作用の関数を使用してデータを読み込む/書き込む
- ファイルを閉じる
ファイルの読み書きの前には,ライブラリ関数fopenでファイルをオープンする必要がある. fopen関数のプロトタイプ宣言は次の通りである.
FILE *fopen(const char *name, const char *mode);
fopenを使用するには,入出力ライブラリ<stdio.h>をインクルードする必要がある. fopenは,ファイル名およびモードを引数とし,ファイル読み書きに必要なポインタ(ファイルポインタ)を返す関数である. ファイルのオープンに失敗した場合はNULLを返す.
使えるモードは,読み込み("r"), 書き出し("w"),追加("a")の3種である. また,画像データなどのバイナリファイルをオープンする際には, モードの文字列の末尾にbを付加する.ファイルポインタは, ファイルバッファの位置,バッファ内の現在の文字位置, ファイルが読み書きのどちらをされるのかなどファイルに関する情報を含む構造体を指している. また,この構造体はFILEという型名が付けられており,typedefを用いてstdio.h内で定義されている.
fopenの使用例1:テキストファイルsample.txtを読み込みモードでオープン.
FILE *fp;
char name[20] = "sample.txt";
fp = fopen(name, "r");fopenの使用例2:バイナリファイルfoo.rawをバイナリ書き込みモードでオープン.
FILE *fp;
fp = fopen("foo.raw", "wb");
■入力に使用される関数
int getc(FILE *fp); fp から1文字読み込む.ファイル終了またはエラーのときはEOFを返す. |
char *fgets(char *line, int maxline, FILE *fp); fp から,改行(\n)あるいは(maxlineで指定した文字数-1)までの1行を,文字配列lineに読み込む.戻り値は,lineへのポインタ.ファイル終了時にはNULLが返る. |
int fscanf(FILE *fp, const char *format, ...); fp からの書式付き入力.戻り値は,正しく変換し割り当てたフィールドの数.戻り値が 0 の場合は、フィールドの代入が行われていないことを意味する.エラーが発生するか,最初の変換の前にファイルストリームの終端を検出するとEOFを返す. |
size_t fread(void *buffer, size_t size, size_t count, FILE *fp); 書式なしデータをストリームから読み出す関数.fp から size バイトの項目を count 数まで読み出し,buffer に格納する.戻り値は,実際に読み出した全項目の数. |
■出力に使用される関数
int putc(int c, FILE *fp);
fpへ文字cを出力.エラー時にはEOFを返す. |
int fputs(const char *line, FILE *fp);
ファイルに文字列lineを書き込む.エラーが起きるとEOFを返し,そうでなければ0を返す. |
int fprintf(FILE *fp, const char *format, ...);
ファイルへの書式付き出力.戻り値は出力したバイト数. |
size_t fwrite(const void *buffer, size_t size, size_t count, FILE *fp); 書式なしデータをストリームへ書き込む関数.size バイトの項目を count 数だけ buffer から,fp へ書き込む.戻り値は,実際に書き込んだ項目数. |
関数fcloseはファイルを閉じる関数である.関数のプロトタイプ宣言は以下の通り. オープンしたファイルは,最後に必ず閉じること.
int fclose(FILE *fp);
fcloseの使用例:fpが指しているファイルを閉じる.
fclose(fp);
以下,ファイルからテキストを1行読み込みlineBufferに格納する例と, lineBufferに格納されたテキストをファイルに書き込む例を示す.
![]()
図7 ファイル入出力の例(左がファイルからの読み込み,右が書き込み)
![]()
図8 演習課題4_1_1の概要
PPM画像のヘッダ情報をファイルに出力する処理をiioSaveFile関数内に作成せよ. また,実行時に同フォルダにheader.txtが作成され, 入力画像のヘッダ情報が出力されていることを確認せよ.
int main(int argc, char* argv[])
main関数の最初の引数 argc は argv に格納されている引数の数を表す整数である. プログラム名も引数として渡されるので,必ず1以上の値になる.
2番目の引数 argv は ”「文字列を指すポインタ」 の配列 ”を指すポインタで, ”「文字列を指すポインタ」の配列 ”の末尾にはNULLが入る. (下図でargv[3]にNULLが入っている点に注目.) プログラムのユーザーが入力したコマンドライン引数を表す. argv[0]にはプログラムを起動したコマンド名へのポインタ, argv[1]には最初のコマンドライン引数へのポインタが格納される.
コマンドプロンプト上で,
> ex4_1.exe sample.txt result.txt
と入力した場合, argc=3,argv[0]="ex4_1.exe",argv[1]="sample.txt",argv[2]="result.txt"が格納され, メインルーチンに渡される.このときの,概念図を以下に示す.
図9 コマンドライン引数の概念図
演習課題4_1_1で作成した(Debugフォルダ内の)exeファイルをコマンドラインで実行せよ.
現時点でのmain関数では,図10のようにファイル名をプログラム内に記載している. これを図11に示す通り,入力ファイル名と出力ファイル名をコマンドライン引数で与えられるようにmain.cを改良せよ. また,コマンドラインからプログラムを実行し,ユーザが出力ファイル名を自由に変えられることを確認せよ.
図10 演習課題4_1_1の時点でのファイル名の指定方法
図11 コマンドライン引数によるファイル名の指定方法
Visual Studioでデバッグ実行する際のコマンドライン引数の設定方法を調べ,演習課題4_1_2をVisual Studio上で実行せよ.