« 2012年2月 | トップページ | 2012年4月 »

2012年3月

2012年3月21日 (水)

RGB24をx264(10bit)のi444で可逆圧縮し、どれだけ同じ色を保持できるか調べる。

※この記事はまだ暫定公開の段階であり、現在継続調査中です。
  ディザ処理やデコードなど、挙動がいまいちよくわからない部分がでてきたので、
  今の時点の記事はあまり参考にしないほうがよいです。

※RGB24は、x264(8bit)で--output-csp rgbでエンコードすればRGBのまま圧縮できます。
  この記事は、
     「RGB24を10bitYUV化した場合にどこまで正確に色を保持できるか」
  という実験目的で書いたものです。


■使用ツール
  Avisynth 2.6 Alpha3
  dither v1.14.1
  masktools v2.0 a48
  flash3kyuu_deband v1.5.0
  avs2pipemod v0.4.1
  x264 r2184 32bit 10bit-depth (x264.nl)

■画像ファイル
  こちらのサイトにある4096x4096のall16777216rgb.pngを、2048x2048に4分割したもの。
  元画像には、RGB24で表せる16777216色全てが含まれている。

■使用したavsファイル(これをベースに、色々なパターンにあわせて必要箇所だけ利用)

LoadPlugin("D:\MovieTool\Avisynth\masktools-v2.0a48\mt_masktools-26.dll")
LoadPlugin("D:\MovieTool\Avisynth\dither-1.14.1\dither.dll")
Import("D:\MovieTool\Avisynth\dither-1.14.1\dither.avsi")
LoadPlugin("D:\MovieTool\Avisynth\flash3kyuu_deband_1.5.0_x86\flash3kyuu_deband.dll")

ImageSource("allRGB左上.png",end=1,fps=1)

# RGB24をstack16形式の16bitYUVに変換する。
#Dither_convert_rgb_to_yuv(matrix="601",tv_range=true,lsb=true,mode=-1,output="YV24") 

# Ditherのドキュメントに書いてあった「10bit深度相当にする方法」。ただし出力ビット深度は16。
/*
a = Dither_get_lsb ()
b = Dither_get_msb ()
c1 = a.mt_lut ("x 6 >>",        y=3, u=3, v=3)
c2 = a.mt_lut ("x 2 << 255 &u", y=3, u=3, v=3)
DitherPost (c1, c2, mode=1)	# Add the mode you want and other parameters here
mt_lut ("x 6 <<", y=3, u=3, v=3)
StackVertical (b, last)
*/

# stack16形式の16bitYUVを、f3kdb()で10bitのinterleaved formatで出力する
#
# ・sample_mode=0は廃止予定らしいのでsample_mode=1にしてgrainY,grainCを0にしてみた。
#  sample_mode=0だとdither_algoは0(8bit processing)だけしか使えず、しかもこれも廃止予定らしいし、
#  さらにdither_algo=0だとoutput_mode=0、つまり8bit出力しかできない。
# ・dither_algo=1(ディザ無しでLSBを丸めるだけ)とした。
# ・input_mode=1はstacked format
# ・output_mode=2はinterleaved format
# ・output_depthで10bit出力を選ぶ
#
/*
f3kdb(range=0,Y=0,Cb=0,Cr=0,grainY=0,grainC=0,sample_mode=1,dither_algo=1, \
         keep_tv_range=true,input_mode=1,input_depth=16, \
         output_mode=2,output_depth=10)
*/

# stack16形式の16bitYUVを実質YUV4xxp16となるinterleaved formatにする
#Dither_convey_yuv4xxp16_on_yvxx ()

■avsの例

 ●Ditherで16bit(Rec601)にして16bitのinterleaved formatで出力するavs

LoadPlugin("D:\MovieTool\Avisynth\masktools-v2.0a48\mt_masktools-26.dll")
LoadPlugin("D:\MovieTool\Avisynth\dither-1.14.1\dither.dll")
Import("D:\MovieTool\Avisynth\dither-1.14.1\dither.avsi")
ImageSource("allRGB右下.png",end=1,fps=1)
Dither_convert_rgb_to_yuv(matrix="601",tv_range=true,lsb=true,mode=-1,output="YV24") 
Dither_convey_yuv4xxp16_on_yvxx()

 ●Ditherで16bit(PC.709)にしてからf3kdbで10bitのinterleaved formatで出力するavs

LoadPlugin("D:\MovieTool\Avisynth\masktools-v2.0a48\mt_masktools-26.dll")
LoadPlugin("D:\MovieTool\Avisynth\dither-1.14.1\dither.dll")
Import("D:\MovieTool\Avisynth\dither-1.14.1\dither.avsi")
LoadPlugin("D:\MovieTool\Avisynth\flash3kyuu_deband_1.5.0_x86\flash3kyuu_deband.dll")
ImageSource("allRGB右下.png",end=1,fps=1)
Dither_convert_rgb_to_yuv(matrix="709",tv_range=false,lsb=true,mode=-1,output="YV24") 
f3kdb(range=0,Y=0,Cb=0,Cr=0,grainY=0,grainC=0,sample_mode=1,dither_algo=1, \
         keep_tv_range=true,input_mode=1,input_depth=16, \
         output_mode=2,output_depth=10)

 ●RGB24をそのまま出力するavs

ImageSource("allRGB右下.png",end=1,fps=1)

■avs2pipemodを用いたエンコードのコマンド例

 ●Ditherで16bit(PC.709)→x264(10bit)の例 (フルレンジなので--input-range pcをつけている)
   avs2pipemod_0_4_1.exe -rawvideo allRGB左上-Dither16-PC709.avs | x264_r2184_10bit.exe  - --demuxer raw --input-csp i444 --input-depth 16 --input-res 2048x2048 --input-range pc --output-csp i444 --frames 2 --fps 1/1 --qp 0 --colormatrix bt709 -o allRGB左上-Dither16-PC709.mp4

 ●Ditherで16bit(Rec601)→f3kdbで10bit出力→x264(10bit)の例
   avs2pipemod_0_4_1.exe -rawvideo allRGB左上-f3kdb10.avs | x264_r2184_10bit.exe  - --demuxer raw --input-csp i444 --input-depth 10 --input-res 2048x2048 --output-csp i444 --frames 2 --fps 1/1 --qp 0 --colormatrix smpte170m -o allRGB左上-f3kdb10-Rec601.mp4

 ●RGB24→x264(10bit)【--vf resize:csp=i444:16 (bgr24→yuv444p16le)】の例
   avs2pipemod_0_4_1.exe -rawvideo=vflip allRGB左上.avs | x264_r2184_10bit.exe - --vf resize:csp=i444:16 --demuxer raw --input-csp bgr --input-depth 8 --input-res 2048x2048 --output-csp i444 --frames 2 --fps 1/1 --qp 0 --colormatrix smpte170m -o allRGB左上-10bit-resize.mp4

