第12回 プログラミング入門 演習

【演習の注意事項】を良く読んで回答してください


リダイレクトとパイプ

【主な内容】

  • リダイレクション

    これまでの講義・演習ではプログラムに対するデータの入出力に キーボードと画面表示を利用してきました。 これは標準入力がキーボード、 標準出力が画面になっていたからですが、 標準入出力は簡単に切替えることができます。
    例えば、キーボード入力をファイル入力に変更すれば、 定型データや大量のデータを簡単に入力することができ、画面出力をファイル出力に変更すれば 画面に表示していた内容をファイルに出力したりすることができます。
    この入出力の切り替えをリダイレクションと呼びます。

    リダイレクションの書式と例についてはハンドアウトLec12-7ページ付近 を参照してください。

  • パイプ

    あるプログラムの標準出力を他のプログラムの標準入力にすることを パイプと呼びます。

    パイプの書式と例についてはハンドアウトLec 12-10ページ付近を 参照してください。

演習課題

以下の例題と、3つのレベル (初級レベル, 中級レベル, 上級レベル) の 課題すべてに回答しなさい。各々の課題毎にファイルを作成しなさい。

今回の課題はテキスト(文書)ファイルで提出するものと C言語のプログラムファイルで提出するものがあります。 前者は".txt"、後者は".c" という拡張子をつけて下さい。 各課題にはファイル名が指定してあるので、その指示にしたがって下さい。

なお、実行例やコマンドなどで赤色の字はキーボードから 入力したもの、深緑の字はコンピュータからの表示を示しています。 また、%はプロンプト (皆さんの環境ではstd1dc1{s1160000}1:等に相当する) を示しており、皆さんが入力するのはその次の文字からになります。

また、今回はパイプを使う関係上、実行ファイル名をa.out ではなく、個別に指定してある場合もあります。その際は以下のように コンパイルオプション -o を使用して下さい。

% gcc ソースファイル名.c -o 実行ファイル名


初級レベル

(A-1)
ls コマンドと du コマンドおよび出力リダイレクション( > , >> ) を利用して、以下の内容を一つのファイル ex12a1.txt に保存しなさい。
ls -l ~/Prog0 の実行結果
du ~/Prog0/* の実行結果



(A-2)
du コマンドと sort コマンド、パイプ( | )と出力リダイレクション( > , >> ) を利用して、du コマンドの標準出力をパイプで sort コマンドに渡し、 さらに sort コマンドの結果(標準出力)をファイル ex12a2.txt に保存しなさい。
なお、du コマンドは (A-1) と同じものを使用し、sort コマンドには -n オプションをつけること。

コマンドの動作やコマンドの様々なオプションについて 詳しく知りたい場合は、コマンドラインから man コマンド名 と入力すれば、そのコマンドに関する情報が表示される。


(A-3)
ハンドアウトLec12-9の例題をやってみよう。
データファイル lec12-1.data とプログラム lec12-1.c を以下のコマンドでコピーする。
% cp /home/course/prog0/public_html/2008/lec/source/lec12-1.c .
% cp /home/course/prog0/public_html/2008/lec/source/lec12-1.data .
なお、プログラムへの入力は入力リダイレクションでlec12-1.dataを使用し, 出力結果を出力リダイレクションでファイル ex12a3.txt に保存すること。



中級レベルに行く前に課題の準備が必要です。 ここをクリックして、表示されたページを良く読んでください。


中級レベル

(B-1)
以下に、プログラムで画像を上下反転させるための手順とそのプログラムのソースがあります。 プログラム中の下線部を埋めて、完成させなさい。
なお、プログラム名は gflpud.c 、 実行ファイル名は gflpud とすること。

「手順」
/*
 * gflpud.c
 *
 * pgm形式の画像データを受け取って,上下反転する。
 *
 * 終了コード
 * 0 : 正常終了
 * 1 : 入力データの形式がおかしい
 * 2 : 入力データの画素数または階調が範囲外
 * 3 : 入力データがおかしいかデータが揃わないうちにEOFになった
 * 4 : 入力データの画素の値が範囲外
 */

