Цифровая реставрация культурного наследия — не то, о чём часто пишут на 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)
U235a
31.05.2025 15:33"На этом этапе фреска уже выглядит не как грязная стена, а как загадка. "
Вы сами ваш код вообще запускали? Или нейросеть сгенерировала и все..? Любой кто хоть немного работал с OpenCV занет, что на предпросмотре вы увидите, что каналы Blue и Red перепутаны...
nikolay_karelin
Интересно, но без иллюстраций или исходных данных что понять ну никак нельзя...