Инвертирование фильтра сепии

Я наложил инвертирующий фильтр сепия. Результат инвертирования фильтра не соответствует ожидаемому.

Моя логика следующая: обрабатываемый_пиксель = np.dot (sepia_filter, original_pixel)

Это означает, что: original_pixel = np.dot (np.inverse (sepia_filter), обрабатываемый_пиксель)

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

Требования:

import numpy as np
from PIL import Image, ImageDraw

Код фильтра сепия:

def get_pixel_after_sepia(source, pixel, sepia_filter):
    colors = np.array(source.getpixel(pixel)) 
    colors_new = tuple(map(int, np.dot(sepia_filter,colors))) + (255,) #  apply filter, transform results to ints, cut to 255
    return colors_new
                       
def sepia(source, result_name):
    result = Image.new('RGB', source.size)
    sepia_filter = np.array([[0.393,0.769,0.189], [0.349,0.686,0.168], [0.272,0.534,0.131]])

    #  for every pixel                   
    for x in range(source.size[0]):
        for y in range(source.size[1]):
            new_pixel = get_pixel_after_sepia(source, (x,y), sepia_filter)
            result.putpixel((x, y),new_pixel)
            
    result.save(result_name, "JPEG")
    return result

Перевернутый код сепии:

def get_pixel_before_sepia(source, pixel, inversed_sepia_filter):
    colors = np.array(source.getpixel(pixel)) 
    colors_new = tuple(map(int, np.dot(inversed_sepia_filter, colors)))+ (255,)
    return colors_new

def inverse_sepia(image_with_sepia, result_file):
    result = Image.new('RGB', image_with_sepia.size) 
    sepia_filter = np.array([[0.393,0.769,0.189], [0.349,0.686,0.168], [0.272,0.534,0.131]])
    inverse_sepia_filter = np.linalg.inv(sepia_filter)
    
    for x in range(image_with_sepia.size[0]):
        for y in range(image_with_sepia.size[1]):
            new_pixel = get_pixel_before_sepia(image_with_sepia, (x,y), inverse_sepia_filter)
            result.putpixel((x, y),new_pixel)
            
    result.save(result_file, "JPEG")
    return result

Выполнение функций:

image = Image.open("original_image.jpg")
filtered_image = sepia(image, "filtered.jpg") # result_pixel = dot_product(Filter, origin_pixel)  
image_after_filter_reversing = inverse_sepia(filtered_image,'restored.jpg' ) # result_pixel = dot_product(Filter^(-1), filtering_result_pixel)  

Исходное изображение

Отфильтрованное_изображение

Image_after_filter_reversing


Я понимаю, что совершенный реверс невозможен, так как мы обрезаем результаты расчета и округляем их до int. Но я ожидаю, что изображение после реверса будет довольно близко к оригиналу. Я новичок в обработке изображений, но математическая задача выглядит для меня совершенно правильно.

В чем проблема?

mapf 17.12.2020 17:51

Проблема в том, что алгоритм инвертирования мисепии не работает должным образом

Stas Buzuluk 17.12.2020 17:52

Вы можете быть более конкретным? Потому что мы не можем запустить ваш код. Вы получаете сообщения об ошибках или результат не соответствует вашим ожиданиям?

mapf 17.12.2020 17:53

Извините, результаты не такие, как ожидалось.

Stas Buzuluk 17.12.2020 17:55

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

Stas Buzuluk 17.12.2020 17:56

что произойдет, если вы подадите filtered_image в функцию inverse_sepia?

mapf 17.12.2020 18:02

Это на самом деле то, что я делаю. Просто изначально была опечатка в коде - извините.

Stas Buzuluk 17.12.2020 18:04

У меня появилось новое понимание. Смотрите мои комментарии под ответом Дэвида.

mapf 17.12.2020 21:11

@mapf Спасибо за ваши идеи. Я просто пытаюсь понять, как работают фильтры, так что вы правы - у меня всегда есть оригинал. Ваши мысли были очень полезны. Я также попробую вашу идею с сохранением значений без отсечения и покажу результаты здесь, если вам интересно, как только у меня будет свободное время.

Stas Buzuluk 18.12.2020 09:58

Не беспокойся! Но если я правильно понимаю ответ user3386109, моя идея тоже не сработает.

mapf 18.12.2020 10:01

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

Stas Buzuluk 18.12.2020 10:03

Если вы заинтересованы в восстановлении изображений, посмотрите, как GAN использовались для задач аналогичного типа: DeOldify , Раскрашивание с помощью GAN

dhanushka 18.12.2020 14:34

@dhanushka Спасибо за ссылки. Я просто пытаюсь выяснить, как работает фильтрация в деталях - за моим вопросом нет реального проекта, я просто изучаю. Но я буду знать, как подходить к таким проблемам в будущем :) Кстати, восстановление выглядит впечатляюще.

