Как я могу еще больше оптимизировать эту функцию разницы цветов?
Я сделал эту функцию для вычисления цветовых различий в цветовом пространстве библиотеки CIE Lab, но ей не хватает скорости. Поскольку я не эксперт по Java, мне интересно, есть ли у какого-нибудь Java-гуру некоторые подсказки, которые могут улучшить скорость здесь.
Код основан на функции matlab, указанной в блоке комментариев.
/**
* Compute the CIEDE2000 color-difference between the sample color with
* CIELab coordinates 'sample' and a standard color with CIELab coordinates
* 'std'
*
* Based on the article:
* "The CIEDE2000 Color-Difference Formula: Implementation Notes,
* Supplementary Test Data, and Mathematical Observations,", G. Sharma,
* W. Wu, E. N. Dalal, submitted to Color Research and Application,
* January 2004.
* available at http://www.ece.rochester.edu/~gsharma/ciede2000/
*/
public static double deltaE2000(double[] lab1, double[] lab2)
{
double L1 = lab1[0];
double a1 = lab1[1];
double b1 = lab1[2];
double L2 = lab2[0];
double a2 = lab2[1];
double b2 = lab2[2];
// Cab = sqrt(a^2 + b^2)
double Cab1 = Math.sqrt(a1 * a1 + b1 * b1);
double Cab2 = Math.sqrt(a2 * a2 + b2 * b2);
// CabAvg = (Cab1 + Cab2) / 2
double CabAvg = (Cab1 + Cab2) / 2;
// G = 1 + (1 - sqrt((CabAvg^7) / (CabAvg^7 + 25^7))) / 2
double CabAvg7 = Math.pow(CabAvg, 7);
double G = 1 + (1 - Math.sqrt(CabAvg7 / (CabAvg7 + 6103515625.0))) / 2;
// ap = G * a
double ap1 = G * a1;
double ap2 = G * a2;
// Cp = sqrt(ap^2 + b^2)
double Cp1 = Math.sqrt(ap1 * ap1 + b1 * b1);
double Cp2 = Math.sqrt(ap2 * ap2 + b2 * b2);
// CpProd = (Cp1 * Cp2)
double CpProd = Cp1 * Cp2;
// hp1 = atan2(b1, ap1)
double hp1 = Math.atan2(b1, ap1);
// ensure hue is between 0 and 2pi
if (hp1 < 0) {
// hp1 = hp1 + 2pi
hp1 += 6.283185307179586476925286766559;
}
// hp2 = atan2(b2, ap2)
double hp2 = Math.atan2(b2, ap2);
// ensure hue is between 0 and 2pi
if (hp2 < 0) {
// hp2 = hp2 + 2pi
hp2 += 6.283185307179586476925286766559;
}
// dL = L2 - L1
double dL = L2 - L1;
// dC = Cp2 - Cp1
double dC = Cp2 - Cp1;
// computation of hue difference
double dhp = 0.0;
// set hue difference to zero if the product of chromas is zero
if (CpProd != 0) {
// dhp = hp2 - hp1
dhp = hp2 - hp1;
if (dhp > Math.PI) {
// dhp = dhp - 2pi
dhp -= 6.283185307179586476925286766559;
} else if (dhp < -Math.PI) {
// dhp = dhp + 2pi
dhp += 6.283185307179586476925286766559;
}
}
// dH = 2 * sqrt(CpProd) * sin(dhp / 2)
double dH = 2 * Math.sqrt(CpProd) * Math.sin(dhp / 2);
// weighting functions
// Lp = (L1 + L2) / 2 - 50
double Lp = (L1 + L2) / 2 - 50;
// Cp = (Cp1 + Cp2) / 2
double Cp = (Cp1 + Cp2) / 2;
// average hue computation
// hp = (hp1 + hp2) / 2
double hp = (hp1 + hp2) / 2;
// identify positions for which abs hue diff exceeds 180 degrees
if (Math.abs(hp1 - hp2) > Math.PI) {
// hp = hp - pi
hp -= Math.PI;
}
// ensure hue is between 0 and 2pi
if (hp < 0) {
// hp = hp + 2pi
hp += 6.283185307179586476925286766559;
}
// LpSqr = Lp^2
double LpSqr = Lp * Lp;
// Sl = 1 + 0.015 * LpSqr / sqrt(20 + LpSqr)
double Sl = 1 + 0.015 * LpSqr / Math.sqrt(20 + LpSqr);
// Sc = 1 + 0.045 * Cp
double Sc = 1 + 0.045 * Cp;
// T = 1 - 0.17 * cos(hp - pi / 6) +
// + 0.24 * cos(2 * hp) +
// + 0.32 * cos(3 * hp + pi / 30) -
// - 0.20 * cos(4 * hp - 63 * pi / 180)
double hphp = hp + hp;
double T = 1 - 0.17 * Math.cos(hp - 0.52359877559829887307710723054658)
+ 0.24 * Math.cos(hphp)
+ 0.32 * Math.cos(hphp + hp + 0.10471975511965977461542144610932)
- 0.20 * Math.cos(hphp + hphp - 1.0995574287564276334619251841478);
// Sh = 1 + 0.015 * Cp * T
double Sh = 1 + 0.015 * Cp * T;
// deltaThetaRad = (pi / 3) * e^-(36 / (5 * pi) * hp - 11)^2
double powerBase = hp - 4.799655442984406;
double deltaThetaRad = 1.0471975511965977461542144610932 * Math.exp(-5.25249016001879 * powerBase * powerBase);
// Rc = 2 * sqrt((Cp^7) / (Cp^7 + 25^7))
double Cp7 = Math.pow(Cp, 7);
double Rc = 2 * Math.sqrt(Cp7 / (Cp7 + 6103515625.0));
// RT = -sin(delthetarad) * Rc
double RT = -Math.sin(deltaThetaRad) * Rc;
// de00 = sqrt((dL / Sl)^2 + (dC / Sc)^2 + (dH / Sh)^2 + RT * (dC / Sc) * (dH / Sh))
double dLSl = dL / Sl;
double dCSc = dC / Sc;
double dHSh = dH / Sh;
return Math.sqrt(dLSl * dLSl + dCSc * dCSc + dHSh * dHSh + RT * dCSc * dHSh);
}
Ответы
Ответ 1
cos
является дорогостоящим, особенно 4 раза подряд. Вы, кажется, вычисляете cos (na + b), где b - константа, а n - малое целое число. Это означает, что вы можете прекомпомировать cos (b) и sin (b), а во время выполнения вычислить только cos (hp) и sin (hp). Вы можете получить cos (na + b), повторив использование
cos(a+b) = cos(a)*cos(b)-sin(a)*sin(b)
Вы будете торговать несколькими sin
и cos
для некоторых умножений и дополнений, почти наверняка стоит.
Вы можете сделать лучше, если вы чувствуете амбициозность. Вы получаете hp
косвенно из atan2
. Шаблон trig-function(rational-function(inverse-trig-function(x)))
часто может быть заменен некоторой комбинацией полиномов и корней, которые быстрее оценивают, чем триг-функции.
Я не знаю, как pow
реализуется в Java, но если он использует журналы, вам может быть лучше получить Cp7
с помощью Cp2=Cp*Cp;Cp4=Cp2*Cp2;Cp7=Cp4*Cp2*Cp;
Обновление: сейчас немного более спекулятивно, так как у меня нет времени, чтобы переписать код. Оптимизация мощности и оптимизация триггеров на самом деле та же самая, что скрывается! Оптимизация триггера - это вариант оптимизации мощности, применяемый к комплексным числам. Что еще, строка
double dH = 2 * Math.sqrt(CpProd) * Math.sin(dhp / 2);
является частью операции квадратного корня с комплексным числом. Это заставляет меня думать, что большой кусок этого кода действительно может быть написан для использования сложных чисел, устраняющих почти все тригг-функции. Я не знаю, как ваша сложная арифметика чисел, хотя...
Ответ 2
Как правило, любая система, которая реализует это и имеет серьезные проблемы с производительностью, не будет делать случайные цвета. Это будет делать несколько разных цветов. Даже гигантское изображение, полное разных цветов, обычно имеет всего несколько тысяч цветов. Я очень рекомендую алгоритм кэширования. Хотя, если скорость вызывает беспокойство, вы должны сворачивать свои собственные (вы хотите только примитивы, скорость).
Там не так много оптимизма, что нужно делать с реальной процедурой цветового расстояния, но я написал систему кеширования для этой вещи, и она пошла в порядке, в 100 раз быстрее. Рулевая дистанция перешла от подавляющего доминирующего фактора к ошибке. Вы не должны стремиться уменьшить скорость этого. Вы можете что-то исправить. Но уменьшите количество раз, когда вы вызываете вещь должным образом.
У вас есть два набора входных данных, и он производит один выходной сигнал, и делает это очень долгое время. 7 удвоений за индекс кэширования. Это 14 байт. Для 14-мегапиксельной памяти (или так, игнорируя хэши, а что нет, скорее всего, мы говорим о двойном). Вы можете хранить миллион записей, и достаточно, чтобы, если у вас есть 1k типичных разных цветов, вы получите высокие 90% -ные кэш-запросы. Вы даже можете значительно уменьшить это, если вы конвертируете исходные цвета из RGB в Lab (эти конверсии также должны быть кэшированы). Вы увидите ускорение, если вы ударите, как 5% времени. И вы получите хиты, вероятно, в 99% случаев (если вы не делаете что-то нечетное, как случайные сравнения цветов). Из моих наблюдений он делает CIEDE2000 примерно одинаковым во времени, чем Euclidean RGB.