「長いものには巻かれろ」
という処世訓は好きではない。
しかし、結論から言って、
今度ばかりは巻かれてしまおう。
(他にテがないし…)
さて、どんな問題が見つかったのかから始めよう。
前々回、作ったJpeg保存dllの処理時間を調べた時に
10000 x 10000 pixのBMP画像を作成した。
この画像は、約80の写真をコラージュして作ったもので
風景やポートレートなど様々なシーンが入っている。
しかも、高周波成分を増やすためにニアレスト・ネイバーでの
縮小を施したりして…それなりに手が込んでいる。
で、処理時間計測だけではもったいないので
Q値100 (量子化係数ALL 1) にてJpeg保存した画像と
オリジナル画像との各ピクセルRGB値の差異を調べて見た。
方法は、「Jpeg保存dllを使う」で作ったVB.netプログラムで
10000 x 10000 pixのBMP画像をQ値100でJpeg保存。
コレを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作成シリーズ終了