#include <stdio.h>
#include <stdlib.h>
#define MSIZE 800
main()
{
 int dat[MSIZE][MSIZE];
 int i, j, width, height, maxval;
 /* 入力 */
 /* ファイル形式のチェック */
 /* この部分はプログラミング入門の範囲を越えているので、このまま使って下さい。*/
 if (getchar() != 'P' || getchar() != '2'){
   fprintf(stderr, "データの形式が違います\n");
   exit(1);
 }

 /* x,yそれぞれの画素数を得る */
 scanf("%d %d", &width, &height);

 /* Maxval(白の値)を得る */
 scanf("%d",&maxval);

 /* 画素数が範囲外の場合 */
 if (width < 1 || height < 1 || width > MSIZE || height > MSIZE){
   fprintf(stderr, "設定サイズが範囲外です。\n");
   exit(2);
 }

 /* Maxvalが範囲外 */
 if (maxval < 1 || maxval >= 65536){
   fprintf(stderr, "階調が範囲外です\n");
   exit(2);
 }

 /* 実際のデータ入力 */
 for (i = 0; i < height; i++){
   for (j = 0; j < width; j++){
     /* scanf入力データがおかしいかEOFになった場合 */
     if(scanf("%d",&dat[i][j]) != 1){
       fprintf(stderr, "データ入力に異常があります\n");
       exit(3);
     }
     /* データが範囲外 */
     if(dat[i][j] < 0 || dat[i][j] > maxval){
       fprintf(stderr, "データが異常でした\n");
       exit(4);
     }
   }
 }
 /* これより上はどのプログラムもほぼ同じ */

 /* 出力 */
 /* 最初にP2とx,yの画素数、Maxvalを出力 */
 printf("P2\n");
 printf("%d %d\n", width, height);
 printf("%d\n", maxval);

 /* 実際のデータ出力 */
 for (i = 0; i < height; i++){
   for (j = 0; j < width; j++){
     printf("%2d ",dat[________][________]);
   }
   printf("\n");
 }
}
[実行例1]
% ./gflpud < feep64.pgm
P2
24 7
63
0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
0 12  0  0  0  0  0 29 29 29 29  0  0 46 46 46 46  0  0 63  0  0  0  0
0 12  0  0  0  0  0 29  0  0  0  0  0 46  0  0  0  0  0 63  0  0  0  0
0 12 12 12  0  0  0 29 29 29  0  0  0 46 46 46  0  0  0 63 63 63 63  0
0 12  0  0  0  0  0 29  0  0  0  0  0 46  0  0  0  0  0 63  0  0 63  0
0 12 12 12 12  0  0 29 29 29 29  0  0 46 46 46 46  0  0 63 63 63 63  0
0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0


[実行例2]
% ./gflpud < feep64.pgm | display

(注)表示用に拡大しています。



(B-2)
画像を右90度回転するプログラムを作成しなさい。プログラムのソースファイル名は grotr.c 、実行ファイル名は grotr として作成すること。
[実行例1]
% ./grotr < feep64.pgm
P2
7 24   (←縦、横が反対になることに注意!)
63
0  0  0  0  0  0  0
0 12 12 12 12 12  0
0  0  0 12  0 12  0
0  0  0 12  0 12  0
0  0  0  0  0 12  0
0  0  0  0  0  0  0
0  0  0  0  0  0  0
0 29 29 29 29 29  0
0 29  0 29  0 29  0
0 29  0 29  0 29  0
0 29  0  0  0 29  0
0  0  0  0  0  0  0
0  0  0  0  0  0  0
0 46 46 46 46 46  0
0 46  0 46  0 46  0
0 46  0 46  0 46  0
0 46  0  0  0 46  0 
0  0  0  0  0  0  0
0  0  0  0  0  0  0
0 63 63 63 63 63  0
0  0  0 63  0 63  0
0  0  0 63  0 63  0
0  0  0 63 63 63  0
0  0  0  0  0  0  0

[実行例2]
% ./grotr < neko64.pgm | display





(B-3)
色の濃淡を反転(白を黒、黒を白へ)するプログラムを作成しなさい。
プログラムのソースファイル名は ginv.c 、 実行ファイル名は ginv として作成すること。
(以下はヒントです。まずは見ずに挑戦しましょう。分からない場合は 以下の部分でマウスの左ボタンをクリックし、マウスをドラッグすると ヒントが見えます。また、以下の問題でもヒントは同じ方法で見ることが出来ます。)
「考え方」
画像データを以下のように変換していけば良い。
0 -> Maxval (黒を白へ)
1 -> Maxval-1
2 -> Maxval-2
   :
   :