■デコード環境と、画像の取得・比較方法

  ●環境
    MPC-HC v1.6.0.4014
    Haali Media Splitter 1.11.288.0
    ffdshow tryouts rev4371 (※1)

    ※ffdshow tryoutsは、「ビデオデコーダーの設定」の「RGB変換」で、
        ◎「手法」の「YV12からRGBへの高画質変換」のチェックを外す。
            →外さないと余計な処理が入るため。
        ◎「入力レベル」を「標準」から「自動」に変更。
            →H.264のVUIに含まれるfullrangeフラグを判定させるため。
      という設定変更をしています。
      他の設定はデフォルトです。

  ●エンコード後の画像の取得
    MPC-HCの「Save Image」でPNGとして保存。

  ●元画像とエンコード後画像の一致部分の比較
    AviUtl 0.99k2+拡張編集プラグイン 0.89pで、元画像の上にエンコード後画像を
    合成モード(差分)で合成し、フレームバッファを取得。
    差分合成により、RGB(0,0,0)となっているピクセルは、元画像と同じだと判断できる。
    スクリプト制御で、RGB(0,0,0)となっているピクセルを数えるLuaスクリプトを書き、その数を調べた。
    2048x2048の4ファイルの合計を調べ、RGB24の16777216色のうち、
    どれだけのピクセルが元と同じ色を保っているかを調べている。

■元の画像の色と同じ色を保てたピクセル数の調査結果

  ●RGBのavsファイルをそのままx264に渡してエンコードした場合

  ●avsファイルで各種プラグインによるcolorspace変換を行ってからx264でエンコードした場合

x264
bit-depth
avs  x264
option
同じ色 割合
処理概要 BT.601
or
BT.709
range output
depth
10bit Ditherで16bitに。 709 pc 16 16777119 99.999%
601 pc 16 16713766 99.622%
709 tv 16 16774667 99.985%
601 tv 16 16760115 99.898%
Ditherで16bitにした後
f3kdbで10bitに。
601 tv 10 10883930 64.873%
RGB24をそのまま。 601 tv 8 --vf
resize:
csp=i444:16
2660612 15.858%
RGB24をそのまま。 601 tv 8 2660612 15.858%
8bit Ditherで16bitに。 601 tv 16 2381956 14.198%
RGB24をそのまま。 601 tv 8 2660612 15.858%

  ●RGB24のavsを、ffmegやavconvによりYUV4:4:4(16bit)やYUV4:4:4(10bit)に変換してからx264でエンコードした場合

Tool x264の
bit-depth
Toolからの
raw出力
BT.601
or
BT.709
range 同じ色 割合
ffmpeg r38996 10bit YUV4:4:4(16bit)
(yuv444p16le)
601 tv 16731867 99.730%
YUV4:4:4(10bit)
(yuv444p10le)
601 tv 16776524 99.996%
avconv r32956 10bit YUV4:4:4(16bit)
(yuv444p16le)
601 tv 2660612 15.858%
YUV4:4:4(10bit)
(yuv444p10le)
601 tv 2660612 15.858%

■メモ

 ●x264の「--vf resize:csp=i444:16」でbgr24→yuv444p16leへの変換が行われたはずなのに
   なぜbgr24→yuv444p(8bit)した時と同じ結果になっているのかがよくわからない。

 ●「--vf resize:csp=bgr:16」としてみたが、ログではbgr24→bgr48le→yuv444p16leという
   変換を行っているように見えるものの、これも結果はbgr24→yuv444p(8bit)の時と変わらなかった。

 ●Ditherで16bitのBT.709のpcレンジにした場合でも100%にはならなかった。

 ●Ditherで16bitのBT.601にする場合、pcレンジよりもtvレンジのほうが良い結果になっている。

 ●8bitエンコードする場合、Ditherで16bitにしたものを渡すとRGB24をそのまま渡す場合よりも悪い結果になっている。

■サンプルの提供

 この記事で使った画像とは違いますが、ColorBarsをベースにした画像を
 Ditherやf3kdbを利用してエンコードするサンプルを作成しました。
 色変化のチェックがしやすいと思いますのでよろしければどうぞ。

      RGB24→10bitYUV実験のサンプルファイル

 詳細については同梱してある「説明.txt」をご覧下さい。
 一応この記事で使った「all16777216rgb.pngを4分割した画像」も入れています。

| | コメント (0) | トラックバック (0)

2012年3月18日 (日)

Chikuzen氏のavs2pipemodとRawSourceの記事へのリンクwith変更履歴

※重要
  これは自分用のメモです。
  2012/03/18時点でのまとめですので最新ではありません。
  最新版は必ずChikuzen氏のブログでチェックするようにして下さい。


Chikuzen氏のavs2pipemodを使わせていただこうと思って過去記事を読んでいたのですが、
使い方だけでなく関連知識についても色々書いて下さっていて非常に参考になるものの、
自分は物覚えが悪いのでまた何度も見に行くことになりそう・・・。

そんなわけで、現時点での過去記事へのリンクを、簡単な変更履歴とともにまとめてみました。
よし、これでオプションの使い方や意味がわからなくなってもすぐ調べられる・・・はず。
RawSource.dllのほうはリンクだけです。

  avs2pipemod

  RawSource.dll


■avs2pipemod

2012/03/14 avs2pipemod その16 (0.4.1)
   'x264raw(tc)'のエラーハンドリングを修正。

2012/03/04 avs2pipemod その15 (0.4.0)
   'dumpyuv'を'dumptxt'に変更し、YUY2やRGBに対応。

2012/02/18 avs2pipemod その14 (0.3.7)
  新機能 "dumpyuv" を追加

