1.3 Графические фильтры: изменение тона и гаммы изображения

Блоговая публикация пользователя: Alexei16 Эта публикация была перенесена из личного блога пользователя в общие разделы уровок сайта.
В данном уроке вы научитесь изменять тон и гамму изображения.

Изменение тона изображения

Для изменения тона будет использоваться цветовую модель HSL, в которой координатами цвета являются Hue (Тон), Saturation (Насыщенность) и Lightness (Светлота).

Цветовые составляющие HSL принимают следующие значения:
  • H (Тон) — [0; 360]
  • S (Насыщенность) — [0; 1]
  • L (Светлота) — [0; 1]

На рисунке 1 показано графическое представление модели HSL:
Обработка изображений: графическое представление модели HSL Рисунок 1. Графическое представление модели HSL.
Для того, чтобы можно было изменять тон, нам нужно будет конвертировать цветовую модель RGB в HSL и обратно.

Напишем две функции:

1. RGB -> HSL

Код:
/*http://esate.ru, Alexei16*/

public static HSL_Color RGB_TO_HSL(RGB_Color CL)
{
    double H = 0, S = 0, L = 0;

    double R = (double)CL.R / 255.0; //
    double G = (double)CL.G / 255.0; // Приводим к диапазону от 0 до 1
    double B = (double)CL.B / 255.0; //

    double Max = Math.Max(R, Math.Max(G, B));
    double Min = Math.Min(R, Math.Min(G, B));

    //Вычисляем тон
    if (Max == Min)
    {
        H = 0;
    }
    else if (Max == R && G >= B)
    {
        H = 60.0 * (G - B) / (Max - Min);
    }
    else if (Max == R && G < B)
    {
        H = 60.0 * (G - B) / (Max - Min) + 360.0;
    }
    else if (Max == G)
    {
        H = 60.0 * (B - R) / (Max - Min) + 120.0;
    }
    else if (Max == B)
    {
        H = 60.0 * (R - G) / (Max - Min) + 240.0;
    }

    //Вычисляем светлоту 
    L = (Max + Min) / 2.0;

    //Вычисляем насыщенность
    if (L == 0 || Max == Min)
    {
        S = 0;
    }
    else if (0 < L && L <= 0.5)
    {
        S = (Max - Min) / (Max + Min);
    }
    else if (L > 0.5)
    {
        S = (Max - Min) / (2 - (Max + Min)); 
    }
    return new HSL_Color(H, S, L);
}


2. HSL -> RGB

Код:
/*http://esate.ru, Alexei16*/

public static RGB_Color HSL_TO_RGB(HSL_Color CL)
{
    int R, G, B;
    if (CL.S == 0)
    {
        R = (int)Math.Round(CL.L * 255.0); //
        G = (int)Math.Round(CL.L * 255.0); //Округляем значения
        B = (int)Math.Round(CL.L * 255.0); //
    }
    else
    {
        double Q = (CL.L < 0.5) ? (CL.L * (1.0 + CL.S)) : (CL.L + CL.S - (CL.L * CL.S));
        double P = (2.0 * CL.L) - Q;

        double HK = CL.H / 360.0;
        double[] T = new double[3];   //Массив для хранения значений R,G,B

        T[0] = HK + (1.0 / 3.0);   // R
        T[1] = HK;         // G
        T[2] = HK - (1.0 / 3.0);   // B

        for (int i = 0; i < 3; i++)
        {
            if (T[i] < 0) T[i] += 1.0;
            if (T[i] > 1) T[i] -= 1.0;

            if ((T[i] * 6) < 1)
            {
                T[i] = P + ((Q - P) * 6.0 * T[i]);
            }
            else if ((T[i] * 2.0) < 1)
            {
                T[i] = Q;
            }
            else if ((T[i] * 3.0) < 2)
            {
                T[i] = P + (Q - P) * ((2.0 / 3.0) - T[i]) * 6.0;
            }
            else
            {
                T[i] = P;
            }
        }

        R = (int)(T[0] * 255.0); //
        G = (int)(T[1] * 255.0); //Приводим к диапазону от 0 до 255
        B = (int)(T[2] * 255.0); //
    }
    return new RGB_Color(R, G, B);
}



Структуры RGB_Color и HSL_Color:

Код:
/*http://esate.ru, Alexei16*/

public struct RGB_Color
{
    public RGB_Color(int Red, int Green, int Blue)
    {
        R = Red;
        G = Green;
        B = Blue;
    }
    public int R, G, B;
}

public struct HSL_Color
{
    public HSL_Color(double Hue, double Saturation, double Lightness)
    {
        H = Hue;
        S = Saturation;
        L = Lightness;
    }
    public double H, S, L;
}


Когда всё необходимое для написания функции корректировки тона готово, можем браться за дело.

Напишем функцию тона:

Код:
/*http://esate.ru, Alexei16*/

