Obróbka obrazka - jak przyspieszyć algorytm

0

Poniższy kod przedstawia próbę filtrowania i obróbki obrazu. Chodzi mi wyłącznie o filtrowanie jakby pikselowe, czyli uzyskuje się kolor piksela w postaci RGB, potem na drodze oblicze matematycznych uzyskuje się nowy kolor piksela w postaci RGB. Operację powtarza się dla każdego piksela obrazka. Tak mogą działać takie filtry, jak jasność, kontrast, negatyw, skala szarości, sepia, odcień, nasycenie i inne.

Inne filtry, które są stosowane w programach graficznych polegają na operacjach takich, że w danej chwili pod uwagę bierze się kilka pikseli. Wtedy złożoność algorytmu rośnie. Takimi filtrami mogą być usuwanie szumu, rozmycie, mediana, szukanie krawędzi, wyostrzenie i inne. Jednak implementacja takich filtrów mnie nie interesuje.

W poniższym fragmencie jest zawarta procedura, która obrabia obrazek. Na próbę zaprogramowałem wykonywanie negatywu i zmniejszenie kontrastu (obrazek jest szarawy). Obrabiany obrazek jest zapisany w PictureI typu TBitmap, a do obrobienia w ABitmap typu TBitmap. Przebieg filtracji polega na tym, że dla każdego piksela obrazka wykonujemy następujące czynności:

  1. Uzyskujemy kolor piksela i zapisujemy go w zmiennych RI,GI,BI
  2. Obliczamy nowy kolor piksela na podstawie starego i zapisujemy w RO,GO,BO
  3. Nadajemy pikselowi nowy kolor na podstawie wartośći RO,GO,BO

Problem polega na tym, że wykonanie poniższej procedury zajmuje ok. 1 sekundę dla obrazka o rozdzielczości 320x240 na komputerze Pentium IV o częstotliwości 2,4Ghz z HT i 512MB RAM, Windows XP. Sprawdziłem, że wtedy jest pełne obciążenie procesora.

Natomiast w programach do obróbki zdjęć to samo jest wykonywane dużo szybciej. Jak przerobić podaną procedurę, żeby czas obróbki nie przekraczał 20-30 milisekund, a więc, żeby program mógł generować obraz z częstotliwością minimum 25-30 klatek na sekundę. Z pominięciem tej procedury program może tworzyć i wyświetlać obrazek z szybkością 100 klatek na sekundę i więcej, a obciążenie procesora wtedy jest bardzo niewielkie, rzędu maksimum kilka procent.

Procedura programu napisanego w Delphi 5

procedure TFormMain.DoColorFilters;
var
RGBindex:LongInt;          // Kod koloru odczytany z piksela
XXXXX,YYYYY:Word;         // Liczniki pętli FOR
RI,GI,BI,RO,GO,BO:Byte;  //  Zmienne przechowujące informacje RGB o starym i nowym kolorze
ContrastFactor:Real;          //  Stała użyta w filtrze
BrightnessFactor:Byte;       //  Stała użyta w filtrze
Begin
 If FiltrForm.FilterColor.Checked Then  // Sprawdzenie, czy jest włączona funkcja filtrowania
 Begin
  For YYYYY:=0 To (PictureI.Height-1) Do
  Begin
   For XXXXX:=0 To (PictureI.Width-1) Do
   Begin

    // Uzyskanie koloru piksela źródłowego:
    RGBindex:=ABitmap.Canvas.Pixels[XXXXX,YYYYY];
    RI:=((StringToColor('$0000'+Copy(IntToHex(RGBindex,6),5,2)+''))   div   1);
    GI:=((StringToColor('$00'+Copy(IntToHex(RGBindex,6),3,2)+'00'))  div  256);
    BI:=((StringToColor('$'+Copy(IntToHex(RGBindex,6),1,2)+'0000')) div 65536);


    // RI,GI,BI - w tych zmiennych jest przechowywana informacja RGB aktualnego koloru piksela
    // RO,GO,BO - w tych zmiennych będzie przechowywana informacja RGB o nowym kolorze piksela

    // Modyfikacja koloru i zapis do RO,GO,BO koloru uzyskanego na podstawie RI,GI,BI

    // Negatyw:
    RO:=255-RI;
    GO:=255-GI;
    BO:=255-BI;

    // Kontrast i jasność:
    ContrastFactor:=0.5;
    BrightnessFactor:=64;
    RO:=(Round(RI*ContrastFactor)+BrightnessFactor);
    GO:=(Round(GI*ContrastFactor)+BrightnessFactor);
    BO:=(Round(BI*ContrastFactor)+BrightnessFactor);

    // Nadanie pikselowi nowego koloru
    PictureI.Canvas.Pixels[XXXXX,YYYYY]:=RGB(RO,GO,BO);
   End;
  End;
 End;
