RAW画像はなぜ必要か
一般に、デジカメで写真を撮ると、
1. レンズで光を集め
2. 撮像素子で光の量を電気信号に換え
3. 画像処理LSIで画像ファイルに変換する
という工程をカメラで行い、画像ファイルとして保存される。
即ち、良い写真を得るには、
1. 良いレンズを買い
2. 良い撮像素子の載ったカメラを買い
3. 良い画像処理を行う
ことが必要となる *0 。
では、この画像処理、いわゆるデジタル現像は、なぜ必要か。
まず、撮像素子は以下の特性を持つ。
1. 画素の配列やカラーフィルターなどの特性が各社異なる。(普通のセンサーは単色しか取れないため、ベイヤ配列等によってフルカラーを得る。3板式や単板でフルカラーを撮るものもあるが、コストの関係で少数派)
2. ダイナミックレンジが12bitから14bit程度ある
3. 1画素あたりのノイズが大きい
さらに、人間は勝手に様々な補正を脳内で行っているため、センサーの値をそのまま出すと人にとっては不自然な画像となる。
このため、JPEG等の画像ファイルへ変換するためには、
1. 通常の画素配列への変換
2. 明るさやトーンカーブの調整
3. ノイズリダクション
4. 環境に応じた色の補正
等が必要となる(さらに言えば、レンズの特性の補正などもここでやる。最近は画像処理LSIでデジタル補正をする前提で光学系の歪みを許容して設計の自由度を上げることも多い)。
撮像素子の生の情報を丸々残しておけば、この現像処理をカメラのその場の判断でやるのではなく、後からPCを使って気の済むまでできる。そのために、撮像素子の生の情報を保存したものがRAW画像と呼ばれる。
JPEGでもある程度は補正は可能だが、既に階調が落ちてダイナミックレンジ(DR)が圧縮されているため、そこからさらに変換を行うと画質の劣化が目立つ。特に黒潰れや白飛びした場合、JPEGの時点で情報がほぼ喪失しており補正しようが無い。
以上から、後から補正する可能性があるならRAWで保存するべきである。
1. レンズで光を集め
2. 撮像素子で光の量を電気信号に換え
3. 画像処理LSIで画像ファイルに変換する
という工程をカメラで行い、画像ファイルとして保存される。
即ち、良い写真を得るには、
1. 良いレンズを買い
2. 良い撮像素子の載ったカメラを買い
3. 良い画像処理を行う
ことが必要となる *0 。
では、この画像処理、いわゆるデジタル現像は、なぜ必要か。
まず、撮像素子は以下の特性を持つ。
1. 画素の配列やカラーフィルターなどの特性が各社異なる。(普通のセンサーは単色しか取れないため、ベイヤ配列等によってフルカラーを得る。3板式や単板でフルカラーを撮るものもあるが、コストの関係で少数派)
2. ダイナミックレンジが12bitから14bit程度ある
3. 1画素あたりのノイズが大きい
さらに、人間は勝手に様々な補正を脳内で行っているため、センサーの値をそのまま出すと人にとっては不自然な画像となる。
このため、JPEG等の画像ファイルへ変換するためには、
1. 通常の画素配列への変換
2. 明るさやトーンカーブの調整
3. ノイズリダクション
4. 環境に応じた色の補正
等が必要となる(さらに言えば、レンズの特性の補正などもここでやる。最近は画像処理LSIでデジタル補正をする前提で光学系の歪みを許容して設計の自由度を上げることも多い)。
撮像素子の生の情報を丸々残しておけば、この現像処理をカメラのその場の判断でやるのではなく、後からPCを使って気の済むまでできる。そのために、撮像素子の生の情報を保存したものがRAW画像と呼ばれる。
JPEGでもある程度は補正は可能だが、既に階調が落ちてダイナミックレンジ(DR)が圧縮されているため、そこからさらに変換を行うと画質の劣化が目立つ。特に黒潰れや白飛びした場合、JPEGの時点で情報がほぼ喪失しており補正しようが無い。
以上から、後から補正する可能性があるならRAWで保存するべきである。
RAWの圧縮形式
さて、そのRAW画像形式は標準化されておらず、メーカーによってバラバラの保存形式を持っている上に、非公開となっている。これは、各社撮像素子のカラーフィルターの配列や素子の特性、画質調整の設定等のため、標準化が難しいということと、企業秘密やノウハウの露出の恐れがあるためと思われる *1 。一応、AdobeがDNGというRAWの標準形式を提唱してはいるが、カメラメーカーからは無視されている(気がする)。
では、その保存形式が全く分らないかと言えば、実は既に各社のRAW画像ファイルをデコードする神懸った dcraw というオープンソースが存在する。どうやってdcrawを作ったのか不思議だけど、これを読めば保存形式が分かる。dcrawが完全にメーカーの意図通りの復元ができる保証はないが(特にレンズ特性の補正が前提のカメラの場合)、少なくとも大まかな構造は把握できる。
ということで、その中から幾つかの画像フォーマットを紹介する。ただ、残念ながらNEX-5しか実機がないため、それ以外の機種についてはソースコード上のみの理解で確認は取っていない。読み違いや勘違いがあるかもしれないけど悪しからず(間違いを教えていただけると助かります)。
では、その保存形式が全く分らないかと言えば、実は既に各社のRAW画像ファイルをデコードする神懸った dcraw というオープンソースが存在する。どうやってdcrawを作ったのか不思議だけど、これを読めば保存形式が分かる。dcrawが完全にメーカーの意図通りの復元ができる保証はないが(特にレンズ特性の補正が前提のカメラの場合)、少なくとも大まかな構造は把握できる。
ということで、その中から幾つかの画像フォーマットを紹介する。ただ、残念ながらNEX-5しか実機がないため、それ以外の機種についてはソースコード上のみの理解で確認は取っていない。読み違いや勘違いがあるかもしれないけど悪しからず(間違いを教えていただけると助かります)。
SONY
拡張子はARWだが、内部的に大きくARW1とARW2の形式がある。ARW1はα100専用で、最近は全てARW2のようだ。少なくとも自分の持っているNEX-5ではARW2.2。
ARW1
(おそらく)可逆圧縮。RGB各12bit。
隣り合う画素の差分をハフマン符号化し、保存しているだけのようだ。
2次元画像であることを考慮していないので、左端の画素はその前のカラムの右端との差分となっている。よく言えばシンプル、悪く言えば工夫の無い投げやりな印象を受ける。PNGやLossless Jpegのようにもう少しまともに画素の予想を行えば、圧縮率は上がるはずだが、すぐにARW2に切り替わったところをみると、α100のためだけの使い捨て規格だったと思われる。
なお、差分をハフマン符号化するのは、これ以降述べる殆どの形式が採用している基本中の基本の戦略。
- 1void CLASS sony_arw_load_raw()
- 2{
- 3 ushort huff[32768];
- 4 static const ushort tab[18] =
- 5 { 0xf11,0xf10,0xe0f,0xd0e,0xc0d,0xb0c,0xa0b,0x90a,0x809,
- 6 0x708,0x607,0x506,0x405,0x304,0x303,0x300,0x202,0x201 };
- 7 int i, c, n, col, row, len, diff, sum=0;
- 8
- 9 for (n=i=0; i < 18; i++)
- 10 FORC(32768 >> (tab[i] >> 8)) huff[n++] = tab[i];
- 11 getbits(-1);
- 12 for (col = raw_width; col--; )
- 13 for (row=0; row < raw_height+1; row+=2) {
- 14 if (row == raw_height) row = 1;
- 15 len = getbithuff(15,huff);
- 16 diff = getbits(len);
- 17 if ((diff & (1 << (len-1))) == 0)
- 18 diff -= (1 << len) - 1;
- 19 if ((sum += diff) >> 12) derror();
- 20 if (row < height) RAW(row,col) = sum;
- 21 }
- 22}
隣り合う画素の差分をハフマン符号化し、保存しているだけのようだ。
2次元画像であることを考慮していないので、左端の画素はその前のカラムの右端との差分となっている。よく言えばシンプル、悪く言えば工夫の無い投げやりな印象を受ける。PNGやLossless Jpegのようにもう少しまともに画素の予想を行えば、圧縮率は上がるはずだが、すぐにARW2に切り替わったところをみると、α100のためだけの使い捨て規格だったと思われる。
なお、差分をハフマン符号化するのは、これ以降述べる殆どの形式が採用している基本中の基本の戦略。
ARW2.x
非可逆圧縮。RGB各12bit。
気を取り直して簡単にアルゴリズムを説明すると、これは単色16画素値ごとに独立して圧縮を行う。16画素の中の最大と最小値を各11bitで記録する。その後、最大と最小を記録した画素の位置を各4bitで記録する。残りの14画素は最小値から最大値の間を7bitで表現するように符号化されている。本来なら、(画素値-最小値)*127/(最大値-最小値)を記録すべきだが、除算が発生しないように右シフトで済むように符号化されている。
ここまでは11bitの精度だが、最後に対数的なcurveを与えて12bitのダイナミックレンジを得ている。ただ、curveは画像ごとに変えれるため、機種や画像によってはもっと高いダイナミックレンジを持っている可能性もあるが、手元の画像では12bitであった。
結局、16画素を計128bitに圧縮するわけで、丁度画素当たり8bitになる。画質の面で言うと、ダイナミックレンジは保持されるが、ハイダイナミックレンジの場合ディティールが失われる恐れがある。あくまで16画素の中の話なので、通常は差が見えないだろうけど、RAWなのにこれで良いのかという疑問はある。
良い点としては、エンドユーザーとしてはどうでもいいが、このアルゴリズムは非常にハードウェア化に都合がよろしい。あと、圧縮後のサイズが固定のため、残り撮影枚数等の予測はし易い。
思わず近藤哲二郎先生を思い起こして、ドッキリ。心臓に悪い。
- 1void CLASS sony_arw2_load_raw()
- 2{
- 3 uchar *data, *dp;
- 4 ushort pix[16];
- 5 int row, col, val, max, min, imax, imin, sh, bit, i;
- 6
- 7 data = (uchar *) malloc (raw_width);
- 8 merror (data, "sony_arw2_load_raw()");
- 9 for (row=0; row < height; row++) {
- 10 fread (data, 1, raw_width, ifp);
- 11 for (dp=data, col=0; col < raw_width-30; dp+=16) {
- 12 max = 0x7ff & (val = sget4(dp));
- 13 min = 0x7ff & val >> 11;
- 14 imax = 0x0f & val >> 22;
- 15 imin = 0x0f & val >> 26;
- 16 for (sh=0; sh < 4 && 0x80 << sh <= max-min; sh++);
- 17 for (bit=30, i=0; i < 16; i++)
- 18 if (i == imax) pix[i] = max;
- 19 else if (i == imin) pix[i] = min;
- 20 else {
- 21 pix[i] = ((sget2(dp+(bit >> 3)) >> (bit & 7) & 0x7f) << sh) + min;
- 22 if (pix[i] > 0x7ff) pix[i] = 0x7ff;
- 23 bit += 7;
- 24 }
- 25 for (i=0; i < 16; i++, col+=2)
- 26 if (col < width) RAW(row,col) = curve[pix[i] << 1] >> 2;
- 27 col -= col & 1 ? 1:31;
- 28 }
- 29 }
- 30 free (data);
- 31}
気を取り直して簡単にアルゴリズムを説明すると、これは単色16画素値ごとに独立して圧縮を行う。16画素の中の最大と最小値を各11bitで記録する。その後、最大と最小を記録した画素の位置を各4bitで記録する。残りの14画素は最小値から最大値の間を7bitで表現するように符号化されている。本来なら、(画素値-最小値)*127/(最大値-最小値)を記録すべきだが、除算が発生しないように右シフトで済むように符号化されている。
ここまでは11bitの精度だが、最後に対数的なcurveを与えて12bitのダイナミックレンジを得ている。ただ、curveは画像ごとに変えれるため、機種や画像によってはもっと高いダイナミックレンジを持っている可能性もあるが、手元の画像では12bitであった。
結局、16画素を計128bitに圧縮するわけで、丁度画素当たり8bitになる。画質の面で言うと、ダイナミックレンジは保持されるが、ハイダイナミックレンジの場合ディティールが失われる恐れがある。あくまで16画素の中の話なので、通常は差が見えないだろうけど、RAWなのにこれで良いのかという疑問はある。
良い点としては、エンドユーザーとしてはどうでもいいが、このアルゴリズムは非常にハードウェア化に都合がよろしい。あと、圧縮後のサイズが固定のため、残り撮影枚数等の予測はし易い。
Nikon
(おそらく)可逆圧縮と、不可逆圧縮の2種類。RGB各12bitと14bitの2種類。
保存形式を可逆か非可逆か選べる仕組みを持つ。
可逆、非可逆が選べるがアルゴリズム自体はほぼ同じ。ARW1と同じで、隣接画素の差分をハフマン符号化している。ただし、左端の画素の予測には上の画素を使っている。また、非可逆圧縮の場合、差分が大きい場合単純に差分を記録するのではなく、浮動小数点で差を記録するようだ。これによると、非可逆RAWのサイズが一定(16MB?)を超えると、浮動小数点を使うように符合が切り替わると思われる(after splitとコメントにあるのがそれ)。
アルゴリズム上で、非可逆と可逆で違いがないが、非可逆の場合は予めノイズリダクション等を掛けて、隣接画素の差分を小さくして圧縮率を高めていると思われる。
あと、場合によってはトーンカーブが埋め込まれており、それによる補正もかけるようだ。それでさらに記録ビット数を減らすことがあるようだ(とはいえ、符号的には12bit lossyでも12bitの変化を記録可能な作りのようなので、その場合にこの符号では無駄があるのだけれど)。
保存形式を可逆か非可逆か選べる仕組みを持つ。
- 1void CLASS nikon_load_raw()
- 2{
- 3 static const uchar nikon_tree[][32] = {
- 4 { 0,1,5,1,1,1,1,1,1,2,0,0,0,0,0,0, /* 12-bit lossy */
- 5 5,4,3,6,2,7,1,0,8,9,11,10,12 },
- 6 { 0,1,5,1,1,1,1,1,1,2,0,0,0,0,0,0, /* 12-bit lossy after split */
- 7 0x39,0x5a,0x38,0x27,0x16,5,4,3,2,1,0,11,12,12 },
- 8 { 0,1,4,2,3,1,2,0,0,0,0,0,0,0,0,0, /* 12-bit lossless */
- 9 5,4,6,3,7,2,8,1,9,0,10,11,12 },
- 10 { 0,1,4,3,1,1,1,1,1,2,0,0,0,0,0,0, /* 14-bit lossy */
- 11 5,6,4,7,8,3,9,2,1,0,10,11,12,13,14 },
- 12 { 0,1,5,1,1,1,1,1,1,1,2,0,0,0,0,0, /* 14-bit lossy after split */
- 13 8,0x5c,0x4b,0x3a,0x29,7,6,5,4,3,2,1,0,13,14 },
- 14 { 0,1,4,2,2,3,1,2,0,0,0,0,0,0,0,0, /* 14-bit lossless */
- 15 7,6,8,5,9,4,10,3,11,12,2,0,1,13,14 } };
- 16 ushort *huff, ver0, ver1, vpred[2][2], hpred[2], csize;
- 17 int i, min, max, step=0, tree=0, split=0, row, col, len, shl, diff;
- 18
- 19 fseek (ifp, meta_offset, SEEK_SET);
- 20 ver0 = fgetc(ifp);
- 21 ver1 = fgetc(ifp);
- 22 if (ver0 == 0x49 || ver1 == 0x58)
- 23 fseek (ifp, 2110, SEEK_CUR);
- 24 if (ver0 == 0x46) tree = 2;
- 25 if (tiff_bps == 14) tree += 3;
- 26 read_shorts (vpred[0], 4);
- 27 max = 1 << tiff_bps & 0x7fff;
- 28 if ((csize = get2()) > 1)
- 29 step = max / (csize-1);
- 30 if (ver0 == 0x44 && ver1 == 0x20 && step > 0) {
- 31 for (i=0; i < csize; i++)
- 32 curve[i*step] = get2();
- 33 for (i=0; i < max; i++)
- 34 curve[i] = ( curve[i-i%step]*(step-i%step) +
- 35 curve[i-i%step+step]*(i%step) ) / step;
- 36 fseek (ifp, meta_offset+562, SEEK_SET);
- 37 split = get2();
- 38 } else if (ver0 != 0x46 && csize <= 0x4001)
- 39 read_shorts (curve, max=csize);
- 40 while (curve[max-2] == curve[max-1]) max--;
- 41 huff = make_decoder (nikon_tree[tree]);
- 42 fseek (ifp, data_offset, SEEK_SET);
- 43 getbits(-1);
- 44 for (min=row=0; row < height; row++) {
- 45 if (split && row == split) {
- 46 free (huff);
- 47 huff = make_decoder (nikon_tree[tree+1]);
- 48 max += (min = 16) << 1;
- 49 }
- 50 for (col=0; col < raw_width; col++) {
- 51 i = gethuff(huff);
- 52 len = i & 15;
- 53 shl = i >> 4;
- 54 diff = ((getbits(len-shl) << 1) + 1) << shl >> 1;
- 55 if ((diff & (1 << (len-1))) == 0)
- 56 diff -= (1 << len) - !shl;
- 57 if (col < 2) hpred[col] = vpred[row & 1][col] += diff;
- 58 else hpred[col & 1] += diff;
- 59 if ((ushort)(hpred[col & 1] + min) >= max) derror();
- 60 RAW(row,col) = curve[LIM((short)hpred[col & 1],0,0x3fff)];
- 61 }
- 62 }
- 63 free (huff);
- 64}
可逆、非可逆が選べるがアルゴリズム自体はほぼ同じ。ARW1と同じで、隣接画素の差分をハフマン符号化している。ただし、左端の画素の予測には上の画素を使っている。また、非可逆圧縮の場合、差分が大きい場合単純に差分を記録するのではなく、浮動小数点で差を記録するようだ。これによると、非可逆RAWのサイズが一定(16MB?)を超えると、浮動小数点を使うように符合が切り替わると思われる(after splitとコメントにあるのがそれ)。
アルゴリズム上で、非可逆と可逆で違いがないが、非可逆の場合は予めノイズリダクション等を掛けて、隣接画素の差分を小さくして圧縮率を高めていると思われる。
あと、場合によってはトーンカーブが埋め込まれており、それによる補正もかけるようだ。それでさらに記録ビット数を減らすことがあるようだ(とはいえ、符号的には12bit lossyでも12bitの変化を記録可能な作りのようなので、その場合にこの符号では無駄があるのだけれど)。
Cannon
保存形式を可逆か非可逆か選べる仕組みを持ち、大きく2つの形式に分かれる。
RAW
(公式には)可逆圧縮。RGB各10bitと12bitの記録方式がある。12bitの場合、下位2bitだけは無圧縮で記録。
ここで妙なのが、可逆のはずなのに64画素ブロックで処理を区切っていることと、ランレングスハフマンを使っていること。通常の写真においてランレングスが効く場面など頻繁にはないし、そもそもそれで良いのならば64画素毎に1度通常のハフマン符号を挟む必要は無いはず。非可逆ならば、誤差が蓄積されるのを防ぐために定期的に正しい値を入れるという意味で筋が通る。
と思って符号を良く見たら、ランレングスハフマン時に、ランレングス長が1だと-512~+511の差分までしか記録できないことが判明。非可逆圧縮だぞ、これ。つまり、残念ながら、画素毎に白黒白黒となる画は記録できない。現実にはそんな高性能なレンズはありえないので気にしなくても良いが。
これも基本的には、隣接画素の差分を取ってハフマン符号化。符号のテーブルは画像全体で3種類選べるようだ。面白いのは、ランレングスハフマンを使っているということ。符号の定義についてはcrw_init_tablesを参照。ただし、64画素ごとに1画素、通常のハフマン符号化が行われる。ここまでが上位10bit分の圧縮アルゴリズム。さらに下位2bit分を持つ場合は圧縮をかけずにベタで持つ。
- 1void CLASS canon_load_raw()
- 2{
- 3 ushort *pixel, *prow, *huff[2];
- 4 int nblocks, lowbits, i, c, row, r, save, val;
- 5 int block, diffbuf[64], leaf, len, diff, carry=0, pnum=0, base[2];
- 6
- 7 crw_init_tables (tiff_compress, huff);
- 8 lowbits = canon_has_lowbits();
- 9 if (!lowbits) maximum = 0x3ff;
- 10 fseek (ifp, 540 + lowbits*raw_height*raw_width/4, SEEK_SET);
- 11 zero_after_ff = 1;
- 12 getbits(-1);
- 13 for (row=0; row < raw_height; row+=8) {
- 14 pixel = raw_image + row*raw_width;
- 15 nblocks = MIN (8, raw_height-row) * raw_width >> 6;
- 16 for (block=0; block < nblocks; block++) {
- 17 memset (diffbuf, 0, sizeof diffbuf);
- 18 for (i=0; i < 64; i++ ) {
- 19 leaf = gethuff(huff[i > 0]);
- 20 if (leaf == 0 && i) break;
- 21 if (leaf == 0xff) continue;
- 22 i += leaf >> 4;
- 23 len = leaf & 15;
- 24 if (len == 0) continue;
- 25 diff = getbits(len);
- 26 if ((diff & (1 << (len-1))) == 0)
- 27 diff -= (1 << len) - 1;
- 28 if (i < 64) diffbuf[i] = diff;
- 29 }
- 30 diffbuf[0] += carry;
- 31 carry = diffbuf[0];
- 32 for (i=0; i < 64; i++ ) {
- 33 if (pnum++ % raw_width == 0)
- 34 base[0] = base[1] = 512;
- 35 if ((pixel[(block << 6) + i] = base[i & 1] += diffbuf[i]) >> 10)
- 36 derror();
- 37 }
- 38 }
- 39 if (lowbits) {
- 40 save = ftell(ifp);
- 41 fseek (ifp, 26 + row*raw_width/4, SEEK_SET);
- 42 for (prow=pixel, i=0; i < raw_width*2; i++) {
- 43 c = fgetc(ifp);
- 44 for (r=0; r < 8; r+=2, prow++) {
- 45 val = (*prow << 2) + ((c >> r) & 3);
- 46 if (raw_width == 2672 && val < 512) val += 2;
- 47 *prow = val;
- 48 }
- 49 }
- 50 fseek (ifp, save, SEEK_SET);
- 51 }
- 52 }
- 53 FORC(2) free (huff[c]);
- 54}
ここで妙なのが、可逆のはずなのに64画素ブロックで処理を区切っていることと、ランレングスハフマンを使っていること。通常の写真においてランレングスが効く場面など頻繁にはないし、そもそもそれで良いのならば64画素毎に1度通常のハフマン符号を挟む必要は無いはず。非可逆ならば、誤差が蓄積されるのを防ぐために定期的に正しい値を入れるという意味で筋が通る。
と思って符号を良く見たら、ランレングスハフマン時に、ランレングス長が1だと-512~+511の差分までしか記録できないことが判明。非可逆圧縮だぞ、これ。つまり、残念ながら、画素毎に白黒白黒となる画は記録できない。現実にはそんな高性能なレンズはありえないので気にしなくても良いが。
sRAW
非可逆圧縮。
後で書く。
後で書く。
- 1void CLASS canon_sraw_load_raw()
- 2{
- 3 struct jhead jh;
- 4 short *rp=0, (*ip)[4];
- 5 int jwide, slice, scol, ecol, row, col, jrow=0, jcol=0, pix[3], c;
- 6 int v[3]={0,0,0}, ver, hue;
- 7 char *cp;
- 8
- 9 if (!ljpeg_start (&jh, 0)) return;
- 10 jwide = (jh.wide >>= 1) * jh.clrs;
- 11
- 12 for (ecol=slice=0; slice <= cr2_slice[0]; slice++) {
- 13 scol = ecol;
- 14 ecol += cr2_slice[1] * 2 / jh.clrs;
- 15 if (!cr2_slice[0] || ecol > raw_width-1) ecol = raw_width & -2;
- 16 for (row=0; row < height; row += (jh.clrs >> 1) - 1) {
- 17 ip = (short (*)[4]) image + row*width;
- 18 for (col=scol; col < ecol; col+=2, jcol+=jh.clrs) {
- 19 if ((jcol %= jwide) == 0)
- 20 rp = (short *) ljpeg_row (jrow++, &jh);
- 21 if (col >= width) continue;
- 22 FORC (jh.clrs-2)
- 23 ip[col + (c >> 1)*width + (c & 1)][0] = rp[jcol+c];
- 24 ip[col][1] = rp[jcol+jh.clrs-2] - 16384;
- 25 ip[col][2] = rp[jcol+jh.clrs-1] - 16384;
- 26 }
- 27 }
- 28 }
- 29 for (cp=model2; *cp && !isdigit(*cp); cp++);
- 30 sscanf (cp, "%d.%d.%d", v, v+1, v+2);
- 31 ver = (v[0]*1000 + v[1])*1000 + v[2];
- 32 hue = (jh.sraw+1) << 2;
- 33 if (unique_id >= 0x80000281 || (unique_id == 0x80000218 && ver > 1000006))
- 34 hue = jh.sraw << 1;
- 35 ip = (short (*)[4]) image;
- 36 rp = ip[0];
- 37 for (row=0; row < height; row++, ip+=width) {
- 38 if (row & (jh.sraw >> 1))
- 39 for (col=0; col < width; col+=2)
- 40 for (c=1; c < 3; c++)
- 41 if (row == height-1)
- 42 ip[col][c] = ip[col-width][c];
- 43 else ip[col][c] = (ip[col-width][c] + ip[col+width][c] + 1) >> 1;
- 44 for (col=1; col < width; col+=2)
- 45 for (c=1; c < 3; c++)
- 46 if (col == width-1)
- 47 ip[col][c] = ip[col-1][c];
- 48 else ip[col][c] = (ip[col-1][c] + ip[col+1][c] + 1) >> 1;
- 49 }
- 50 for ( ; rp < ip[0]; rp+=4) {
- 51 if (unique_id < 0x80000218) {
- 52 rp[0] -= 512;
- 53 goto next;
- 54 } else if (unique_id == 0x80000285) {
- 55next: pix[0] = rp[0] + rp[2];
- 56 pix[2] = rp[0] + rp[1];
- 57 pix[1] = rp[0] + ((-778*rp[1] - (rp[2] << 11)) >> 12);
- 58 } else {
- 59 rp[1] = (rp[1] << 2) + hue;
- 60 rp[2] = (rp[2] << 2) + hue;
- 61 pix[0] = rp[0] + (( 50*rp[1] + 22929*rp[2]) >> 14);
- 62 pix[1] = rp[0] + ((-5640*rp[1] - 11751*rp[2]) >> 14);
- 63 pix[2] = rp[0] + ((29040*rp[1] - 101*rp[2]) >> 14);
- 64 }
- 65 FORC3 rp[c] = CLIP(pix[c] * sraw_mul[c] >> 10);
- 66 }
- 67 ljpeg_end (&jh);
- 68 maximum = 0x3fff;
- 69}
Panasonic
非可逆圧縮。RGB各12bit。
14画素の先頭2画素(ベイヤなので2色分)は12bitの生で持ち、それ以降は色毎の差分を仮数8bitで記録。不思議なのは、指数部2bitが3画素毎に更新される点。つまり、最初のGRGが同じ指数、次のRGRが同じ指数、となる。普通は色毎にまとめて相関を取ると思うのだが、指数に関してだけはそうなっていないのが(良いのか悪いのか知らないけど)面白い。
14画素で12*2+8*12+4*2=128bit、1画素当たり9bitとなる。
指数に関して謎があるが素直な形式で好感が持てる。特性としては、ARW2と同じで非可逆であり14画素内でダイナミックレンジが高いとディティールが失われる可能性は残る。
ベイヤ配列の14画素ごとに差分を半浮動小数点で記録。
- 1void CLASS panasonic_load_raw()
- 2{
- 3 int row, col, i, j, sh=0, pred[2], nonz[2];
- 4
- 5 pana_bits(0);
- 6 for (row=0; row < height; row++)
- 7 for (col=0; col < raw_width; col++) {
- 8 if ((i = col % 14) == 0)
- 9 pred[0] = pred[1] = nonz[0] = nonz[1] = 0;
- 10 if (i % 3 == 2) sh = 4 >> (3 - pana_bits(2));
- 11 if (nonz[i & 1]) {
- 12 if ((j = pana_bits(8))) {
- 13 if ((pred[i & 1] -= 0x80 << sh) < 0 || sh == 4)
- 14 pred[i & 1] &= ~(-1 << sh);
- 15 pred[i & 1] += j << sh;
- 16 }
- 17 } else if ((nonz[i & 1] = pana_bits(8)) || i > 11)
- 18 pred[i & 1] = nonz[i & 1] << 4 | pana_bits(4);
- 19 if (col < width)
- 20 if ((RAW(row,col) = pred[col & 1]) > 4098) derror();
- 21 }
- 22}
14画素の先頭2画素(ベイヤなので2色分)は12bitの生で持ち、それ以降は色毎の差分を仮数8bitで記録。不思議なのは、指数部2bitが3画素毎に更新される点。つまり、最初のGRGが同じ指数、次のRGRが同じ指数、となる。普通は色毎にまとめて相関を取ると思うのだが、指数に関してだけはそうなっていないのが(良いのか悪いのか知らないけど)面白い。
14画素で12*2+8*12+4*2=128bit、1画素当たり9bitとなる。
指数に関して謎があるが素直な形式で好感が持てる。特性としては、ARW2と同じで非可逆であり14画素内でダイナミックレンジが高いとディティールが失われる可能性は残る。
Fujifilm
後で書く。
まとめ
- RAWといっても可逆や非可逆の圧縮をしている
- 非可逆でもダイナミックレンジは確保されているため、実際に困ることはなさそう
- 可逆といっても、符号を見る限り生の値を記録しているかは怪しい
- 各社、アルゴリズムは適当なものが多く、もっと圧縮率を高める努力をした方がいいのではないかと思う。Lossless JPEGが神に思えるレベル
*0 : もちろん、最も重要なのはセンスだが、それはここでは置いておく
*1 : さらに勝手な推測としては、最終的な画質は現像技術に依存するため、生のセンサー情報を取り出されて、そこで勝手に比較等をされても困るという事情もあるのかも。撮像素子も製造工程でばらついたりするし
*1 : さらに勝手な推測としては、最終的な画質は現像技術に依存するため、生のセンサー情報を取り出されて、そこで勝手に比較等をされても困るという事情もあるのかも。撮像素子も製造工程でばらついたりするし