Maxval-2 -> 2
Maxval-1 -> 1
Maxval -> 0 (白を黒へ)
      
[実行例1]
% ./ginv < feep64.pgm
P2
24 7
63
63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63
63 51 51 51 51 63 63 34 34 34 34 63 63 17 17 17 17 63 63  0  0  0  0 63
63 51 63 63 63 63 63 34 63 63 63 63 63 17 63 63 63 63 63  0 63 63  0 63
63 51 51 51 63 63 63 34 34 34 63 63 63 17 17 17 63 63 63  0  0  0  0 63
63 51 63 63 63 63 63 34 63 63 63 63 63 17 63 63 63 63 63  0 63 63 63 63
63 51 63 63 63 63 63 34 34 34 34 63 63 17 17 17 17 63 63  0 63 63 63 63
63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63


[実行例2]
% ./ginv < ajisai64.pgm | display


(注)表示用に縮小しています。



上級レベル

(C-1)
グレースケールの画像を2値化するプログラムを作成しなさい。
すなわち、濃淡階調のある画像を黒色か白色しかない画像に変換する
プログラムのソースファイル名は gbin.c 、 実行ファイル名は gbin として作成すること。
「考え方」
適当な色(階調、濃度)を閾(しきい)値として決めて、その値以上ものを maxval(白)、未満のものを 0(黒)にしてやれば良い。この処理には if 文または整数演算を利用します。
閾値はどのような画像処理を行うかで決めますが、この課題では最高輝度の 半分(すなわち maxval/2 ) を使うことにしましょう。
[実行例1]
% ./gbin < feep64.pgm P2 24 7 63 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 63 63 63 63 0 0 63 63 63 63 0 0 0 0 0 0 0 0 0 0 0 0 0 0 63 0 0 0 0 0 63 0 0 63 0 0 0 0 0 0 0 0 0 0 0 0 0 0 63 63 63 0 0 0 63 63 63 63 0 0 0 0 0 0 0 0 0 0 0 0 0 0 63 0 0 0 0 0 63 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 63 63 63 63 0 0 63 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 [実行例2] % ./gbin < nakaniwa64.pgm | display (注)表示用に縮小しています。


(C-2)
輝度を上げるプログラムを作成しなさい。
「輝度を上げる」とは下の実行例の画像を見ても分かるように、各点の明るさ(輝度)の値を増すことで全体として元画像より明るい(白に近い)画像を得る事です。
プログラムのソースファイル名は gcscale.c 、 実行ファイル名は gcscale として作成すること。
「考え方」
画像データの値を数倍(とりあえず1.5倍)して、Maxvalを越えた場合は 強制的に Maxvalにします。つまり、濃淡が64階調 ( 0 から 63 までの値をとる) のときの 32 という値は 白と黒のほぼ中間の灰色になりますが、 1.5 倍すると 48 となり やや明るい灰色になります。 しかし、63 を越えてしまうわけにはいかないので、 越えた画素の値については、強制的に 63 にしてしまうことになります。
なお、全データに対して底上げ(+10 にするなど) する方法や、 濃度ごとに重み付け(普通は数学的な関数を利用する)する方法もあります。
[実行例1]
% ./gcscale < feep64.pgm
P2
24 7
63
0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
0 18 18 18 18  0  0 43 43 43 43  0  0 63 63 63 63  0  0 63 63 63 63  0
0 18  0  0  0  0  0 43  0  0  0  0  0 63  0  0  0  0  0 63  0  0 63  0
0 18 18 18  0  0  0 43 43 43  0  0  0 63 63 63  0  0  0 63 63 63 63  0
0 18  0  0  0  0  0 43  0  0  0  0  0 63  0  0  0  0  0 63  0  0  0  0
0 18  0  0  0  0  0 43 43 43 43  0  0 63 63 63 63  0  0 63  0  0  0  0
0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0

[実行例2]
% ./gcscale < nakaniwa64.pgm | display

(注)以下の画像はいずれも表示用に縮小しています。

