ここでは画像処理に慣れ親しんでもらうことを目的に、基本的な画像処理を紹介します。前半ではMATLABという商用ソフトウェアを使って、後半ではC言語でプログラミングして処理する内容を紹介します。
そもそも、画像ファイルにはどのような仕組みでデータが記録されているのでしょうか?大別して、画像の大きさ等の情報が記録されているヘッダ部分と、画像データ本体に分かれます。画像は1つ1つの単位である画素(ピクセル)の集合体で、下記に示しますように、画像そのものは行と列から構成されているように見えます。リモートセンシングの分野では、画像上の行は ライン(line)(または行: row)、列はピクセル(pixel)(または列: column)と呼ばれることもあります。「ピクセル」という表現は、画素と列のどちらを意味するか、文脈から判断するようにしましょう。
しかし実際のファイルには、ライン数、ピクセル数に関係なく、単に一列のデータが並んでいるだけです。こちらがわが画像の幅であるピクセル数を指定することで、画像処理ソフトはピクセル数ごとに折り返してくれるので、行と列から構成される画像として表示されるというわけです。
大事な点としては、
画素単位での処理の場合には、「ファイルの終わりまで処理する」という命令を指示すればいいですから、特にライン数、ピクセル数は要りません。ライン数、ピクセル数を明示し、処理しても構いませんが、必ずしも要求されるわけではない、ということです。
一方、「近傍での処理」と書きましたが、ある画素を中心とした3×3や5×5の領域において、平均や差分などを計算することもよくあります。その場合には、画像における縦と横がわかっていないといけませんので、ライン数、ピクセル数は必要不可欠になります。
それでは実際の処理に移ります。まずは下記の画像を保存して下さい。 リンク先で右クリックして、「保存」を選択して画像を保存して下さい。
演習室の端末上で、下記のいずれかの方法でMATLABを起動しましょう。
または
MATLABが起動すると、下記のように「>>」が表示されて、入力できる状態になります。ここで、適当な変数を使って(ここでは「A」)画像データを読み込んでいきます。下記のように入力して下さい。以下、入力文字を赤字・下線付きで示します。
この時点で変数Aは、3次元の配列
A (行:列:バンド)
の構造を持つことになります。「バンド」と書きましたが、RGB画像の場合、3バンドが与えられます。例えば、Rバンドの10行目、3列目の輝度値を表示させたい場合には、
>> A (10,3,1) ans = 116
と入力します。「116」という値が記録されていると返ってきました。この構造の概念は本ページの後の演習で早速使いますので、理解して置いて下さい。
単に表示させるだけなら「image()」という関数を使います。
または
複数の画面に異なる画像を表示させたい時が生じます。そのためのコマンドです。
と入力すると1番目の画面が現れます(上記のimage等を用いて既に画面が現れている場合には、何の変化もありません)。続いて2番目の画面を表示させたい時には、次のように入力します。
わざわざimreadを用いた読み込みも書いていますが、既に実行している場合には省略して下さい。
>> A = imread('sampleimg1.jpg') >> B = A * 2 >> image(B)
わざわざimreadを用いた読み込みも書いていますが、既に実行している場合には省略して下さい。
>> A = imread('sampleimg1.jpg') >> B = A(:,end:-1:1,:) >> image(B)
ここで補足をします。RGBのJPEG画像のデータが配列Aに記録されています上述したように、配列Aは、
A (行:列:バンド)
という構造を持っています。
A (:,:,:)
と書くと、
の意味になります。つまり、最初の要素(1)から最後の要素(end)まで、1ずつ加算して処理することを意味しています。
は、
と同値で、列を逆順に読み込んでBに代入することを意味します。ですので、左右を反転させた画像が表示される訳です。
上記の演習2を参考に自分で考えましょう。
演習2・3の応用でできます。例えば、1/3に縮小することを考えると3つ置きにデータを読み取ればいい訳です。
>> A = imread('sampleimg1.jpg') >> B = A(1:3:end,1:3:end,:) >> image(B)
表示される画像の座標値が1/3になり、また画像が粗くなっていることを確認しましょう。この応用で、ラインだけあるいはピクセルだけを縮小することも可能です。
imrotateという関数を使います。例えば、反時計回りに30度回転する場合には、次のように実行します。
>> A = imread('sampleimg1.jpg') >> B = imrotate(A, 30) >> image(B)
これまでは、逐一コマンドを入力して実行させる処理を説明してきました。ただ処理が多数に亘る複雑な一連の処理や、また後日再度利用する可能性のある処理はMATLABファイル(またはM-ファイル)と呼ばれるファイルに保存するのが適切です。以下、その方法を説明します。
下記のように入力すると編集画面(エディタ)が起動します。
事前に新規のM-ファイルの名前を決めていたり(例えば、image1.m)、あるいは既存のM-ファイルを開きたい時にはそのファイル名を続けて入力します。
M-ファイルに記述した内容を実行するには、下記の方法かあるいはF5キーを押します。
コマンドラインを用いた演習1で同じ内容を演習しましたが、全く同じ内容を記述します。このM-ファイルを「image1.m」というファイル名で保存し、実行してみて下さい。
A = imread('sampleimg1.jpg') B = A * 2 image(B)
これまではMATLABでの処理を説明してきました。自分が処理したい内容に相当する関数がMATLABで用意されていない場合には、自分自身で処理内容を実装する必要があります。また画像処理ではfor文を使った処理が多数出てきますが、MATLABでのfor文等の繰り返し処理は極めて遅いです。そのため、MATLABを使わなくても画像処理ができるようになるために、ここではC言語を使った画像処理を演習します。
それでは実際の処理に移ります。まずは下記の画像を保存して下さい。 リンク先で右クリックして、「保存」を選択して画像を保存して下さい。
演習室の端末上で、Visual C++を起動しましょう。
C言語を用いて処理を記述すると下記のようになります。 C言語をコンパイル、実行できる環境を備えたソフトウェアを起動し、下記の内容を単純にコピーして貼り付け、 image1.cという名前で保存します。
/* image1.c PBMフォーマット画像(RGB)を読み込んで、 輝度値に一定値を掛けた上で出力するプログラム */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <math.h> // 輝度値の倍数 #define SCALE 2 // 入力ファイル名 char INPUTFILE[100]="sampleimg1.pbm"; // 出力ファイル名 char OUTPUTFILE[100]="sampleimg1_rst.pbm"; int main(int argc, char *argv[]) { int i, j, k; int line, pixel; FILE *fpi, *fpo; unsigned char data[3]; char str[256]; ///// 入出力ファイルを開く処理:ファイルポインタを設定 ///// // 入力ファイル(PBMフォーマット)オープン if((fpi = fopen(INPUTFILE, "rb")) == NULL){ fprintf(stderr, "Can not open %s \n", INPUTFILE); exit(1); } // 出力ファイル(PBMフォーマット)オープン if((fpo = fopen(OUTPUTFILE, "wb")) == NULL){ fprintf(stderr, "Can not open %s \n", OUTPUTFILE); exit(1); } ///// ヘッダファイルの処理 ///// // 入力ファイル:1行目読み込み(PBMタイプ:P6=RGB rawフォーマット) fgets(str, 256, fpi); fputs(str, fpo); // 出力ファイルへ書き込み fputs(str, stdout); // 標準出力(画面)へ書き込み // 入力ファイル:2行目読み込み(ピクセル、ライン数) fgets(str, 256, fpi); fputs(str, fpo); fputs(str, stdout); sscanf(str, "%d %d", &pixel, &line); // 整数値を変数へ代入 // 入力ファイル:3行目読み込み(階調数:255=8ビット=1バイト) fgets(str, 256, fpi); fputs(str, fpo); fputs(str, stdout); // 確認用:ライン、ピクセル数を標準出力へ表示 fprintf(stdout, "line = %d, pixel = %d\n", line, pixel); ///// データ本体の処理 ///// for (i=0; i<line; i++) { for (j=0; j<pixel; j++) { // 1画素ずつ入力ファイルからデータを読み込む // RGBの計3つ分をまとめて読み込む fread(data, sizeof(unsigned char), 3, fpi); // 画像輝度値をSCALE倍 for (k=0; k<3; k++) { if (data[k] * SCALE > 255) { data[k] = 255; } else { data[k] *= SCALE; } } // RGBの計3つ分を出力ファイルへ書き込む fwrite(data, sizeof(unsigned char), 3, fpo); } } ///// 入出力ファイルを閉じる処理 ///// fclose(fpi); fclose(fpo); }
上記の「image1.c」を実行すると、ファイル「sampleimg1.pbm」を読み込んで、「sampleimg1_rst.pbm」というファイルが出力されます。正しく実行されたか画像を確認してみましょう。画像を開くエディタは何でもいいのですが、上記でMATLABの使い方を説明しましたので、MATLABで開いてみましょう。(参考:画像閲覧・処理用のフリーソフトウェアとしてはgimpが有名です)
>> A = imread('sampleimg1_rst.pbm') >> image(A)
演習1と同じで、下記の内容を単純にコピーして貼り付け、 image2.cという名前で保存し、その後実行してみて下さい。
/* image2.c PBMフォーマット画像(RGB)を読み込んで、 画像の左右を反転した結果を出力するプログラム */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <math.h> // 入力ファイル名 char INPUTFILE[100]="sampleimg1.pbm"; // 出力ファイル名 char OUTPUTFILE[100]="sampleimg2_rst.pbm"; int main(int argc, char *argv[]) { int i, j, k; int line, pixel; FILE *fpi, *fpo; unsigned char tmp[3]; char str[256]; // RGB輝度値を格納する構造体を自分で定義 struct rgb_data { unsigned char r, g, b; }; struct rgb_data *data; // この時点では必要な大きさが未定 ///// 入出力ファイルを開く処理:ファイルポインタを設定 ///// // 入力ファイル(PBMフォーマット)オープン if((fpi = fopen(INPUTFILE, "rb")) == NULL){ fprintf(stderr, "Can not open %s \n", INPUTFILE); exit(1); } // 出力ファイル(PBMフォーマット)オープン if((fpo = fopen(OUTPUTFILE, "wb")) == NULL){ fprintf(stderr, "Can not open %s \n", OUTPUTFILE); exit(1); } ///// ヘッダファイルの処理 ///// // 入力ファイル:1行目読み込み(PBMタイプ:P6=RGB rawフォーマット) fgets(str, 256, fpi); fputs(str, fpo); // 出力ファイルへ書き込み fputs(str, stdout); // 標準出力(画面)へ書き込み // 入力ファイル:2行目読み込み(ピクセル、ライン数) fgets(str, 256, fpi); fputs(str, fpo); fputs(str, stdout); sscanf(str, "%d %d", &pixel, &line); // 整数値を変数へ代入 // 入力ファイル:3行目読み込み(階調数:255=8ビット=1バイト) fgets(str, 256, fpi); fputs(str, fpo); fputs(str, stdout); // 確認用:ライン、ピクセル数を標準出力へ表示 fprintf(stdout, "line = %d, pixel = %d\n", line, pixel); ///// データ本体の処理 ///// // 確定したライン、ピクセル数を用いて、構造体用のメモリを確保 data = (struct rgb_data *)malloc(sizeof(struct rgb_data)*line*pixel); // データ全体を一度読み込んで構造体に保存 for (i=0; i<line; i++) { for (j=0; j<pixel; j++) { // 1画素ずつ入力ファイルからデータを読み込む // RGBの計3つ分をまとめて読み込む fread(tmp, sizeof(unsigned char), 3, fpi); // 画像輝度値を構造体に代入 data[i * pixel + j].r = tmp[0]; data[i * pixel + j].g = tmp[1]; data[i * pixel + j].b = tmp[2]; } } // 画像の左右を反転しながら出力 for (i=0; i<line; i++) { for (j=0; j<pixel; j++) { // 左右の反転:j → pixel - 1 - jに変換される tmp[0] = data[i * pixel + (pixel - 1 - j)].r; tmp[1] = data[i * pixel + (pixel - 1 - j)].g; tmp[2] = data[i * pixel + (pixel - 1 - j)].b; // RGBの計3つ分を出力ファイルへ書き込む fwrite(tmp, sizeof(unsigned char), 3, fpo); } } // メモリの開放 free (data); ///// 入出力ファイルを閉じる処理 ///// fclose(fpi); fclose(fpo); }
上記の演習2を参考に自分で考えましょう。
上記の演習2を参考に自分で考えましょう。
まず、下記のように「SKIP」という変数を定義しましょう。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <math.h> #define SKIP 3
ヒント1:全ての画素の値を読み込んだ後で、出力する際にはライン、ピクセル方向共にSKIPだけ飛ばして処理します。
// ライン、ピクセル方向共にSKIPだけ飛ばして処理 for (i=0; i<line; i += SKIP) { for (j=0; j<pixel; j += SKIP) { // 処理内容を自分で考えてここに記述する } }
ヒント2:「ヘッダファイルの処理」の「2行目の処理」において、出力ファイルのライン数、ピクセル数共に1/SKIPの値を出力する必要があります。
// 入力ファイル:2行目読み込み(ピクセル、ライン数) fgets(str, 256, fpi); fputs(str, stdout); sscanf(str, "%d %d", &pixel, &line); // 整数値を変数へ代入 // 縮小後のピクセル、ライン数書き込み fprintf(fpo, "%d %d\n", pixel/SKIP, line/SKIP);
下記に示すソースを利用して、プログラムを完成して下さい。但し、一部***で隠した処理は自分自身で考えて下さい。
/* image5.c PBMフォーマット画像(RGB)を読み込んで、 画像中央を回転中心として、画像をthetaだけ反時計回りに 回転して出力するプログラム */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <math.h> #define THETA 60 // 度 #define PI 3.1415926535 #define COS(a) cos((a)*PI/180) // 度を入力して計算 #define SIN(a) sin((a)*PI/180) // 度を入力して計算 #define TAN(a) tan((a)*PI/180) // 度を入力して計算 // 入力ファイル名 char INPUTFILE[100]="sampleimg1.pbm"; // 出力ファイル名 char OUTPUTFILE[100]="sampleimg5_rst.pbm"; int main(int argc, char *argv[]) { int i, j, k, ii, jj; int line, pixel; int line2, pixel2; FILE *fpi, *fpo; unsigned char tmp[3]; char str[256]; // RGB輝度値を格納する構造体を自分で定義 struct rgb_data { unsigned char r, g, b; }; struct rgb_data *data; // この時点では必要な大きさが未定 double d, alpha; int x, y; int h, w; double h2, w2; unsigned char nodata[3] = {0, 0, 0}; ///// 入出力ファイルを開く処理:ファイルポインタを設定 ///// // 入力ファイル(PBMフォーマット)オープン if((fpi = fopen(INPUTFILE, "rb")) == NULL){ fprintf(stderr, "Can not open %s \n", INPUTFILE); exit(1); } // 出力ファイル(PBMフォーマット)オープン if((fpo = fopen(OUTPUTFILE, "wb")) == NULL){ fprintf(stderr, "Can not open %s \n", OUTPUTFILE); exit(1); } ///// ヘッダファイルの処理 ///// // 入力ファイル:1行目読み込み(PBMタイプ:P6=RGB rawフォーマット) fgets(str, 256, fpi); fputs(str, fpo); // 出力ファイルへ書き込み fputs(str, stdout); // 標準出力(画面)へ書き込み // 入力ファイル:2行目読み込み(ピクセル、ライン数) fgets(str, 256, fpi); fputs(str, stdout); sscanf(str, "%d %d", &pixel, &line); // 整数値を変数へ代入 // 回転後の画像サイズを確定 // 中心から画像端までの距離 d = pow((double)line*line + pixel*pixel, 0.5) / 2.0; // 対角線とx軸の間の角度 alpha = atan ((double)line/pixel) * 180 / PI; // 回転後のピクセル数 pixel2 = ****************************; // 回転後のライン数 line2 = ****************************; fprintf(fpo, "%d %d\n", pixel2, line2); // 回転後のピクセル、ライン数 // 確認用:ライン、ピクセル数を標準出力へ表示 fprintf(stdout, "d = %6.2f, alpha = %6.2f\n", d, alpha); fprintf(stdout, "line = %d, pixel = %d\n", line, pixel); fprintf(stdout, "line2 = %d, pixel2 = %d\n", line2, pixel2); // 入力ファイル:3行目読み込み(階調数:255=8ビット=1バイト) fgets(str, 256, fpi); fputs(str, fpo); fputs(str, stdout); ///// データ本体の処理 ///// // 確定したライン、ピクセル数を用いて、構造体用のメモリを確保 data = (struct rgb_data *)malloc(sizeof(struct rgb_data)*line*pixel); // データ全体を一度読み込んで構造体に保存 for (i=0; i<line; i++) { for (j=0; j<pixel; j++) { // 1画素ずつ入力ファイルからデータを読み込む // RGBの計3つ分をまとめて読み込む fread(tmp, sizeof(unsigned char), 3, fpi); // 画像輝度値を構造体に代入 data[i * pixel + j].r = tmp[0]; data[i * pixel + j].g = tmp[1]; data[i * pixel + j].b = tmp[2]; } } // 回転前の座標を計算しながら出力 w = pixel / 2; h = line / 2; w2 = pixel2 / 2.0; h2 = line2 / 2.0; for (ii=0; ii<line2; ii++) { for (jj=0; jj<pixel2; jj++) { // 回転前のxy座標の値 x = **************************************; y = **************************************; // 回転前のij座標の値 i = **************************************; j = **************************************; if (i < 0 || i > line - 1 || j < 0 || j > pixel - 1) { // 該当するデータが存在しない // →黒の輝度値{0,0,0}を書き込む fwrite(nodata, sizeof(unsigned char), 3, fpo); } else { // 該当するデータが存在する tmp[0] = data[i * pixel + j].r; tmp[1] = data[i * pixel + j].g; tmp[2] = data[i * pixel + j].b; // RGBの計3つ分を出力ファイルへ書き込む fwrite(tmp, sizeof(unsigned char), 3, fpo); } } } // メモリの開放 free (data); ///// 入出力ファイルを閉じる処理 ///// fclose(fpi); fclose(fpo); }