はじめに
グレースケール画像を二値化処理する必要があったので、作ってみました。
今回のプログラムについて、条件として次のような画像を処理することとしました。
- PGMファイルである(テキストファイルP2)
- 最大値は255まで
- ヘッダ部に余計な空白はない
- データ部の数値は空白で仕切られている(行末にも空白)
- 一行に最大100字まで
二値化処理が目的ではなく、短時間で作ったため作りが荒いところもあります。
追記:プログラムがよりコンパクトで簡単になった改正版をこちらの記事で紹介しています。
アルゴリズムの説明
簡単に作ったプログラムのアルゴリズムの説明をしていきます。
画像ファイルを全て読み込んで、最後に出力するというプログラムを作るのは大変そうだったので、画像ファイルを一行ずつ読み込んで、同時に一行ずつ出力するというプログラムを作りました。
ヘッダー情報の処理
条件に合っているか判定する
- グレースケール画像であるか判定
- 最大値が255までの画像であるか判定
出力情報
- グレースケール画像の指定
- コメント情報、サイズ情報のコピー
- 最大値の指定
画像情報の処理
画像ファイルの特徴
- 全ての数値は空白で区切られている
- 数値の上限は255
- #以降はコメントとして扱われる
- 全て文字データ
画像ファイルの処理アルゴリズム
- ファイルから一行取り出す
- 一行から一文字ずつ取り出す
- 文字の判別
- 数字の場合
- 文字データを数値データに変換
- 数値データを配列に先頭から順に格納
- 一文字の処理を終了
- 空白の場合
- 配列に数値データが格納されているか判別
- 格納されている場合
- 配列の後ろから順に取り出す
- 位の調整をして数値データを画像ファイル用の数値に変換
- 数値と空白を出力
- 一文字の処理を終了
- 格納されていない場合
- 空白を出力
- 一文字の処理を終了
- 格納されている場合
- 配列に数値データが格納されているか判別
- #である場合
- 一行の処理を終了
- 数字の場合
- 次の行にも同じことを実行
ソースコード
#include <iostream> #include <fstream> #include <string> #include <cmath> std::string input = "input.pgm"; std::string output = "output.pgm"; ////////////////////////////////////////////////// ////////////////////////////////////////////////// //二値化処理 ////////////////////////////////////////////////// ////////////////////////////////////////////////// int do_binary(int tsd_v){ //二値化処理 std::ifstream ifs(input); //入力ファイルストリーム std::ofstream ofs(output); //出力ファイルストリーム std::string str_i; //文字列型 if(ifs.fail() || ofs.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; } ofs << "P2" << std::endl; //P2(グレースケール画像)を指定 一行目の書き出し while(1){ //コメント行からサイズ行までを読み取り,書き出しを繰り返す. getline(ifs, str_i); //一行を読み取り ofs << str_i << std::endl; //読み取った行をそのまま書き出し if(str_i[0] != '#'){ //コメント行でなければループ処理を抜ける break; } } getline(ifs, str_i); //最大値の読み込み int max = atoi(str_i.c_str()); if(max > 255){ //最大値が255より大きい時のエラー処理 std::cerr << "Can not handle." << std::endl; return -1; } ofs << "255" << std::endl; //最大値を255に指定 四行目の書き出し 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){ //数値が0,もしくは数値がない場合 if(st_value[0] == 0){ //数値が0の場合 ofs << 0 << ' '; //書き込み }else{ //数値がない場合 ofs << ' '; //書き込み } }else{ //数値がある場合 if(value >= tsd_v){ value = 255; }else{ value = 0; } ofs << value << ' '; //書き込み } 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(); ofs.close(); std::cout << "閾値:" << tsd_v << "\nで二値化しました." << std::endl; return 0; } ////////////////////////////////////////////////// ////////////////////////////////////////////////// //固定閾値法 ////////////////////////////////////////////////// ////////////////////////////////////////////////// int f_tsd(){ //固定閾値法 int tsd_v; //閾値 std::cout << "閾値を入力してください." << std::endl; std::cin >> tsd_v; return tsd_v; //閾値を返す } ////////////////////////////////////////////////// ////////////////////////////////////////////////// //メイン関数 ////////////////////////////////////////////////// ////////////////////////////////////////////////// int main(){ do_binary(f_tsd()); return 0; }
実際に実行する。
入力ファイルを次のグレースケール画像、閾値を120として実行する。
得られた結果は次の通りです。
最後に
今回はこの辺で。
気が向いたらその他の閾値決定法のプログラムも作って載せます。
コメント