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

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

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

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

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

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

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

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

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

Такой результат вполне логичен — пиксели не усредняются, а просто прибавляются один к другому, а так же каждый последующий слой имеет больший «вес» над нижестоящими.
Немного поискав в интернете, обнаружил, что программы такого типа уже существуют и активно используются в среде астрофотографов. Выдержки, на которых снимают звезды, огромны (и соответственно шум), а сам объект съемки статичен (при съемке с гидированием), что позволяет устранять шум путем объединения идентичных снимков. Однако осталось одно но: все найденные мной программы подобного типа являлись очень сложными для использования, заточенными именно для астрофото и практически все стоили боль��их денег. Такой функционал мне был не нужен, и я продолжил поиск.
Спустя некоторое время я нашел сайт известного в кругах панорамных фотографов немецкого математика Хельмута Дерша. На данный момент практически все ПО для склейки панорамных изображений основывается на его алгоритмах. Помимо ПО для обработки панорам на его сайте я наткнулся на программу, устраняющую шум с изображений — 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+ кадров).

Конечно, описанный мною алгоритм невероятно прост, и мало применим в реальной практике, но, я надеюсь, возможно кто-то будет пользоваться им, или почерпнет из статьи что-то полезное.