Какой алгоритм стоит за функцией "цвет в Альфа" Gimp?

для тех, кто еще не знаком с функцией "цвет в Альфа" Gimp, вот страница на ней из документации Gimp:цвет в Альфа-канал. Это действительно хорошая работа, и мне очень интересно, как именно Gimp делает это с точки зрения цветовых манипуляций, в каком бы цветовом пространстве цвета не находились. Большое спасибо за любые подсказки.

EDIT 1: генерация информации прозрачности для пикселя на основе его сходства с ключевым цветом (тот, который вы выберите в диалоговом окне "цвет в Альфа"), как некоторые люди предложили, прежде чем удалить его ответ по какой-то причине, будет звучать как хорошее понимание, но я полагаю, что это сложнее. Предположим, мы оцениваем сходство цветов в диапазоне единиц от 0.0 до 1.0, и у нас есть пиксель, цвет которого, например, 0.4 похож на, скажем, цвет белого (например, вы выбрали бы белый в диалоговом окне "цвет в Альфа"), и поэтому пиксель получает Альфа-значение 0.6, тогда как бы вы изменили цвет фактический цвет пикселя для компенсации потери яркости/яркости / насыщенности, когда результирующий пиксель отображается на белом фоне с Альфой 0,6?

EDIT 2: фактически обновление: на вопрос, связанный с первым редактированием, был дан ответ в как изменить Альфа пикселя без изменения результирующего цвета? но это, вероятно, не полная история, потому что то, что происходит в источнике Gimp для " цвета в Альфа" функция не так проста и, похоже, основана на определенном алгоритме, а не на Формуле.

4 ответов


вам нужно придумать механизм для сравнения сходства цветов. Существует множество цветовых пространств, в которых вы можете это сделать. RGB часто не лучший для такого рода вещей. Но вы можете использовать HSV, YCbCr или другое пространство яркости/цветности. Часто расстояние в одном из этих мест даст вам лучший ответ, чем Евклидово расстояние в RGB. Как только у вас есть расстояние, вы можете разделить его на максимальное расстояние, чтобы получить процент. Этот процент будет обратным Альфа, которую вы хотите использовать, как одну из возможностей.

Если вы хотите знать, как это делает GIMP, вы можете посмотреть на источник. Например, вот одно недавнее изменение кода к этому плагину.


Я взглянул на исходный код, и его мясо-это функция colortoalpha. Параметры *a1 - * a4-это вход / выход красный, зеленый, синий и Альфа, соответственно, а C1-c3-цвет для создания Альфа.

когда вы объединяете два цвета c1 и c2 с определенной Альфой a (0 ≤ a ≤ 1), результатом является

y = a * c1 + (1-a) * c2

здесь мы делаем обратную операцию: мы знаем конечный результат y и цвет фона c2 и хотим выяснить c1 и a. С тех пор это под заданным уравнением, существует бесконечное количество решений. Однако диапазоны 0 ≤ c1 ≤ 255 и 0 ≤ a ≤ 1 добавляют к решению границы.

способ работы плагина Gimp заключается в том, что для каждого пикселя он минимизирует Альфа-значение (т. е. максимизирует прозрачность). И наоборот, это означает, что для каждого результирующего пикселя, который не является полностью прозрачным (т. е. не был точно цветом фона), один из компонентов RGB равен 0 или 255.

это производит изображение, которое при наложении поверх указанного цвета будет создавать исходное изображение (при отсутствии ошибок округления) и имеет максимальную прозрачность для каждого пикселя.

стоит отметить, что весь процесс выполняется в цветовом пространстве RGB, но может выполняться и в других, если операция объединения выполняется в том же цветовом пространстве.


поэтому я заглянул в исходный код GIMP... РЭБ! Я сделал его общим и читаемым. Но все равно довольно быстро. Для математического объяснения см. Сампо -. Вот реализация C# (легко конвертируемая в C / C++):

static class PixelShaders {

