Конференция "Media" » Удаление шума на изображении [D7, WinXP]
 
  • shart © (27.02.07 20:56) [0]
    Какой есть алгоритм для быстрого удаления шума на изображении? Изображение берётся с DV-камеры и соответственно слишким много шума...
  • MBo © (28.02.07 06:18) [1]
    медианная фильтрация, гауссово сглаживание
  • shart © (28.02.07 11:48) [2]
    MBo, спасибо! А реализации нигде не найдётся? )
  • Jeer © (28.02.07 13:23) [3]
    Реализация медианного фильтра примитивна.

    Выбирается размер окна, например 3*3 pix = 9 pix
    Извлекается массив из 9 pix и производится его сортировка, извлекается значение среднего по номеру пикселя т.е. 5 -го и возвращается в исходное изображение на место среднего элемента окна, т.е. px[2,2].
    Процесс повторяется со сдвигом на 1 px для всего изображения раздельно для каждого из каналов RGB.
    Краевые точки (контур) остается необработанным, либо дообрабатывается одномерной медианой.
  • SamBrook © (01.03.07 00:01) [4]

    > Реализация медианного фильтра примитивна.

    В таком виде - конечно :)
    В правильном - сортировки вообще не используются.

    А вообще-то, для устранения шума нужно, как минимум, знать его характеристики.
    А то вместе с водой и младенец увяжется.
  • Jeer © (01.03.07 10:34) [5]

    > В правильном


    "Правильный" <> "Эффективный", к тому же для уяснения понятия "эффективный" должны быть озвучены критерии, а понятие "правильный" вообще не является техническим.

    Приведенное мной словесное описание алгоритма медианной фильтрации основано на классическом определении медианы и легко для понимания начинающими сути вопроса и реализации алгоритма.

    Разновидностей же реализации медианной фильтрации много, достаточно привести пример м.фильтрации с произвольной апертурой (не только не квадратной, но и не прямоугольной), основанный на разовом вычислении гистограммы и медианы для начального положения апертуры и последующей их корректировкой.
    Данный алгоритм, по количеству элементарных операций, имеет заметное преимущество перед классическими.
     

    > А вообще-то, для устранения шума нужно, как минимум, знать
    > его характеристики.


    Бесспорно, но это можно тоже сделать выполнив съемку в темноте (или при различных уровнях монотонной засветки) и проанализировав статистические и спектральные характеристики шума.

    В общем случае нормальные элементы фото-матриц имеют шум класса "белый", а аномальные -  шум класса 1/f.
  • Shart © (01.03.07 23:42) [6]
    Ещё раз спасибо! Пока реализовал только "примитивный" алгоритм медианной фильтрации. И тут же возник ещё один вопрос : как избавится от цветового шума, т.е. если на жёлтой стене появляются небольшие синие, зелёные и красные пятна?
  • Jeer © (02.03.07 10:00) [7]
    Можно так, для общего случая цветового шума:
    - перейти в Lab-цветовое пространство
    - gaussian blur каналов a и b
    - обратно в RGB
  • Shart © (03.03.07 00:39) [8]
    Вобщем, сделал я прогу... только работает она долго. По-идее, я придумал новый способ фильтрации...назову его темпоральный медианный фильтр (а может не новый, где-то я его видел).
    Вот файл - http://www.sendspace.com/file/tutp6a (весит ~2метра)
    Короче, надо ложить несколько (а лучше много) последовательных съёмок одного кадра (т.е. чтоб камера не двигалась, в архиве есть пример, там...эээ...мой стол:) ) в папку с:\ca\ названия у файлов - shotN.bmp, где N номер кадра от 0.
    Потом в программе надо изменить константы FRAMES (кол-во кадров), W(ширина), H(высота кадра), скопилить и запустить...
    сейчас вопрос - как это всё дело ускорить?
  • Shart © (03.03.07 00:41) [9]
    А, забыл написать описание алгоритма:
    1. Во всех кадрах один пиксель добавляю в массив
    2. Нахожу медиану
    3. Ставлю пиксель на конечное изображение
  • Pavia © (03.03.07 02:15) [10]
    Оптимизация.
    Я бы всетаки использовал доступ к пикселям через ScanLine и пользовался бы стандартным TBitmap.
    Не обязательно делать медианную фильтрацию от большого числа кадров.
    Или можно заменить медианную фильтрациию на усредненние(ищем среднее арефметическое).

    Предложение.
    Адаптивный фильтр. Делаем размытие с сохронением краев.
  • Jeer © (05.03.07 10:52) [11]
    Улучшение изображения по серии однотипных снимков - это немного другое.

    Можно попробовать медианный фильтр к каждому снимку для устранения "горячих" пикселей, а затем осреднение однокоординатных точек по серии снимков.
    Вот, что примерно должно получиться:
    http://webfile.ru/1335346
  • Kikky © (17.01.08 19:33) [12]
    Очень надо! Срочно! Объясните пожалуйчта программу построчно! Такое требование преподавателя! Буду очень признательна!

    unit Unit1;

    interface

    uses
     Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
     Dialogs, StdCtrls, ExtCtrls, Math;

    type
     TScanlines = class
       private
         FScanLine0 : PByte;
         FScanLineIncrement : integer;
         FMaxRow : integer;
         function GetRowPtr(Index: integer): PByte;
       public
         constructor Create(Bitmap: TBitmap);
         destructor Destroy; override;
         property RowPtr[Index: Integer]: PByte read GetRowPtr; default;
     end;

     TForm1 = class(TForm)
       Panel1: TPanel;
       Image1: TImage;
       Button1: TButton;
       Button2: TButton;
       Button3: TButton;
       OpenDialog1: TOpenDialog;
       SaveDialog1: TSaveDialog;
       procedure Button1Click(Sender: TObject);
       procedure Button2Click(Sender: TObject);
       procedure Button3Click(Sender: TObject);
     private
       { Private declarations }
     public
       { Public declarations }
     end;

    var
     Form1: TForm1;

    implementation

    {$R *.dfm}

    procedure TForm1.Button1Click(Sender: TObject);
    begin
    if OpenDialog1.Execute then Image1.Picture.LoadFromFile(OpenDialog1.FileName);
    end;

    procedure TForm1.Button2Click(Sender: TObject);
    begin
    if SaveDialog1.Execute then Image1.Picture.SaveToFile(SaveDialog1.FileName);
    end;

    procedure TForm1.Button3Click(Sender: TObject);
    procedure AntiAliasRect(clip: tbitmap; XOrigin, YOrigin, XFinal, YFinal: Integer);
    var
     Memo, x, y : Integer; (* Composantes primaires des points environnants *)
     p0, p1, p2 : pbytearray;
     SL : TScanlines;
    begin
      if XFinal<XOrigin then begin Memo := XOrigin; XOrigin := XFinal; XFinal := Memo; end;  (* Inversion des valeurs   *)
      if YFinal<YOrigin then begin Memo := YOrigin; YOrigin := YFinal; YFinal := Memo; end;  (* si diff,rence n,gative*)
      XOrigin := max(1,XOrigin);
      YOrigin := max(1,YOrigin);
      XFinal := min(clip.width-2,XFinal);
      YFinal := min(clip.height-2,YFinal);
      clip.PixelFormat := pf24bit;
      SL := TScanlines.Create(clip);
      try
        for y := YOrigin to YFinal do begin
         //p0 := clip.ScanLine [y-1];
         //p1 := clip.scanline [y];
         //p2 := clip.ScanLine [y+1];
         p0 := PByteArray(SL[y-1]);
         p1 := PByteArray(SL[y]);
         p2 := PByteArray(SL[y+1]);
         for x := XOrigin to XFinal do begin
           p1[x*3]   := (p0[x*3]+p2[x*3]+p1[(x-1)*3]+p1[(x+1)*3])div 4;
           p1[x*3+1] := (p0[x*3+1]+p2[x*3+1]+p1[(x-1)*3+1]+p1[(x+1)*3+1])div 4;
           p1[x*3+2] := (p0[x*3+2]+p2[x*3+2]+p1[(x-1)*3+2]+p1[(x+1)*3+2])div 4;
           end;
        end;
      finally
        SL.Free;
      end;
    end;
    begin
    AntiAliasRect(Image1.Picture.Bitmap, 0, 0, Image1.Picture.Bitmap.Width, Image1.Picture.Bitmap.Height);
    end;

    { TScanlines }

    constructor TScanlines.Create(Bitmap: TBitmap);
    begin
    inherited Create;
     FScanLine0 := nil;
     FScanLineIncrement := 0;
     FMaxRow := 0;
     if Bitmap <> nil then begin
       FScanLine0 := Bitmap.ScanLine[0];
       FScanLineIncrement := integer(Bitmap.Scanline[1]) -integer(FScanLine0);
       FMaxRow := Bitmap.Height;
     end;
    end;
    destructor TScanlines.Destroy;
    begin
     inherited;
    end;
    function TScanlines.GetRowPtr(Index: integer): PByte;
    begin
    if (Index >= 0) and (Index < FMaxRow) then begin
       result := FScanLine0;
       Inc(result, FScanLineIncrement *Index);
     end else
       result := nil;
    end;

    end.
  • antonn © (17.01.08 22:42) [13]

    > Объясните пожалуйчта программу построчно! Такое требование
    > преподавателя!



    > { TScanlines }

    это комментарий, он ничего не делает в программе %)


    >  //p0 := clip.ScanLine [y-1];
    >      //p1 := clip.scanline [y];
    >      //p2 := clip.ScanLine [y+1];

    это тоже комментарии...


    > (* Inversion des valeurs   *)

    и это.
  • homm © (17.01.08 23:05) [14]
    > [12] Kikky ©   (17.01.08 19:33)
    > Очень надо! Срочно! Объясните пожалуйчта программу построчно!

    А не пойти бы тебе… учится.
  • Kenny (18.01.08 14:12) [15]
    // Декларирование названия модуля
    unit Unit1;

    // Начало интерфейсной части
    interface

    // Список изпользуемых модулей
    uses
    Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
    Dialogs, StdCtrls, ExtCtrls, Math;

    // Объявление класса TScanlines
    type
    TScanlines = class
    // Приватная (видимая только внутри класса) часть
      private
        FScanLine0 : PByte;
        FScanLineIncrement : integer;
        FMaxRow : integer;
        function GetRowPtr(Index: integer): PByte;
    // Публичная (видимая) часть
      public
        constructor Create(Bitmap: TBitmap);
        destructor Destroy; override;
        property RowPtr[Index: Integer]: PByte read GetRowPtr; default;
    end;

    // Класс формы
    TForm1 = class(TForm)
      Panel1: TPanel;
      Image1: TImage;
      Button1: TButton;
      Button2: TButton;
      Button3: TButton;
      OpenDialog1: TOpenDialog;
      SaveDialog1: TSaveDialog;
      procedure Button1Click(Sender: TObject);
      procedure Button2Click(Sender: TObject);
      procedure Button3Click(Sender: TObject);
    private
      { Private declarations }
    public
      { Public declarations }
    end;

    // Глобальная переменная класса TForm1
    var
    Form1: TForm1;

    // Начало части реализации
    implementation

    // Прикрепление ресурса в виде dfm файла (Unit1.dfm)
    {$R *.dfm}

    // обработчик нажатия кнопки Button1
    procedure TForm1.Button1Click(Sender: TObject);
    begin
     // Если в диалоге открытия файла был выбран файл, то загрузаем картинку в Image1
     if OpenDialog1.Execute then Image1.Picture.LoadFromFile(OpenDialog1.FileName);
    end;

    // обработчик нажатия кнопки Button2
    procedure TForm1.Button2Click(Sender: TObject);
    begin
     // Если в диалоге сохранения файла ввели имя файла и нажали Сохранить, то сохраняем картинку из Image1
     if SaveDialog1.Execute then Image1.Picture.SaveToFile(SaveDialog1.FileName);
    end;

    // обработчик нажатия кнопки Button3
    procedure TForm1.Button3Click(Sender: TObject);

    // Функция антиалиаслинга (сглаживания изображения)
    // К слову сказать, процедура работает неправильно, т.к. берет значения из уже измененных пикселей
    procedure AntiAliasRect(clip: tbitmap; XOrigin, YOrigin, XFinal, YFinal: Integer);
    // объявление переменных
    var
    Memo, x, y : Integer; // Memo - временная переменная, [x, y] - координаты указывающие точку на изображении
    p0, p1, p2 : pbytearray; // указатели на массивы байтов (по сути на строки картинки)
    SL : TScanlines; // объект класса TScanlines
    begin
     // Если правая точка по оси абсцисс левее левой, то меняем их значения
     if XFinal<XOrigin then begin Memo := XOrigin; XOrigin := XFinal; XFinal := Memo; end;  
     // Если верхняя точка по оси ординат ниже нижней, то меняем их значения
     if YFinal<YOrigin then begin Memo := YOrigin; YOrigin := YFinal; YFinal := Memo; end;
     // Ограничение левой точки по оси абсциссы - минимальное значение 1
     XOrigin := max(1,XOrigin);
     // Ограничение верхней точки по оси ординат - минимальное значение 1
     YOrigin := max(1,YOrigin);
     // Ограничение правой точки по оси абсцисс - максимальное значение - это ширина картинки минус 2
     XFinal := min(clip.width-2,XFinal);
     // Ограничение нижней точки по оси ординат - максимальное значение - это высота картинки минус 2
     YFinal := min(clip.height-2,YFinal);
     // устанавливаем глубину цветов картинки 24 бит (3 байта)
     clip.PixelFormat := pf24bit;
     // Создаем объект класса TScanlines
     SL := TScanlines.Create(clip);
     // Блок try - "Пытаемся делать то-то"
     try
       // Цикл обработки изображения по строкам сверху вниз
       for y := YOrigin to YFinal do begin
        // Указателю p0 присваиваем ссылку на строку предыдущую строке y
        p0 := PByteArray(SL[y-1]);
        // Указателю p1 присваиваем ссылку на строку y
        p1 := PByteArray(SL[y]);
        // Указателю p1 присваиваем ссылку на строку следующую после y
        p2 := PByteArray(SL[y+1]);
        // Цикл обработки строки слева направо
        for x := XOrigin to XFinal do begin
          // Первому байту (синяя компонента цвета, по-моемц) пикселя в точке [x, y]
          // присвается усредненное значение синих компонент соседних пикселей (слева, справа, сверху и снизу)
          p1[x*3]   := (p0[x*3]+p2[x*3]+p1[(x-1)*3]+p1[(x+1)*3])div 4;
          // То же самое для второго байта пикселя (зеленая состовляющая)
          p1[x*3+1] := (p0[x*3+1]+p2[x*3+1]+p1[(x-1)*3+1]+p1[(x+1)*3+1])div 4;
          // То же самое для третьего байта пикселя (красная состовляющая)
          p1[x*3+2] := (p0[x*3+2]+p2[x*3+2]+p1[(x-1)*3+2]+p1[(x+1)*3+2])div 4;
          end;
       end;
     // Финальная часть блока try - "В любом случае выполняем это"
     finally
       // Уничтожаем объект SL
       SL.Free;
     // Конец блока try
     end;
    end;

    begin
     // Вызов функции сглаживания изображения
     AntiAliasRect(Image1.Picture.Bitmap, 0, 0, Image1.Picture.Bitmap.Width, Image1.Picture.Bitmap.Height);
    end;

    // Реализация методов класса TScanlines - он сделан для быстрого высчитывания
    // указателя на конкретную строку растра, т.к. обычная функция TBitmap.ScanLine[] работает несколько тормознута из-за универсальности

    { TScanlines }

    // Конструктор класса
    constructor TScanlines.Create(Bitmap: TBitmap);
    begin
    // Выхов конструктора класса-предка
    inherited Create;
    // Инициализация переменных (полей)
    FScanLine0 := nil; // К слову сказать, это лишнее Delphi сама присвоит nil
    FScanLineIncrement := 0;  // К слову сказать, это лишнее Delphi сама присвоит 0
    FMaxRow := 0; //  // К слову сказать, это лишнее Delphi сама присвоит 0

    // Если растр не равен nil (т.е. существует обект, на который Bitmap ссылается), то
    if Bitmap <> nil then begin
      // указателю FScanLine0 присваиваем ссылку на нулевую строку
      FScanLine0 := Bitmap.ScanLine[0];
      // полю FScanLineIncrement присваиваем длину строки в байтах (разница между адресом первой и нулевой строки)
      FScanLineIncrement := integer(Bitmap.Scanline[1]) -integer(FScanLine0);
      // Полю FMaxRow присваиваем высоту растра (картинки)
      FMaxRow := Bitmap.Height;
    end;
    end;

    // Деструктор
    destructor TScanlines.Destroy;
    begin
    // Вызов деструктора класса-предка
    inherited;
    end;

    // Реализация функции GetRowPtr - возвращает указатель на строку с номером Index
    function TScanlines.GetRowPtr(Index: integer): PByte;
    begin
    // Если Index больше либо равен нулю и больше FMaxRow
    if (Index >= 0) and (Index < FMaxRow) then begin
    // то  result присваиваем указатель на нулевую строку FScanLine0
      result := FScanLine0;
    // и смещаем полученный указатель на число строк (Index)
      Inc(result, FScanLineIncrement *Index);
    end else
    // иначе возвращаем nil
      result := nil;
    end;

    // конец
    end.
  • antonn © (18.01.08 21:58) [16]
    так ведь у TBitmap уже есть scanline О_о
  • Kenny (18.01.08 22:29) [17]
    Ты видел исходник этого сканлайна? Такое выполнять H*3 раз расточительно. Читай коменты )
  • homm © (19.01.08 00:02) [18]
    > [17] Kenny   (18.01.08 22:29)
    > Такое выполнять H*3 раз расточительно

    Достаточно 2-х вызовов для полного счастья.
  • Kenny (19.01.08 12:43) [19]
    > Достаточно 2-х вызовов для полного счастья.

    Да никто не спорит. Но задача была прокомментировать приведенный код :)
  • Kenny (19.01.08 12:43) [20]
    Кстати для картинок высотой 1 пиксель будет ошибка.
  • homm © (19.01.08 20:33) [21]
    > [20] Kenny   (19.01.08 12:43)
    > Кстати для картинок высотой 1 пиксель будет ошибка.

    Дотаточность 2-х вызовов для наступления счастья, еще не означает недостаточность одного вызова для наступления его же, поэтому слово «будет» считаю несколько неуместным.
  • Kenny (19.01.08 21:24) [22]
    > homm ©   (19.01.08 20:33) [21]

    Это относилось к приведенному выше коду )
  • cerber (05.02.08 22:30) [23]
    shart
    Динамическая борьба с шумами это не батоны рисовать. Тут важно знать характеристики как шума так и полезного изображения. Эффективная фильтрация изображения возможна только при весьма жестких ограничениях на отношение сигнал/шум. Способ фильтрации описанный выше ничть иное как сгалаживание вычокочастоных шумов за счет инерности визуального восприятия. При такой фильтрации идет незначительная для телевизора и заметная для ЖК панели потеря резкости изображения. Лучший способ фильтрации только через статистические методы обработки изоражения и спектральные преобразования. Минус этих методов - большие "трудозатраты". Неплохие методы фильтрации реализованы в пакете Матлаб - с функциональным описанием. Читай и пробуй...
 
Конференция "Media" » Удаление шума на изображении [D7, WinXP]
Есть новые Нет новых   [133928   +471][b:0][p:0.001]