Pytanie Jak obliczyć średnie wartości kolorów rgb bitmapy


W mojej aplikacji C # (3.5) potrzebuję uzyskać średnie wartości kolorów dla czerwonego, zielonego i niebieskiego kanału mapy bitowej. Najlepiej bez korzystania z biblioteki zewnętrznej. Czy można to zrobić? Jeśli tak to jak? Z góry dziękuję.

Próba uczynienia rzeczy nieco bardziej precyzyjnymi: każdy piksel w bitmapie ma określoną wartość koloru RGB. Chciałbym uzyskać średnie wartości RGB dla wszystkich pikseli w obrazie.


14
2017-07-01 10:24


pochodzenie


Cóż, naiwną metodą byłoby pójść piksel po pikselu i uzyskać wartości RGB, co nie jest pewne, o co prosisz. Czy możesz określić, jakiego rodzaju średniej szukasz? - lc.
Masz rację. Mam nadzieję, że teraz jest lepiej. - Mats
Iść piksel po pikselu można zrobić inaczej - patrz odpowiedzi. Zastanawiam się, czy GPU może pomóc. - bohdan_trotsenko


Odpowiedzi:


Najszybszym sposobem jest użycie niebezpiecznego kodu:

BitmapData srcData = bm.LockBits(
            new Rectangle(0, 0, bm.Width, bm.Height), 
            ImageLockMode.ReadOnly, 
            PixelFormat.Format32bppArgb);

int stride = srcData.Stride;

IntPtr Scan0 = srcData.Scan0;

long[] totals = new long[] {0,0,0};

int width = bm.Width;
int height = bm.Height;

unsafe
{
  byte* p = (byte*) (void*) Scan0;

  for (int y = 0; y < height; y++)
  {
    for (int x = 0; x < width; x++)
    {
      for (int color = 0; color < 3; color++)
      {
        int idx = (y*stride) + x*4 + color;

        totals[color] += p[idx];
      }
    }
  }
}

int avgB = totals[0] / (width*height);
int avgG = totals[1] / (width*height);
int avgR = totals[2] / (width*height);

Uwaga: nie testowałem tego kodu ... (Mogłem przeciąć kilka rogów)

Ten kod również tworzy obraz 32-bitowy. Dla obrazów 24-bitowych. Zmienić x * 4 do x * 3


20
2017-07-01 10:34



ja Ukradłem powyższy kod, naprawił go zawinął w funkcję, aby odpowiedzieć na inne pytanie SO w stackoverflow.com/questions/6177499/... - Till
Czy możesz wyjaśnić, dlaczego jest o wiele szybciej? - C. Ross
@ C.Ross Uważam, że dzieje się tak, ponieważ konwertujemy wszystkie dane obrazu na bajty i uzyskujemy dostęp do tablicy bajtów zamiast wywoływania i funkcji do ustalenia wartości rbg w danym punkcie. Koszt tej wysokościwidth + imagedata-> bajty. Koszt innej wysokościszerokość * imagedata-> rbg. - Patrick Lorio
Ostatnie trzy linie powyższego kodu są nieprawidłowe. Kolor bajtów w obszarze danych bitmapowych zamawiał BGR, więc czerwony kanał jest naprawdę sumaryczny [2], a niebieski kanał jest sumowany [0]. - David Hay
Łał. Różnica robi to w ten sposób kontra użycie GetPixel jest zdumiewający. Pętla nad obrazem 500x600 dwa razy przy użyciu GetPixel trwało ~ 1000 ms, zastosowanie tej metody trwało ~ 13 ms. - mpen


Oto znacznie prostszy sposób:

Bitmap bmp = new Bitmap(1, 1);
Bitmap orig = (Bitmap)Bitmap.FromFile("path");
using (Graphics g = Graphics.FromImage(bmp))
{
    // updated: the Interpolation mode needs to be set to 
    // HighQualityBilinear or HighQualityBicubic or this method
    // doesn't work at all.  With either setting, the results are
    // slightly different from the averaging method.
    g.InterpolationMode = InterpolationMode.HighQualityBicubic;
    g.DrawImage(orig, new Rectangle(0, 0, 1, 1));
}
Color pixel = bmp.GetPixel(0, 0);
// pixel will contain average values for entire orig Bitmap
byte avgR = pixel.R; // etc.

