目次
  1. 概要
  2. コンストラクタ
  3. シングルプレーンブルーノイズマスク作成
  4. マルチプレーンブルーノイズマスク作成
  5. ユーティリティ関数その1
    インスタンスデータの保存と読み込み
  6. ユーティリティ関数その2
    組織的ディザ法の実行
  7. ユーティリティ関数その3
    誤差拡散法の実行
  8. マトリクス作成高速化
    高速版の実行

概要

Ulichneyの論文"The void-and-cluster method for dither array generation"に基づいたblue noise法による 組織的ディザ(ordered dither)のためのマトリクス生成クラス

アルゴリズムの流れは以下の通り。
  1. 最初にwhite noiseパターンを生成する。
  2. void-and-cluster法によってwhite noiseパターンをblue noiseパターンに変換する
    以下のような処理を繰り返す。256×256くらいになると10分単位の時間がかかる重い処理である。
    ローパスフィルタには、ガウスフィルタを使っている。
    1. 適当なローパスフィルタによって低周波成分の循環畳み込みを得る
      「循環」でないと、境界部分がおかしくなる。循環畳み込みには、fft→ifftという処理が一般的であるが、 当ライブラリでは普通の積分処理で済ましている。2回目以降の循環畳み込みに高速アルゴリズムを使っているので問題ない。
    2. 低周波成分の最大値にある"1"=clusterの座標を取得して除く
    3. 反転画像を元に適当なローパスフィルタによって低周波成分の循環畳み込みを得る
    4. 低周波成分の最大値にある"0"=voidの座標を取得して"1"に変える
    5. 変えられるcluster/void対がなくなるまで繰り返す
  3. blue noiseパターンから組織的ディザ用のマトリクスを生成する
    void-and-cluster法以上の時間がかかる重い処理である。
大きなマトリクスだと処理時間が長いので、結果をセーブ/ロードするユーティリティ関数が提供される。
また、便利のために実際にディザ法を実行するデバッグ用の関数が提供される。

コンストラクタ

引数なし。
CBNMask* pbn = new CBNMask();       // コンストラクタ
.....
delete pbn;                         // ディストラクタ

シングルプレーンブルーノイズマスク作成

makebluenoiseメソッドでwidth×heightのブルーノイズマトリクスを作成する
// ブルーノイズマトリクスを作成する
// 入力
//  int     width;      マスク幅(単位ピクセル)
//  int     height;     マスク高さ(単位ピクセル)
//  int     level;      元画像の1プレーンの階調数(デフォルト値256)
//  int     ws;         ガウスフィルタウィンドウサイズ(デフォルト値7(7x7のウィンドウ))
//  double  sigma;      ガウスフィルタの標準偏差(デフォルト値1.5)
// 戻り値
//  0....正常終了
//  負...エラー(errcode.hに記述、MEMORY_SHORTAGE(-1000)エラーのみ)
int         makebluenoisematrix(int width,int height,int level = 256,int ws = 7,double sigma = 1.5);
マトリクスはsizeof(unsigned int) * mwidth * mheightのサイズのバッファとして返す。
アクセサによって、列数と行数(makebluenoisematrixの引数に相当)とバッファの先頭アドレスが取得できる。
マトリクスの使い方については
組織的ディザ法の実行を参照のこと。
// アクセサ
int             mgetwidth() {return mwidth;}
int             mgetheight() {return mheight;}
unsigned char*	mgetpmask(int no = 0)
{
    if(mcomponentnum == 1)
        return mpmask;
    else
        return mppchild[no]->mgetpmask(0);
}   // mwidth×mheightのマスクを返す
    // ordered dither時には不要、デバッグ・テスト用のアクセサ
unsigned int*	mgetpmatrix(int no = 0)
{
    if(mcomponentnum == 1)
        return mpmatrix;
    else
        return mppchild[no]->mgetpmatrix(0);
}       // ordered dither用のマトリクスを返す

マルチプレーンブルーノイズマスク作成

