All-About調査室 Annex

ふと湧いた疑問や巷を漂うウワサを全部アバウト~に調査・検証
<OCNから漂着 流浪の調査室>

量子化とケタ下げの完成版

2018-03-03 13:13:16 | 4:4:4 Jpeg保存dll作成

前回、「直前の処理ブロックのDC係数との差分を量子化」の
コトバ通りにDC係数の量子化を行うコードを作り、
コレを低Q値指定で実行するとストライプ状のノイズが乗る
という問題が出ることを紹介した。

この問題に気づいた時点ではDC係数の量子化手法に
原因があるとはわかっていなかったが、
状況から見てDC成分に関連する処理が怪しいとは思っていたので
DC成分に関して、DCT・ZigzagScan・量子化・ケタ下げ・Huffmanコード化
とデータ値の変化を追跡したが、全て想定通りに動いていた。

こうなると厄介である。
想定通りに動いていない部分を見つけて修正するのは
メンドクサイなりにも機械的作業で簡単だが、
想定通りに動いているにもかかわらず、結果が正常でないと言うことは
「想定」自体に誤りがあると言うことであり、
根本から処理動作を考え直さなければならないからだ。

…で、途方にくれること数週間…
ふと、「再生表示する側はどう読んでストライプにしてるんだろう?」
と思ったのをきっかけにようやく原因と対処法にたどり着いた。

どういう事かと言うと…

それぞれn番目のブロック要素に関して、
DCTで得られた本来のDC成分値をOrgn
コレを量子化係数Qで量子化し、
Jpegファイルに書き込まれたDC成分値をFdcn
コレをファイルから読み出してDC成分値として逆量子化し
逆DCT処理にかけるデータ値をDatnと書けば、
読み出し先頭ブロックのDCデータ値は



ここでkYCbCrの値域を0255で扱うRGB/YCbCr変換を
用いる再生表示プログラムならばk=1020であり、
Y
CbCrの値域を-127.5127.5で扱うものならk=0である。
以下では(kをフォローして記述するのはメンドクサイので)
dll同様にYCbCr値域を-127.5127.5で扱う再生表示プログラム
を前提としてk=0でハナシを進める。
(実際の再生表示プログラムがどっちを使っていても最終的には同じ)
従って先の式は




さて、①式が与える値と、本来のDC成分値Org1とを比べると
量子化処理に伴う(小数を整数値に丸めたことによる)誤差δがある。



量子化による誤差なのでδの大きさは±Q/2の範囲である。
これを用いて①式は



ここまでは良い。問題は次だ。
2番目のブロックのDCデータ値Dat2
Fdc2 x QがDat1とDat2の差分を与えているので



先と同様にFdc2 x Qと本来の値との誤差δについて考える。
前回の処理コードではDCT後のDC成分値同士で差分を取った。
つまり本来の値はOrg2-Org1として量子化・ファイルデータ化したので



なので②は



さらに①を適用して



同様に次々考えて行くと、結局



というコトになり、
nブロック目の読み出しDC成分値は本来のn番目の値に
そこまでに発生した全ての量子化誤差を作用させたものになる。
だから、nに至る途中に粗い量子化のために
誤差の大きなブロックが存在すると、以降その誤差影響が尾を引き、
再生表示画像上にストライプ状のノイズが現れるワケだ。
(左端部分ではストライプノイズが発生しにくいのも
本dllは画面左端でリスタートマーカを入れてリセットするので納得)

じゃぁ…どーすれば良い?

で、DCT後のDC成分値同士で差分を取っていたのを改める。
今度はOrg2-Org1で差分として量子化していたのをやめて
Org1をQで量子化し、コレに再びQを掛けた値を一旦作る。
この値はすなわちFdc1 x Qであり、①よりDat1である。
この値とOrg2との差分を取って量子化することに改める。

すると量子化誤差を求めるA式は



これを用いて②式は



となる。
同様に次々考えて行けば、



となって、量子化誤差は常に当該ブロックの分だけになり
誤差の累積は解消される。

てなことで、上記のハナシの通りにコードを書くと…

/*****
Quantize & Round lower
*****/
void Quantize
    (long Zig[][3], long DC[]){

    int k, n;
    long Temp;

    // ***** for Y *****
    Temp = ((Zig[0][0] / JPHead[25]) + 524288) >> 20;
    Zig[0][0] = (((Zig[0][0] - DC[0]) / JPHead[25]) + 524288) >> 20;
    DC[0] = (Temp * JPHead[25]) << 20; // Set Previous DC
    for (n = 1; n < 64; n++){
        Zig[n][0] = ((Zig[n][0] / JPHead[25 + n]) + 1048576) >> 21;
    }

    // ***** for Cb,Cr *****
    for (k = 1; k < 3; k++){
        Temp = ((Zig[0][k] / JPHead[94]) + 524288) >> 20;
        Zig[0][k] = (((Zig[0][k] - DC[k]) / JPHead[94]) + 524288) >> 20;
        DC[k] = (Temp * JPHead[94]) << 20; // Set Previous DC
        for (n = 1; n < 64; n++){
            Zig[n][k] = ((Zig[n][k] / JPHead[94 + n]) + 1048576) >> 21;
        }
    }
}

なんだけど、ちょっと長くてクドイので…
ちょいとヒネって以下をSub Functionsセクションに書き込む

/*****
Quantize & Round lower
*****/
void Quantize
    (long Zig[][3], long DC[]){

    int k, n;

    // ***** for Y *****
    Zig[0][0] = (((Zig[0][0] - DC[0]) / JPHead[25]) + 524288) >> 20;
    DC[0] += (Zig[0][0] * JPHead[25]) << 20; // Set Previous DC
    for (n = 1; n < 64; n++){
        Zig[n][0] = ((Zig[n][0] / JPHead[25 + n]) + 1048576) >> 21;
    }

    // ***** for Cb,Cr *****
    for (k = 1; k < 3; k++){
        Zig[0][k] = (((Zig[0][k] - DC[k]) / JPHead[94]) + 524288) >> 20;
        DC[k] += (Zig[0][k] * JPHead[94]) << 20; // Set Previous DC
        for (n = 1; n < 64; n++){
            Zig[n][k] = ((Zig[n][k] / JPHead[94 + n]) + 1048576) >> 21;
        }
    }
}

で、このコードに修正して前回と同じ画像を
ストライプ状ノイズの発生したQ=65でJpeg保存すると



ストライプは解消した。

結論:
DC係数の量子化処理は量子化誤差が累積するので
「直前の処理ブロックのDC係数との差分を量子化」
してはいけない。

正しくは、
「直前の処理ブロックのDC係数を量子化し、さらにこれを逆量子化した値と
当該ブロックのDC係数値との差分を取って量子化する」
としなければならない。

教訓:
Webの情報はヘタに信用すると泣きを見る

- つづく -



最新の画像もっと見る

コメントを投稿

ブログ作成者から承認されるまでコメントは反映されません。