第3回CADセミナー@岐阜 演習1/画像処理基本プログラミング

演習の第1段階は,画像ファイルをコピーするプログラムを作ります.何も処理をしません.
しかし,ここには,いくつかの重要な手順が含まれています.

C言語では,処理全体を細かい「関数」の集まりとして(全体も関数)扱います.
データは関数へ渡して,その関数で処理を行います.
このことは,プログラムを考える上で非常に重要ですので,よく覚えて下さい.

画像表示については,こちらで用意したプログラムを利用してもらいます.
その詳細については,画像処理とは異なる内容となる部分が大半を占めるため,今回の演習では取り扱わないことにします. プログラムは公開予定です.

演習1のサンプルプログラムは,そのプログラムになります.
ここでは,まず,ハードディスク上に保存された画像ファイルを開き,
それをコンピュータのメモリ上に保存します.
そして,そのメモリ上の画像をハードディスク上に別名をつけて保存します.

それぞれが,関数で表現されており,main文のなかから,いろいろな関数が 順序よく呼ばれて,処理が行われます.

以下は,すべて非常に重要な動作ですので,順序よく理解されることを望みます.
演習1のサンプルプログラム

その基本的な流れは,
  1. インクルードファイル,変数の宣言
    先頭の#include行,#define行,int, unsigned charと書かれた行が該当します. ここで重要なことは,main関数の外に書いた変数は(原則として)どこの関数からも自由に参照することができます. したがって,変数の名前の付け方には他の関数内部で使った変数の名前と重ならないように注意が必要になります.

    さらに,unsigned char *image;として宣言しているのは,このあとの画像保存のための領域の仮確保です.
    このプログラムの中では画像を保存する領域をimageという変数(実は配列といいます.imageはその名前のこと) して扱い,大きさは別に決めますよという意味で「*」がついていると考えて下さい.

    さらに,unsignedは正負の符号なし,charは8ビットデータの意味になります.
    つまり,ここで1画素が8ビットの符号無し(0から255まで)の画像を扱いますよ,ということが宣言されています.

  2. コマンドラインからの変数の読み込み
    ファイルの下の方をごらんください.
    処理は,int main(...)が最初に実行されるため,main関数の中から void get_args(int ..... )となっている関数が 最初に実行される関数になっています.
    その詳細は,ファイルの先頭の方にあるvoid get_args(...)が担当してることになります. つまり,mainを実行して,進むとget_argsという関数が「呼ばれる」ので,上の方にある get_argsに記述しある処理が実行されることになります.

  3. 必要なメモリ量の確保
    get_argsでは,画像のファイル名,幅,高さ,の情報が得られます(コマンドラインから).
    したがって,その情報に基づいて処理に必要な画像ファイルの大きさを計算します.
    これはバイト数で与える必要があります.
    この演習では,1画素1バイトからなる画像を扱っていますので,

    画素数 = 画像の幅のピクセル数 × 画像の高さのピクセル数

    から計算され,画素数がそのままバイト数になります.

    プログラム中では,
    image = (char *)malloc(width.....);
    となっているところで確保しています.mallocという関数がその動作の中心です.
    malloc(必要なバイト数)としてメモリが確保されることになります.

  4. 画像ファイルのメモリへの転送
    通常,画像はハードディスクなどの外部記憶装置にファイルとして保存されています.
    一方,プログラムからは,コンピュータの物理メモリ上のデータにしか(原則として)アクセスしないため, ハードディスク上のファイルをメモリに持ってくる必要があります.

    プログラムの中では,「配列」というデータの数列として画像を扱います.
    前のステップでは,その「配列の大きさ」をバイト数で決めたことになります.
    したがって,この段階では,その領域に画像を読み込む動作をします.

    そのために,image_read_char()という関数を作りました.
    これは,画像のファイル名,ヘッダーサイズ,配列名を渡すとその配列に画像の画素値を保存します.
    ここで,ヘッダーとは,患者情報などの付加情報が保存されている領域のことです. 今回の演習ではヘッダーはありませんので,image_read_char()の前でheader_size=0としています. 単位はバイト数になります.

  5. メモリ上のデータの他の関数への引き渡し
    前の段階で,配列名「image」をimage_read_charに渡しました.これが,メモリ上のデータを 他の関数に渡すということになっています.
    先のファイルの読み込みの段階では,空の領域をimage_read_charに渡し,そこの中に画素値を詰めてもらった ことになります.image_read_charは空のポリタンクに灯油を詰めるガソリンスタンドのような関数だったわけです.

    image_read_charと同じように使えば,他の関数にデータの入った領域を渡すことができ,その 関数で別の処理を行うことも可能になるわけです.しかも,その処理は関数で代表されますから, 全体の流れの見通しが崩れることなく,「関数呼び出し」という方法で処理を組み立てていけることになります.

  6. メモリ上のデータのハードディスクへの保存
    画像の読み出し(image_read_char)と逆の動作をする関数image_write_charを作りました. ここでは,保存する画像のファイル名,幅,高さ,画像の保存された配列名を渡すと目的の画像を ハードディスク上に作成します.やはり,関数に配列を渡す,という基本的なテクニックを使っていることに注意して下さい. さらに,image_read_charでは説明しませんでしたが,配列ではない整数の渡し方も確認して下さい.

    渡す方法は同じでも,受け取る側が異なっているはずです.


以上,少し長いですが,このような処理が画像処理の基本的なプログラミングになると思います.
特に,画像ファイルが配列に入った後から,画像ファイルを保存するまでの5の部分が具体的な処理部分になります.

少し複雑な処理では,画像保存用の配列を複数用意したり(左右画像や過去画像の比較では,2つの画像が処理の入力に なりますよね?),処理前の画像はそのままで,処理の結果を保存する配列を別途用意しておいたりすることになります.

次の演習では,そのような例を扱ってみましょう.