グレースケール画像クラス MonoDouble の設計(Matlab)

Matlab を購入して自由に使えるようになったので、オブジェクト指向及びテストファーストプログラミングを始めてみました。すでに作成して動いているものですが、ちゃんとした設計仕様書を残していないので、せっかくなのでまとめ直してみます。

設計理由

8bpp以外の画像への対応
HDR の研究を始めてから、1pixel = 8bits という暗黙の了解がなくなり、いちいち画像のビット数を意識しなければならなくなりました。例えば、PSNR の計算をとっても、Peek 値が画像によって異なるので、計算のためにパラメータを渡さなければなりません。オブジェクトであれば、この属性は自分で保持できます。
ループ処理の記述簡略化
画像処理は左上から右下にシーケンシャルに処理する比較的単純なループを行うことが多いです。しかし、まともに記述するとループ変数を二つ自分で管理する必要があります。これもオブジェクトであれば保持できます。また、ブロック処理も多いので、それが簡単に書けるようにします。
1から始まる添字からの脱却
Matlab は伝統的に添字が1から始まります。他の言語は 0 からのものが多いので結構混乱し、バグの温床になっていることが多いです。オブジェクトにしてしまえば、内部データへのアクセス時に違いを吸収することが可能になります。

設計指針

クラス名
MonoDoubleとします。必要があれば、MonoInteger などを作ろうかと思っています。近々、ColorDouble などを作る予定です。
プロパティ
buffer(データ本体), bits(ビット数), nowr(現在行数), nowc(現在列数), blockHeight(ブロックの高さ), blockWidth(ブロックの幅), block(ブロック)
オブジェクトの型
handle 型。メソッドは参照渡しとし、破壊的メソッドも用意します。Rubyのようにメソッド名に!が使えないので、代わりに_を末尾に書きます。例えば、c = a.add(b) は非破壊的メソッドですが、a.add_(b) は破壊的メソッドとなります。

動作イメージ

実際に使いにくいものを作っても仕方ないので、シミュレーション時に自分がどういうプログラムを書きたいかをイメージします。JPEG の DCT + 量子化あたりが簡単に書けるようにということで、イメージしてみます。符号化側では画像を8×8画素単位でDCTして量子化します。復号側では8×8画素単位で逆量子化しIDCTして復号画像を作ります。最後に原画像に対する復号画像PSNRを計算します。Matlab なら普通に書いても簡単ですが、さらに単純になるものを目指します。

lena = MonoDouble.imread('lena.pgm'); % 8bit ならファイル名のみ
% cafe = MonoDouble.imread('Cafe.tif', 16); % 8bit 以外はビット数を指定
q = MonoDouble(qmat); % 行列からオブジェクトを作成(qmat 行列はすでに用意されているとする)
qa = q.mulMat(alpha); % q を定数(alpha)倍した量子化行列を作成
lena.setBlockSize_(8, 8); % lena を 8×8 のブロック、移動量も8×8、初期位置 0,0 で初期化
dlena = lena.zeros.setBlockSize_(8, 8);
% lena と同じ大きさの零行列をもつ画像を作成し、ブロック初期化
% 符号化側の処理
cond = true; while (cond)
    x = lena.getBlock_.dct_.div_(qa).round_;
    % lena からブロックを取得し、2次元DCT したのち、量子化行列で、割り算し、丸めて整数化
    dlena.setBlock_(x); 計算したブロックを dlena の該当部にセット
    cond = (lena.next_ * dlena.next_ == 1);
    % ブロック位置を次に移動する。次があれば 1、なければ 0 を返すので、それを条件としてループする
end
% 復号側の処理
dlena.setBlock_(8 , 8); % dlena を 8×8 のブロック、移動量も8×8、初期位置 0,0 で再初期化
olena = lena.zeros.setBlockSize_(8, 8);
% lena と同じ大きさの零行列をもつ画像を作成し、ブロック初期化
cond = true; while (cond)
    x = dlena.getBlock_.mul_(qa).idct_.round_;
    % dlena からブロックを取得し、逆量子化、2次元IDCT したのち、丸めて整数化
    olena.setBlock_(x); 計算したブロックを olena の該当部にセット
    cond = (olena.next_ * dlena.next_ == 1);
    % ブロック位置を次に移動する。次があれば 1、なければ 0 を返すので、それを条件としてループする
end
lena.printPSNR(olena, 'olena');
% 量子化の影響を PSNR で確認。lena が 8bpp なので、peak は 255 として自動で計算される

こうみると、もはや Matlab のコードに見えないですね。これが動くように MonoDouble クラスをテストファーストで実装していきます。

7/9 追記

記述が簡単になっているかがわかりにくいので,普通に Matlab で記述してみた.

lena = double(imread('lena.pgm'));
% qmat 行列はすでに用意されているとする
qa = qmat * alpha; % q を定数(alpha)倍した量子化行列を作成
% 符号化側の処理
[sy, sx] = size(lena);
dlena = zeros(sy, sx);
for r = 1:8:sy
    for c = 1:8:sx
        x = lena(r:r+7, c:c+7);
        dlena(r:r+7, c:c+7) = round(dct(x) ./ qa);
    end
end
% 復号側の処理
olena = zeros(sy, sx);
for r = 1:8:sy
    for c = 1:8:sx
        x = dlena(r:r+7, c:c+7);
        olena(r:r+7, c:c+7) = round(idct(x .* qa));
    end
end
printPSNR(lena, olena, 'olena');

matlab ならこれでも簡単なんだけど,余計な変数 r, c, sy, sx を意識しなければならないし,座標も自分たちで管理しなければなりません.やはり,このあたりは手を抜きたいところですね.