Шумоподавление путем объединения изображений на Java

Здраствуй, Хабр! Хочу поделиться кодом простой программы, которую я использую для уменьшения шума с цифровых фотограффий.

Примерно восемь лет назад, рассматривая фотографии, снятые на свой первый цифровой фотоаппарат, я обнаружил, что некоторые снимки с тусклым освещением имеют какую-то странную мутность, цветные пятна, не резкость. В то время я еще не знал, что такое шум, как он зависит от параметра ISO и был очень разочарован, что фотоаппарат такой «некачественный». Однако, я обратил внимание, что на одинаковых снимках эти цветные пятна выглядят несколько по разному, меняются от кадра к кадру. Время шло, я научился снимать на ручных настройках, узнал, что такое шум, как правильно выставить светочуствительность и т.д.

Спустя несколько лет, когда уже начал заниматься программированием, снова обратил внимание на то, что шум на изображениях не является статичным. В голове возникла идея: а что если взять, снять несколько абсолютно одинаковых изображений, а потом неким образом объединить их, устранив разность между снимками, т.е. шум?

Итак, ниже представлены 4 изображения, демонстрирующие некие фотографии одного и того-же обьекта, со случайным шумом на каждом снимке. В качестве объекта представлены красные круги, в качестве шума — белые.

пример снимков

Первым делом, я решил попробовать банальным способом совместить эти изображения в фотошопе, установив каждому из изображений прозрачность = 50%. Конечно, ничего хорошего из этого не вышло.

результат обработки в adobe photoshop

Такой результат вполне логичен — пиксели не усредняются, а просто прибавляются один к другому, а так же каждый последующий слой имеет больший «вес» над нижестоящими.

Немного поискав в интернете, обнаружил, что программы такого типа уже существуют и активно используются в среде астрофотографов. Выдержки, на которых снимают звезды, огромны (и соответственно шум), а сам объект съемки статичен (при съемке с гидированием), что позволяет устранять шум путем объединения идентичных снимков. Однако осталось одно но: все найденные мной программы подобного типа являлись очень сложными для использования, заточенными именно для астрофото и практически все стоили больших денег. Такой функционал мне был не нужен, и я продолжил поиск.

Спустя некоторое время я нашел сайт известного в кругах панорамных фотографов немецкого математика Хельмута Дерша. На данный момент практически все ПО для склейки панорамных изображений основывается на его алгоритмах. Помимо ПО для обработки панорам на его сайте я наткнулся на программу, устраняющую шум с изображений — PTAverage. Программа была невероятно простой — просто перетягиваешь фотографии на ярлык — и получаешь результат. Как раз то, что я и искал. Однако немного поигравшись с PTAverage, я понял, что это совсем не то, что хотелось бы.

Результат обработки изображений программой PTAverage:

результат обработки в PTAverage

Как видно, программа работает простейшим образом: получает цвет пикселя каждого изображения, складывает их между собой, а затем делит на общее количество всех изображений. Однако мне хотелось некой селективности: например, если на двух изображениях пиксель черного цвета, а на третьем изображении он белый — логично предположить, что на третьем изображении пиксель является шумом. В конечном итоге ничего подходящего я так и не нашел, и в итоге решил написать такое ПО самому, благо что все выглядело весьма легко.

Саму программу писал на java, т.к. изучал ее к тому времени уже около года. Единственной загвоздкой была загрузка изображений в формате tiff, но позже я разобрался с библиотекой JAI. Недостатком программы является огромное потребление памяти — JAI не умеет (а может я просто не нашел) читать изображение попиксельно, не загружая всё изображение в память.

Код программы
Чтобы код был понятнее, убрал все проверки (такие как разрешение изображений, бит на канал и т.д.):

public class Denoise {
    
