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

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

С тех пор как я впервые попал в архив Института цифровой реставрации, у меня свербело в мозгу одно: а можно ли доверить восстановление этих изображений алгоритму? Не просто научить его «дорисовывать», а сделать это осмысленно, с уважением к автору и эпохе. Ответ: можно, но придётся попотеть.


1. Сканирование и подготовка изображения

Всё начинается с цифры. Мы получаем фреску в виде многомегапиксельного TIFF-файла. Иногда — даже с LIDAR-данными, если повезло с финансированием. Для работы используется OpenCV и PIL. Первый шаг — привести всё это безобразие в читаемый вид.

# Язык: Python 3
from PIL import Image
import cv2
import numpy as np

# Загружаем изображение
image_path = 'damaged_fresco.tiff'
pil_image = Image.open(image_path).convert('RGB')
image = np.array(pil_image)

# Уменьшаем размер для предпросмотра
preview = cv2.resize(image, (1024, 1024))
cv2.imshow("Fresco Preview", preview)
cv2.waitKey(0)
cv2.destroyAllWindows()

На этом этапе фреска уже выглядит не как грязная стена, а как загадка. Самое время заняться шумом.


2. Удаление шума и артефактов времени

Пятна, трещины, грибок, остатки штукатурки — всё это нужно убрать, но осторожно. Мы не хотим уничтожить след кисти мастера XIV века. Поэтому никакой агрессивной фильтрации. Только bilateral filtering и ручная маска повреждений.

# Фильтрация с сохранением краёв
filtered = cv2.bilateralFilter(image, d=9, sigmaColor=75, sigmaSpace=75)

# Простой способ маскировать пятна вручную (пока без ML)
mask = cv2.inRange(filtered, (0, 0, 0), (50, 50, 50))
cv2.imshow("Damage Mask", mask)
cv2.waitKey(0)

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


3. Inpainting: цифровое восстановление утраченного

Тут начинается магия. OpenCV предоставляет две классические функции для inpainting: cv2.INPAINT_TELEA и cv2.INPAINT_NS. Первая работает быстрее, вторая — мягче. В большинстве случаев Telea даёт более «живую» текстуру, что особенно важно в живописи.

inpainted = cv2.inpaint(filtered, mask, 3, cv2.INPAINT_TELEA)
cv2.imshow("Restored Fresco", inpainted)
cv2.waitKey(0)

Это уже почти искусство: иногда результат получается настолько красивым, что хочется подписать — restored by algorithmic brush of 2025.


4. Углубляемся: GAN для генерации утраченных фрагментов

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

Модель обучалась на 50 000 патчах из разных музеев Европы. Использовали подход DeepFill v2 с модифицированной encoder-частью.

# Пример использования обученной модели (упрощённый псевдокод)
import torch
from model import InpaintingModel

model = InpaintingModel()
model.load_state_dict(torch.load("gan_fresco_model.pth"))
model.eval()

input_image = preprocess_for_model(inpainted)
output = model(input_image)
restored_final = postprocess(output)

На выходе — фрагмент, который стилистически соответствует оригиналу. Да, это всё ещё синтетика, но гораздо ближе к реальности, чем белое пятно.


5. Контроль качества: оценка результата

Вот где всё становится субъективным. Мы не можем сравнивать с оригиналом — его нет. Но можем считать метрики по соседним участкам: SSIM, perceptual loss, цветовую согласованность.

from skimage.metrics import structural_similarity as ssim

ssim_value = ssim(original_crop, restored_crop, multichannel=True)
print(f"SSIM: {ssim_value:.3f}")

И всё равно — финальное слово за искусствоведом. Иногда он говорит: «Хм, нос Петра получился подозрительно похожим на Джастина Бибера». Тогда откатываем и ищем другой подход.


Заключение

Алгоритмы не заменят художника. Но они становятся его соавторами. Python, OpenCV, PyTorch и немного человеческой одержимости — вот инструменты цифрового Рубенса. Мы не создаём, мы воскрешаем. И пусть наш код когда-нибудь тоже кто-то найдёт и отреставрирует.

Комментарии (2)


  1. nikolay_karelin
    31.05.2025 15:33

    Интересно, но без иллюстраций или исходных данных что понять ну никак нельзя...


  1. U235a
    31.05.2025 15:33

    "На этом этапе фреска уже выглядит не как грязная стена, а как загадка. "
    Вы сами ваш код вообще запускали? Или нейросеть сгенерировала и все..? Любой кто хоть немного работал с OpenCV занет, что на предпросмотре вы увидите, что каналы Blue и Red перепутаны...