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 を意識しなければならないし,座標も自分たちで管理しなければなりません.やはり,このあたりは手を抜きたいところですね.