らくさん

2008-07-11

sRGBからCIELABへの変換

普通に計算するとすっげー遅いよね、と言ってみるテスト

import java.util.Random;

public class CIELAB {

	private static final int[] _gammaTable;

	private static final int[] _labTable;

	static {
		_gammaTable = createGammaTable();
		_labTable = createLabTable();
	}

	private static int[] createGammaTable() {
		int[] table = new int[256];
		for (int i = 0; i < 256; ++i) {
			double d = i / 255.0;
			table[i] = (int) (((d > 0.04045) ? Math.pow((d+0.055)/1.055,2.4) : d/12.92) * 100000);
		}
		return table;
	}

	private static int[] createLabTable() {
		int[] table = new int[100001];
		for (int i = 0; i < table.length; ++i) {
			double d = i / 100001.0;
			table[i] = (int) (((d > 0.008856) ? Math.pow(d,1/3.0) : (7.787*d)+(16/116.0)) * 1000000);
		}
		return table;
	}

	public static void sRGBToLab_fast(int srgb, int[] lab) {
		int r = _gammaTable[(srgb & 0x00FF0000) >> 16];
		int g = _gammaTable[(srgb & 0x0000FF00) >> 8 ];
		int b = _gammaTable[ srgb & 0x000000FF       ];

		// r,g,b 各値は 0 から 100000 の範囲

		int x = 4124 * r + 3576 * g + 1805 * b;
		int y = 2126 * r + 7152 * g +  722 * b;
		int z =  193 * r + 1192 * g + 9505 * b;

		// x: 0-950500000
		// y: 0-1000000000
		// z: 0-1089000000

		// 0-1000000000 の範囲へ揃えるのと _labTable のインデックス範囲 0-100000 へのマッピングを同時に行う。
		x = x / 9505;
		y = y / 10000;
		z = z / 10890;

		// x,y,z 各値は 0 から 100000 の範囲

		x = _labTable[x];
		y = _labTable[y];
		z = _labTable[z];

		// x,y,z 各値は 0 から 1000000 の範囲

		lab[0] = 116 * y - 16000000;	// L*
		lab[1] = 500 * (x - y);		// a*
		lab[2] = 200 * (y - z);		// b*
	}

	public static void sRGBToLab_orig(int srgb, double[] lab) {
		double r = ((srgb & 0x00FF0000) >> 16) / 255.0;
		double g = ((srgb & 0x0000FF00) >> 8 ) / 255.0;
		double b = ((srgb & 0x000000FF)      ) / 255.0;

		r = ((r > 0.04045) ? Math.pow((r+0.055)/1.055,2.4) : r/12.92) * 100;
		g = ((g > 0.04045) ? Math.pow((g+0.055)/1.055,2.4) : g/12.92) * 100;
		b = ((b > 0.04045) ? Math.pow((b+0.055)/1.055,2.4) : b/12.92) * 100;

		// Observer. = 2°, Illuminant = D65
		double x = 0.4124 * r + 0.3576 * g + 0.1805 * b;
		double y = 0.2126 * r + 0.7152 * g + 0.0722 * b;
		double z = 0.0193 * r + 0.1192 * g + 0.9505 * b;

		x = x/ 95.047;	//ref_X =  95.047   Observer= 2°, Illuminant= D65
		y = y/100.000;	//ref_Y = 100.000
		z = z/108.883;	//ref_Z = 108.883
		x = (x > 0.008856) ? Math.pow(x,1/3.0) : (7.787*x)+(16/116.0);
		y = (y > 0.008856) ? Math.pow(y,1/3.0) : (7.787*y)+(16/116.0);
		z = (z > 0.008856) ? Math.pow(z,1/3.0) : (7.787*z)+(16/116.0);

		lab[0] = 116 * y - 16;
		lab[1] = 500 * (x - y);
		lab[2] = 200 * (y - z);
	}

	public static void main(String[] args) {
		if (args.length == 3) {
			int srgb = ((Integer.parseInt(args[0]) & 0xff) << 16)
					 | ((Integer.parseInt(args[1]) & 0xff) << 8)
					 |  (Integer.parseInt(args[2]) & 0xff);

			int[] lab_i = new int[3];
			sRGBToLab_fast(srgb, lab_i);

			System.out.printf("fast: L=%f, a=%f, b=%f%n",
					lab_i[0]/1000000.0, lab_i[1]/1000000.0, lab_i[2]/1000000.0);

			double[] lab_d = new double[3];
			sRGBToLab_orig(srgb, lab_d);

			System.out.printf("orig: L=%f, a=%f, b=%f%n",
					lab_d[0], lab_d[1], lab_d[2]);

		} else {
			int n = 100000;
			if (args.length > 0) {
				try {
					n = Integer.parseInt(args[0]);
				} catch (NumberFormatException e) {
				}
			}
			benchmark(n);
		}
	}