Zasadniczo używasz DrawImage do kopiowania oryginalnej bitmapy do 1-pikselowej bitmapy. Wartości RGB tego 1 piksela będą wówczas reprezentowały średnie dla całego oryginału. GetPixel działa stosunkowo wolno, ale tylko wtedy, gdy używa się go w dużej bitmapie, piksel po pikselu. Nazywając to raz tutaj, to nie jest biggie.

Używanie LockBits jest rzeczywiście szybkie, ale niektórzy użytkownicy Windowsa mają zasady bezpieczeństwa, które uniemożliwiają wykonanie "niebezpiecznego" kodu. Wspominam o tym, ponieważ ten fakt ostatnio mnie ugryzł.

Aktualizacja: przy ustawieniu InterpolationMode na HighQualityBicubic, ta metoda zajmuje około dwa razy tyle czasu, co uśrednianie za pomocą LockBits; z HighQualityBilinear, zajmuje tylko nieco dłużej niż LockBits. Więc chyba, że ​​Twoi użytkownicy mają zakazaną politykę bezpieczeństwa unsafe Kod, zdecydowanie nie korzystam z mojej metody.

Aktualizacja 2:  z biegiem czasu, teraz rozumiem, dlaczego to podejście w ogóle nie działa. Nawet algorytmy interpolacji o najwyższej jakości zawierają tylko kilka sąsiednich pikseli, więc istnieje limit na to, ile obrazu można przyciąć bez utraty informacji. A zgniecenie obrazu do jednego piksela znacznie przekracza ten limit, bez względu na to, jakiego algorytmu używasz.

Jedynym sposobem, aby to zrobić, jest stopniowe zmniejszanie obrazu (może zmniejszać go o połowę za każdym razem), aż osiągniesz rozmiar jednego piksela. Nie potrafię wyrazić słowami, jakie byłyby zupełne marnowanie czasu na pisanie czegoś takiego, więc cieszę się, że powstrzymałem się, gdy o tym pomyślałem. :)

Proszę, nikt już nie głosuje na tę odpowiedź - to może być mój najgłupszy pomysł w historii.


10
2017-08-26 15:36



Lol, Interesujące. Zastanawiam się, czy jest to szybsze niż metoda LockBits? (Czas bym sam sobie zrobił, ale jestem LAZY! Lol) +1 - CodeAndCats
+1 Czysty geniusz! - David Heffernan
Nie można się oprzeć pomimo wyraźnej prośby o nieprzyjęcie uprowadzenia. Dużo się nauczyłem czytając twoją odpowiedź. - prokilomer
Dziękuję za wzmiankę o zasadach bezpieczeństwa blokujących niebezpieczny kod - Vasiliy
To nie jest głupi pomysł. To dobry pomysł, żeby się nie udać. :-) - Joe B


Tego typu rzeczy będą działać, ale mogą nie być wystarczająco szybkie, aby były tak przydatne.

public static Color getDominantColor(Bitmap bmp)
{

       //Used for tally
       int r = 0;
       int g = 0;
       int b = 0;

     int total = 0;

     for (int x = 0; x < bmp.Width; x++)
     {
          for (int y = 0; y < bmp.Height; y++)
          {
               Color clr = bmp.GetPixel(x, y);

               r += clr.R;
               g += clr.G;
               b += clr.B;

               total++;
          }
     }

     //Calculate average
     r /= total;
     g /= total;
     b /= total;

     return Color.FromArgb(r, g, b);
}

9
2017-07-01 10:33



Będzie wolno. Po prostu przetestuj to. To, co faktycznie robi GetPixel (), to blokowanie bitmapy i pobieranie pojedynczego piksela. - bohdan_trotsenko
działa idealnie - garik