    /// <summary>
    /// Generic color space color to alpha.
    /// </summary>
    /// <param name="pA">Pixel alpha.</param>
    /// <param name="p1">Pixel 1st channel.</param>
    /// <param name="p2">Pixel 2nd channel.</param>
    /// <param name="p3">Pixel 3rd channel.</param>
    /// <param name="r1">Reference 1st channel.</param>
    /// <param name="r2">Reference 2nd channel.</param>
    /// <param name="r3">Reference 3rd channel.</param>
    /// <param name="mA">Maximum alpha value.</param>
    /// <param name="mX">Maximum channel value.</param>
    static void GColorToAlpha(ref double pA, ref double p1, ref double p2, ref double p3, double r1, double r2, double r3, double mA = 1.0, double mX = 1.0) {
        double aA, a1, a2, a3;
        // a1 calculation: minimal alpha giving r1 from p1
        if (p1 > r1) a1 = mA * (p1 - r1) / (mX - r1);
        else if (p1 < r1) a1 = mA * (r1 - p1) / r1;
        else a1 = 0.0;
        // a2 calculation: minimal alpha giving r2 from p2
        if (p2 > r2) a2 = mA * (p2 - r2) / (mX - r2);
        else if (p2 < r2) a2 = mA * (r2 - p2) / r2;
        else a2 = 0.0;
        // a3 calculation: minimal alpha giving r3 from p3
        if (p3 > r3) a3 = mA * (p3 - r3) / (mX - r3);
        else if (p3 < r3) a3 = mA * (r3 - p3) / r3;
        else a3 = 0.0;
        // aA calculation: max(a1, a2, a3)
        aA = a1;
        if (a2 > aA) aA = a2;
        if (a3 > aA) aA = a3;
        // apply aA to pixel:
        if (aA >= mA / mX) {
            pA = aA * pA / mA;
            p1 = mA * (p1 - r1) / aA + r1;
            p2 = mA * (p2 - r2) / aA + r2;
            p3 = mA * (p3 - r3) / aA + r3;
        } else {
            pA = 0;
            p1 = 0;
            p2 = 0;
            p3 = 0;
        }
    }

}

реализация GIMP (здесь) использует цветовое пространство RGB, использует альфа-значение как float с диапазоном от 0 до 1 и R, G, B как float от 0 до 255.

RGB реализация не удается эффектно, когда изображение имеет JPEG артефакты, потому что они означают незначительные воспринимаемые отклонения цвета, но довольно значительные абсолютные отклонения R, G, B. Использование LAB colorspace должно сделать трюк для случая.

Если вы хотите просто удалить сплошной фон из изображения, алгоритм color to alpha не является оптимальным вариантом. Я получил хорошие результаты при расчете расстояния цветового пространства для каждого пикселя с помощью лабораторного цветового пространства. Затем вычисленное расстояние было применено к Альфа-каналу исходного изображения. Главный разница между этим и цветом в Альфа-это то, что оттенок пикселей не будет изменен. Background remove просто устанавливает Альфа (непрозрачность) для разницы в цветовом пространстве. Он хорошо работает, если цвет фона не встречается на переднем плане изображения. Если это так, фон не может быть удален, или алгоритм BFS должен использоваться только для обхода внешних пикселей (что-то вроде использования выбора волшебной палочки в GIMP, а затем удаления выбора).

фон не может быть удален, если изображение имеет как отверстия, так и пиксели по цвету похожи на цвет фона. Такие изображения требуют некоторой ручной обработки.


Я перевел метод colortoalpha из gimp в C# как можно лучше. Проблема в том, что значения RGBA берутся как байты для каждого канала в библиотеке, такой как ImageSharp. Некоторые преобразования теряют данные во время преобразования, но я старался сохранить как можно больше. Это использует ImageSharp для мутации изображения. ImageSharp полностью управляется, поэтому он будет работать на разных платформах. Его также быстро. Все эти методы работают примерно в ~10 мс (менее 10 мс).

здесь код для реализации C#:

public static unsafe void ColorToAlpha(this Image<Rgba32> image, Rgba32 color)
    {
        double alpha1, alpha2, alpha3, alpha4;
        double* a1, a2, a3, a4;

        a1 = &alpha1;
        a2 = &alpha2;
        a3 = &alpha3;
        a4 = &alpha4;

        for (int j = 0; j < image.Height; j++)
        {
            var span = image.GetPixelRowSpan(j);

            for (int i = 0; i < span.Length; i++)
            {
                ref Rgba32 pixel = ref span[i];

                // Don't know what this is for
                // *a4 = pixel.A;

                if (pixel.R > color.R)
                    *a1 = (pixel.R - color.R) / (255.0 - color.R);
                else if (pixel.R < color.R)
                    *a1 = (color.R - pixel.R) / color.R;
                else
                    *a1 = 0.0;

                if (pixel.G > color.G)
                    *a2 = (pixel.G - color.G) / (255.0 - color.G);
                else if (pixel.G < color.G)
                    *a2 = (color.G - pixel.G) / color.G;
                else
                    *a2 = 0.0;

                if (pixel.B > color.B)
                    *a3 = (pixel.B - color.B) / (255.0 - color.B);
                else if (pixel.B < color.B)
                    *a3 = (color.B - pixel.B) / color.B;
                else
                    *a3 = 0.0;

                if (*a1 > *a2)
                    *a4 = *a1 > *a3 ? *a1 * 255.0 : *a3 * 255.0;
                else
                    *a4 = *a2 > *a3 ? *a2 * 255.0 : *a3 * 255.0;

                if (*a4 < 1.0)
                    return;

                pixel.R = (byte)Math.Truncate((255.0 * (*a1 - color.R) / *a4 + color.R));
                pixel.G = (byte)Math.Truncate((255.0 * (*a2 - color.G) / *a4 + color.G));
                pixel.B = (byte)Math.Truncate((255.0 * (*a3 - color.B) / *a4 + color.B));

                pixel.A = (byte)Math.Truncate(*a4);
            }
        }
    }