Внимание!

Эта публикация перенесена в раздел уроков по адресу Графические фильтры на основе попиксельной обработки изображений (Часть 3).
К ней прикреплена новая отдельная ветка комментариев форума, которую вы можетет найти после текста публикации.
Обсуждение публикации рекуомендуется вести по новому адресу, который указан выше.

Графические фильтры на основе попиксельной обработки изображений (Часть 3)

В этом уроке мы научимся изменять тон и гамму изображения. Для изменения тона будем использовать цветовую модель HSL.О ней я сейчас расскажу поподробней...

Цветовая модель HSL

HSL – это цветовая модель, в которой координатами цвета являются Тон(Hue),Насыщенность(Saturation),Светлота(Lightness).
Такие значения принимают цветовые составляющие HSL :
H — тон [0; 360]
S — насыщенность [0; 1]
L — светлота [0; 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!

Гамма

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


Оригинал


При повышеном значения гаммы.

Для коррекции гаммы,сначала вычисляется таблица значений(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;
}


Готово!!!

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

• Тон

Оригинал


Тон = 280
/*http://esate.ru, Alexei16*/


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

• Gamma
Результат работы программы(скриншоты) я приводил в начале рассказа о гамме!
/*http://esate.ru, Alexei16*/


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

Удачи!
</cut>
0       1059        13.01.2011        2

Внимание!

Эта публикация перенесена в раздел уроков по адресу Графические фильтры на основе попиксельной обработки изображений (Часть 3).
К ней прикреплена новая отдельная ветка комментариев форума, которую вы можетет найти после текста публикации.
Обсуждение публикации рекуомендуется вести по новому адресу, который указан выше.

0  
13.01.2011 00:00:00
Классный урок!
0  
14.01.2011 00:00:00
молодец, полезный урок!
^