元画像
輝度を上げた画像


(C-3)
画像の平滑化(スムージング)を行なうプログラムを作成しなさい。
「平滑化」するとは下の実行例の画像を見ても分かるように、各点の明るさ(輝度)の値を周囲の点からあまり掛け離れた値にしないことで、なめらかな画像を 得る物です。平滑化の方法によっては実行例のように少しピンぼけ風になる 事もあります。
プログラムのソースファイル名は gsmooth.c 、実行ファイル名は gsmooth として 作成すること。
平滑化する方法は特に指定しない。必要なら以下の考え方を参考にしても良い。
なお、プログラムの最初にどのような平滑化を行なったのかコメントで説明を入れて下さい。
「考え方」
平滑化の基本的な考え方は、対象とする1画素とその周辺の画素との平均を 対象画素の値とするというものです。(平滑化を施す事により、画像的には 少しぼやけた感じになりますが、ノイズ(ゴミとでも言いましょうか) が多いような画像だとゴミを除去する事が出来ます。(無論全てのノイズを 除去出来る訳ではありません。ハンドアウト Lec12-21〜23 参照)

例えば、横3画素を使って平滑化する場合は、以下のようにすれば良い。 (元の画像用配列とは別な配列に代入していることに注意すること。 同一配列では 既に平滑化してしまった値を再度利用してしまうことになる。)
b[i][j]= (a[i][j-1] + a[i][j] + a[i][j+1])/3.0 (普通の平均)
b[i][j]= (a[i][j-1] + n*a[i][j] + a[i][j+1])/(float)(2+n) (重み付き平均)
上記の重み付き平均例の場合、自分の画素の重みを他の画素のn倍 にしているので、結果が自分の画素の画素値に近いものになり、 画像的にはあまりぼやけたものにならない反面、あまりノイズを 除去出来ないかも知れない。 n の値を色々変化させてどのような画像に なるか見てみるのも面白いだろう。( n を float 型にして、1 以下にするのも 又面白い結果が出る)
他に平滑に利用する画素を上下左右にしたり、近傍8画素 (対象画素の縦横斜隣りの8画素)にしたりすると又画像の様相が変ります。
なお、画像の端は上記の式では対応できないので、注意が必要となる。
[実行例1](横3画素の平均による平滑化)
% ./gsmooth < feep64.pgm
P2
24 7
63
 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0 
 0  8 12 12  8  4  9 19 29 29 19  9 15 30 46 46 30 15 21 42 63 63 42  0 
 0  4  4  0  0  0  9  9  9  0  0  0 15 15 15  0  0  0 21 21 21 21 21  0 
 0  8 12  8  4  0  9 19 29 19  9  0 15 30 46 30 15  0 21 42 63 63 42  0 
 0  4  4  0  0  0  9  9  9  0  0  0 15 15 15  0  0  0 21 21 21  0  0  0 
 0  4  4  0  0  0  9 19 29 29 19  9 15 30 46 46 30 15 21 21 21  0  0  0 
 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0 

[実行例2](3x3画素による平滑化)
% ./gsmooth < nakaniwa64.pgm | display

(注)以下の画像はいずれも表示用に縮小しています。
元画像
平滑化した画像



オプション問題(採点対象外)

(D-1)
これまでに演習で作成した grotr, gflpud, ginv, gbin, gcscale と、 display コマンドを組み合わせて、いろいろな画像処理を試してみなさい。
そして、試した画像処理がどのコマンドの組み合せによって 実現できるかを ex12d1.txt に書いて下さい(使用する画像は任意)。
「ex12d1.txtの記述例」
180度回転(右90度回転を2回行なうと180度回転となる)
% ./grotr < u-aizu64.pgm | ./grotr | display

[実行例](ex12d1.txtに実行例は入れなくて良い)

    
(注)表示用に縮小しています。
「考えられる画像処理の例」 などなど



(D-2)
今回の課題・例題以外の画像処理に関するプログラムを作成しなさい。 ファイル名は ex12d2.c とします。また、プログラムの最初にコメントで 何をするプログラムなのか説明を入れて下さい。  
「画像処理の例」




何か疑問や間違いがあれば以下に連絡して下さい。
プログラミング入門演習問題担当 (prog0)