シングルプレーンのブルーノイズマスクを元に、マルチプレーンのブルーノイズマスクを作成する。 したがって、makebluenoisematrix後にコールする。
全てシングルプレーンと同じパラメータを使う。マルチプレーンのブルーノイズマスクを作成する ブルーノイズマトリクスが十分な大きさを持つことが必要(64×64以上が望ましい)。
処理時間は、makebluenoisematrix()×プレーン数と同等。プレーン数は最大8まで可能である。
9以上だと、余地がないために各プレーンのブルーノイズマスクが重ならないようにするのは 不可能となる。どうしても9プレーン以上使う場合、ある程度重なるのを承知で別のインスタ ンス(ホワイトノイズの乱数のシードが異なっているもの)を作るほかはない。
// 入力
//  int      componentnum;     プレーン数(RGBなら3、CMYKなら4。最大9まで可能。
//                             プレーン数が多い場合は128×128や256×256で実行する方が品質が向上する)
// 戻り値
//  0....正常終了
//	負...エラー
int				mseparatebluenoisematrix(int componentnum);

インスタンスデータの保存と読み込み

makebluenoisematrixの後に、インスタンスデータを保存することができる。インスタンス作成後に mloadbnmを呼び出せば、makebluenoisematrix直後の状態に復帰できる。
大きいブルーノイズマトリクスの場合、マトリクス作成処理に長時間 (256×256で約1時間(Pentium-M/1.6GHz))かかるので保存したほうが良い。
mclearwork()を実行すると、マトリクス以外のワーク領域が全て解放され、使用メモリが最小限の 状態でディザ法を実行することができる。

// 途中経過save/load
// 入力
//  char*       filename;   ファイル名
int             msavebnm(char* filename);
int             mloadbnm(char* filename);
void			mclearwork();
サンプルプログラム
// 256×256マトリクスの作成
CBNMask* pbn = new CBNMask();       // コンストラクタ
int i1 = pbn->makebluenoisematrix(256,256);
if(i1 < 0) {
    delete pbn;
    エラー処理;
}
i1 = pbn->msavebnm("c:\tmp\256x256.bnm");
if(i1 < 0) {
    delete pbn;
    エラー処理;
}
delete pbn;                         // ディストラクタ
// あとで利用
CBNMask* pbn = new CBNMask();       // コンストラクタ
int i1 = pbn->mloadbnm("c:\\tmp\\256x256.bnm");
if(i1 < 0) {
    delete pbn;
    エラー処理;
}
.....
delete pbn;                         // ディストラクタ

組織的ディザ法の実行

デバッグ用ユーティリティ。
// 1...組織的ディザユーティリティ
// 生成したマトリクスを使ってordered ditherを実行する
//     (通常は、アプリケーション側でマトリクスを取得して、diteringを実行する)
// 入力
// 画像は、1プレーン分の画像(256階調画像)
//  int             no;             使用するマスクのプレーン番号(0~プレーン数-1)を指定
//                                  同じプレーン番号を指定するとdot-on-dotのブルーノイズマトリクス処理となる
//                                  RGBの場合、Rを0、Gを1、Bを2とは限らず好きな組み合わせでよい。
//  int             width;          画像の幅(単位ピクセル)
//  int             height;         画像の高さ(単位ピクセル)
//  int             scanlinesize1;  画像の1ラインのバイト数
//  unsigned char*  pdata1;         256階調画像
//  int             scanlinesize2;  出力2/4階調画像の1ラインのバイト数
// 出力
//  unsigned char*  pdata2;         2/4階調画像
//                                  2階調では(0/255)、4階調では(0/85/170/255)で返す。
//                                  ライブラリは、1プレーン256階調に限定していないが、これはデバッグ用なので手抜き
void            mordereddither2(int no,int width,int height,int scanlinesize1,unsigned char* pdata1,
                        int scanlinesize2,unsigned char* pdata2);
void            mordereddither4(int no,int width,int height,int scanlinesize1,unsigned char* pdata1,
                        int scanlinesize2,unsigned char* pdata2);