2012/02/16 avs2pipemod その13 (0.2.2)
  x264raw/x264bdの出力内容に"--frames"を追加

2012/01/30 avs2pipemod その12 (0.2.1)
  新機能'x264raw','x264rawtc'を追加

2012/01/28 avs2pipemod その11 (0.1.2)
  エラーの出るavsを使用した際にクラッシュしていたのを修正。

2012/01/05 avs2pipemod その10 (0.1.1)
  コードを大幅に書き直し。
  avisynth.libが不要に。
  avisynth.dllのバージョンを検出し、挙動を切り替えるように。

2011/12/31 avs2pipemod その9 (20111230)
  ビデオ出力処理をavs2pipeの最新のものから移植。

2011/09/19 avs2pipemod その8 (20110919)
  -trim追加

2011/07/09 バッファサイズ (20110709)
  バッファを256KiBから128KiBに減らしてパフォーマンスを調整

2011/07/03 avs2pipemod その7 (20110703,20110703-2)
  -audioオプションを廃止し、-wavと-extwavオプションを追加
  -rawvideoの引数にvflipを追加
  -audioでも-extwavと同じ挙動をするように変更

2011/05/10 avs2pipemod その6 (20110510)
  x264bd=tffをx264bdtに。

2011/05/07 avs2pipemod その5
  'packedraw'を廃止して'rawvideo'に変更

2011/05/04 avs2pipemod その4
  getoptを使うことに。aud16やrawaud24とかはなくなり、かわりにオプションの後ろに'=16bit'とかを付けるように。
  x264bdの出力をx264のr1936以降(--bluray-compatあり)用に変更

2011/05/01 avs2pipemod その3
  YUV4MPEG2出力時の色空間変換を改良
  'rawaudio''rawaud16''rawaud24'を追加

2011/04/28 avs2pipemod その2
  'packedraw'オプションを追加

2011/04/26 avs2pipemod
  'audio'オプションの他に'aud16''aud24'というオプションを作った
  'video'オプションを'y4mp''y4mt''y4mb'の3種類に変更
  入力がフィールドベースだったときのフレームベースへの変換
  'info'の出力情報を変更
  経過時間の表示
  'benchmark'オプションを追加
  Y4M出力時のstdoutのバッファを拡張


■RawSource.dll

2011/09/26 RawSource.dll その6

2011/06/15 RawSource.dll その5

2011/05/30 RawSource.dll その4

2011/05/23 RawSource.dll その3

2011/05/21 RawSource.dll その2

2011/01/11 RawSource.dll


ついでにavs2pipemod 0.4.1のヘルプも貼っておきます。

avs2pipemod  ver 0.4.1
built on Mar 14 2012 20:50:37

Usage: avs2pipemod [option] input.avs
  e.g. avs2pipemod -wav=24bit input.avs > output.wav
       avs2pipemod -y4mt=10:11 input.avs | x264 - --demuxer y4m -o tff.mkv
       avs2pipemod -rawvideo -trim=1000,0 input.avs > output.yuv

   -wav[=8bit|16bit|24bit|32bit|float  default unset]
        output wav format audio(WAVEFORMATEX) to stdout.
        if optional arg is set, audio sample type of input will be converted
        to specified value.

   -extwav[=8bit|16bit|24bit|32bit|float  default unset]
        output wav extensible format audio(WAVEFORMATEXTENSIBLE) containing
        channel-mask to stdout.
        if optional arg is set, audio sample type of input will be converted
        to specified value.

   -rawaudio[=8bit|16bit|24bit|32bit|float  default unset]
        output raw pcm audio(without any header) to stdout.
        if optional arg is set, audio sample type of input will be converted
        to specified value.

   -y4mp[=sar  default 0:0]
        output yuv4mpeg2 format video to stdout as progressive.
   -y4mt[=sar  default 0:0]
        output yuv4mpeg2 format video to stdout as tff interlaced.
   -y4mb[=sar  default 0:0]
        output yuv4mpeg2 format video to stdout as bff interlaced.

   -rawvideo[=vflip default unset]
        output rawvideo(without any header) to stdout.

   -x264bdp[=4:3  default unset(16:9)]
        suggest x264(r1939 or later) arguments for bluray disc encoding
        in case of progressive source.
        set optarg if DAR4:3 is required(ntsc/pal sd source).
   -x264bdt[=4:3  default unset(16:9)]
        suggest x264(r1939 or later) arguments for bluray disc encoding
        in case of tff interlaced source.
        set optional arg if DAR4:3 is required(ntsc/pal sd source).

   -x264raw[=input-depth(8 to 16) default 8]
        suggest x264 arguments in case of -rawvideo output.
        set optional arg when using interleaved output of dither hack.
   -x264rawtc[=input-depth(8 to 16) default 8]
        suggest x264 arguments in case of -rawvideo output with --tcfile-in.
        set optional arg when using interleaved output of dither hack.

   -info  - output information about aviscript clip.

   -benchmark - do benchmark aviscript, and output results to stdout.

   -dumptxt - dump pixel values as tab separated text to stdout.

   -trim[=first_frame,last_frame  default 0,0]
        add Trim(first_frame,last_frame) to input script.
        in info, this option is ignored.

note1 : in yuv4mpeg2 output mode, RGB input that has 720pix height or more
        will be converted to YUV with Rec.709 coefficients instead of Rec.601.
        and, if your avisynth version is 2.5.x, YUY2 input will be converted
        to YV12.

note2 : '-x264bd(p/t)' supports only for primary stream encoding.

note3 : in fact, it is a spec violation to use WAVEFORMATEX(-wav option)
        except 8bit/16bit PCM.
        however, there are some applications that accept such invalid files
        instead of supporting WAVEFORMATEXTENSIBLE.

note4 : '-extwav' supports only general speaker positions.

 Chan. MS channels                Description
 ----- -------------------------  ----------------
  1    FC                         Mono
  2    FL FR                      Stereo
  3    FL FR BC                   First Surround
  4    FL FR BL BR                Quadro
  5    FL FR FC BL BR             like Dpl II (without LFE)
  6    FL FR FC LF BL BR          Standard Surround
  7    FL FR FC LF BL BR BC       With back center
  8    FL FR FC LF BL BR FLC FRC  With front center left/right