	private static void benchmark(int n) {
		int[] srgb = new int[n];
		int[] lab_i = new int[3];
		double[] lab_d = new double[3];

		Random random = new Random();
		for (int i = 0; i < srgb.length; ++i) {
			srgb[i] = random.nextInt(0x1000000);
		}

		long start;

		start = System.currentTimeMillis();
		for (int i = 0; i < srgb.length; ++i) {
			sRGBToLab_fast(srgb[i], lab_i);
			lab_d[0] = lab_i[0]/1000000.0;
			lab_d[1] = lab_i[1]/1000000.0;
			lab_d[2] = lab_i[2]/1000000.0;
		}
		System.out.printf("fast: %d ms%n", System.currentTimeMillis() - start);

		start = System.currentTimeMillis();
		for (int i = 0; i < srgb.length; ++i) {
			sRGBToLab_orig(srgb[i], lab_d);
		}
		System.out.printf("orig: %d ms%n", System.currentTimeMillis() - start);
	}

}

1280x720, 10フレーム分(=9216000ピクセル)で計ってみる。

>java CIELAB 9216000
fast: 437 ms
orig: 13438 ms

まくらんまくらん 2008/07/17 14:18 sRGBToLab_origメソッドの冒頭6行くらいだけど
R,G,Bの各値は相互に影響しないし元の値は0〜255の範囲なので
変換テーブルを予め作っておけば高速化できるんじゃないかな。


double rTbl[0x100];
double gTbl[0x100];
double bTbl[0x100];

// ↓コンストラクタの辺りにこんな感じで書いておく。

// 中間計算結果の作成
for(int i=0; i<0x100; ++i){
rTbl[i] = (double)i / 255.0;
gTbl[i] = (double)i / 255.0;
bTbl[i] = (double)i / 255.0;

rTbl[i] = ((rTbl[i] > 0.04045) ? Math.pow((rTbl[i]+0.055)/1.055,2.4) : rTbl[i]/12.92) * 100;
gTbl[i] = ((gTbl[i] > 0.04045) ? Math.pow((gTbl[i]+0.055)/1.055,2.4) : gTbl[i]/12.92) * 100;
bTbl[i] = ((bTbl[i] > 0.04045) ? Math.pow((bTbl[i]+0.055)/1.055,2.4) : bTbl[i]/12.92) * 100;
}


元ソース
double r = ((srgb & 0x00FF0000) >> 16) / 255.0;
double g = ((srgb & 0x0000FF00) >> 8 ) / 255.0;
double b = ((srgb & 0x000000FF) ) / 255.0;

r = ((r > 0.04045) ? Math.pow((r+0.055)/1.055,2.4) : r/12.92) * 100;
g = ((g > 0.04045) ? Math.pow((g+0.055)/1.055,2.4) : g/12.92) * 100;
b = ((b > 0.04045) ? Math.pow((b+0.055)/1.055,2.4) : b/12.92) * 100;

改変ソース
double r = rTbl[(srgb & 0x00FF0000) >> 16];
double g = gTbl[(srgb & 0x0000FF00) >> 8 ];
double b = bTbl[(srgb & 0x000000FF) ];

ではでは。

まくらんまくらん 2008/07/17 14:31 よく読んだらsRGBToLab_fastで既にそうなってたorz
しかもR,G,Bで全部係数いっしょだから1個でいいじゃん。

7m4mon7m4mon 2016/02/20 23:41 はじめまして。すばらしいコードを公開してくださりありがとうございます。
当方でVB.netに移植して、4096*4096の画像を16色に減色するプログラムを実行したところ
354秒かかっていたものが182秒へと、49%減になりました!
VB.netへと移植したコードを公開させていただいても良いでしょうか?

butyricacidbutyricacid 2016/02/23 21:48 お返事遅くなり申し訳ありません。
こちらのコードはご自由にお使いください。
著作権表記等も不要です(パブリックドメインとします)

7m4mon7m4mon 2016/02/28 22:31 ご回答ありがとうございます。
http://nomulabo.com/sam/color_reduction.html
↑のような形で公開しました。以上ご報告まで。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/butyricacid/20080711/1215772093