1
2009
年度 機械工学総合演習第二
計算機演習
B4
グラフィックスと
GUI
担当:谷川智洋 講師,西村邦裕 助教
TA
:林 織部,西坂 信哉
http://www.cyber.t.u-tokyo.ac.jp/˜tani/class/mech enshu/
2008
年
6
月
11
日
1
演習の目的
前回までの演習で,音声デバイスやカメラのコントロール,ネットワーク越しで1対1でチャットを行うプ ログラム,複数人の人が同時にチャットを行うプログラムの作成を行ってきた. • デバイスのコントロール • ソケット通信 • マルチスレッド • マルチクライアントへの拡張第4回となる今回は,本演習では,C言語とGUI(Graphical User Interface)ツールキット・ライブラリを 用いた,X Window System上で動作する音声ファイルの取り扱いやネットワークプログラムの作成を通し て,GUIプログラミングの基礎を学ぶ.第5回で,これまで学習したプログラムにGUIを付け加えることで, 最終的に普段我々が使っているアプリケーションに近いマルチクライアント音声チャットの作成を目指す.
2 Gtk+
による
GUI
プログラミングの基礎
2.1 GUI
プログラミング
GUIプログラミングは,マウスなどのポインティングデバイスを介した入力と,グラフィカルな出力を求 められるので,これまで学んできた文字ベースのプログラミングに比べると,本質的な機能の部分よりもユー ザーインターフェースの実装により多くの労力をとられてしまう.また,これまで学んできた,文字ベースの 逐次処理型のプログラムとは異なり,GUI環境では,プログラムは常にユーザーの入力を待機し,ユーザーか らの操作があって初めて機能が呼び出される「イベント駆動型(event driven)型」のプログラミングとなり, プログラミングの手法も大きく異なってくる. GUIツールキットは,GUIプログラミングに伴う煩わしい作業を簡素化するために用いられるライブラリである.多くの開発者はGUIツールキットを用いてGUIアプリケーションの開発を行なっている.GUI
2 2 Gtk+によるGUIプログラミングの基礎 ウィジェット(widget=部品)”があらかじめ用意されていて,プログラムのソースコードからはほんの数行 の呼び出しの手続きを書くだけで,美しくデザインされたこれらのウィジェットを自分のプログラムで利用す ることができる. 近年,数多くの優れたGUIツールキットが公開されているが,今回はその中でも急速に普及しつつある Gtk+を取り上げて,GUIプログラミングを実践する.本演習で取り上げるのはGtk+のみであるが,他の GUIツールキットを用いても基本的なプログラミングスタイルは似ており,演習で学んだことは,今後,あら ゆる場面で役立てることができる.
2.2 Gtk
アプリケーションの処理の流れ
まずは,簡単な例題プログラムを使って,GUIプログラミングにおけるウィジェットの作成,イベントの 処理について見てみよう.最初にサンプルのsample1.cのコンパイルを行って,実行ファイルを作成する.以下ののWebページ(http://www.cyber.t.u-tokyo.ac.jp/˜tani/class/mech enshu/)からリンクをたどり,
enshu2009gui1.tar.gzをダウンロードし,以下のコマンドを実行する. % tar xvzf enshu2009gui1.tar.gz % cd 2009gui1 % make でき上がったファイルを実行し,図1のようなウィンドウが現れれば,コンパイルは成功である. 図1 sample1.c実行時の画面 Gtk+を利用するには,Gtk+ライブラリのヘッダファイルgtk.hをソースコードの冒頭でインクルードす る必要がある.sample1.c 12行目の#include<gtk/gtk.h>がそれにあたる.gtk.hには,Gtk+を扱うのに 必要な関数,構造体,マクロ等が定義されている.*1 次に54行目以降のメイン関数を見てみよう.Gtkアプリケーションにおける処理の流れは,基本的には 初期化 → ウィジェットの生成 → 入力待機状態の開始 のようになっている.以下でそれぞれの処理について説明する. *1実際は gtk.h 自体は,さらに細かい機能ごとに定義されたヘッダファイルをインクルードしているだけである.興味のある人は一 度 gtk.h を覗いてみるとよい.
2.3 ウィジェット 3
初期化
初期化の処理では主に,プログラム呼び出し時の引数の処理,ロケールの設定(文字コードなどの言語に関
する設定),設定ファイルの読み込み等を行なう.最も単純なGtkプログラムのメイン関数は大体以下のよう
になるはずである.
int main (int argc, char* argv[]) { { gtk_set_local(); /* ロケールの設定 */ gtk_init(&argc, &argv); /* ライブラリ初期化と引数処理 */ gtk_get_rc_parse("設定ファイル"); /* Gtk設定ファイルの読みこみ */ ... } sample1.cではロケールの設定と設定ファイルの読みこみは省略してある.gtk_init()へは,アプリケー ション自体への引数のリストargvがそのまま渡され,アプリケーション呼び出しの際に指定できるGtkオプ ションが処理される.処理された引数は消去され,それ以外の引数は引数リストに残るので,gtk_init()の あとで,アプリケーション固有の引数を処理すればよい. ウィジェットの生成 Gtk+をはじめとするGUIツールキットではウィンドウやボタンなどの要素を「ウィジェット(部品)」と 呼ぶ.ウィジェットには,ボタンやテキストボックス,スライダーなど目に見えるGUI部品の他に,パッキ ングボックスと呼ばれるウィジェットを配置を管理するための枠も含まれ(後述),作成したウィジェットを パッキングボックスへ格納(=パック)することでGUIのレイアウトを行なう. 基本的にGtk+でのGUIの構築は、ウィジェットを格納するGtkWidget型の構造体を確保し,パッキン グボックスに格納したあとgtk_widget_show()画面に表示する,という手順を踏む.gtk_widget_show() を呼び出す順番はとくに決りはないが,gtk_widget_show()が呼ばれても,その下にあるウィジェットが表 示されないかぎり表示できないので,一番最後にメインウィンドウを表示するように記述すると,全てのウィ ジェットが一度に表示されるようになる. 入力待機状態の開始 全てのウィジェットを作成したあとは,GUIプログラムはユーザーからの入力などのイベントを待機し,イ ベントが生じた場合に必要な処理を行なう関数を呼び出すという状態に入る.Gtkプログラミングにおいては 開発者が待機状態のコードを自前で用意する必要はなく,125行目にあるようにgtk_main()を呼び出すだけ でよい.プログラムを終了するためのイベントが生じるとgtk_main()を抜けだし,プログラムはmain()関 数の最後まで到達して終了する.
2.3
ウィジェット
Gtk+で最も単純な3つのウィジェットである,ウィンドウ,ラベル,ボタンの使い方を述べる.4 2 Gtk+によるGUIプログラミングの基礎 windowウィジェット Gtk+のアプリケーションで最初に作成すべきウィジェットは,メインウィンドウそのもので,sample1.c では67∼69行目にあたる.メインウィンドウの作成は以下のように行なう. /* ウィジェットの宣言 */ GtkWidget *window; /* 新しいwindow ウィジェットの確保 */ window = gtk_window_new(GTK_WINDOW_TOPLEVEL); /* ウィンドウタイトルの設定 */ gtk_window_set_title(GTK_WINDOW(window), "ウィンドウタイトル"); /* ウィンドウの枠から中味までの間隔の設定 */ gtk_container_border_width(GTK_CONTAINER(window), 10); labelウィジェット ウィンドウ上に文字列を表示するためにはlabelウィジェットを用いる.labelウィジェットの作成は以下 のようなコードを書けば良い. /* ウィジェットの宣言 */ GtkWidget *label; /* 新しいlabel ウィジェットの確保,文字列の設定 */ label = gtk_label_new("ラベル文字列"); sample3.c 78∼80行目ではウィンドウにあらわれるボタンを押した回数の表示のためのlabelウィジェット を定義している.labelの文字列は,アプリケーションの動作中に,動的に変更することもできる.この場合, gtk_label_set(GTK_LABEL(label), "新しいラベル"); とすればよい. buttonウィジェット ボタンの表示も,これまで見て来たウィジェットと同様に
2.4 コールバック関数の設定 5 /* ウィジェットの宣言 */ GtkWidget *button; /* 新しいbutton ウィジェットの確保,文字列の設定 */ button = gtk_button_new_with_label("ボタンに表示される文字列"); で作成できる.ただし,ボタンの場合ユーザーの操作という“イベント” 発生に伴う処理を開発者が自ら定義 してやらなければならない(次節参照).
2.4
コールバック関数の設定
イベント処理の細かい概念や実装は,GUIツールキットやOSによって多少異なる.Gtk+では基本的に イベント発生時に“シグナル”が発行されるようになっており,開発者はシグナル発生時に行なうべき手続を 記述した関数(シグナルハンドラ)を登録することによってウィジェット操作時の動作を定義する.このイベ ントに対応づけられたシグナルハンドラのように,プログラムに対する入力等の通知を処理する手続をコール バックと呼ぶ. コールバックの登録は主に2種類ある.これはコールバック関数への引数の数によって使いわける.より汎 用性が高いのはgint gtk_signal_connect( GtkObject *object, gchar *name, GtkSignalFunc func, gpointer func_data ); object : シグナルを発行するGTKオブジェクト(ウィジェット) name : シグナル名 func : コールバック関数 func_data : コールバック関数へ渡すデータへのポインタ で,この場合コールバック関数の型式は,
void callback_func( GtkWidget *widget, gpointer data ); widget : シグナルを発行したウィジェット
data : コールバックへ渡されるデータへのポインタ
6 2 Gtk+によるGUIプログラミングの基礎
gint gtk_signal_connect_object( GtkObject *object, gchar *name, GtkSignalFunc func, GtkObject *slot_object ); object : シグナルを発行するGTKオブジェクト(ウィジェット) name : シグナル名 func : コールバック関数 slot_object : funcへの最初の引数として渡すGTKオブジェクト で,この場合コールバック関数は,
void callback_func( GtkWidget *widget ); widget : シグナルを発行したウィジェット である.つまり,コールバック関数に何かしらのデータを引数として渡したい場合は,gtk_signal_connect(), それ以外の場合は,gtk_signal_connect_object()を使えばよい. シグナル名はあらかじめ定義されており,表1のようなものがある. 表1 Gtk+のイベントの例 イベント名 説明 pressed マウスボタンが押された released マウスボタンが離された clicked マウスボタンがクリックされた enter マウスポインタが重なった leave マウスポインタが外れた sample1.cでは,ボタンが押されるとカウンター変数counterをインクリメントし,さらにラベルの表示 を変更して,ボタンが押された回数をカウントするようにしている. ◇ 課題1◇ 1. sample1.cをコンパイルし,実行せよ. 2. sample1.cにbutton2を追加し,カウンターを減らす機能を実装せよ(図2).
2.5
ウィジェットの配置
より多機能なGUIプログラムを作るためには、複数のウィジェットを思い通りの位置に配置する必要があ る.しかし、各ウィジェットの位置を座標の絶対値で指定するだけでは,ユーザーの操作によってウィンド2.5 ウィジェットの配置 7 図2 課題1.2実行時の画面 ウのサイズやGUIの概観,フォントの種類が変更されると,開発者の意図どおりの配置を保つことが難しく なる.また,ウィジェットの配置の調整に多くの労力を要してしまう.そこで,Gtk+をはじめとして多くの GUIツールキットでは“パッキング”という操作によってウィジェットの配置を行なう.Gtk+での基本的な ウィジェットの配置を理解するためには,パッキングボックスの概念を知る必要がある. パッキングの操作では,“見えない箱”の中にウィジェットを詰め(=パック),さらにその箱をより大きな 箱に詰めていって,多数のウィジェットを高率良くウィンドウの中に整理して並べるのが基本である.この“ 見えない箱”を“パッキングボックス”と呼ぶ.パッキングボックスには以下の2種類がある. 図3 水平ボックスと垂直ボックス 図4 パッキングボックスを更にパックするこ とで,自由にウィジェットをレイアウトするこ とができる. 水平ボックス 水 平 ボ ッ ク ス は ウ ィ ジ ェ ッ ト を ,横 に 並 べ て 配 置 す る .水 平 ボ ッ ク ス の 作 成 は gtk_hbox_new() で 行 な う .水 平 ボ ッ ク ス の 場 合 ,ウ ィ ジ ェ ッ ト は gtk_box_pack_start() で パックした順に左から右へ,あるいはgtk_box_pack_end()でパックした順に右から左へ詰めこま れる. 垂直ボックス 垂 直 ボ ッ ク ス は ウ ィ ジ ェ ッ ト を ,縦 に 並 べ て 配 置 す る .垂 直 ボ ッ ク ス の 作 成 は
8 3 画像処理プログラミング gtk_vbox_new() で 行 な う .垂 直 ボ ッ ク ス の 場 合 ,ウ ィ ジ ェ ッ ト は gtk_box_pack_start() で パックした順に上から下へ,あるいはgtk_box_pack_end()でパックした順に下から上へ詰めこま れる. ◇ 課題2◇ 1. sample2.cのプログラムを元に電卓の数字ボタン及びC(クリア)ボタンの挙動を実現せよ. (図5) 2. 演算(+,-,*,/)ボタンのコールバック関数を実装せよ. 3. 追加した各ボタンにコールバック関数を自由に割り当てよ.(オプション) 図5 課題2.1実行時の画面
3
画像処理プログラミング
3.1
画像の表示
まずは,簡単な例題プログラムを使って,画像の表示方法を学ぼう.サンプルのsample3.cのコンパイルを 行って,実行ファイルを作成する.でき上がったファイルsample3を実行し,スクリーン上にウィンドウが 現れれば,コンパイルは成功である.3.2 画像ファイルの読みこみ 9 ◇ 課題3◇ 1. サンプルプログラムsample3.cをコンパイルして実行せよ. 2. sample3.c内の関数image_proportion()を書き換え,図6のように画面の上から徐々に明 るくなるような画像が表示されるようにせよ. 3. sample3.c内の関数image_skew()を書き換え,図7のように画面の右上から徐々に明るく なるような画像が表示されるようにせよ. 4. sample3.c内の関数image_radial()を書き換え,図8のような画面の中心から徐々に明る くなるような画像が表示されるようにせよ.(オプション) 注意: 画像作成関数はサンプル内の関数image_processing()で呼び出されている。そのため関 数の中身を書き換えた後、image_processing()内で呼び出す関数を変更させること。そうしない と結果が反映されない。 図6 課題3.2の実行画面 図7 課題3.3の実行画面 図8 課題3.4の実行画面
3.2
画像ファイルの読みこみ
画像ファイルには,PNG, JPEG, TIFF, BMP, GIFなど様々な型式があるが,本演習ではプログラムから 扱いやすいPNM(Portable aNyMap)型式の一種であるPGM(Portable Grayscale Map)型式を用いる.既 にデバイスプログラミングの演習で行ったように,フルカラー画像を扱うPPM(Portable Pixel Map)形式の 先頭部分は,以下のようになっている. P6 ← ファイル形式識別記号 256 256 ← x方向のピクセル数 y方向のピクセル数 255 ← 最大輝度 ...(バイナリの画像データ,RGB順,左から右,上から下へ操作した順)... また,グレースケールを画像を扱うPGM型式のファイルの先頭部分は以下のようになっている.
10 3 画像処理プログラミング P5 256 256 255 ...(以下バイナリの画像データ)... 1行目の“P5”“P6”はマジックナンバーと呼ばれ,画像形式を宣言している.グレースケールのバイナリで あればP5,フルカラーのバイナリであればP6となる.2行目の2つの数字は画像のサイズで,上の例だと 256 × 256 pixelのサイズの画像であることを表す.その次の行の255は階調で,各pixelの値を0∼255の範 囲で表現することを示す.0∼255の値を表現するためには8 bit(=1 byte)の情報量が必要なので,この画像 はグレースケールの場合1pixelあたり1byteのデータであることがわかる.カラー画像の場合は,1pixel当 たり,RGBそれぞれ8bitで順番に格納され3byteのデータであることがわかる.
3.3
描画のためのウィジェット
GTKはGDK (GIMP Drawing Kit)のうえに実装されており,GDKは基本的には基礎となるウィンドウ 関数(X Window systemではXLibにあたる)へアクセスする低レベル関数を包むラッパー(Wrapper)であ る.GDKのリファレンスとしては,http://www.gnome.gr.jp/docs/gtk+-1.2.x-refs/gdk/index.htmlを見 ると良い.
スクリーンへの描画の処理の為に使うウィジェットはDrawingArea ウィジェットである.描画領域ウィ
ジェットは,空白のキャンバスでそこに好きなものを何でも描ける.描画領域ウィジェットは以下の呼び出し によって作成される.
GtkWidget* gtk_drawing_area_new ( void );
/* ウィジェットのデフォルトサイズはこの呼び出しで指定できる.*/
void gtk_drawing_area_size ( GtkDrawingArea *darea, gint width, gint height); 描画領域を生成した後,次のシグナルに接続する必要がある場合がある. • ユーザ入力に応答するためのマウスとボタン押下シグナル • ウィジットが生成されて表示されたときに必要な処理を行う“realize” シグナル • ウィジットのサイズが変更されたときに必要な処理を行う “size allocate”シグナル • ウィジットの内容を再描画するための“expose event”シグナル RGB,白黒とカラーのイメージ(画像)をウィンドウのピクセルに変換して通常のウィンドウに表示するた め,GDKにはGdkRgbという呼び出しが用意されている.GdkRgbの機能を使用する前にgdk_rgb_init()
3.3 描画のためのウィジェット 11
関数を呼び出す.もしこの処理に失敗すると,coreダンプする.(GtkPreviewを含む) GdkRgbを使用する 全てのGTK+ウィジットは自分のクラスの初期化メソッド(class_init)の中でgdk_rgb_init()関数を
呼び出す.よって,間接的にのみGdkRgbを使用する場合は,その呼び出しを考慮する必要はない.
void gdk_rgb_init (void);
void gdk_draw_rgb_image ( GdkDrawable *drawable, GdkGC *gc, gint x, gint y, gint width, gint height, GdkRgbDither dith, guchar *rgb_buf, gint rowstride ); void gdk_draw_gray_image ( GdkDrawable *drawable,
GdkGC *gc, gint x, gint y, gint width, gint height, GdkRgbDither dith, guchar *buf, gint rowstride ); drawable : 描画する GdkDrawable (通常は GdkWindow)
gc : グラフィックス・コンテキスト(全てのGDK描画関数が必要とするもの 中身は無視される) x : drawable の左上端の X座標 y : drawable の左上端の Y座標 width : 描画する矩形の幅 height : 描画する矩形の高さ dith : GdkRgbDither の値で、お好みのディザ・モードを選択する rgb_buf : ピクセル・データでPacked24ビットデータとして表現される (Packed : ビットの格納方式の一つ.一ピクセルにつきDepthを 並べて格納する) rowstride : rgb_buf である桁の始点から次の桁が開始する点までのバイト数
12 3 画像処理プログラミング
gdk_draw_rgb_image () は Drawable の 中 に RGB 画 像 を 描 画 し ,gdk_draw_gray_image () は
Drawable の中に白黒画像を描画する.引数rowstride はより柔軟に線を配置するためのもので,一般的に,
0 <= i < widthと0 <= j < heightで,pixel(x+i, y+j)は赤(R) = rgb_buf[j*rowstride + i*3], 緑(G) = rgb_buf[j*rowstride + i*3 + 1],青(B) = rgb_buf[j*rowstride + i*3 + 2] となる.
3.4
ウィジェットからの情報の取得
また,GUIによるインタラクティブなプログラムを作るためには,ボタンのような押す/離すの単純な入力 だけでなく,文字列や数値など,より複雑な情報を入力できるようにする必要がある.ここでは,文字列の入 力方法としてentryウィジェット,数値の入力方法としてscaleウィジェットの使い方を学ぶ. entryウィジェット 文字列を入力するためにはentryウィジェットを用いる.entryウィジェットの作成は以下のようなコード を書けば良い. /* ウィジェットの宣言 */ GtkWidget *entry; /* 新しいentry ウィジェットの確保,文字数の設定 */ entry = gtk_entry_new_with_max_length (50); また,entryウィジェットの文字列を取り出すには以下のようにすればよい. gchar *entry_text;entry_text = gtk_entry_get_text (GTK_ENTRY(entry));
scaleウィジェット 数値を入力するためには,entryウィジェットに直接数値を打ち込んでも良いが,よりGUIらしい入力方法 として,scaleウィジェットが用意されている.scaleウィジェットの使用方法はこれまでのウィジェットより 若干複雑になっている.まず,scaleウィジェットの範囲や初期値,増減値などを設定するための,adjustment オブジェクトを作成する.adjustmentオブジェクトを作成するための関数gtk_adjustment_new()の使い 方は以下の通りである.
3.4 ウィジェットからの情報の取得 13
GtkObject* gtk_adjustment_new ( gfloat value, gfloat lower, gfloat upper, gfloat step_increment, gfloat page_increment, gfloat page_size ); value : 初期値 lower : 最小値 upper : 最大値 step_increment : 増減値(小) page_increment : 増減値(大) page_size : スケールを移動するウィジェットの大きさ
upperは表示領域の最大値であり,値の最大値ではないことに注意せよ.値の最大値は upper - page_size になる. その後,これまでのウィジェットと同様にscaleウィジェットを宣言して確保し,先に作成したadjustment オブジェクトを指定してやればよい.まとめると,以下のようなコードを書けばよい. /* オブジェクト,ウィジェットの宣言 */ GtkObject *adjustment; GtkWidget *scale; /* 新しいadjustment オブジェクトの確保 */ adjustment = gtk_adjustment_new(0, 0, 255, 1, 1, 0); /* コールバック関数の設定(ここではon_slider_moved()という関数をよそで定 義している) */ gtk_signal_connect(GTK_OBJECT(adjustment), "value_changed", GTK_SIGNAL_FUNC(on_slider_moved), adjustment); /* 新しい水平scale ウィジェットの確保,adjustment オブジェクトの指定 */ scale = gtk_vscale_new(GTK_ADJUSTMENT(adjustment)); /* 小数点以下の桁数の設定 */ gtk_scale_set_digits(GTK_SCALE(scale), 0); また,scaleウィジェットの値をコールバック関数から取り出すには以下のようにすればよい.
14 3 画像処理プログラミング
void on_slider_moved(GtkWidget * widget, GtkAdjustment *adj) { int scale_value; scale_value = adj->value; } ◇ 課題4◇
1. sample4.cをコンパイルし,さらにsample4.c内の関数load_pgm()を完成させ,図9のよ うにlena.pgmが表示されるようにせよ. 2. entryウィジェットからファイル名を取得して画像ファイルを開けるようにせよ(図9). 図9 課題4実行時の画面
3.5
画像の変換
二値化 しきい値Tを定め,画像中の画素値がTより大きいときその画素値を1,それ以外のとき画素値を0にす る変換を画像の二値化(binarization)と呼ぶ. 二値化は,文書や図面のような白黒の画像をスキャナで濃淡画 像として入力した場合などに,濃淡画像から二値画像に変換するために行われる.また,一般の画像で対象物 と背景を分離するために二値化が行われることも多い. コントラスト変換 コントラスト変換は,画像が明るすぎたり暗すぎたりする場合や,微妙に濃淡を変化させたいときなどに用 いられる.画像の濃淡を一定の規則に従って変換するため,コントラストだけでなく階調性も変化する. もっとも簡単な変換は,画像の濃淡を目的に応じた適当な関数で変換する方法である.入力画像の濃淡レベ3.6 線形フィルタを用いた画像処理 15
ルをxとすると,このxを目的に応じた関数fでy = f (x)に変換することで画像のコントラストの改善など を行う.画像の濃淡レベル域[xmin, xmax]が,一部分にしか分布していないような場合など,図10に示す
ように画像の濃淡レベル域をより広い濃淡レベル線形拡大する.変換関数は,以下の式で表される.
xmax≥ xのとき,y = ymax
xmin≥ x < xmaxのとき,y = ymax− ymin
xmax− xmin(x − xmin) + ymin
x < xminのとき,y = ymin xmin xmax ymin ymax 図10 線形変換の例
3.6
線形フィルタを用いた画像処理
次は3 × 3の線形フィルタを実装しよう.線形フィルタは入力画像と重み行列(フィルタ)の単純な畳み込 み演算で画像に効果を与えることができる最も基本的な画像操作のひとつである.線形フィルタには一般に 3 × 3や5 × 5の行列が用いられ,行列の変更のみで効果が変わってくる.ここでは平滑化フィルタ,差分フィ ルタ(エッジ抽出)について簡単に説明する. 平滑化フィルタ 平滑化フィルタは,注目している画素の近傍の画素値の重みつき積分を,出力画素の画素値とし,主に入力 画像のノイズを除去するために使われる.最も単純なのは近傍画素の単純な平均値をとるフィルタで,3 × 3のフィルタでは,f (i, j)を入力画像,g(i, j)を出力画像の画素(i, j)の画素値とすると,
g(i, j) = 1 9 1 X m=−1 1 X n=−1 f (i + m, j + n) で定義される.これは入力画像と行列 1 9 19 19 1 9 19 19 1 9 19 19 のコンボリューションをとる操作である.この単純な平均値を用いたフィルタは,注目する画素が,周囲の画 素にくらべて極端に大きい,あるいは小さい画素値をとる場合に,周囲の画素値に近づけてる効果をもつ.ノ
16 3 画像処理プログラミング イズは一般に近傍の画素と大きく異なる画素値を持つため,このフィルタを適用すれば,ノイズを目立たなく することができる.しかし,同時に画像上の輪郭もぼかしてしまうことになる. エッジ抽出フィルタ エッジ抽出フィルタは,注目画素の近傍の画素の変化率から,画像上の線や輪郭を抽出する.平滑化が積分 なのに対してこちらは微分演算にあたる.実際には近傍画素の差分をとるだけのことで,平滑化フィルタの足 し算が引き算に変わっただけである. ■微分フィルタ,Sobelフィルタ 垂直方向のエッジを抽出する場合,入力画像と以下の行列のコンボリュー ションを計算すればよい. −1 0 1−1 0 1 −1 0 1 これを1次線形微分フィルタと呼ぶ.更に,注目画素に近い近傍画素ほど重みをつけたものもある.つまり 行列 −1 0 1−2 0 2 −1 0 1 をフィルタに用いるのである.これをSobelフィルタという. ■ラプラシアンフィルタ 画素の輝度値の1階微分によるフィルタでは,画像上の輪郭は,隣り合った画素の 輝度差が大きいところである,という仮定が元になっている.しかし,実際は輪郭以外でも輝度値がなだらか に変化している部分も存在し,必ずしも1次線形微分フィルタで輪郭を抽出できるとはいえない.そこで,輝 度値の変化自体が大きく変化する部分を輪郭と仮定して輪郭抽出を行なうという考え方がでてくる. すなわち輝度分布の2階微分であるラプラシアンを計算することになる.(図11). 実際のラプラシアンの計算は,これまで同様に3 × 3のマトリクスとのコンボリューションで表現できる. 輝度値の分布がf (i, j)である画像の,輝度値の水平方向,垂直方向の2階微分をそれぞれ, ∂2
∂i2f (i, j) ≡ {f (i + 1, j) − f (i, j)} − {f (i, j) − f (i − 1, j)}
∂2
∂j2f (i, j) ≡ {f (i, j + 1) − f (i, j)} − {f (i, j) − f (i, j − 1)}
と定義できるとき,画像のラプラシアン ∂2 ∂i2f (i, j) + ∂2 ∂j2f (i, j) を求めるフィルタを3 × 3で表現し,実際の画像でエッジ検出ができる.
17 図11 輝度値が変化しているところでは,(A),(B)で変化の度合い(微分係数)は同じであるが,(B)のよ うに局所的に輝度値が変化しない限り輪郭(エッジ)とはならない.つまり輝度値の分布の微分では必ずし もエッジを抽出できるとは限らないが,2階微分すればより精度よく抽出できることがわかる. ◇ 課題5◇ 1. sample5.cをもとに,scaleウィジェットを用いて閾値を指定し画像を二値化せよ(図12). 2. sample5.c内の関数image_filter()を書き換え,上の平滑化フィルタを,3 × 3の配列を 用いて実装せよ.(図13).(オプション) 3. 水平・垂直方向のエッジを強調するSobelフィルタを作成し,上のフィルタと結果を比較せ よ.(オプション) 4. ラプラシアンフィルタを作成し,上のフィルタと結果を比較せよ.(オプション) 図12 課題5.1実行時の画面 図13 課題5.2実行時の画面
4
カメラデバイスの取り扱い
デバイスプログラミングの演習で行ったように,カメラのデバイスファイル(/dev/video)からデータを読 み込むことで,カメラ画像を取得することができる.今回の演習では,デバイスプログラミングの演習を引き 継いで,mmap関数によるカメラのデバイスの読み書きをしたvideocapture.cを元にGUIアプリケーション を作成する.18 4 カメラデバイスの取り扱い 前節では,画像処理の容易さのためグレースケールの画像に対して読み出しや書き出し,画像処理を行って いた.使用するカメラはカラーカメラであり取得できる画像はカラー画像のため,保存や表示もフルカラーに 対応したものになる. ◇ 課題6◇
1. gtk captureディレクトリのvideocapture gui.cをもとに,キャプチャした画像を任意の ファイルに保存できるようにせよ.(図14). 2. キャプチャした画像を二値化して表示し,保存できるようにせよ. 図14 課題6実行時の画面
4.1 OpenCV
を使った画像処理アプリケーション
OpenCVライブラリを利用することで画像の取得や上記の画像処理は容易になる.実際にOpenCVを使用 して2値化処理を行う部分は,下記のbinarization()関数にあるように変更はない.4.1 OpenCVを使った画像処理アプリケーション 19
// 2値画像に変換し,表示する
void binarization(void){
// BGRからグレースケールに変換する
cvCvtColor( sourceImage, grayImage, CV_BGR2GRAY ); //グレースケールから2値に変換する
cvThreshold( grayImage, binaryImage, levels,
THRESHOLD_MAX_VALUE, CV_THRESH_BINARY ); // 画像を表示する
cvShowImage( windowNameBinarization, binaryImage); }
// トラックバーの値が変わったときに呼ばれるコールバック関数
void on_change( int pos ){ binarization();
}
ただし,GTKによるプログラムと同様に,ウインドウの作成後,トラックバー(GTKではscaleウィジェッ
ト)を生成し,イベント発生に伴う処理をコールバック関数として定義し登録する必要がある.
char windowNameBinarization[] = "Binarization"
// 画像を表示するウィンドウの名前
int levels = 128; // トラックバーの値(2値化の際の閾値) char trackbarNameThreshold[] = "Threshold";
// 閾値を変更するトラックバーの名前
// ウインドウを生成する
cvNamedWindow( windowNameBinarization );
// トラックバーを生成し,コールバック関数を設定する
cvCreateTrackbar( trackbarNameThreshold, windowNameBinarization, &levels,
TRACKBAR_MAX_VALUE, on_change ); // ウィンドウを破棄する
20 4 カメラデバイスの取り扱い ◇ 課題7◇
1. binarization opencvにあるプログラムを参考にOpenCVを使用したエッジ抽出プログラム を作成せよ.(オプション)