ここでは画像処理に慣れ親しんでもらうことを目的に、基本的な画像処理を紹介します。前半では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);
}