    /**
     * @param inputFiles массив с файлами для обработки
     * @param outputFile файл, в который сохранится результат обработки
     * @param difference максимальная разница между пикселями (0-255)
     * @throws IOException 
     */
    Denoise(File[] inputFiles, File outputFile, int difference) throws IOException {
        
        //Создаем массив для данных изображений
        Raster[] rasters = new Raster[inputFiles.length];
        
        //В цикле читаем каждое изображение
        for(int i = 0; i<inputFiles.length; i++) {
            try (ImageInputStream is = ImageIO.createImageInputStream(inputFiles[i])) {
                Iterator<ImageReader> imageReaders = ImageIO.getImageReaders(is) ;
                ImageReader imageReader = imageReaders.next();
                imageReader.setInput(is);
                if(imageReader.canReadRaster()) {
                    rasters[i] = imageReader.readRaster(0, null);
                }
                else {
                    rasters[i] =  imageReader.readAsRenderedImage(0, null).getData();
                }
            }
        }
        
        //Получаем ширину и высоту первого изображения, считая что размеры всех изображений равны
        int width = rasters[0].getWidth();
        int height = rasters[0].getHeight();
        
        //Создаем растр для записи результирующего изображения, используя характеристики первого изображения
        WritableRaster outputRaster = rasters[0].createCompatibleWritableRaster();
        
        //В цикле обходим каждый пиксель каждого изображения, усредняя значения по каждому каналу
        for(int x = 0; x<width; x++){
            for(int y = 0; y<height; y++){
                //Массив, со значениями цветов пикселя
                int[] color = new int[3];
                
                for(int band = 0; band<3; band++){
                    //Массив, со значениями канала определенного пикселя
                    int data[] = new int[rasters.length];
                    
                    for (int imageNum = 0; imageNum<rasters.length; imageNum++) {
                        data[imageNum] = rasters[imageNum].getSample(x, y, band);
                    }
                    
                    //Получаем усредненное значение канала
                    color[band]  = average(data, difference); 
                }
                
                //Устанавливаем цвет пикселю результирующего изображения
                outputRaster.setPixel(x, y, color);
            }
        }
        
        //Сохраняем изображение
        BufferedImage output = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        output.setData(outputRaster);
        ImageIO.write(output, "tiff", outputFile);
    }
    
    /**
     * 
     * @param data массив с данными пикселя всех изображений для отдельного канала 
     * @param difference максимальная разница между пикселем
     * @return усредненное значение канала
     */
    private int average(int[] data, int difference){
        /**Количество изображений*/
        int imagesCount = data.length;
        /**Медианное значение цвета пикселей*/
        int median;
        
        //Сортируем массив, чтобы цвет пикселя выстроился в порядке возрастания
        Arrays.sort(data);
        
        //Если количество изображений является четным, используем для получения медианного значения 
        //среднее арифметическое значение двух центральных пикселей
        if(imagesCount % 2 == 0) { 
            median = (data[imagesCount / 2 - 1] + data[imagesCount / 2]) / 2;
        }
        else {
            median = data[(int)Math.floor(imagesCount / 2)];
        }
        
        //Максимальное и минимальное отклонение цвета пикселя от медианного значения
        int min = median - difference;
        int max = median + difference;

        //сумма значений канала всех изображений
        int sumBands = 0;
        //Общее количество изображений, не выходящих за рамки min и max
        int counter = 0;

        //В цикле рассчитываем сумму значений канала всех изображений
        for(int i = 0; i<imagesCount; i++){
            //Если значение не превышает указанные пороги - добавляем его к общему значению
            if(data[i]>=min && data[i]<= max){
                sumBands = sumBands+data[i];
                counter++;
            }
        }
        
        //Если отклонение от медианного значения пикселя не превышает только одно (или ни одно)
        //из изображений - просто усредняем все полученные значения,
        //в противном случае - усредняем только те, которые вошли в указанные рамки
        if(counter <= 1){
            sumBands = 0;
            for(int i = 0; i<imagesCount; i++){
                sumBands = sumBands + data[i];
            }
            sumBands = sumBands/imagesCount;
        }
        else {
            sumBands = sumBands / counter;
        }
        
        return sumBands;
    }
    
}



Закидываем четыре оригинальных кадра в программу и получаем изображение без шума.

результат обработки

Практическое применение алгоритму найти сложно — объект для съемки должен быть статичен, освещение объекта не должно изменяться, ну и ничего не получится без очень устойчивого штатива.