// 2...組織的ディザユーティリティ
//     1行単位で処理するバージョン
// 入力
//   int            y;               行番号
//   その他の引数は、mordereddither2/mordereddither4に同じ
void			morderedditherline2(int no,int width,int y,unsigned char* pdata1,unsigned char* pdata2);
void			morderedditherline4(int no,int width,int y,unsigned char* pdata1,unsigned char* pdata2);
2階調の場合のソース
void CBNMask::mordereddither2(int no,int width,int height,int scanlinesize1,unsigned char* pdata1,
						int scanlinesize2,unsigned char* pdata2)
{
    if(mcomponentnum == 1) {
        int            total = mwidth * mheight;
        for(int i = 0 ; i < height ; i++) {
            unsigned char*  ps1 = pdata1 + scanlinesize1 * i;
            unsigned char*  ps2 = pdata2 + scanlinesize2 * i;
            int             y = i % mheight;
            unsigned int*   ps3 = mpmatrix + mwidth * y;
            for(int j = 0 ; j < width ; j++,ps1++,ps2++) {
                int            x = j % mwidth;
                // マトリクスの値以上(しきい値以上)
                if(ps3[x] <= *ps1) {
                    *ps2 = 255;
                }
                else {
                    *ps2 = 0;
                }
            }
        }
    }
    else {
        mppchild[no]->mordereddither2(0,width,height,scanlinesize1,pdata1,scanlinesize2,pdata2);
    }
}

誤差拡散法の実行

画質や速度を比較するための誤差拡散プログラム
// 品質、速度比較用の誤差拡散プログラム
// Floyd & Steinberg方式
// 入力
// 画像は、1プレーン分の画像(256階調画像専用)
// RGBの場合、各プレーンに対して処理を3回行なう
//  int             width;          画像の幅(単位ピクセル)
//  int             height;         画像の高さ(単位ピクセル)
//  int             scanlinesize1;  画像の1ラインのバイト数
//  unsigned char*  pdata1;         256階調画像
//  float*          pdata2;         計算用のワークエリア sizeof(float) * width * heightバイト必要
// 出力
//  unsigned char*  pdata3;         2/4階調画像
void            mfloydsteinberg2(int width,int height,int scanlinesize1,unsigned char* pdata1,float* pdata2,unsigned char* pdata3);
void            mfloydsteinberg4(int width,int height,int scanlinesize1,unsigned char* pdata1,float* pdata2,unsigned char* pdata3);

高速版の実行

マトリクス作成を高速化した高速版を実行するには、基本的には以下の点を変えるだけである。
CBNMask* pbn = new CBNMask();       // コンストラクタ
↓
CBNMaskFast* pbn = new CBNMaskFast();       // コンストラクタ
高速化版は、従来のものと比較して、10倍から1000倍の速度でマトリクスを作成する。速度向上比率は マトリクスサイズが大きくなるほど顕著になる。
作成可能な最大マトリクスサイズは、
  • 8プレーンの場合、12Gバイトメモリのマシンで8192×8192程度。 2^26
  • 4プレーンの場合、8Gバイトメモリのマシンで8192×8192程度。 2^26
  • グレイスケールの場合、6Gバイトメモリのマシンで8192×8192程度。 2^26
  • グレイスケールの場合、16Gバイトメモリのマシンで16384×16384程度。2^28
  • 3プレーンの場合、24Gバイトメモリのマシンで16384×16384程度。
マトリクスサイズは別に2のべき乗である必要はないので、適宜搭載メモリ量にあわせてマトリクスサイズを調節すると よい。また、高速化版でも8192×8192の1プレーンのマトリクス作成では数時間を要するので覚悟が必要となる。
複数プレーンのマトリクス作成のマルチスレッド化は、各プレーンにおいて異なるワーク領域を取得する余裕が 無いので今回は見送った。

マトリクスを保存するには、1要素あたり4バイトの領域が必要となる。乱数ではないが規則性の小さい数列なのでファイル圧縮の効果は小さい。
8192×8192のマトリクスのファイルサイズは256Mバイト。16384×16384のマトリクスのファイルサイズは1Gバイトとなる。

高速化処理のために膨大なメモリを必要とするため、プレーン分割をする場合は、makebluenoisematrix API後、 mseparatebluenoisematrix API前に、mclearwork APIを使って結果以外のワーク領域を解放していくと多少余裕が 生まれる。内部でもワーク領域は適宜解放、取得を繰り返して可能な限りメモリ使用量を減らしている。