note5 : in '-x264raw(tc)' with dither hack, output format needs to be
        interleaved(not stacked).

| | コメント (0) | トラックバック (0)

2012年3月13日 (火)

YUV(8bit~10bit)で表せる色数の再計算

前の記事でのAvisynthでの実験結果を基にして、以前の記事で書いた計算プログラムを修正してみました。
今回計算したのは、以下の2つの内容です。

  1.8bit~10bitのYUVについて全ての組み合わせからRGB24の値を計算し、再現できる色数を調べる。
    ただし、求められたR,G,Bの値のどれかが0~255の範囲に無い場合は
    RGBの範囲外となる組み合わせだとみなし、その組み合わせは無効とする。
    (以前の計算では飽和処理をしていた)
    ついでに無効となる組み合わせの数も求める。

  2.RGB24の16777216色それぞれを一度8bit~10bitのYUVに変換し、
    更にそれをRGB24に戻して、再現できる色数を調べる。
    ついでに入力と出力が同じ色になる色数も調べる。

■結果

 ●1について
   ※「除外数」というのは、RGB範囲外になるので無効とみなされたYUVの組み合わせの数

 
	 8bit-Rec601: 再現色数=[2627304] 除外数=[8510196]
	 8bit-PC.601: 再現色数=[3964583] 除外数=[12812633]
	 8bit-Rec709: 再現色数=[2721568] 除外数=[8415932]
	 8bit-PC.709: 再現色数=[4106090] 除外数=[12671126]
	 9bit-Rec601: 再現色数=[15808872] 除外数=[67484600]
	 9bit-PC.601: 再現色数=[16711693] 除外数=[102315476]
	 9bit-Rec709: 再現色数=[16134243] 除外数=[66732848]
	 9bit-PC.709: 再現色数=[16777216] 除外数=[101174033]
	10bit-Rec601: 再現色数=[16777216] 除外数=[537495090]
	10bit-PC.601: 再現色数=[16777216] 除外数=[817768098]
	10bit-Rec709: 再現色数=[16777216] 除外数=[531483720]
	10bit-PC.709: 再現色数=[16777216] 除外数=[808616695]

 ●2について
   ※元々RGBから変換したYUV値だからほとんど問題ないだろうということで、
     最終的にRGB変換する際には飽和処理を行っている。
   ※「可逆色数」というのは、入力したRGB値と出力したRGB値が一致したものの数。
   ※パーセント表示は16777216色に対しての値で、小数第三位以下切捨て。

 
	 8bit-Rec601: 再現色数=[2667904](15.90%) 可逆色数=[2660636](15.85%)
	 8bit-PC.601: 再現色数=[4007380](23.88%) 可逆色数=[3996769](23.82%)
	 8bit-Rec709: 再現色数=[2760514](16.45%) 可逆色数=[2753782](16.41%)
	 8bit-PC.709: 再現色数=[4146794](24.71%) 可逆色数=[4141028](24.68%)
	 9bit-Rec601: 再現色数=[13911017](82.91%) 可逆色数=[13037997](77.71%)
	 9bit-PC.601: 再現色数=[15102283](90.01%) 可逆色数=[14802234](88.22%)
	 9bit-Rec709: 再現色数=[13398699](79.86%) 可逆色数=[12586505](75.02%)
	 9bit-PC.709: 再現色数=[15037350](89.62%) 可逆色数=[14428999](86.00%)
	10bit-Rec601: 再現色数=[16777142](99.99%) 可逆色数=[16777142](99.99%)
	10bit-PC.601: 再現色数=[16777216](100.00%) 可逆色数=[16777216](100.00%)
	10bit-Rec709: 再現色数=[16777216](100.00%) 可逆色数=[16777216](100.00%)
	10bit-PC.709: 再現色数=[16777216](100.00%) 可逆色数=[16777216](100.00%)

■結果について

  あいかわらず、こんな計算でいいのかどうかはいまいち自信が持てませんが、
  ちょっとバラつきがあるものの、8bitについてはだいたいにおいて
  Avisynthでの実験結果に近い値が出ている感じですかね。

  2については
    ●9bitではBT.601よりもBT.709のほうが再現色数が少ない。
    ●10bit-Rec601では16777216色にあと74色足りない。

  という結果になっているのがちょっと気になります。
  特に前者は何か計算間違えてるんじゃないかとちょっと不安・・・。

  今回も以下にソースコードを置いておきますので、おかしなとこがあったら
  コメントでツッコミをいただければ幸いです。

  
■ソースコード(クリックで展開、展開後ソース部分をダブルクリックでコピー可能)

  ●1のソースコード

/* yuvcolor_calc.cpp */
#include <stdio.h> 
#include <stdlib.h> 
#include <math.h> 

#define CLIP_Y(Y) Y = (Y < 0) ? 0.0 : (Y > 1.0) ? 1.0 : Y 
#define CLIP_C(C) C = (C < -0.5) ? -0.5 : (C > 0.5) ? 0.5 : C 
#define SATURATE(X) X = (X < 0) ? 0 : (X > 255) ? 255 : X 

/* RGB24の色数(256*256*256色) */
#define RGB_TABLE_SIZE 16777216 

/* H.264のRound()がこんな感じなので */
int myRound(double x) 
{ 
return (x >= 0) ? (int)floor(x + 0.5) : (int)(-floor(abs(x) + 0.5)); 
} 

