Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Замена неторопливого Bitmap.GetPixel при приобретении HSB-колляций изображения

Anna | 17.06.2014 | нет комментариев
Так уж получилось, что мне нужно было написать маленькую программку для приобретения HSB-колляций изображения. Самое банальное решение пришло в голову сразу:

public struct HSB
{
      public float H, S, B;
}

public static HSB GetHSB(Bitmap img)
{
      HSB imgHSB = new HSB();
      int width = img.Width, height = img.Height;
      int pixelsCount = height * width;

      for (int i = 0; i < pixelsCount; i  )
      {
            int y = i / width, x = i % height;
            imgHSB.H  = img.GetPixel(x, y).GetHue();
            imgHSB.S  = img.GetPixel(x, y).GetSaturation();
            imgHSB.B  = img.GetPixel(x, y).GetBrightness();
      }

      imgHSB.H /=  pixelsCount;
      imgHSB.S /= pixelsCount;
      imgHSB.B /=  pixelsCount;
      return imgHSB;
}

Но оно не удовлетворило меня своей медлительностью: для изображения с размерами 2100х1500 пикселей способ выполнялся длинных 14209мс. Оказалось, что во каждому повинен способ Bitmap.GetPixel.
Следовало искать другие, больше стремительные методы решения задачи.

Первое, что пришло на ум — распараллелить цикл суммирования как-то так:

Parallel.For(0, pixelsCount, i =>
{
      int y = i / width, x = i % height;
      imgHSB.B  = img.GetPixel(x, y).GetBrightness();
      imgHSB.S  = img.GetPixel(x, y).GetSaturation();
      imgHSB.H  = img.GetPixel(x, y).GetHue();
});

Но компилятор был вопреки, потому как невозможно применять System.Drawing.Image из нескольких потоков единовременно, он доступен только в том потоке, которые его сотворил.
Пришлось искать новое решение. Я порылся в справке и на глаза попались способы Bitmap.LockBits иBitmap.UnlockBits, с поддержкой которых дозволено было преобразовать Bitmap в byte[]:

public static byte[] ConvertBitmapToArray(Bitmap img)
{
      Rectangle rect = new Rectangle(0, 0, img.Width, img.Height);
      System.Drawing.Imaging.BitmapData tempData =
            img.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite,
            img.PixelFormat);
      IntPtr ptr = tempData.Scan0;
      int bytes = img.Width * img.Height * 3;
      byte[] rgbValues = new byte[bytes];
      System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);
      img.UnlockBits(tempData);
      return rgbValues;
}

Осталось лишь преобразовать RGB в HSB. Но это теснее не так трудно — формула нашлась на первой же странице гугла по соответствующему запросу:

public static HSB GetHSB(Bitmap img)
{
      byte[] inData = ConvertBitmapToArray(img);
      HSB imgHSB = new HSB();
      int pixelsCount = inData.Count();
      float hue = 0, saturation = 0, brightness = 0;

      for (int i = 0; i < pixelsCount; i  = 3)
      {
            float MinRGB, MaxRGB, Delta;
            float R = inData[i];
            float G = inData[i   1];
            float B = inData[i   2];
            hue = 0;
            MinRGB = Math.Min(Math.Min(R, G), B);
            MaxRGB = Math.Max(Math.Max(R, G), B);
            Delta = MaxRGB - MinRGB;
            brightness = MaxRGB;

            if (MaxRGB != 0.0)
            {
                  saturation = 255 * Delta / MaxRGB;
            }

            else
            {
                  saturation = 0;
            }

            if (saturation != 0.0)
            {
                  if (R == MaxRGB)
                  {
                        hue = (G - B) / Delta;
                  }

                  else if (G == MaxRGB)
                  {
                        hue = 2   (B - R) / Delta;
                  }

                  else if (B == MaxRGB)
                  {
                        hue = 4   (R - G) / Delta;
                  }
            }

            else
            {
                  hue = -1;
                  hue = hue * 60;
            }

            if (hue < 0)
            {
                  hue = hue   360;
            }

            imgHSB.H  = hue;
            imgHSB.S  = saturation * 100 / 255;
            imgHSB.B  = brightness * 100 / 255;
      }

      imgHSB.H /= pixelsCount;
      imgHSB.S /= pixelsCount;
      imgHSB.B /= pixelsCount;
      return imgHSB;
}

Вот и все. Полученные способы ConvertBitmapToArray и GetHSB выполняются на том же изображении каждого за 289мс.

Хотелось еще увеличить скорость, распараллелив цикл из вышеприведенного способа GetHSB с поддержкой того же Parallel.For, но время исполнения увеличилось (311мс), да и полученные значения HSB для одного изображения все время были различные.

Вот и все, верю данная статья кому-нибудь поможет. Не претендую на идеальный код, возможно, кто-то напишет реализацию получше.

P.S.
Смею добавить, что значения HSB, полученные при помощи способов GetPixel(x,y).GetHue(), GetPixel(x,y).GetSaturation(), GetPixel(x,y).GetBrightness() не будут совпадать со значениями, полученными с применением моей реализации.

Источник: programmingmaster.ru
Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB