Какой алгоритм стоит за функцией Gimp "Color to Alpha"? - PullRequest
7 голосов
/ 14 февраля 2012

Для тех, кто еще не знаком с функцией Gimp «Color to Alpha», есть страница из документации Gimp: Color to Alpha . Это действительно хорошая работа, и мне интересно, насколько точно Gimp делает это с точки зрения цветовой манипуляции, в каком цветовом пространстве могут находиться цвета. Спасибо большое за любые подсказки.

РЕДАКТИРОВАТЬ 1: Создание информации о прозрачности для пикселя на основе его сходства с цветом ключа (тот, который вы выбираете в диалоговом окне «Цвет к альфа-каналу»), как некоторые люди предложили перед удалением ответа по какой-то причине, это звучит как хорошее понимание, но я полагаю, что это более сложное, чем это. Давайте предположим, что мы оцениваем цветовое сходство в диапазоне единиц от 0,0 до 1,0, и у нас есть пиксель, цвет которого, например, 0,4, похож, скажем, на цвет белого (как вы бы выбрали белый цвет в «Color to Альфа »и, следовательно, пиксель получает альфа-значение 0,6, тогда как бы вы изменили фактический цвет пикселя, чтобы компенсировать потерю яркости / яркости / насыщенности, когда результирующий пиксель отображается на белом фоне с альфа-каналом 0,6?

РЕДАКТИРОВАТЬ 2: На самом деле обновление: ответ на подвопрос, связанный с первым редактированием, дан в Как изменить альфа-точку пикселя без изменения результирующего цвета? , но это, вероятно, не полная история, потому что то, что происходит в исходном коде Gimp для функции «Color to Alpha», не так просто и, кажется, основано на конкретном алгоритме, а не на формуле.

Ответы [ 4 ]

8 голосов
/ 17 февраля 2013

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

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

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

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

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

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

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

1 голос
/ 29 ноября 2016

Итак, я посмотрел на исходный код 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 должно помочь в этом случае.

Если вы хотите просто удалить сплошной фон с изображения, алгоритм преобразования цвета в альфа-канал не является оптимальным вариантом. Я получил хорошие результаты, когда вычислял расстояние цветового пространства для каждого пикселя, используя цветовое пространство LAB. Рассчитанное расстояние затем было применено к альфа-каналу исходного изображения. Основное различие между этим и цветом в альфа состоит в том, что оттенок пикселей не изменится. Удаление фона просто устанавливает альфа (непрозрачность) на разницу цветов. Это хорошо работает, если цвет фона не появляется на переднем плане изображения. Если это так, либо фон не может быть удален, либо должен использоваться алгоритм BFS для обхода только внешних пикселей (что-то вроде использования выбора волшебной палочки в GIMP, затем удаления выделения).

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

1 голос
/ 17 февраля 2012

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

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

0 голосов
/ 04 сентября 2018

Я перевел метод 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);
            }
        }
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...