int main(void) 
{ 
const struct { 
  char *name;   // 色空間の名称 
  int bitDepth; // ビット深度 
  int y_min;    // 輝度Yの最小値 
  int y_max;    // 輝度Yの最大値 
  int uv_min;   // 色差の最小値 
  int uv_max;   // 色差の最大値 
  double r_cr;  // YUV→RGB変換の係数1 
  double g_cb;  // YUV→RGB変換の係数2 
  double g_cr;  // YUV→RGB変換の係数3 
  double b_cb;  // YUV→RGB変換の係数4 
} matrix[] = { 
  {" 8bit-Rec601",  8, 16,  235, 16,  240, 1.402 , -0.344 , -0.714 , 1.772 }, 
  {" 8bit-PC.601",  8,  0,  255,  0,  255, 1.402 , -0.344 , -0.714 , 1.772 }, 
  {" 8bit-Rec709",  8, 16,  235, 16,  240, 1.5748, -0.1873, -0.4681, 1.8556}, 
  {" 8bit-PC.709",  8,  0,  255,  0,  255, 1.5748, -0.1873, -0.4681, 1.8556}, 
  {" 9bit-Rec601",  9, 32,  470, 32,  480, 1.402 , -0.344 , -0.714 , 1.772 }, 
  {" 9bit-PC.601",  9,  0,  511,  0,  511, 1.402 , -0.344 , -0.714 , 1.772 }, 
  {" 9bit-Rec709",  9, 32,  470, 32,  480, 1.5748, -0.1873, -0.4681, 1.8556}, 
  {" 9bit-PC.709",  9,  0,  511,  0,  511, 1.5748, -0.1873, -0.4681, 1.8556}, 
  {"10bit-Rec601", 10, 64,  940, 64,  960, 1.402 , -0.344 , -0.714 , 1.772 }, 
  {"10bit-PC.601", 10,  0, 1023,  0, 1023, 1.402 , -0.344 , -0.714 , 1.772 }, 
  {"10bit-Rec709", 10, 64,  940, 64,  960, 1.5748, -0.1873, -0.4681, 1.8556}, 
  {"10bit-PC.709", 10,  0, 1023,  0, 1023, 1.5748, -0.1873, -0.4681, 1.8556}, 
  {NULL} 
}; 

bool *rgbTable = (bool *)malloc(RGB_TABLE_SIZE*sizeof(bool)); 
if(!rgbTable){ 
  fprintf(stderr,"malloc(rgbTable) failed\n"); 
  return -1; 
} 

double Y_a=0,Cb_a=0,Cr_a=0; 
int r=0,g=0,b=0; 
int i=0,count=0,exCount=0,num=0; 
int rgbNumber; 

for(int m=0; m<=11; m++){ 

  printf("%s: ", matrix[m].name); 

  // rgbTableをクリア(falseにする) 
  for(i=0;i < RGB_TABLE_SIZE;i++){ 
   rgbTable[i]=false; 
  } 

  count=0;
  exCount=0;
  for (int Y = matrix[m].y_min; Y <= matrix[m].y_max; Y++){ 
   for (int Cb = matrix[m].uv_min; Cb <= matrix[m].uv_max; Cb++){ 
    for (int Cr = matrix[m].uv_min; Cr <= matrix[m].uv_max; Cr++) { 

     // まずはデジタルYCbCr→アナログYCbCr変換 
     // (Y_a:0.0~1.0、Cb_a,Cr_a:-0.5~0.5) 
     // "アナログYCbCr"という表現はよろしくないけどスルーの方向で。 
     // bitDepthをnとすると、H.264で定義されてる量子化式はだいたいこんな感じなので、 
     // ここから逆算してY_a、Cb_a、Cr_aを求める。 
     // ●TVレンジ 
     //   Y = ( 1 << (n-8) ) * (219 * Y_a + 16) 
     //   Cb = ( 1 << (n-8) ) * (224 * Cb_a + 128) 
     //   Cr = ( 1 << (n-8) ) * (224 * Cr_a + 128) 
     // ●フルレンジ 
     //   Y = ( (1 << n) - 1) * Y_a 
     //   Cb = ( (1 << n) - 1) * Cb_a + ( 1 << (n-1) ) 
     //   Cr = ( (1 << n) - 1) * Cr_a + ( 1 << (n-1) ) 

     // minとmaxを用意しとけばTVレンジでもフルレンジでも 
     // 1つの式でいけるので、そのように変形してみた。 
     Y_a  = (Y -  matrix[m].y_min) / (double)(matrix[m].y_max - matrix[m].y_min); 
     Cb_a = (Cb - ( 1 << (matrix[m].bitDepth - 1) ) ) / (double)(matrix[m].uv_max - matrix[m].uv_min); 
     Cr_a = (Cr - ( 1 << (matrix[m].bitDepth - 1) ) ) / (double)(matrix[m].uv_max - matrix[m].uv_min); 
     CLIP_Y(Y_a); 
     CLIP_C(Cb_a); 
     CLIP_C(Cr_a); 

     // 続いてアナログYCbCr→デジタルRGB変換 
     // 基本的には、まるも氏の 
     //  http://www.marumo.ne.jp/db2002_5.htm#15 
     // の記事を参照。 
     // ただしこの記事は2002年のものであり、 
     // BT.709の係数はBT.709-1のものになっている。 
     // H.264ではBT.709-2以降の係数が使われているので、 
     // ここではBT.709-2の係数で計算している。 
     // YUV→RGBのアナログ変換式のみ載せておく。 
     // (R,G,B,Y:0.0~1.0、U,V:-0.5~0.5) 
     // ●BT.601 
     //   R = Y          + 1.402 × V  
     //   G = Y - 0.344 × U - 0.714 × V  
     //   B = Y + 1.772 × U  
     // ●BT.709-2 
     //   R = Y           + 1.5748 × V 
     //   G = Y - 0.1873 × U - 0.4681 × V 
     //   B = Y + 1.8556 × U 

     r = myRound(255.0 * (Y_a                         + matrix[m].r_cr * Cr_a)); 
     g = myRound(255.0 * (Y_a + matrix[m].g_cb * Cb_a + matrix[m].g_cr * Cr_a)); 
     b = myRound(255.0 * (Y_a + matrix[m].b_cb * Cb_a                        )); 

     // RGBの値が範囲外になるものは除外する。
     if(r<0 || r>255 || g<0 || g>255 || b<0 || b>255){
      exCount++;
      continue;
     }

     /*
     SATURATE(r); 
     SATURATE(g); 
     SATURATE(b); 
     */

     // 再現できた色はtrueにする 
     rgbNumber = ((r << 16) | (g << 8) | b); 
     rgbTable[rgbNumber] = true; 
    } 
   } 
  } 

  // trueになっている色の数をカウントして出力 
  num = 0; 
  for (i = 0; i < RGB_TABLE_SIZE; i++){ 
   if (rgbTable[i]){ 
    num++; 
   } 
  } 
  printf("再現色数=[%d] 除外数=[%d]\n", num, exCount); 
} 
free(rgbTable); 
return 0; 
}

  ●2のソースコード

