All-About調査室 Annex

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

Jpeg保存dllのバグ修正 <長いものは、やはり長い>

2018-05-14 21:51:13 | 4:4:4 Jpeg保存dll作成

「長いものには巻かれろ」
という処世訓は好きではない。
しかし、結論から言って、
今度ばかりは巻かれてしまおう。
(他にテがないし…)

さて、どんな問題が見つかったのかから始めよう。

前々回、作ったJpeg保存dllの処理時間を調べた時に
10000 x 10000 pix
BMP画像を作成した。
この画像は、約80の写真をコラージュして作ったもので
風景やポートレートなど様々なシーンが入っている。
しかも、高周波成分を増やすためにニアレスト・ネイバーでの
縮小を施したりして…それなりに手が込んでいる。

で、処理時間計測だけではもったいないので
Q
100 (量子化係数ALL 1) にてJpeg保存した画像と
オリジナル画像との各ピクセルRGB値の差異を調べて見た。

方法は、Jpeg保存dllを使う」で作ったVB.netプログラムで
10000 x 10000 pix
BMP画像をQ100Jpeg保存。
コレをPhotoShopで開き、BMP形式で保存しなおす。
(コレで再生表示通りのRGB値が確保される)
この再保存したBMP画像とオリジナルのBMP
バイナリ形式でExcel VBAでデータアクセスし、
対応する画素のRGB値の差分をVBAで調べ、
差分の値ごとに数をカウントしてワークシートに書き出すもの。

…と、結果はこうなった。



グラフにしてみると、ピークが差異±0の1点に集中せず、
怪しい結果になった。 /(-_-;

ちなみに同じ10000 x 10000 pixのBMP画像をGIMPを使い
Q値100でJpeg保存し、同様にフォトショでBMP変換して
Excel VBAでRGB差異をカウントすると
結果はこうなった。
(GIMPでのJpeg保存は整数演算指定にて)



ついでにGIMPでのJpeg保存で浮動小数演算指定にすると
結果はこうなった。



(整数演算よりちょっと良い程度で、大差無い)

いずれにしても、コチラは差異±0の1点にピークを持つ。

原因に心当たりはあった。
RGBからYCbCrへの変換式である。

詳細はコチラで書いたが…
JPEG File Interchange Format Version 1.02という文書には
RGB to YCbCr Conversionに
YCbCr (256 levels) can be computed directly from 8-bit RGB as follows:として

Y  = 0.299   R + 0.587  G + 0.114  B
Cb =-0.16874 R - 0.3313 G + 0.5    B + 128
Cr = 0.5     R - 0.4187 G - 0.0813 B + 128

とある。
この式で最大・最小条件を考えると
YCbCrを「小数部切捨てで整数化」するならば
YCbCrの値域は全て0~255で揃うのだが、
YCbCrを「小数のまま整数化しない」のならば
値域はYが0~255、CbCrが0.5~255.5とズレが出る。

今回のdllでは整数演算ではあるものの
YCbCrは1024倍して実質的に小数部をフォローしている。
すると上記の0.5のズレがキモチ悪いので
「+128」の部分を「+127.5」として値域を揃える方針とし、
さらに計算上の有効ケタの一層の確保と、
先頭処理ブロックのDC係数の差分処理の合理化を狙って
今回のdllでは以下の変換式とした。
(実際のプログラムでは係数を1024倍している)

Y  = 0.299   R + 0.587   G + 0.114   B - 127.5
Cb =-0.16874 R - 0.33126 G + 0.5     B
Cr = 0.5     R - 0.41869 G - 0.08131 B

値域はYCbCrとも -127.5 ~ 127.5

結果的にはコレがマズかった!
世間様では0.5ズレがキモチ悪かろうが関係なく、
128で変換していたのだ!

で、どこを直すのかと言うと、
RGB2YBR()ファンクションの以下の青文字部分

/****
Convert RGB to YCbCr
****/
void RGB2YBR
    (unsigned char Inp[], long YBR[][8][3],
     long *DST, long *Num, long *p, long *q){ // x1024

    int x, y;
    long N;
    long S[3] = { 0, 0, 0 };

    for (y = 0; y<*q; y++){
        for (x = 0; x < *p; x++){
            N = *Num + *DST * y + 3 * x;
            YBR[x][y][0] =  306 * Inp[N+2] +601 * Inp[N+1] +117 * Inp[N] -130560; // Y
            YBR[x][y][1] = -173 * Inp[N+2] -339 * Inp[N+1] +512 * Inp[N];         // Cb
            YBR[x][y][2] =  512 * Inp[N+2] -429 * Inp[N+1] - 83 * Inp[N];         // Cr
<以下省略>

以下のように直す。

/****
Convert RGB to YCbCr
****/
void RGB2YBR
    (unsigned char Inp[], long YBR[][8][3],
     long *DST, long *Num, long *p, long *q){ // x1024

    int x, y;
    long N;
    long S[3] = { 0, 0, 0 };

    for (y = 0; y<*q; y++){
        for (x = 0; x < *p; x++){
            N = *Num + *DST * y + 3 * x;
            YBR[x][y][0] =  306 * Inp[N+2] +601 * Inp[N+1] +117 * Inp[N] -131072; // Y
            YBR[x][y][1] = -173 * Inp[N+2] -339 * Inp[N+1] +512 * Inp[N];         // Cb
            YBR[x][y][2] =  512 * Inp[N+2] -429 * Inp[N+1] - 83 * Inp[N];         // Cr
<以下省略>

Yに-128せず、CbCrに+128する変換式を用いる場合には、
先頭処理ブロックのDC係数の差分処理では1024(=128x8)との差分を取る。
(小数部フォローのカサ上げの係数も見直さないとオーバーフローするよ)

で、上記の修正を行って
(モチロン、オーバーフロー可能性やカサ上げ係数の妥当性もチェックしたよ)
再びQ値100 でJpeg保存した画像と
オリジナル画像との各ピクセルRGB値の差異を調べて見たら
今度はちゃんと差異±0の1点にピークを持つようになった。



ただ、個人的にはやはり0.5ズレはキモチ悪く、
「小数部フォローでやるなら、やっぱ127.5じゃないの?」
という思いは残っている。
しかし、できるだけオリジナル通りに表示できてなんぼなので
世間様が128で出来上がっている以上、
長いものには巻かれるってコトなのだ。。。

(たぶんコレで本当に)
4:4:4 Jpeg
保存dll作成シリーズ終了



最新の画像もっと見る

コメントを投稿

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