Конференция "Начинающим" » Проблема с использованием GetMem [D2005, XP]
 
  • Илья_666 (01.03.18 19:17) [0]
    В общем суть проблемы такая: неправильно "размывается" изображение.
    Некогда я как-то спрашивал на форуме помощи по поводу реализации blur'а, и мне помогли. Правда сейчас, спустя некоторое время, я понял, что та реализация немного хромает. Конкретно у нее несколько недостатков:
    - низкая скорость работы;
    - края исходного изображения не обрабатываются.
    Я не силен в реализации различного рода фильтров, поэтому поискал готовые решения. Нашел на одном (немецком) сайте вполне приемлемый вариант, однако, мне показалось, что есть возможность реализацию немного "подправить" (+ получить новые знания в области работы с памятью).
    В принципе все прошло неплохо, но есть один нюанс: модифицированный вариант исходного кода "размывает" изображение только по оси Х, что дает интересный эффект, но не совсем полноценный blur.

    Если текст выше лень читать, то у меня есть кое-что покороче: помогите понять, в чем дело с модифицированным вариантом кода.
    Для сравнения я прикладываю оригинал (со всеми исходными комментариями) кода ниже.

    Оригинальный код

    procedure TForm1.BmpGBlur(Bmp: TBitmap; radius: Single);
    Type
     TRGB      = Packed Record b, g, r: Byte End;
     TRGBs     = Packed Record b, g, r: Single End;
     TRGBArray = Array[0..0] of TRGB;
    Var
     MatrixRadius: Byte;
     Matrix : Array[-100..100] of Single;

     Procedure CalculateMatrix;
     Var x: Integer; Divisor: Single;
     Begin
       radius:=radius+1; // der mittel/nullpunkt muss mitgerechnet werden
       MatrixRadius:=Trunc(radius);
       If Frac(radius)=0 Then Dec(MatrixRadius);
       Divisor:=0;
       For x:=-MatrixRadius To MatrixRadius Do Begin
         Matrix[x]:=radius-abs(x);
         Divisor:=Divisor+Matrix[x];
       End;
       For x:=-MatrixRadius To MatrixRadius Do
         Matrix[x]:=Matrix[x]/Divisor;
     End;

    Var
     BmpSL              : ^TRGBArray;
     BmpRGB             : ^TRGB;
     BmpCopy            : Array of Array of TRGBs;
     BmpCopyRGB         : ^TRGBs;
     R, G, B            : Single;
     BmpWidth, BmpHeight: Integer;
     x, y, mx           : Integer;
    Begin
     Bmp.PixelFormat:=pf24bit;
     If radius<=0 Then radius:=1 Else If radius>99 Then radius:=99; // radius bereich 0 < radius < 99
     CalculateMatrix;
     BmpWidth:=Bmp.Width;
     BmpHeight:=Bmp.Height;
     SetLength(BmpCopy,BmpHeight,BmpWidth);
     // Alle Bildpunkte ins BmpCopy-Array schreiben und gleichzeitig HORIZONTAL blurren
     For y:=0 To Pred(BmpHeight) Do Begin
       BmpSL:=Bmp.Scanline[y];
       BmpCopyRGB:=@BmpCopy[y,0];
       For x:=0 to Pred(BmpWidth) Do Begin
         R:=0; G:=0; B:=0;
         For Mx:=-MatrixRadius To MatrixRadius Do Begin
           If x+mx<0 Then
             BmpRGB:=@BmpSL^[0]              // erster Pixel
           Else If x+mx>=BmpWidth Then
             BmpRGB:=@BmpSL^[Pred(BmpWidth)] // letzter Pixel
           Else
             BmpRGB:=@BmpSL^[x+mx];
           B:=B+BmpRGB^.b*Matrix[mx];
           G:=G+BmpRGB^.g*Matrix[mx];
           R:=R+BmpRGB^.r*Matrix[mx];
         End;
         BmpCopyRGB^.b:=B;  // Farbwerte werden im Typ Single zwischengespeichert !
         BmpCopyRGB^.g:=G;
         BmpCopyRGB^.r:=R;
         Inc(BmpCopyRGB);
       End;
     End;
     // Alle Bildpunkte zur&#252;ck ins Bmp-Bitmap schreiben und gleichzeitig VERTIKAL blurren
     For y:=0 To Pred(BmpHeight) Do Begin
       BmpRGB:=Bmp.ScanLine[y];
       For x:=0 to Pred(BmpWidth) Do Begin
         R:=0; G:=0; B:=0;
         For mx:=-MatrixRadius To MatrixRadius Do Begin
           If y+mx<=0 Then
             BmpCopyRGB:=@BmpCopy[0,x]                // erster Pixel
           Else If y+mx>=BmpHeight Then
             BmpCopyRGB:=@BmpCopy[Pred(BmpHeight),x]  // letzter Pixel
           Else
             BmpCopyRGB:=@BmpCopy[y+mx,x];
           B:=B+BmpCopyRGB^.b*Matrix[mx];
           G:=G+BmpCopyRGB^.g*Matrix[mx];
           R:=R+BmpCopyRGB^.r*Matrix[mx];
         End;
         BmpRGB^.b:=Round(B);
         BmpRGB^.g:=Round(G);
         BmpRGB^.r:=Round(R);
         Inc(BmpRGB);
       End;
     End;
    End;

  • Илья_666 (01.03.18 19:18) [1]
    Продолжение

    Модифицированный код
    Код в комментариях - это оригинальный код

    procedure TForm1.BmpGBlur(Bmp: TBitmap; radius: single);
    Type
     TRGB      = Packed Record b, g, r: Byte End;
     TRGBs     = Packed Record b, g, r: Single End;
     TRGBArray = Array[0..0] of TRGB;
     PArrayX = ^ArrayX;
     ArrayX  =  array [0..0] of TRGBs;

    Var
     MatrixRadius: Byte;
     Matrix : Array[-100..100] of Single;

     Procedure CalculateMatrix;
     Var x: Integer; Divisor: Single;
     Begin
       radius:=radius+1; // der mittel/nullpunkt muss mitgerechnet werden
       MatrixRadius:=Trunc(radius);
       If Frac(radius)=0 Then Dec(MatrixRadius);
       Divisor:=0;
       For x:=-MatrixRadius To MatrixRadius Do Begin
         Matrix[x]:=radius-abs(x);
         Divisor:=Divisor+Matrix[x];
       End;
       For x:=-MatrixRadius To MatrixRadius Do
         Matrix[x]:=Matrix[x]/Divisor;
     End;

    Var
     BmpSL              : ^TRGBArray;
     BmpRGB             : ^TRGB;
     BmpCopy            : Array of Array of TRGBs;
     BmpCopyRGB         : ^TRGBs;
     R, G, B            : Single;
     BmpWidth, BmpHeight: Integer;
     x, y, mx           : Integer;
     arr, pRow          : PArrayX;
    Begin
     Bmp.PixelFormat:=pf24bit;
     If radius<=0 Then radius:=1 Else If radius>99 Then radius:=99; // radius bereich 0 < radius < 99
     CalculateMatrix;
     BmpWidth:=Bmp.Width;
     BmpHeight:=Bmp.Height;
    //  SetLength(BmpCopy,BmpHeight,BmpWidth);

     try
     GetMem(Arr, (BmpWidth * BmpHeight) * SizeOf(TRGBs));
     // Alle Bildpunkte ins BmpCopy-Array schreiben und gleichzeitig HORIZONTAL blurren
     For y:=0 To Pred(BmpHeight) Do Begin
       pRow := @Arr[Y * BmpWidth];
       BmpSL:=Bmp.Scanline[y];
    //    BmpCopyRGB:=@BmpCopy[y,0];
       For x:=0 to Pred(BmpWidth) Do Begin
         R:=0; G:=0; B:=0;
         For Mx:=-MatrixRadius To MatrixRadius Do Begin
           If x+mx<0 Then
             BmpRGB:=@BmpSL^[0]              // erster Pixel
           Else If x+mx>=BmpWidth Then
             BmpRGB:=@BmpSL^[Pred(BmpWidth)] // letzter Pixel
           Else
             BmpRGB:=@BmpSL^[x+mx];
           B:=B+BmpRGB^.b*Matrix[mx];
           G:=G+BmpRGB^.g*Matrix[mx];
           R:=R+BmpRGB^.r*Matrix[mx];
         End;
    //      BmpCopyRGB^.b:=B;  // Farbwerte werden im Typ Single zwischengespeichert !
    //      BmpCopyRGB^.g:=G;
    //      BmpCopyRGB^.r:=R;
         pRow[x].b:=b;  // Farbwerte werden im Typ Single zwischengespeichert !
         pRow[x].g:=G;
         pRow[x].r:=R;
    //      Inc(BmpCopyRGB);
       End;
     End;

     // Alle Bildpunkte zur&#252;ck ins Bmp-Bitmap schreiben und gleichzeitig VERTIKAL blurren
     For y:=0 To Pred(BmpHeight) Do Begin
       BmpRGB:=Bmp.ScanLine[y];
       pRow := @Arr[Y * BmpWidth];
       For x:=0 to Pred(BmpWidth) Do Begin
         R:=0; G:=0; B:=0;
         For mx:=-MatrixRadius To MatrixRadius Do Begin
           If y+mx<=0 Then
             BmpCopyRGB:=@pRow[0]//@BmpCopy[0]                // erster Pixel
           Else If y+mx>=BmpHeight Then
             BmpCopyRGB:=@pRow[Pred(BmpHeight)]//@BmpCopy[Pred(BmpHeight)]  // letzter Pixel
           Else
             BmpCopyRGB:=@pRow[y+mx];//@BmpCopy[y+mx];
           B:=B+BmpCopyRGB^.b*Matrix[mx];
           G:=G+BmpCopyRGB^.g*Matrix[mx];
           R:=R+BmpCopyRGB^.r*Matrix[mx];
         End;
    //      BmpRGB^.b:=Round(B);
    //      BmpRGB^.g:=Round(G);
    //      BmpRGB^.r:=Round(R);
         BmpRGB^.b:=Round(pRow[x].b);
         BmpRGB^.g:=Round(pRow[x].g);
         BmpRGB^.r:=Round(pRow[x].r);
         Inc(BmpRGB);
       End;
     End;

     finally
       FreeMem(Arr, (BmpWidth * BmpHeight) * SizeOf(TRGBs));
     end;



    В итоге, использование GetMem дает небольшой прирост в скорости выполнения процедуры, но вот результат не совсем тот, на который я рассчитывал.
    Буду благодарен за любые подсказки в плане того, где кроется ошибка с применением GetMem.
    Спасибо всем откликнувшимся.
  • RWolf © (02.03.18 10:46) [2]
    Вообще говоря, во втором цикле исправленного кода вычисленные переменные R, G, B явно не используются (сохранение результатов закомментировано); здравый смысл подсказывает, что блюр обсчитывается именно в них.
  • Илья_666 (02.03.18 16:45) [3]
    RWolf
    Добрый день!
    Действительно, допустил ошибку, исправил ее, но результат теперь еще хуже.
    Ниже 4-ре ссылки на изображения, чтобы визуально было понятнее о чем речь.


    Изображение 1
    Оригинальное изображение, которое я "размываю" с целью тестирования

    https://yadi.sk/i/L9UrXpJK3SwVuV


    Изображение 2
    Оригинал изображения, "размытый" с помощью исходного кода

    https://yadi.sk/i/tBRgdzB73SwVuZ


    Изображение 3
    Оригинал изображения, "размытый" с помощью модифицированного кода до исправления моей ошибки, указанной в сообщении №2

    https://yadi.sk/i/Or1LN2sr3SwVrC


    Изображение 4
    Оригинал изображения, "размытый" с помощью модифицированного кода после исправления моей ошибки, указанной в сообщении №2

    https://yadi.sk/i/gUGhH-Bo3Sweja

    Все-таки никак не могу понять, что не так с кодом. Заменил ведь только SetLength на GetMem и все.
  • Styx © (02.03.18 21:07) [4]

    > Действительно, допустил ошибку, исправил ее, но результат
    > теперь еще хуже.

    Так где исправленный код-то?
  • Илья_666 (02.03.18 22:54) [5]
    Styx
    Код в сообщении #1, раскомментировать три строки с BmpRGB^ в конце второго цикла и там же закомментировать последующие строки с BmpRGB^ := Round(pRow[x]). После скомпилировать, выполнить и получим результат как на Изображение 4, сообщение #3.
  • Redmond (03.03.18 21:20) [6]
    Сделать вы что конкретно хотите? Матрица 100 на 100?? Нафига такая? Вам надо скорость или качество? Или что?
    И если что - нельзя просто брать и "размывать" один единственный битмап сам в себе. Должно быть два битмапа - источник и приёмник.
    А по поводу "краёв" - это всем известная спорная ситуация, описана в любой хорошей книге по базовым алгоритмам графики - однозначного решения нет, три или четыре варианта как их обрабатывать.
  • Илья_666 (04.03.18 17:54) [7]
    Redmond

    > Сделать вы что конкретно хотите?

    Конкретно хочу понять, почему замена SetLength на GetMem прошла неудачно и изображение "размывается" только по горизонтали.


    > Вам надо скорость или качество?

    Хотелось бы и скорость и качество, но не в этом случае. Я и так попытался увеличить скорость работы кода путем применения функции GetMem, но каким-то однобоким получился результат.


    > нельзя просто брать и "размывать" один единственный битмап
    > сам в себе

    Не пойму, почему нельзя, но таки посмею заявить: в вышеприведенном коде содержимое битмапа копируется в буфер (создаваемый SetLength (в моем варианте - GetMem)) и только потом с ним идет работа.


    > А по поводу "краёв"

    А вот по поводу "краев" я ничего и не говорил. Проблема, если внимательно прочесть вопрос и посмотреть код, заключается в неправильном применении GetMem и дальнейшей работе с буфером (правда я так и не могу уразуметь, где же я там напортачил).
    Просто скомпилируйте программку с обоими вариантами кода и подсуньте ему свою картинку. Вы сразу поймете, о чем идет речь.
  • Redmond (05.03.18 16:27) [8]
    Просто вы упомянули:

    > - края исходного изображения не обрабатываются.

    Вот и пояснял. :)
    Если совсем кратко - они и не должны обрабатываться основным алгоритмом. Они обрабатываются отдельно, одним из модифицированных алгоритмов.

    > в вышеприведенном коде

    омм... Действительно. Недоглядел. Но скорее потому что это сделано странно и капец как не продумано на перспективу. :)
    Как локальная копия? Да ещё и в общем случае не идентичного размера? А копирование порциями, да даже не по 32 бита, а по 8 бит?? *готовит банку огурцов*

    Это нужно не "чинить", а "половину переделывать". Попробую, ток чуть позже.
    А вам действительно надо MatrixRadius от 0 до 99 и Matrix 100 на 100? Более упрощённые версии размытия не подходят или чего?
  • Redmond (05.03.18 20:29) [9]
    Что-то не получается у меня найти время чтоб спокойно и полностью вчитаться в этот код.
    Там всё же используется "алгоритм обработки краёв" (хоть и не самый хороший) - там где подписано "erster Pixel" и "letzter Pixel", но вот почему он работает вместе с основным? Это плохая идея.
    И ещё фильтр там оказывается не полноценный, а упрощённый (Hor/Ver). Я с такими не работал, они обычно слишком врут. :) Но тут всего лишь размытие, может для него не так критично. Надо протестировать.
  • Плохиш © (06.03.18 13:08) [10]
    Прикольно

    hab meinen code nochmal überarbeitet und ist jetzt wesentlich schneller geworden, vorallem bei großen radien (um das 20-fache etwa)!!!!!

    http://www.delphipraxis.net/93757-post10.html
  • Sha © (06.03.18 19:56) [11]
    > Прикольно

    это не тот код, который нельзя еще раз ускорить )
  • Илья_666 (06.03.18 21:39) [12]
    Redmond

    > Просто вы упомянули:
    >
    > > - края исходного изображения не обрабатываются.
    >

    Да, действительно, упомянуть-то упомянул, но не пояснил. По поводу отсутствия обработки краев - это относилось к тому коду для "размытия", который я использовал ранее. Не к тому, который опубликован в данной теме.


    > сделано странно и капец как не продумано на перспективу.
    >  :)

    С этим не буду спорить- в обработке изображений не разбираюсь совсем, поэтому не могу судить насколько дальновиден был автор оригинального кода когда писал его. В конце концов - он же работает)).


    > А вам действительно надо MatrixRadius от 0 до 99 и Matrix
    > 100 на 100? Более упрощённые версии размытия не подходят
    > или чего?

    Матрица, видимо, создается с запасом как раз под максимальный размер радиуса "размытия". Думаю, что максимальный радиус можно оставить в рамках 10 ед. (соответственно, матрица будет -10..+10).


    > но вот почему он работает вместе с основным? Это плохая
    > идея.

    Как я и говорил ранее - вообще не разбираюсь в этом, но автору кода виднее.Если есть идеи по изменению кода в лучшую сторону - дерзайте, выложите их здесь. У меня таких идей нет. Точнее, попытался заменить SetLength на GetMem и что-то пошло совсем не так((

    Плохиш

    > Прикольно

    Действительно, забавная формулировка. Правда, исходный код я брал из другого форума:
    https://www.entwickler-ecke.de/viewtopic.php?sid=54f81316bc966742e70265bfe1a5900d&t=19270&view=dl

    Sha

    > это не тот код, который нельзя еще раз ускорить )

    Дорогу осилит идущий))
    Я вот уже который день пытаюсь заставить модифицированный код "размывать" пиксели не только горизонтально, но и вертикально, но все без толку. Сдается мне, что придется использовать оригинальный вариант кода.
  • Styx © (07.03.18 12:31) [13]
    Imho, стоит разобраться не с кодом, а с алгоритмом. А вникать в чужой код, не понимая, что он делает - напрасная трата времени. Если Вам нужно размытие - то, как я понимаю, нужно делать свёртку с двумерным гауссианом. Чтобы работало быстрей, можно это делать в потоках - для каждого пикселя же расчёт независимый - или вообще на видеокарте. Я так это понимаю.
  • Илья_666 (19.03.18 21:07) [14]
    Добрый вечер, это снова я.
    В общем, разобрался с кодом и решил использовать оригинал - оптимизатор из меня все равно никакой))
 
Конференция "Начинающим" » Проблема с использованием GetMem [D2005, XP]
Есть новые Нет новых   [134427   +34][b:0][p:0.005]