/* yuvcolor_calc2.cpp */
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#define CLIP_Y(Y) Y = (Y < 0) ? 0.0 : (Y > 1.0) ? 1.0 : Y
#define CLIP_C(C) C = (C < -0.5) ? -0.5 : (C > 0.5) ? 0.5 : C
#define SATURATE(X) X = (X < 0) ? 0 : (X > 255) ? 255 : X

/* RGB24の色数(256*256*256色) */
#define RGB_TABLE_SIZE 16777216

/* H.264のRound()がこんな感じなので */
int myRound(double x)
{
	return (x >= 0) ? (int)floor(x + 0.5) : (int)(-floor(abs(x) + 0.5));
}

int main(void)
{
	const struct {
		char *name;		// 色空間の名称
		int bitDepth;	// ビット深度
		int y_min;		// 輝度Yの最小値
		int y_max;		// 輝度Yの最大値
		int uv_min;		// 色差の最小値
		int uv_max;		// 色差の最大値
		double r_cr;	// YUV→RGB変換のRを求める式のCrの係数
		double g_cb;	// YUV→RGB変換のGを求める式のCbの係数
		double g_cr;	// YUV→RGB変換のGを求める式のCrの係数
		double b_cb;	// YUV→RGB変換のBを求める式のCbの係数
		double y_r;		// RGB→YUV変換のYを求める式のRの係数
		double y_g;		// RGB→YUV変換のYを求める式のGの係数
		double y_b;		// RGB→YUV変換のYを求める式のBの係数
		double cb_r;	// RGB→YUV変換のCbを求める式のRの係数
		double cb_g;	// RGB→YUV変換のCbを求める式のGの係数
		double cb_b;	// RGB→YUV変換のCbを求める式のBの係数
		double cr_r;	// RGB→YUV変換のCrを求める式のRの係数
		double cr_g;	// RGB→YUV変換のCrを求める式のGの係数
		double cr_b;	// RGB→YUV変換のCrを求める式のBの係数
	} matrix[] = {
		{" 8bit-Rec601",  8, 16,  235, 16,  240, 1.402 , -0.344 , -0.714 , 1.772 ,
							0.299 , 0.587 , 0.114 , -0.169 , -0.331 , 0.5 , 0.5 , -0.419 , -0.081},
		{" 8bit-PC.601",  8,  0,  255,  0,  255, 1.402 , -0.344 , -0.714 , 1.772 ,
							0.299 , 0.587 , 0.114 , -0.169 , -0.331 , 0.5 , 0.5 , -0.419 , -0.081},
		{" 8bit-Rec709",  8, 16,  235, 16,  240, 1.5748, -0.1873, -0.4681, 1.8556 ,
							0.2126 , 0.7152 , 0.0722 , -0.1146 , -0.3854 , 0.5 , 0.5 , -0.4542 , -0.0458},
		{" 8bit-PC.709",  8,  0,  255,  0,  255, 1.5748, -0.1873, -0.4681, 1.8556 , 
							0.2126 , 0.7152 , 0.0722 , -0.1146 , -0.3854 , 0.5 , 0.5 , -0.4542 , -0.0458},
		{" 9bit-Rec601",  9, 32,  470, 32,  480, 1.402 , -0.344 , -0.714 , 1.772 ,
							0.299 , 0.587 , 0.114 , -0.169 , -0.331 , 0.5 , 0.5 , -0.419 , -0.081},
		{" 9bit-PC.601",  9,  0,  511,  0,  511, 1.402 , -0.344 , -0.714 , 1.772 ,
							0.299 , 0.587 , 0.114 , -0.169 , -0.331 , 0.5 , 0.5 , -0.419 , -0.081},
		{" 9bit-Rec709",  9, 32,  470, 32,  480, 1.5748, -0.1873, -0.4681, 1.8556 ,
							0.2126 , 0.7152 , 0.0722 , -0.1146 , -0.3854 , 0.5 , 0.5 , -0.4542 , -0.0458},
		{" 9bit-PC.709",  9,  0,  511,  0,  511, 1.5748, -0.1873, -0.4681, 1.8556 , 
							0.2126 , 0.7152 , 0.0722 , -0.1146 , -0.3854 , 0.5 , 0.5 , -0.4542 , -0.0458},
		{"10bit-Rec601", 10, 64,  940, 64,  960, 1.402 , -0.344 , -0.714 , 1.772 ,
							0.299 , 0.587 , 0.114 , -0.169 , -0.331 , 0.5 , 0.5 , -0.419 , -0.081},
		{"10bit-PC.601", 10,  0, 1023,  0, 1023, 1.402 , -0.344 , -0.714 , 1.772 ,
							0.299 , 0.587 , 0.114 , -0.169 , -0.331 , 0.5 , 0.5 , -0.419 , -0.081},
		{"10bit-Rec709", 10, 64,  940, 64,  960, 1.5748, -0.1873, -0.4681, 1.8556 , 
							0.2126 , 0.7152 , 0.0722 , -0.1146 , -0.3854 , 0.5 , 0.5 , -0.4542 , -0.0458},
		{"10bit-PC.709", 10,  0, 1023,  0, 1023, 1.5748, -0.1873, -0.4681, 1.8556 ,
							0.2126 , 0.7152 , 0.0722 , -0.1146 , -0.3854 , 0.5 , 0.5 , -0.4542 , -0.0458},
		{NULL}
	};

	bool *rgbTable = (bool *)malloc(RGB_TABLE_SIZE*sizeof(bool));
	if(!rgbTable){
		fprintf(stderr,"malloc(rgbTable) failed\n");
		return -1;
	}

	double Y_a=0,Cb_a=0,Cr_a=0;
	int Y=0,Cb=0,Cr=0;
	int r=0,g=0,b=0;
	int i=0,count=0,num=0,equalCount=0;
	int rgbNumber;

	for(int m=0; m<=11; m++){

		printf("%s: ", matrix[m].name);

		// rgbTableをクリア(falseにする)
		for(i=0;i<RGB_TABLE_SIZE;i++){
			rgbTable[i]=false;
		}

		count=0;
		equalCount=0;
		for (int r0 = 0; r0 <= 255; r0++){
			for (int g0 = 0; g0 <= 255; g0++){
				for (int b0 = 0; b0 <= 255; b0++) {

					// まずはデジタルRGBからアナログYCbCr(Y_a:0.0~1.0、Cb_a,Cr_a:-0.5~0.5)を求める
					// "アナログYCbCr"という表現はよろしくないけどスルーの方向で。
					// 基本的には、まるも氏の
					//  http://www.marumo.ne.jp/db2002_5.htm#15
					// の記事を参照。
					// ただしこの記事は2002年のものであり、BT.709の係数はBT.709-1のものになっている。
					// H.264ではBT.709-2以降の係数が使われているので、ここではBT.709-2の係数で計算している。
					// RGB→YUVのアナログ変換式は以下のとおり。(R,G,B,Y:0.0~1.0、U,V:-0.5~0.5)
					// ●BT.601
					//		Y =  0.299 × R + 0.587 × G + 0.114 × B
					//		U = -0.169 × R - 0.331 × G + 0.500 × B
					//		V =  0.500 × R - 0.419 × G - 0.081 × B
					// ●BT.709-2
					//		Y =  0.2126 × R + 0.7152 × G + 0.0722 × B
					//		U = -0.1146 × R - 0.3854 × G + 0.5000 × B
					//		V =  0.5000 × R - 0.4542 × G - 0.0458 × B
					//
					Y_a = (matrix[m].y_r * r0 + matrix[m].y_g * g0 + matrix[m].y_b * b0)/255.0;
					Cb_a = (matrix[m].cb_r * r0 + matrix[m].cb_g * g0 + matrix[m].cb_b * b0)/255.0;
					Cr_a = (matrix[m].cr_r * r0 + matrix[m].cr_g * g0 + matrix[m].cr_b * b0)/255.0;

					// あまり意味はないと思うけど一応飽和処理
					CLIP_Y(Y_a);
					CLIP_C(Cb_a);
					CLIP_C(Cr_a);

					// 次にアナログYCbCrを一度デジタルYCbCrに変換する
					// bitDepthをnとすると、H.264で定義されてる量子化式はだいたいこんな感じ。
					// ●TVレンジ
					//   Y = ( 1 << (n-8) ) * (219 * Y_a + 16)
					//   Cb = ( 1 << (n-8) ) * (224 * Cb_a + 128)
					//   Cr = ( 1 << (n-8) ) * (224 * Cr_a + 128)
					// ●フルレンジ
					//   Y = ( (1<<n) - 1) * Y_a
					//   Cb = ( (1<<n) - 1) * Cb_a + ( 1 << (n-1) )
					//   Cr = ( (1<<n) - 1) * Cr_a + ( 1 << (n-1) )
					//
					// minとmaxを用意しとけばTVレンジでもフルレンジでも1つの式でいけるので変形してみた。
					//
					Y = myRound((matrix[m].y_max - matrix[m].y_min) * Y_a + matrix[m].y_min);
					Cb = myRound((matrix[m].uv_max - matrix[m].uv_min) * Cb_a + ( 1 << (matrix[m].bitDepth - 1) ));
					Cr = myRound((matrix[m].uv_max - matrix[m].uv_min) * Cr_a + ( 1 << (matrix[m].bitDepth - 1) ));

					// あまり意味はないと思うけど一応飽和処理
					Y = (Y < matrix[m].y_min) ? matrix[m].y_min : (Y > matrix[m].y_max) ? matrix[m].y_max : Y;
					Cb = (Cb < matrix[m].uv_min) ? matrix[m].uv_min : (Cb > matrix[m].uv_max) ? matrix[m].uv_max : Cb;
					Cr = (Cr < matrix[m].uv_min) ? matrix[m].uv_min : (Cr > matrix[m].uv_max) ? matrix[m].uv_max : Cr;

					// 次に、デジタルYCbCrをあらためてアナログYCbCrに変換する
					Y_a  = (Y -  matrix[m].y_min) / (double)(matrix[m].y_max - matrix[m].y_min);
					Cb_a = (Cb - ( 1 << (matrix[m].bitDepth - 1) ) ) / (double)(matrix[m].uv_max - matrix[m].uv_min);
					Cr_a = (Cr - ( 1 << (matrix[m].bitDepth - 1) ) ) / (double)(matrix[m].uv_max - matrix[m].uv_min);

					// あまり意味はないと思うけど一応飽和処理
					CLIP_Y(Y_a);
					CLIP_C(Cb_a);
					CLIP_C(Cr_a);

					// 続いてアナログYCbCr→デジタルRGB変換
					// 基本的には、まるも氏の
					//  http://www.marumo.ne.jp/db2002_5.htm#15
					// の記事を参照。
					// ただしこの記事は2002年のものであり、BT.709の係数はBT.709-1のものになっている。
					// H.264ではBT.709-2以降の係数が使われているので、ここではBT.709-2の係数で計算している。
					// YUV→RGBのアナログ変換式は以下のとおり。(R,G,B,Y:0.0~1.0、U,V:-0.5~0.5)
					// ●BT.601
					//   R = Y          + 1.402 × V 
					//   G = Y - 0.344 × U - 0.714 × V 
					//   B = Y + 1.772 × U 
					// ●BT.709-2
					//   R = Y           + 1.5748 × V
					//   G = Y - 0.1873 × U - 0.4681 × V
					//   B = Y + 1.8556 × U

					r = myRound(255.0 * (Y_a                         + matrix[m].r_cr * Cr_a));
					g = myRound(255.0 * (Y_a + matrix[m].g_cb * Cb_a + matrix[m].g_cr * Cr_a));
					b = myRound(255.0 * (Y_a + matrix[m].b_cb * Cb_a                        ));

					// RGBの値が範囲外になるものは除外する。
					if(r<0 || r>255 || g<0 || g>255 || b<0 || b>255) continue;

					/*
					// 飽和処理
					SATURATE(r);
					SATURATE(g);
					SATURATE(b);
					*/

					// 入力したRGBと、変換後のRGBとが同じになるものをカウントする
					if((r0==r) && (g0==g) && (b0==b)) equalCount++;

					// 再現できた色はtrueにする
					rgbNumber = ((r << 16) | (g << 8) | b);
					rgbTable[rgbNumber] = true;
				}
			}
		}

		// trueになっている色の数をカウントして出力
		num = 0;
		for (i = 0; i < RGB_TABLE_SIZE; i++){
			if (rgbTable[i]){
				num++;
			}
		}
		printf("再現色数=[%d](%02.2f%%) 可逆色数=[%d](%02.2f%%)\n", num, floor(num/(double)RGB_TABLE_SIZE*10000)/100.0, equalCount , floor(equalCount/(double)RGB_TABLE_SIZE*10000)/100.0);
	}
	free(rgbTable);
	return 0;
}