End;
0

użyj scanline zamiast pixels

0

co tam wyprawiasz z tymi StringToColor, jeszcze jakeś hex...

mosz kolor w long: kolor
wtedy:
r := Byte(kolor);
g := Byte((kolor and 0x00FF) shr 8);
b := Byte((kolor and 0x0000FF) shr 16);

albo

var prgb : PChar;

prgb := @kolor;

r := prgb[0];
g := prgb[1];
b := prgb[2];

0

Twoja procedura w mojej wersji:

procedure DoColorFilters(Bitmap:TBitmap; ContrastFactor:real; BrightnessFactor:byte);
type TRGBarray=array[Word] of TRGBTriple;
     PRGBarray=^TRGBarray;
var x,y:integer;
    line:PRGBarray;
begin
Bitmap.PixelFormat:=pf24bit;
for y:=Bitmap.Height-1 downto 0 do
  begin
  line:=Bitmap.ScanLine[y];
  for x:=Bitmap.Width-1 downto 0 do
    begin
    //Negatyw
    line[x].rgbtRed:=byte(not line[x].rgbtRed);
    line[x].rgbtGreen:=byte(not line[x].rgbtGreen);
    line[x].rgbtBlue:=byte(not line[x].rgbtBlue);

    //Kontrast i jasność
    line[x].rgbtRed:=byte(Round(line[x].rgbtRed*ContrastFactor)+BrightnessFactor);
    line[x].rgbtGreen:=byte(Round(line[x].rgbtGreen*ContrastFactor)+BrightnessFactor);
    line[x].rgbtBlue:=byte(Round(line[x].rgbtBlue*ContrastFactor)+BrightnessFactor);
    end;
  end;
end;

Przykład użycia:

DoColorFilters(Image1.Picture.Bitmap, 0.5, 32);
Image1.Refresh;

Jak chcesz samego negatywu, bo zależy Ci na setkach klatek na sekundę, to możesz to robić tak:

procedure DoColorFilters(Bitmap:TBitmap);
type TPixels=array[Word] of DWORD;
     PPixels=^TPixels;
var x,y:integer;
    line:PPixels;
begin
Bitmap.PixelFormat:=pf32bit;
for y:=Bitmap.Height-1 downto 0 do
  begin
  line:=Bitmap.ScanLine[y];
  for x:=Bitmap.Width-1 downto 0 do
    line[x]:=not line[x];
  end;
end;
0

zawsze możesz jeszcze trochę to podśrubować robiąc wstawki asemblerowe(szkoda tylko że delphi tworzy kod dla klasycznych procesorów 386 i nie <ort>używa </ort>mmx'ów bo urzywając tego można nieźle przyspieszyć)

0

Wersja kolegi Szczawika jest istotnym postępem, ale koleg zatrzymał się w pół kroku.
Czemy zmienny przecinek?
r=lo(round(r*Kontrast))
0 <= kontrast <= 1
kontrast = licznik/mianownik
licznik i mianownik są całkowite, czyli
r = r * licznik div mianownik

0

Dałem zmiennoprzecinkowo, bo taki typ był pierwszym poście andrzejlisek0, osobiście bym to robił przez MulDiv - tak chyba najwygodniej. Ewentualnie kontrast można potraktować jako licznik8/256 co zapewnia cały zakres od 0 do 1 z przyzwoitą precyzją (jak nie pasuje, to licznik16/65536). Wystarczy wtedy zrobić:

r = hi(r * licznik8);

Można jeszcze kilka rzeczy przyspieszyć, jak na przykład Bitmap.Width odczytać raz do zmiennej, a potem czytać ze zmiennej, a nie z obiektu. Ale to detale. W każdym razie, wzrost szybkości do pierwotnego kodu powinien być znaczny.

1 użytkowników online, w tym zalogowanych: 0, gości: 1