はじめに
前回の一次差分法による画像のエッジ検出プログラムに引き続きエッジ(画像中の対象と背景との境界のこと)の検出を行なっていきます。
画像の種類や動作条件については、こちらで二値化を行った時と同様です。
プログラムにするのは簡単なので具体的なアルゴリズムについては省略します。
二次差分法について
エッジ検出における二次差分法は、ある画素から見て左右上下の画素に対する輝度の差分を取り、その合計値を輝度とする手法です。
具体的には次のようになります。
ある特定の画素\((x,y)\)の輝度値を\(E_{x,y}\)とし、二次差分法適応後の輝度値を\(E_{x”,y”}\)とすると
\[E_{x”,y”} = \left|4E_{x,y} – \left(E_{x+1,y} + E_{x,y+1} + E_{x-1,y} + E_{x,y-1}\right)\right|\]
プログラムでは画像を読み込んでから、全ての画素にこの式を適用します。
また、画像の端にあたる部分については処理を書くのが少し手間だったので除外します。
ソースコード
#include <iostream> #include <fstream> #include <string> #include <cstdlib> #include <cmath> std::string input = "input.pgm"; int main(){ std::ifstream ifs(input); //入力ファイルストリーム std::string str_i; //文字列型 if(ifs.fail()){ //ファイルを開けない時のエラー処理 std::cerr << "Failed to open file." << std::endl; return -1; } getline(ifs, str_i); //一行目の読み込み if(str_i != "P2"){ //P2画像でない時のエラー処理 std::cerr << "Can not handle." << std::endl; return -1; } int width,height; while(1){ //コメント行から最大値行までを読み込み getline(ifs, str_i); //一行を読み取り if(str_i[0] != '#'){ //コメント行でなければ最大値行を読み込んでループ処理を抜ける int sp_p = str_i.find_first_of(' '); std::string wid_str = str_i.substr(0,sp_p); std::string hei_str = str_i.substr(sp_p+1); width = atoi(wid_str.c_str()); height = atoi(hei_str.c_str()); getline(ifs, str_i); //最大値の読み込み int max = atoi(str_i.c_str()); if(max > 255){ //最大値が255より大きい時のエラー処理 std::cerr << "Can not handle." << std::endl; return -1; } break; } } int data[width][height]; int row = 0,column = 0; int st_value[3] = {-1,-1,-1}; //一時記憶用の配列 while(getline(ifs, str_i)){ //最終行まで繰り返し,一行ずつ読み込み for(int i = 0;i < str_i.length();i++){ //文字列の長さだけループ if(str_i[i] == '#'){ //コメントがついた時点でループを抜け出す. break; } else if(str_i[i] == ' '){ //空白だった時の処理 int digits = 0; int value = 0; for(int j = 2;j >= 0;j--){ //配列にスタックされたデータを全て取り出す. if(st_value[j] > -1){ value += st_value[j]*std::pow(10,digits); //各桁の数字から実際の数値に変換(ab) > (10*a + b) digits++; } } if(value > 0 || st_value[0] == 0){ //数値が何かあった場合の処理 if(row == width){ column++; row = 0; } data[row][column] = value; row++; } for(int l = 0;l < 3;l++){ //配列の初期化 st_value[l] = -1; } }else{ //空白でなかった時の処理(データを格納する場所を探す) for(int k = 0;k < 3;k++){ //0から2までで格納(最大値が255)[0] = 一の位 [2] = 100の位 if(st_value[k] == -1){ //-1であれば値が入っていない=まだ決まっていない桁 int data_v = 0; data_v = (int)(str_i[i] - '0'); //char型の数字をint型に変換 st_value[k] = data_v; //各桁の数字のみを配列に格納 break; } } } } } //開いたファイルを閉じる ifs.close(); std::cout << "二次差分法を適応します." << std::endl; int s_data[width][height]; for(int j = 0;j < height-1;j++){ for(int i = 0;i < width-1;i++){ s_data[i][j] = 255 - abs(4*data[i][j] - data[i+1][j] - data[i-1][j] - data[i][j+1] - data[i][j-1]); //差分の計算 } } for(int i = 0;i < width;i++){ //外周を除去 s_data[i][height-1] = 255; s_data[i][0] = 255; } for(int i = 0;i < height;i++){ //外周を除去 s_data[width-1][i] = 255; s_data[0][i] = 255; } s_data[width-1][height-1] = 255; //角の除去 s_data[0][0] = 255; std::ofstream osfs("xys_differ.pgm"); //出力ファイルストリーム if(osfs.fail()){ //ファイルを開けない時のエラー処理 std::cerr << "Failed to open file." << std::endl; return -1; } osfs << "P2" << std::endl; //P2(グレースケール画像)を指定 一行目の書き出し osfs << width << ' ' << height << std::endl; osfs << "255" << std::endl; for(int j = 0;j < height;j++){ for(int i = 0;i < width;i++){ osfs << s_data[i][j] << std::endl; } } osfs.close(); return 0; }
プログラムを実行する
次の画像に対してプログラムを実行する。
結果として、次のような画像が出力された。
最後に
二次差分法の方が一次差分法よりも雑音が少なく取れますが、全体的に少しぼやけた感じになります。
そのほかのエッジ検出法については別の記事で書いていきます。
今回はこの辺で。
コメント