| | コメント (0) | トラックバック (0)

2012年3月 9日 (金)

1677万7216色の画像をAvisynth 2.6でYV24にしてからRGB24に戻して色を数えてみた

以前の記事で、
  「RGB24の16777216色のうち、8bit~10bitのYUVで表せる色の数」
をプログラムで計算してみたのですが、今回はその記事の続きです。

今回は実際に、
  「RGB24の全ての色を含んだ画像を一度8bit YUVにしてからRGBに戻す」
という処理をして、処理後の色数を調べてみます。

使用するツールやソース画像などは以下のとおりです。
x264スレでIrfanViewに画像の色数を数える機能があることを教えてくれた人ありがとう。

 ■使用ツール

    ● Avisynth 2.6 Alpha3 (20110525)

    ● AvsPmod 2.2.1

    ● IrfanView 4.32窓の杜

 ■ソース画像

    ● 以下のサイトにある「all16777216rgb.png」
     各ピクセルは全て異なる色になっており、
     4096x4096でRGB24の16777216色全てを表しています。

        All 16,777,216 RGB colours - David Naylor: Blog

 
これまではなんとなく「Avisynth 2.5.8」を使っていたのですが、
今回の調査ではYUV4:4:4での変換処理が必要だったこともあり、
今更ながら「Avisynth 2.6 Alpha3」を導入してみました。
Avisynth 2.6では、2.5.8にはなかったYV24(YUV4:4:4)などの
新たな色空間がサポートされています。