Stas Buzuluk 18.12.2020 14:44
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
3
13
722
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

Это звучит как хорошее объяснение. Я попробовал код с более простыми данными, и он сработал. Итак, реальный вопрос в том, как вы заставляете это работать?

mapf 17.12.2020 18:43

@mapf Проблема в том, что фильтр сепия плюс округление с потерями, так что на самом деле вы не можете. Лучшее, что вы можете сделать, это, вероятно, что-то вроде осторожного дизеринга к фильтру сепии, а затем добавления размытия после обратного фильтра, но мы далеко уходим от моей рулевой рубки.

David Eisenstat 17.12.2020 19:36

Хм интересно. Это дает мне идею.

mapf 17.12.2020 20:54

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

mapf 17.12.2020 21:05

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

mapf 17.12.2020 21:07

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

Stas Buzuluk 18.12.2020 11:47

@StasBuzuluk они не линейно зависимы - просто очень близки. Вот почему я говорю об сингулярных значениях.

David Eisenstat 18.12.2020 15:32

@DavidEisenstat Да, ваш ответ совершенно правильный - я просто недостаточно хорошо знаю математику, чтобы правильно ее понять. Теперь я вижу, что вы на самом деле пытались показать мне ту же проблему. Спасибо за ваш ответ и дальнейшие пояснения - если вы не возражаете - я оставлю еще один ответ, отмеченный как правильный, несмотря на то, что вы были первым, просто потому, что это немного проще для людей с небольшими знаниями в линейной алгебре, поскольку сам.

Stas Buzuluk 18.12.2020 16:15
Ответ принят как подходящий

Интересный вопрос.
Ответ может быть немного разочаровывающим: фильтр сепии необратим ни теоретически, ни на практике.

Теория

Числовая матрица, используемая в коде:

0.393   0.769   0.189
0.349   0.686   0.168
0.272   0.534   0.131

Соответствующая символьная матрица:

 x       y       z
mx      my      mz
nx      ny      nz

где x=0.393 y=0.769 z=0.189 m=0.89 n=0.69.
Когда вы вычисляете определитель символьной матрицы, он равен нулю. Следовательно, матрица необратима, как и фильтр сепии.

Тот факт, что числовая матрица имеет ненулевой определитель, объясняется просто ограниченной точностью (3 цифры) чисел. Вычисление определителя числовой матрицы дает 0,000000121, что по существу равно 0 плюс/минус несколько ошибок округления.

В качестве примечания: умножение значения пикселя на числовую матрицу эквивалентно следующим вычислениям.

R = 0.393*r + 0.769*g + 0.189*b
G = 0.89*R
B = 0.69*R

где «rgb» — исходное значение пикселя, а «RGB» — значение пикселя сепии.

Упражняться

Новый Оксфордский американский словарь определяет сепию как «красновато-коричневый цвет, особенно ассоциирующийся с монохромными фотографиями 19-го и начала 20-го веков».

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

Чтобы лучше понять, попробуйте использовать следующую матрицу в своем коде.

0.291  0.569  0.140
0.291  0.569  0.140
0.291  0.569  0.140

Это преобразует изображение в изображение в градациях серого, также известное как черно-белое изображение. Как и сепия, изображение в градациях серого кодирует только видимую яркость каждого пикселя. Он не сохраняет никакой информации о цвете. Разница в том, что оттенки серого используют серый оттенок в качестве основного цвета, тогда как сепия использует коричневый оттенок в качестве основного цвета. Другие цвета, которые вы видите на изображении сепии: оранжевый, персиковый, желтый и черный — это просто разные уровни яркости коричневого в цветовом пространстве RGB.

Еще одно различие между оттенками серого и сепией заключается в том, что сепия способна кодировать больше уровней яркости. Оттенки серого имеют палитру из 256 оттенков. Сепия имеет 346 различных значений пикселей. Причина в клиппинге. Учитывая входной пиксель (255, 255, 255), соответствующий пиксель сепии будет (345, 307, 239) до отсечения и (255, 255, 239) после отсечения. Красный компонент пикселя сепии имеет 346 возможных значений до отсечения. Для каждого красного значения значения зеленого и синего пропорциональны (G=0.89*R и B=0.69*R).

Вот палитра сепии (минус четыре самых темных оттенка):

Следовательно, практическая проблема, с которой вы сталкиваетесь, заключается в том, что исходное цветное изображение имеет палитру из 16 миллионов цветов, тогда как палитра сепии содержит всего 346 цветов. Невозможно воссоздать исходное изображение, поскольку пиксель в изображении сепии соответствует любому из примерно 48000 возможных цветов в оригинале.

Другие вопросы по теме