A問題(50点)
(提出ファイル名: prog01.c)
ハンドアウトLec13-6 (p.145)のサンプルプログラムlec13-4.cは2次元配列を動的に確保する方法の一例である。この方法はハンドアウトにあるとおり、行によって長さが違う場合にも応用ができる。この特徴を使って、各行に単語を一つづつ格納し、かつ各行の長さはその単語の長さちょうどになるような2次元配列を作成するプログラムを完成させよ。
以下に、このプログラムを完成させる際の考え方を列挙する
strlen
を用いて単語の長さを調べる。strcpy
でコピーする。strlen
関数、strcpy
関数を用いるので、string.h
をincludeすること。strlen
関数、strcpy
関数についてはハンドアウトLec04-18 (p.102)を参照せよ。サンプルデータとして、aizu1.txt, aizu2.txtを 用意してあるので、必要なら、自分のディレクトリにコピーすること。
% cp /home/course/prog1/public_html/2024/ex/ex13/aizu*.txt .
実行例: % ./a.out to Advance Knowledge for Humanity control+d 0: to [2] 1: Advance [7] 2: Knowledge [9] 3: for [3] 4: Humanity [8] % cat aizu1.txt The University of Aizu opened in 1993 and was the first university in Japan to specialize in computer science and engineering. % ./a.out < aizu1.txt 0: The [3] 1: University [10] 2: of [2] 3: Aizu [4] 4: opened [6] 5: in [2] 6: 1993 [4] : : 以下省略 % ./a.out < aizu2.txt 0: In [2] 1: the [3] 2: Edo [3] 3: era, [4] 4: the [3] 5: Aizu [4] 6: region [6] : : 以下省略 %
malloc
関数の戻り値を、arr[i] = (int *)malloc(yoko * sizeof(int));のように代入先の変数型に合うようにキャストしている。 一方、lec13-4.cの中ではそのようなキャストを行わず、
arr[i] = malloc(sizeof(int)*yoko);のようにそのまま代入している。
malloc
関数のプロトタイプ宣言を確認(コマンドman malloc
で確認できる)すると、
void *malloc(size_t size);となっている。void *型は、明示的にキャストをする必要なく任意のポインタ型に変換することができるので、本来はハンドアウトの掲載例のようなキャストを行う必要はなく、lec13-4.cのように書いて問題ない。
malloc
関数で確保しようとしている領域の大きさが適切かどうかをコンパイラが確認し、問題がある(意図しない動作をしたり、セキュリティホールの原因となることもある)場合は警告が出せるようになることである。このようなプログラムの書き方についての詳細はコンピュータのセキュリティに関する情報を取りまとめているJPCERT/CCの記事などが参考になる。B問題(50点)
(提出ファイル名: prog02.c)
具体的な手順は以下の通りvoid lconv(char *)
は自分で作成すること。実行例: % ./a.out < aizu1.txt 0: the [3](2) 1: university [10](2) 2: of [2](1) 3: aizu [4](1) 4: opened [6](1) 5: in [2](3) 6: 1993 [4](1) 7: and [3](2) 8: was [3](1) 9: first [5](1) 10: japan [5](1) 11: to [2](1) 12: specialize [10](1) 13: computer [8](1) 14: science [7](1) 15: engineering [11](1) %
最初に、ハンドアウト lec13-11,12 (p146) 中のプログラムlec13-7.cをコピーし、lec13-13 (p147) に示されたコンパイルオプションをつけた場合に例示された通りの動作になることを確認せよ。
まず、以下のコマンドで画像データを自分のディレクトリにコピーすること。
% cp /home/course/prog1/public_html/2024/ex/ex13/pbms/* .
次にlec13-7.c
を以下のようにコンパイルオプションでマクロ定義を変えてコンパイルし、Lec13-13
に例示されている四種類の実行ファイルを生成する。
% gcc lec13-7.c -DINV -o inv % gcc lec13-7.c -DINV -DROTL -o invrotl % gcc lec13-7.c -DFLIPLR -o fliplr % gcc lec13-7.c -DINV -DFLIPLR -DROTL -o invfliplrrotl
コピーした画像データを使って、Lec13-13
に載っている通りの動作をするかを確認する。
% cat p1.pbm | ./inv | display - % cat p1.pbm | ./invrotl | display - % cat p1.pbm | ./fliplr | display - % cat p1.pbm | ./invfliplrrotl | display -
確認が完了したら、以下の説明をよく読んで問題に取り組むこと。
テンプレートprog03_template.cはlec13-7.c
を再設計し、メモリの確保と画像データの読み込みを別々の関数に分離している。
また、lec13-7.c
が備えていた画像処理の機能に代えて、
読み込んだ画像に対し指定した正方形内の白黒を反転させてから出力する機能を追加した。
ただし、いくつかの関数は未完成のままになっている。
このテンプレートを元に、2ステップでプログラムを完成させよ。
(提出ファイル名: prog03a.c, prog03b.c)
各関数の内容は以下のとおりである。未完成、完成の別も表示している。未完成の関数は完成させること。完成済みの関数は変更してはならない。
img_alloc
とimg_read
を実装(作成)する。img_square
とimg_xdot
を実装し、
最終的な結果になることを確認する。
char **img_alloc(int x, int y)
void img_free(char **pbm, int x, int y)
void img_write(char **pbm, int x, int y)
char **img_read(int *x, int *y)
void img_square(char **pbm, int x, int y, int cx, int cy, int d)
void img_xdot(char **pbm, int x, int y, int px, int py)
int main(int argc, char *argv[])
テンプレートはそのままコンパイルが出来るように作ってあるので、ダウンロードしたらまずコンパイルしてみるとよい。 ただし、未完成の関数を埋めない限り、例の通りに動作するわけではない。 まずはテンプレートをファイル名prog03a.cにコピーして、 変更を進めること。 変更の際は、少しずつ処理を足しながらこまめにコンパイルして いくことで、文法の誤りを早く見つけることが出来るはずである。
ステップ1の実行例:(コマンドライン引数の数値はステップ1では実質的に利用してないので、何でも良い。) % cat p1.pbm | ./a.out 300 200 100 | display -
ステップ2の実行例: % cat p1.pbm | ./a.out 300 200 100 | display - % cat p1.pbm | ./a.out 140 200 100 | ./a.out 200 300 80 |display - %
確認のため、他の画像でも同様に実行してみること。
Extra問題
(提出ファイル名: prog04.c)
問題3を応用し、別の画像処理を行なうプログラムを作成する。 下記に、この問題で使用するヘッダファイル、マクロ定数、関数のプロトタイプ 宣言を示す。関数定義は、問題3までの関数の中から必要なものを コピーして使用する。
#include <stdio.h> #include <stdlib.h> #define BLACK '1' #define WHITE '0' char **img_alloc(int, int); void img_free(char **,int, int); char **img_read(int *, int *); void img_write(char **, int, int);本問題では白黒2値画像に対してモルフォロジと呼ばれる処理の一部を作成する。 これから作る処理は、画像上の画素一つを取り出してP(x,y)とするとき、 その周辺の画素値を読み出し、それらの値に対してある演算を行い。 その結果を新たな画像の画素P'(x,y)とするものである。 この処理は、画像上の画素すべてに対して1回ずつ行う。 下図は注目する画素の「周辺」の定義を、注目する画素の上下左右としたときの説明図である。 このとき、大きさW×Hの画像内の点P(x,y)に注目するとすると、 座標値x=0となる点、x=W-1となる点、y=0となる点、y=H-1となる点の 周辺画素は画像をはみ出したところに位置してしまう。 画像の上下左右の端の画素に注目する際、参照できない周辺画素は、 その注目する画素の値を用いることとする。
名前 | 発音 | 意味 | 操作 |
erosion | エロージョン | 収縮 | 注目する画素とその周辺の画素が全て黒なら結果を黒に、それ以外は白にする |
dilation | ダイレーション | 膨張 | 注目する画素とその周辺の画素が一つでも黒なら結果を黒に、それ以外は白にする |
エロージョンは黒い線や領域を細く,ダイレーションは太くする効果がある。白黒2値画像に2つの処理を適用した結果を下に示す。
エロージョン | 元画像 | ダイレーション |
それでは、実際にこれらの処理を行う関数を作成していく。 関数名には動詞のerodeとdilateを使ったほうが良いので以下のように定義する。
char **img_erode(char **pbm, int x, int y); /* エロージョンを行う関数 */ char **img_dilate(char **pbm, int x, int y); /* ダイレーションを行う関数 */pbmは入力する画像の配列。x,yは画像の横幅と高さである。 関数の中で入力する画像と同じ大きさの2次元配列をimg_alloc関数で確保し、 その配列に結果を書き込む。操作を行ったあとは新しい配列のポインタを返すように作る。 各関数の中身は以下のように作成せよ。
画像の端の処理、 ループの範囲の設定(横は0からx-1、縦は0からy-1の範囲である)、 論理演算等を間違えやすいので気をつけること。
作成した関数を呼び出すようにしたmain関数を示すので、これを用いよ。
#elif
は、#else
と#ifdef
が複合したもので、
C言語での「else if」に相当する。
ここに作成した2つの関数を入れ、コンパイル時にERODEかDILATEを定義し、
どちらの関数を呼ぶかを決定する。
int main(){ char **pbm1, **pbm2; int ix,iy; pbm1 = img_read(&ix,&iy); if(pbm1==NULL){ fprintf(stderr,"Invalid image format."); exit(1); } #ifdef ERODE pbm2=img_erode(pbm1,ix,iy); img_free(pbm1,ix,iy); #elif DILATE pbm2=img_dilate(pbm1,ix,iy); img_free(pbm1,ix,iy); #else pbm2=pbm1; #endif /* 画像を書きだす */ img_write(pbm2,ix,iy); /* 画像領域の解放 */ img_free(pbm2,ix,iy); return 0; }最後にコンパイルと実行の例を示す。
-D
オプションは定義するマクロ名の指定、
-o
オプションは生成されるファイルの名前を指定するオプションである。
実行例: % gcc prog04.c -DERODE -o erode % gcc prog04.c -DDILATE -o dilate % cat p1.pbm | ./erode | display - % cat p1.pbm | ./dilate | display -
(提出ファイル名: prog05.c)
この問題は、問題4の続きである。
問題4で作成したエロージョンとダイレーションの処理は、複数回繰り返すことで より効果を大きく出すことができる。 また、エロージョンの後にダイレーションを行うとオープニングという演算になり、 孤立点や細い線の除去ができる。逆、つまり、ダイレーションの後にエロージョンを行うと クロージングという演算になり、不連続な点の接続と穴埋めができる。
以下に、例を挙げる
% cat ryo.pbm | ./erode | ./erode | display - | % cat ryo.pbm | ./dilate | ./dilate | display - |
% cat ryo.pbm | ./erode | ./dilate | display - | % cat ryo.pbm | ./dilate | ./erode | display - |
エロージョンとダイレーションを繰り返せば、白黒画像の画質をいろいろと調整することができるが、
コマンドの連結で表現するのは少し大変である。そこで、コマンドライン引数でエロージョンと
ダイレーションの処理をどの順序で行うのかを与えることにしよう。ついでに、コマンドライン引数に
ファイル名が与えられたら、標準入力ではなく、そのファイルから画像データを取得するようにする。
例えば、実行ファイル名をmorph
として以下のような利用方法を想定する。
実行例: % ./morph - ryo.pbm | display - % ./morph -eeeddd ryo.pbm | display - % cat ryo.pbm | ./morph -dddddeeeee | display - % ./morph ryo.pbm Error: Invalid operation 'r' Usage: ./morph -[ed...] [filename] % ./morph -cde ryo.pbm Error: Invalid operation 'c'実行ファイル名の後に、第1引数としてハイフンの後にeとdで構成された文字列を与える。これが、eがエロージョン、 dがダイレーションの処理を行うことを表す。ハイフンのみの場合は、処理をせずに入力画像を そのまま出力する。また、第2引数として文字列が存在すれば、 それをファイル名として画像を読み込むようにする。出力は前回までと変わらず、標準出力に 出力することとする。以下のリストを参考にして、プログラムを完成させよ。
// プロトタイプ宣言とmainのおおよその流れ #include <stdio.h> #include <stdlib.h> #include <string.h> #define BLACK '1' #define WHITE '0' char **img_alloc(int, int); void img_free(char **,int, int); void img_write(char **, int, int); char **img_read(FILE *, int *x, int *y); /* 要変更 */ char **img_dilate(char **pbm, int x, int y); char **img_erode(char **pbm, int x, int y); //(外部変数は使用しない) int main(int argc, char *argv[]) { // (変数宣言) // (コマンドライン解析) // (行うべき処理が指定されていない場合はエラー) // (ファイルが指定されていない場合はstdinを、それ以外はファイルをopenし // ファイルポインタを得て、img_read関数の引数とする) // (指定された処理の実行:複数指定された場合は処理の繰返し。 // img_dilate, img_erode関数を繰り返し呼ぶと、メモリの解放が必要になる。) // (最後に結果の出力) // (メモリの解放) return 0; } //(各関数の記述)