(SEt氏による「Avisynth 2.6 MT」というのもあるようですが、
 とりあえず今回は「Avisynth 2.6 Alpha3」のほうを使っています。)

調査手順は以下のとおりです。

   1.以下のようなavsファイルを作成。
     ソース画像を読み込み、一度YV24(YUV4:4:4)にしてからRGBに戻しています。
     YV24はYUV4:4:4なので、各ピクセルが独自にY,U,Vの値を持ちます。
     要するに周囲のピクセルの影響を受けずにYUV化できます。

        ImageSource("all16777216rgb.png",end=9,fps=1)
        ConvertToYV24(matrix="Rec601")
        ConvertToRGB(matrix="Rec601")

   2.AvsPmodで1のavsファイルを開き、プレビュー画面を表示。
       プレビュー画面右クリック→「Save image as...」
     でPNGファイルを保存する。

   3.2で保存したPNGファイルをIrfanViewで開き、
       メニュー→Image→Information→「Number of unique colors」
     で、画像に含まれる色の数を調べる。

   4.1のavsにあるConvertTo~の引数のmatrixを変え、
     「Rec601」「PC.601」「Rec709」「PC.709」のそれぞれについて3の色数を調べる。

 
このようにして調べた結果、変換後の画像の色数は以下のようになりました。

  今回の調査結果(RGB画像をYV24化してRGB24に戻したものの色数)
     8bit-Rec601: 2667378 (前の調査結果との差: -288558)
     8bit-PC.601: 3975919 (前の調査結果との差: -286441)
     8bit-Rec709: 2760365 (前の調査結果との差: -286059)
     8bit-PC.709: 4114580 (前の調査結果との差: -285646)

  参考:前の記事での調査結果(YUVが全ての値を取り得るという前提で計算した色数)
     8bit-Rec601: 2955936
     8bit-PC.601: 4262360
     8bit-Rec709: 3046424
     8bit-PC.709: 4400226

前の記事での調査結果と比べると、どれも28万色くらい色数が減ってますね。

前の記事では、
   「YUVはYUV範囲内の全ての値を取る
という前提で計算を行っていました。つまりYUVの値は
   圧縮レンジの場合: Y=16~235、U,V=16~240
   フルレンジの場合: Y=0~255、U,V=0~255
の範囲にある全ての値、全ての組み合わせを取るものとして計算されています。
しかし、Chikuzen氏の記事でも紹介されていた
    YUVとRGBの比較(DTVかくし味)
を読んでもわかるとおり、YUVが取り得る範囲はRGBの範囲よりも広くなっています
つまり、
    「本来RGBとの変換には使われない、RGB範囲外のYUV値の組み合わせ
が存在するということになります。
前回の記事では、そういったRGB範囲外のYUV値の組み合わせも計算し、
飽和処理などで無理やりRGB値に結び付けていたわけです。
そんなわけで、ぶっちゃけるとあまり適切ではない結果が出ていたのかなと・・・。orz

しかし、今回の調査では
  1.最初に、元になるRGB画像からConvertToYV24()でYUV値を求める。
  2.次のYUV→RGB変換では、1でRGBから求められたYUV値だけを使う。
となっています。
つまり、
    「RGB範囲外のYUV値の組み合わせは使わない
という前提で変換処理を行っていることになります。
その他の要因もあるかもしれませんが、こういった違いが28万色の差として出たのではないかと思います。
(このへんは自分でも考察が甘い気がするので詳しい人のツッコミ希望・・・)

元々のRGBデータあってのYUVですから、「YUVが表せる色数」については
今回の調査結果を採用すべきなのでしょうね。
そんなわけで、大雑把に言うと

  「8bit YUVで表せる色の数は、270万~400万色前後」

というのが今回の記事の結論となります。

9bit-depthや10bit-depthについては今回の手法では調べられないので、
前の記事のプログラムを
   RGB24→YUV→RGB24
という流れで計算するように改造して調べてみたほうがよさそうですね・・・。

何かおかしなことを言っていたらコメント欄でツッコミをお願いします。

 
★2012/3/13追記:
  前の記事のプログラムを修正して再計算してみました。

    → YUV(8bit~10bit)で表せる色数の再計算
 

| | コメント (0) | トラックバック (0)

« 2012年2月 | トップページ | 2012年4月 »