STLの利用 - mist-team/mist GitHub Wiki
標準テンプレートライブラリ(STL:Standard Template Library)とは,C++言語でプログラミングする際に よく利用される,汎用的なデータ構造とアルゴリズムを実装した,クラステンプレートおよび関数テンプレートを 利用しやすいかたちでまとめたものです.
ここでは,MISTと関連する事項を簡単に説明します.STL自体の詳細は,各種参考書に譲ります(Web上で読めるSTLの本).
STLはコンテナ,アルゴリズム,イテレータの3つの基本要素で構成されています.
-
コンテナ … 任意の型のデータを格納できる様々な汎用データ構造(mistコンテナのようなもの)
-
アルゴリズム … コンテナに格納されたデータに対して行われる操作(ソート,検索,変換など)
-
イテレータ … コンテナに格納されたデータの場所を示すもの(ポインタのようなもの)
アルゴリズムはイテレータを介してコンテナに作用します. これにより,アルゴリズムはコンテナの種類を意識することなく,適用することができます.
STLには様々な種類のコンテナが用意されていますが,ここでは両端キュー(deque:double ended queue) コンテナを用いた簡単なプログラムを作成してみましょう.
はじめに,dequeコンテナを用いるために次のヘッダをインクルードします.
#include <deque>
次に,dequeコンテナを作成します.
std::deque< char > que(5); //サイズ5のchar型dequeコンテナqueを作成
当然ですが,この段階のqueは空のコンテナです.そこで,適当な文字列「ABCDE」を格納してみましょう.
int i;
for( i=0; i<que.size(); i++ ) //que.size()はコンテナqueのサイズを返すメンバ関数
{
que[i] = 'A' + i;
}
この処理では添え字演算子[]を介してコンテナにアクセスしていますが, 前述したように,STLではイテレータを介してコンテナにアクセスする方法が一般的です. 上記の処理を書き換えてみましょう.
まず,以下のようにイテレータを作成します.
std::deque< char >::iterator itr; //char型dequeコンテナのイテレータを作成
このイテレータitrを介して文字列"ABCDE"を格納してみましょう.
int i;
for( itr=que.begin(), i=0; itr!=que.end(); itr++, i++ )
//que.begin()はコンテナqueの先頭を指すイテレータを返すメンバ関数
//que.end()はコンテナqueの末尾(最後の要素の1つ後)を指すイテレータを返すメンバ関数
{
*itr = 'A' + i;
}
このように,イテレータはポインタのような働きをします.
ここまでで,メンバ関数size,begin,endを利用しましたが, STLには,他にも多くのメンバ関数が用意されています. ここで,dequeコンテナの主要なメンバ関数をいくつか紹介します. dequeはコンテナの両端からのアクセスが可能です.そこで,queの先頭と末尾にそれぞれ適当な文字 "{","}"を追加してみましょう.
que.push_front('{'); //que.push_front()はコンテナqueの先頭に要素を追加するメンバ関数
que.push_back('}'); //que.push_back()はコンテナqueの末尾に要素を追加するメンバ関数
当初,queはサイズ5のコンテナとして作成されましたが,要素数が追加されれば,その都度サイズは確保されます. その逆もまたしかりです.
メンバ関数push_front,push_backの逆処理で,コンテナの先頭と末尾の要素を削除する pop_front,pop_backも用意されています.
最後に,実際にコンテナにアルゴリズムを適用させてみましょう. アルゴリズムを用いる場合は,次のヘッダをインクルードします.
#include <algorithm>
では,"{}"内の文字列を反転アルゴリズムreverse()と検索アルゴリズムfind()用いて, 反転してみましょう.
//reverse()は指定された2つのイテレータ間の順序を逆にするアルゴリズム
//find()は指定された2つのイテレータ間で,指定されたクエリに一致する要素を指すイテレータを返すアルゴリズム
reverse( find( que.begin(), que.end(), '{' ) + 1, find( que.begin(), que.end(), '}' ) );
ここでは,STLを用いて簡単なプログラムを作成しましたが,STLには他にも様々な種類のコンテナ,アルゴリズムが 用意されていますので試してみて下さい.
以下からは,このSTLとMISTコンテナを併用したプログラミングについて説明します.
STLではvectorやqueue等のコンテナに対して適用できるアルゴリズムを提供していますが,MISTの用意したコンテナに対してもこれらのアルゴリズムを適用できます.実際にMISTコンテナをSTLのアルゴリズムに利用してみましょう.
MISTコンテナを利用するために次のヘッダファイルをインクルードしておきます.
#include <mist/mist.h>
また今回は画像を読み込みたいので,次のヘッダファイルもインクルードします.
#include <mist/io/bmp.h>
STLのアルゴリズムを利用するには,次のヘッダファイルをインクルードしておく必要があります.
#include <algorithm>
MISTコンテナとSTLのアルゴリズムを使用する簡単な例を作成してみましょう.まずarray2を利用して,画像を読み込んでおきます.
mist::array2< unsigned char > image;
mist::read_bmp( image, "入力画像ファイル名" );
ついでに書き込み関数も書いておきます.
mist::write_bmp( image, "出力画像ファイル名" );
読み込みと書き込みの間にSTLのアルゴリズムを挿入して,その結果出力された画像を見てみましょう.
#include <mist/mist.h>
#include <mist/io/bmp.h>
#include <algorithm>
main()
{
mist::array2< unsigned char > image;
mist::read_bmp( image, "入力画像ファイル名" );
// ここにアルゴリズムを挿入
mist::write_bmp( image, "出力画像ファイル名" );
}
コンテナ内の要素を反転させて,画像を逆さにしてみましょう.これには先ほど紹介したreverse()を利用します.
std::reverse( 要素の先頭を指すイテレータ, 要素の終わりを指すイテレータ )
イテレータは配列にとってのポインタのようなものなので,MISTコンテナの最初と最後の要素を指定してやります.指定した範囲の要素が反転するので,画像が逆さになります.MISTコンテナはSTLのコンテナと同様の使い方ができるので,上記のソースに次のコードを挿入します.
std::reverse( image.begin(), image.end() );
sort()を使って,画像内の全画素をソートしてみましょう.
std::sort( image.begin(), image.end() );
デフォルトでは昇順にソートされますので,降順にしたい場合は第三引数にgreater関数オブジェクトを記述します. 関数オブジェクトとは,operator()が定義されているクラスのことです.
std::sort( image.begin(), image.end(), std::greater< unsigned char >() );
random_shuffle()を用いることで,画素をランダムに並び替えることができます.
std::random_shuffle( image.begin(), image.end() );
最後に,特定の関数を各画素に対して適用できるアルゴリズムfor_each()を紹介します.
std::for_each( 先頭を指すイテレータ, 終わりを指すイテレータ, 関数 );
for_each()はイテレータで定義される範囲の要素に関数を適用します.この関数はなかなか便利で,毎回for文を使ってアルゴリズムを記述しなくてもよくなります(関数を書かなければいけませんが).
今回は入力画像を二値化する処理を例に用います.呼び出す関数として,しきい値を関数内で自分で書くものと,画像に適したしきい値を渡せるようにクラスで記述したものの,二種類を試してみましょう.
#include <algorithm>
#include <mist/mist.h>
#include <mist/io/bmp.h>
#include <mist/threshold.h>
//任意のしきい値を用いて,参照された要素を二値化する関数を作成します
//受け取るデータ型は,for_each()が適用されるコンテナに格納されるデータの型と
//一致する必要があります
int func_1( unsigned char& val )
{
val = ( val < 150 ) ? 0 : 255; //任意のしきい値を指定します
return 0; //関数内の返り値は無視されます
}
//最適なしきい値を受け取ってインスタンス化する関数オブジェクトを作成します
//operator()で要素を参照し,二値化します
class func_2
{
private:
unsigned char th_;
public:
//コンストラクタで最適なしきい値を受け取ります
func_2( unsigned char thred ):th_( thred )
{
}
void operator()( unsigned char& val ) const
{
val = ( val < th_ ) ? 0 : 255;
}
};
main()
{
//二種類の方法を試すため,二つコンテナを用意しておきます
mist::array2< unsigned char > image1, image2;
mist::read_bmp( image1, "入力画像ファイル名" );
image2 = image1;
//関数内で任意のしきい値を指定する方法
std::for_each( image1.begin(), image1.end(), func_1 );
//最適なしきい値を渡す方法
//判別分析法で最適なしきい値を求めます
unsigned char th = mist::discriminant_analysis::threshold( image2 );
//最適なしきい値でインスタンス化した関数オブジェクトを各画素に適用します
std::for_each( image2.begin(), image2.end(), func_2( th ) );
//両方の結果を書き出して,比べてみましょう
mist::write_bmp( image1, "out1.bmp" );
mist::write_bmp( image2, "out2.bmp" );
}