Кстати, у программы есть некий «побочный» эффект — удаляется не только шум, а любой не статичный объект. Например, сделав большое количество кадров с оживленной площади, теоретически можно «удалить» всех людей. Ниже небольшой пример.

Снимки до обработки; как видно по снимкам, таинственным образом перемещается банан.



А вот результат обработки — как видно, банан пропал не полностью. Однако, сделав большее количество кадров, при условии, что банан продолжил бы двигаться, можно было бы полностью от него избавиться.

результат обработки

А что насчет шума? Тут тоже все отлично, хватило всего трех кадров, чтобы значительно его уменьшить (астрофотографы, к примеру, используют, насколько я знаю, 15+ кадров).

сравнение шума

Конечно, описанный мною алгоритм невероятно прост, и мало применим в реальной практике, но, я надеюсь, возможно кто-то будет пользоваться им, или почерпнет из статьи что-то полезное.
Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 40

    +8
    Первым делом, я решил попробовать банальным способом совместить эти изображения в фотошопе, установив каждому из изображений прозрачность = 50%. Конечно, ничего хорошего из этого не вышло.
    Конечно, ведь вы неправильно присваиваете вес. Чтобы добиться нужного результата, нижний слой должен иметь Opacity 100%, второй — 50%, третий — 33%, четвертый — 25%, пятый — 20%, и так далее, чтобы каждый новый слой имел равные права со всеми нижележащими.
    Результат
    image
      +1
      Получается, фактически, второй способ — усреднение.
        +1
        Именно так.
          0
          Есть алгоритмы и программы объединения нескольких одинаковых изображений снятых с различной глубиной резкости. Преобразования происходят в частотном представлении изображения. Возможно, в вашем случае это так же помогло бы сохранить детали и уменьшить шум.
          0
          В фотошопе вроде есть медианный фильтр.
          –1
          Для подавления шумов, наверное, лучше бы подошел фильтр Калмана.
            +2
            Фильтр Калмана в основном применяется в динамических фильтрах шумоподавления, для шумоподавления в видеопотоке, например. Для статических изображений существуют более эффективные и высококачественные методы, работающие как в пространстве изображения, так и в частотной области.
            0
            Если принять гипотезу о том, что каждый отдельный пиксел матрицы даёт шум не на каждом снимке, то можно использовать банальный AND:

            попиксельно сравнивать цвет на всех фото, принимая за истину тот, что встречается в большем числе снимков (можно ввести маленькую допустимую погрешность), а не усреднять с зашумлёнными кадрами.
              0
                0
                Полагаю, наилучших результатов можно добиться, построив гистограмму цветов для каждого пикселя с разных фото. В качестве результата взять самый вероятный диапазон и усреднить цвет по попавшим в него пикселям. Причем по 7-10 фото уже можно будет избавиться даже от сильных шумов.
                  0
                  Еще этот способ называется «стэкинг», насколько мне известно есть две его разновидности:
                  1) Простое сложение снимков в «пачку», для уменьшения шумов.
                  2) Продвинутое восстановление изображения из исходников где объекты так или иначе изменялись. Например из за движения (Луна, Солнце) и атмосферной рефракции, заставляющей объекты менять видимую форму.
                  Второй способ применяют астрофотографы, собирая качественные снимки из длинных последовательностей кадров. И некоторые пейзажисты в т.ч. я, когда есть много времени, мало света, штатив и потребность получить «сверхчистые» в тенях и деталях снимки.
                    +3
                    Кстати, расскажите поподробнее про сверхчистые пейзажи. Где-нибудь статья есть? Любопытно как сейчас это делать проще всего.
                    +8
                    Еще этот алгоритм доступен в «фотошопе». File — scripts — statistics — median.
                      0
                      Если используется устойчивый штатив, не проще увеличить выдержку?
                        0
                        А если используется гиперзум в условиях ограниченной освещённости, плюс, возможно, ветреная погода?
                          +1
                          При чем тут гиперзум?

                          Увеличение выдержки на штативе убирает проблему низкой освещенности, неважно какая диафрагма открыта.
                          А при ветреной погоде, когда меняется и сама картинка, сабж тоже не поможет.
                          PS. Я конечно не эксперт, но формула на мой взгляд простая, количество света = количество приходящего света в секунду умножить на время выдержки.
                            0
                            Глянул примеры в первом топике, мне кажется качество будет даже лучше при длинной выдержке.
                              +1
                              Я занимался какое-то время фотографией — с увеличением выдержки увеличивается количество шумов (отчасти виноват нагрев матрицы), плюс все объекты, попавшие в кадр, оставять хоть какой-то след (более бледный, чем полноценный банан, но на фотографии все равно будет видно). Кроме того, не получится бесконечно увеличивать выдержку — большинство объективов имеют границу диафрагмы (обычно 16 или 32), что в солнечный день позволит снимать с выдержкой примерно 1/30, но не дольше (можно конечно применять ND фильтры, но это другая история, и там свои погрешности). Кроме того, с увеличением диафрагмы увеличивается глубина резкости, и на фотографии вылезают пылинки и разводы на линзах объектива, а они, в отличие от динамического шума, всегда находятся на одном и том же месте и их подобными алгоритмами вообще никак не убрать
                                0
                                Для последнего есть карта пыли — делается эталонный снимок, который используется как шаблон.
                                  0
                                  Есть еще артефакты самой связки матрица—усилитель, так называемый бандинг. Выглядит как сетка светлых линий. Прекрасно удаляется из изображений путём вычитания так называемого «темнового кадра».
                                    0
                                    Ну в солнечный день шумов обычно не много, как мне кажется, основной источник шумов, это повышенная светочувствительность, уменьшить которую можно различными способами, в том числе увеличением выдержки. Если бы у меня стояла задача сфотографировать в темноте стол на штативе, я бы поставил самый светлый объектив, длинную выдержку, и уменьшил светочувствительность.
                                    Двигающийся банан скорее всего бы конечно оставил след.
                                      0
                                      Часто цифровые камеры имеют нижний предел светочувствительности, на редких зеркалках можно поставить меньше 100
                                      На самом деле у каждой конкретной камеры своя картина с отношением светочувствительности и шума, но в общем случае увеличение выдержки на наиболее низких ISO приводит к бо'льшим шумам, чем если снимать с более высокой светочувстительностью и меньшей выдержкой

                                      По этой причине (как было отмечено немного выше) в астрофотографии для съемки движущихся («крутящихся» по небосводу) звезд используется «набор» из кадров с меньшей выдержкой, которые потом лепятся друг на друга, благодаря чему появляются эти известные кадры с размазанными по небу «путями» звезд.
                                      В общем нельзя однозначно сказать, будет ли уменьшение светочувствительности и увеличение выдержки полезным (более того, можно с определенной уверенностью сказать что при увеличении выдержки шум увеличится), у шума часто свои мнения о том, как себя вести.
                                    +1
                                    Я вмешаюсь в разговор, господа, с практическим наблюдением: если я снимаю ночью, то всегда стараюсь поставить наиболее длинную выдержку и сделав передержку, дабы увести все тона поближе к светам. А потом в редакторе уменьшаю яркость всего изображения и тени становятся чище, а снимок начинает выглядеть как и положено ночному.
                                      0
                                      Был скандал как-то, требовалось снять кадр пейзажа, грубо говоря, без «фотошопа» для конкурса. Первое место — снял на полстопа светлее и напечатал с нормальной экспозицией.
                                        0
                                        Какой-то неправильный конкурс. Любое изображение проходит «внутрикамерный» фотошоп, тонокоррекция — суть цифровой фотографии.
                                      0
                                      Просто при длительной выдержке увеличивается смазаность объекта (особенно в полевых условиях), да к тому же растут шумы. Да-да, именно так! При значительной выдержке шумы как ни странно возрастают! Это — так называемый «тепловой шум», хотя он становится актуален при длительных выдержках. Собственно, если вы снимаете гиперзумом удалённый объект в пасмурный день, то выдержка и должна быть сравнительно большой.
                                        0
                                        Я имел ввиду, что с увеличением выдержки, появляется возможность уменьшить светочувствительность. Про тепловой шум правда не знал.
                                        А съемка в длинных выдержкам подразумевает использование штатива, когда снимал луну на штативе с гиперзумом, вроде бы смазанности не наблюдал при относительно длинной выдержке (меньшей чем заметно движение луны)
                                    0
                                    Вы хотели сказать «уменьшить ISO». При увеличении выдержки при одинаковом ISO шум вырастает.
                                      +1
                                      Увеличение выдержки позволяет уменьшить ISO. Как собственно и увеличение диафрагмы, съемки в светлое время суток и т.д.
                                    0
                                    Для высококачественного шумоподавления статических изображений в последнее время применяют алгоритмы на основе «нелокального среднего» (Non-local means). Первым, кто предложил этот метод для шумоподавления изображений и видео в своей диссертации был Antoni Buades. Затем его стали использовать, например, в пакете dxo. Алгоритм сам по себе очень качественный, но его вычислительная сложность очень высокая, поэтому для обработки видеоданных в реальном времени он практически не годится даже со всеми оптимизациями.

                                    Надо кстати заметить, что для того, чтобы качественно отделить сигнал от шума, необходима точная модель шума (зависимость уровня шума (СКО) от сигнала). Надёжная оценка шума — это тоже интересная и не простая задача. На эту тему написано много статей. Есть как параметрические, так и непараметрические (статистические, эвристические и т. п.) методы оценки шума. Имея точную модель и карту шума можно применять и фильтр Винера, он тоже будет неплохо работать.

                                    Кстати по поводу ПО, есть такая программа Neat Image для качественного шумоподавления, работает даже лучше чем dxo. Разработкой занимается какой-то профессор сотоварищи.
                                      +3
                                      удалить всех людей
                                        0
                                        Теперь то же самое, но наоборот — на матрице не оттирающееся полупрозрачное пятно, почти не видно, но есть на всех снимках до единого. Надо удалить.
                                          0
                                          Нужно снять flat field — фотография затемнений на матрице и затем вычитаете его со снимка. Но вот новая пылинка попадет и надо переделывать.
                                          0
                                          Какой смысл несёт ваша программа? Вы часто видите субпиксельно совпадающие фотографии?
                                          А алгоритм да, медианная фильтрация — один из самых базовых алгоритмов, расписанный на первых страницах любого учебника по обработке изображений.
                                          • UFO just landed and posted this here
                                              0
                                              Еще 10 лет назад таким образом получал «пустые» улицы городов, удаляя все машины. Потому что они уходит в «ошибку», как банан.
                                              Только утром думал, что надо поднять старые скилы, и научить, наконец камеру телефона снимать сквозь грязные окна и вообще повышать эффективное разрешение матрицы.
                                              Принципы одинаковые.
                                                0
                                                А разве HDR работает не сходным образом? Там вроде бы тоже смешивается несколько одинаковых изображений?
                                                  0
                                                  HDR получается из нескольких кадров, снятых с разной экспозицией, чтобы получить детали и в темных и в светлых участках.
                                                  0
                                                  Интересная статья.
                                                  У меня были идеи написать ПО по изменению фокуса на фотографии (для этого, теоритически нужен только один четкий кадр) и увеличения разрешения снимка, типа, интерполяции (двигая камеру, или же вычисляя из нескольких снимков «недостающие» пиксели). Эти две идеи мне «навязывали» низкого качества камеры с малым разрешением и без фокуса, которых было достаточно, а стоимость высококачественных цыфровых камер была сравнима со стоимостью квартиры.
                                                    0
                                                    А что насчет шума? Тут тоже все отлично, хватило всего трех кадров, чтобы значительно его уменьшить
                                                    Извиняюсь, если уже было в комментариях, не читал.
                                                    Предлагаю очевидную идею. Если на обработанных кадрах остается шум, то это своего рода устойчивый, статический шум. Его можно вычислить и вычесть из финального изображения или серии исходных. Для вычисления картинки распределения статического шума делается несколько темновых кадров из которых таким же образом удаляется динамический шум.

                                                    Only users with full accounts can post comments. Log in, please.