public class Hue
{
    public unsafe static Bitmap ProcessImage(Filter Main,int Value)
    {
        RGB_Color CL_RGB = new RGB_Color(); //Создаем структуры
        HSL_Color CL_HSL = new HSL_Color();

        for (int I = 0; I < Main.AllPixelsBytes; I += Main.BytesPerPixel) //Проходимся по каждому пикселю
        {
            CL_RGB.B = *(Main.Unsafe_IMG_Scan0 + (I + 0)); //Получаем значение синего
            CL_RGB.G = *(Main.Unsafe_IMG_Scan0 + (I + 1)); //Получаем значение зелёного
            CL_RGB.R = *(Main.Unsafe_IMG_Scan0 + (I + 2)); //Получаем значение красного

            CL_HSL = RGB.RGB_TO_HSL(CL_RGB); //RGB -> HSL

            CL_HSL.H = (double)Value; //Изменяем тон

            CL_RGB = HSL.HSL_TO_RGB(CL_HSL); //HSL -> RGB

            *(Main.Unsafe_IMG_Scan0 + (I + 0)) = (byte)CL_RGB.B;
            *(Main.Unsafe_IMG_Scan0 + (I + 1)) = (byte)CL_RGB.G;
            *(Main.Unsafe_IMG_Scan0 + (I + 2)) = (byte)CL_RGB.R;
        }
        Main.UnLock();//Разблокируем биты изображения
        return Main.Picture;
    }
}


Здесь мы опять использовали класс Filter, написанный нами в 1-ом уроке и неуправляемые указатели, так как это наилучший способ добиться максимальной производительности в .NET!

Использование тона в приложениях:

Код:
/*http://esate.ru, Alexei16*/

Bitmap TestBitmap = Hue.ProcessImage(new Filter(MyImage), 280); //MyImage - это выше изображение 


Обработка изображений: Изображение до изменения тона Рисунок 2. Изображение до изменения тона.
Обработка изображений: Изображение после изменения тона (тон = 280) Рисунок 2. Изображение после изменения тона (тон = 280).

Изменение гаммы изображения

Повышение гаммы позволяет более отчетливо увидеть темные участки изображения.

Для коррекции гаммы сначала вычисляется таблица значений (RampTable). Таблица состоит из 255 ячеек.

Далее в зависимости от значений R, G, Bберутся данные из таблицы и присваиваются пикселю. Значения гаммы находятся в промежутке от 0.0 до 5.0.

Создадим класс Gamma и добавим в него массив байтов (RampTable):

Код:
/*http://esate.ru, Alexei16*/

public class Gamma
{
    private static byte[] RampTable = new byte[256];
}


Теперь в него необходимо добавить функцию генерации таблицы:

Код:
/*http://esate.ru, Alexei16*/

private static void GenerateRampTable(float Value)//Здесь Value - это значение гаммы
{
    double Gam = Math.Max(0.1, Math.Min(5.0, Value)); //Вычислям общий коэффицент гаммы,который потребуется для вычисления главного значения       
    double G = 1 / Gam; //Главное значение гаммы

    for (int I = 0; I < 256; I++)
    {
        RampTable[I] = (byte)Math.Min(255, (int)(Math.Pow(I / 255.0, G) * 255 + 0.5));//Вычисляем табличные данные
    }
}


Затем необходимо добавить главную функцию ProcessImage:

Код:
/*http://esate.ru, Alexei16*/

public static unsafe Bitmap ProcessImage(Filter Main,float Value)
{
    GenerateRampTable(Value); //Генерируем гамма-таблицу
    for (int I = 0; I < Main.AllPixelsBytes; I += Main.BytesPerPixel) //Проходим по каждому пикселю изображения
    {
        *(Main.Unsafe_IMG_Scan0 + (I + 0)) = RampTable[*(Main.Unsafe_IMG_Scan0 + (I + 0))]; //В зависимости от значения синего,ему присваивается значение из таблицы
        *(Main.Unsafe_IMG_Scan0 + (I + 1)) = RampTable[*(Main.Unsafe_IMG_Scan0 + (I + 1))]; //В зависимости от значения зелёного,ему присваивается значение из таблицы
        *(Main.Unsafe_IMG_Scan0 + (I + 2)) = RampTable[*(Main.Unsafe_IMG_Scan0 + (I + 2))]; //В зависимости от значения красного,ему присваивается значение из таблицы
    }
    Main.UnLock(); //Разблокируем биты изображения
    return Main.Picture;
}


Готово.

Использование гаммы в приложениях:

Код:
/*http://esate.ru, Alexei16*/

Bitmap TestBitmap = Gamma.ProcessImage(new Filter(MyImage), 3.4f); //MyImage - это выше изображение 


Обработка изображений: Изображение до изменения гаммы Рисунок 3. Изображение до изменения гаммы.
Обработка изображений: Изображение после повышения значения гаммы Рисунок 3. Изображение после повышения значения гаммы.

Нет доступа к просмотру комментариев.

^