Вспомни молодость или как раскрасить черно‑белое фото из прошлого века

В жаркую последнюю субботу весны 2021 года, когда на улице за 30°С, природа с озёрами и горами далеко, а кондиционер вместе с увлажнителем создаёт в квартире комфортную обстановку и совершать телодвижения лениво, посмотрел юношеские чёрно‑белые фотографии. В то время, когда я учился в школе, у меня по наследству было три фотоаппарата — ФЭД (Феликс Эдмундович Дзержинский), собранный бывшими малолетними беспризорниками в колонии, которой командовал Макаренко А.С. в начале 30-х годов прошлого века, найденный в деревне, но исправно выполнявший свою работу в 70-х; Зенит-Е, один из первых зеркальных фотоаппаратов СССР образца 1966 года и моя любимая Смена-8М, как говорили раньше «мыльница», не отличающаяся качеством оптики и соответственно качественной фотографий, но свободно помещающаяся в карман. В нашем классе мало у кого не было своего фотоаппарата. В конце этих записок вы увидите галерею старых раскрашенных фотографий, большинство из которых сделаны именно «мыльницами».

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

Этот проект целью своей имеет автоматическое преобразование старых черно-белых фотографий в цветные с помощью Python, используя библиотеки OpenCV, DNN и Caffe. Написанный и отлаженный скрипт Python bw2c-ru.py принимает черно-белое изображение на входе и автоматически возвращает цветное изображение на выходе.

Чтобы продолжить объяснение раскраски черно-белых изображений с помощью Python, нам нужно загрузить 3 файла.

  • colorization_release_v2.caffemodel — предварительно обученная модель, хранящаяся в формате фреймворка Caffe, которую можно использовать для прогнозирования цветов в окрестности опорных точек.
  • colorization_deploy_v2.prototxt — различные параметры, которые определяют архитектуру нейронной сети, а также помогает в развертывании модели Caffe.
  • pts_in_hull.npy — файл NumPy, где хранятся центральные опорные точки кластера в формате NumPy. Он состоит из 313 ядер кластеров, то есть (0-312).

Вы можете скачать все файлы проекта, а именно модель, Caffe, Prototxt, файл .npy и исходный код с облака Яндекс.

Суть проблемы и предпосылки решения

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

Основной подход по сути прост. Первым шагом мы переводим изображение в формат Lab, который содержит раздельно яркостную (L) и цветовую (аb) составляющие — канал a кодирует зелено-красную часть спектра, а канал b кодирует сине-желтую часть видимого спектра. L-канал это фактически и есть исходное черно-белое фото.

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

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

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

Код автоматической раскраски черно-белых изображений

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

Если у вас не установлены эти библиотеки, то их нужно установить с помощью командной строки Windows. Чтобы установить эти библиотеки в свое приложение Python, вы можете использовать приведенные ниже команды. Собственно говоря, как это делать я рассказываю первокурсникам, поэтому не буду повторятся, а просто отсылаю к своим методическим указаниям, где по этому вопросу есть всё необходимое. Прочитать это полезно будет и тем, кого волнует вопрос Что такое бизнес-информатика?.

Вот начало нашего кода по раскраске:

import numpy as np
import matplotlib.pyplot as plt
import cv2

Сделаем настройки для работы скрипта. Первое: имя тестового образца (черно-белое изображение) сохраняется в переменной с именем image. Причина сохранения имени тестового образца в отдельной переменной состоит в том, чтобы использовать то же имя для сохранения цветного изображения тестового образца.

image = 'city-new.jpg'

Таким образом, вставляю сюда на место city-new.jpg, имя файла, результат раскраски которого можно увидеть на картинке в начале этой публикации. Собрав файлы для обработки в одной папке, используя цикл for можно учудить пакетную обработки нескольких файлов.

Далее определим то, что нам нужно для загрузки готовой модели caffee:

prototxt = "./colorization_models/colorization_deploy_v2.prototxt"
caffe_model = "./colorization_models/colorization_release_v2.caffemodel"
pts_npy = "./colorization_models/pts_in_hull.npy"

test_image =  "./input_images/" + image

На моём домашнем компьютере настройки располагаются во вложенной папке /colorization_models корневой папки проекта. Вы можете хранить эти файлы там, где удобнее, только не забывайте в коде указать правильное имя своей папки. В ней должны быть файлы .cafffemodel, .prototxt, .npy. Тестовый файл для колоризации располагается в отдельной папке ./input_images/ исключительно с целью автоматизации пакетной обработки некольких файлов. С этой же цель в дальнейшем у нас появится папка ./output_images/ для хранения раскрашенных фотографий. Обратите внимание, что имя папки расположения с именем файла для раскраски складываются и это предоставляет скрипту доступ к нему.

Загружаем модель Caffee. Функция cv2.dnn.readNetFromCaffe() принимает два параметра.

  1. prototxt — путь к файлу .prototxt
  2. caffe_model — путь к файлу .caffemodel

Далее загружаем файл .npy, используя функцию load из библиотеки NumPy.

net = cv2.dnn.readNetFromCaffe(prototxt, caffe_model)
pts = np.load(pts_npy)

Следующим шагом будет получение идентификатора слоя из модели Caffee с помощью функции .getLayerId(). .GetLayerId() принимает один параметр.

Например: net.getLayerId("name of the layer")

layer1 = net.getLayerId("class8_ab")
print(layer1)
layer2 = net.getLayerId("conv8_313_rh")
print(layer2)
pts = pts.transpose().reshape(2, 313, 1, 1)
net.getLayer(layer1).blobs = [pts.astype("float32")]
net.getLayer(layer2).blobs = [np.full([1, 313], 2.606, dtype="float32")]

Здесь мы извлекаем идентификаторы уровней для двух выходов class8_ab и conv8_313_rh из последнего уровня сети. Далее транспонируем наш файл NumPy и изменяем центры кластеров, хранящиеся в них, как матрицу 1\,\times\,1, а затем добавляем ее в нашу модель.

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

layer {
  name: "class8_ab"
  type: "Convolution"
  bottom: "class8_313_rh"
  top: "class8_ab"
  convolution_param {
    num_output: 2
    kernel_size: 1
    stride: 1
    dilation: 1
  }
}

Это последняя часть кода файла .prototxt. Как мы видим, количество выходов — два и больше, что и есть имена выходов.

Теперь, когда с внедрением модели в наш код, перейдем к решению своих собственных вопросов и коду.

# Читать изображение
test_image = cv2.imread(test_image)

# Преобразовать изображение в оттенки серого
test_image = cv2.cvtColor(test_image, cv2.COLOR_BGR2GRAY)

# Преобразование изображения из серой шкалы в формат RGB
test_image = cv2.cvtColor(test_image, cv2.COLOR_GRAY2RGB)

# Посмотреть на изображение с помощью matplotlib
# Если делать пакетную обработку фотографий в цикле следующие
# два оператора надо закоментировать
plt.imshow(test_image)
plt.show()

Функция imread(test_image) из OpenCV использована для чтения нашей фотографии из папки для неё отведённой. Затем конвертируем изображение из формата BGR в формат GRAY, а затем снова конвертируем его из серого формата в формат RGB. После процесса преобразования используем библиотеку Matplotlib для печати/проверки изображения.

Теперь мы выполним операцию масштабирования, нормализуя пиксели изображения в интервал [0-1]. Затем конвертируем формат изображения из RGB в LAB. Чтобы узнать больше о цветовом пространстве LAB, зайдите на LAB Color Space. Далее изменяем размер изображения до формы 224\;\times\;224. Функция cv2.split() разбивает изображение на три канала, то есть L, A, B. Они используются для извлечения L‑канала из изображения LAB по его порядковому номеру.

# Нормализация изображения
normalized= test_image.astype("float32") / 255.0

# Преобразование изображения в LAB
lab_image = cv2.cvtColor(normalized, cv2.COLOR_RGB2LAB)

# Изменение размера изображения
resized = cv2.resize(lab, (224, 224))

# Извлечение значения L для изображения LAB
L = cv2.split(resized)[0]
L -= 50   # ИЛИ мы можем написать L = L - 50

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

# Настройка входа
net.setInput(cv2.dnn.blobFromImage(L))

# Нахождение значений 'a' и 'b'
ab = net.forward()[0, :, :, :].transpose((1, 2, 0))

# Изменение размера
ab = cv2.resize(ab, (test_image.shape[1], test_image.shape[0]))

Затем снова извлекается L-канал, но из исходного изображения LAB, потому что размеры всех трех плоскостей (L, a, b) должны быть одинаковыми. Затем объединяем L-канал с «a» и «b», используя Numpy, чтобы получить цветное изображение LAB. Используем Matplotlib, чтобы показать изображение в пространстве LAB.

L = cv2.split(lab_image)[0]
# Объединение L, a, b
LAB_colored = np.concatenate((L[:, :, np.newaxis], ab), axis=2)

# Проверка образа LAB
# Если делать пакетную обработку фотографий в цикле, следующие
# три оператора надо закомментировать
plt.imshow(LAB_colored)
plt.title('LAB image')
plt.show()

Мы получили цветное изображение LAB, но изображение непонятное. Итак, нам нужно преобразовать изображение LAB в формат RGB, что мы и сделаем. Далее используем np.clip() для обрезки изображения RGB между «0» и «1». Отсечение означает, что если интервал равен [0,1], то все значения меньше нуля станут нулевыми, а все значения больше единицы станут единицей.

Вспомните, что пиксели у нас нормализованы в диапазоне [0,1]. Поэтому изменяем пиксели изображения обратно в диапазон [0,255].

# Преобразование изображения LAB в цвета RGB_colored
RGB_colored = cv2.cvtColor(LAB_colored,cv2.COLOR_LAB2RGB)

# Ограничивает значения в массиве
RGB_colored = np.clip(RGB_colored, 0, 1)

# Изменение значения пикселей обратно в диапазон [0,255]
RGB_colored = (255 * RGB_colored).astype("uint8")

# Посмотреть на результат изображения
# Если делать пакетную обработку фотографий в цикле, следующие
# три оператора надо закомментировать
plt.imshow(RGB_colored)
plt.title('Colored Image')
plt.show()

Теперь, после построения изображения RGB с помощью Matplotlib, мы получим идеально цветное изображение для нашего черно-белого тестового изображения.

Чтобы сохранить цветное изображение, сначала оно преобразуется из формата RGB в формат BGR, а затем используется OpenCV для сохранения изображения по описанному пути. Функция cv2.imwrite() принимает два аргумента — 1) путь (место, где файл должен быть сохранен) и 2) имя сохраняемого файла RGB_BGR.

# Converting RGB to BGR
RGB_BGR = cv2.cvtColor(RGB_colored, cv2.COLOR_RGB2BGR)

# Сохранение изображения в нужном месте
cv2.imwrite("./output_images/"+image, RGB_BGR)

Программа Python для преобразования черно-белого изображения в цветное

Все показанные выше фрагменты я собрал в единый файл с именем bw2c.py. Вот посмотрите, что получилось:

# Импорт библиотек
import numpy as np
import matplotlib.pyplot as plt
import cv2

# Название тестового изображения
image = 'angel-sculptur.jpg'

# Путь к нашим файлам caffemodel, prototxt и numpy
prototxt = "./colorization_models/colorization_deploy_v2.prototxt"
caffe_model = "./colorization_models/colorization_release_v2.caffemodel"
pts_npy = "./colorization_models/pts_in_hull.npy"

test_image =  "./input_images/" + image

# Загрузка нашей модели
net = cv2.dnn.readNetFromCaffe(prototxt, caffe_model)
pts = np.load(pts_npy)
 
layer1 = net.getLayerId("class8_ab")
print(f'Слой "class8_ab", id ={layer1}')
layer2 = net.getLayerId("conv8_313_rh")
print(f'Слой "conv8_313_rh", id ={layer2}')
pts = pts.transpose().reshape(2, 313, 1, 1)
net.getLayer(layer1).blobs = [pts.astype("float32")]
net.getLayer(layer2).blobs = [np.full([1, 313], 2.606, dtype="float32")]

# Преобразование изображения в RGB и построение его
# Читать изображение с пути
test_image = cv2.imread(test_image)

# Преобразовать изображение в оттенки серого
test_image = cv2.cvtColor(test_image, cv2.COLOR_BGR2GRAY)

# Преобразование изображения из серой шкалы в формат RGB
test_image = cv2.cvtColor(test_image, cv2.COLOR_GRAY2RGB)

# Проверить изображение с помощью matplotlib
plt.imshow(test_image)
plt.show()

# Преобразование изображения RGB в формат LAB
# Нормализация изображения
normalized = test_image.astype("float32") / 255.0

# Преобразование изображения в LAB
lab_image = cv2.cvtColor(normalized, cv2.COLOR_RGB2LAB)

# Изменение размера изображения
resized = cv2.resize(lab_image, (224, 224))

# Извлечение значения L для изображения LAB
L = cv2.split(resized)[0]
L -= 50   # OR we can write L = L - 50

# Прогнозирование значений a и b
# Настройка ввода
net.setInput(cv2.dnn.blobFromImage(L))

# Нахождение значений 'a' и 'b'
ab = net.forward()[0, :, :, :].transpose((1, 2, 0))

# Изменение размера
ab = cv2.resize(ab, (test_image.shape[1], test_image.shape[0]))

# Объединение каналов L, a и b
L = cv2.split(lab_image)[0]

# Объединение L, a, b
LAB_colored = np.concatenate((L[:, :, np.newaxis], ab), axis=2)

# Checking the LAB image
plt.imshow(LAB_colored)
plt.title('LAB image')
plt.show()

## Преобразование изображения LAB в RGB
RGB_colored = cv2.cvtColor(LAB_colored,cv2.COLOR_LAB2RGB)

# Ограничивает значения в массиве
RGB_colored = np.clip(RGB_colored, 0, 1)

# Изменение интенсивности пикселей обратно на [0,255], как мы делали масштабирование во время предварительной обработки и преобразований
# pixel intensity to [0,1]
RGB_colored = (255 * RGB_colored).astype("uint8")

# Проверка изображения
plt.imshow(RGB_colored)
plt.title('Colored Image')
plt.show()

# Сохранение цветного изображения
# Преобразование RGB в BGR
RGB_BGR = cv2.cvtColor(RGB_colored, cv2.COLOR_RGB2BGR)

# Сохранение изображения в нужном месте
cv2.imwrite("./output_images/" + image, RGB_BGR)

Итак, это пошаговое руководство по автоматическому преобразованию любого черно-белого изображения в цветное. Надеюсь, что было понятно. А теперь результат или ради чего всё это делалось в жаркую последнюю субботу весны 2021 года.

Результаты или так теперь выглядит в цвете наша молодость

Начнем с приличных монохромных фотографий, полученных поиском по ключевому слову monochrome на pixabay.com. Результат раскрашивания одной из них есть в самом начале этой заметки, а вот ещё один:
Скульптура ангелов

Эти фото были использованы в качестве теста и их можно скачать с моего облака вместе со всеме прибамбасами этого проекта.

А теперь главное, фото из 70-х. Поехали, начиная с себя любимого, не лысого и не бородатого, «были когда‑то и мы рысаками:
Вадим Костерин

Вадим Костерин

И в кого этот отрок превратился:
Вадим Костерин

Хватит про себя и начинаем с учителей:
Мария Ниловна, наша классная. Кто рядом не узнал. Кто знает напишите в комментах

Надеюсь у моих одноклассников эти фотографии вызовут интерес и они не будут возражать против публикации незабываемых образов здесь. Кстати, если над фото немного подержать указатель «мышки», то увидите кто на фото (то, кого помню), а если щелкнуть, то в соседней вкладке можно рассмотреть.
Игорь Достовалов

Боря Сайфутдинов и Саша Смирнов, видать солнышко согрело, вот и покраснели

Слева Лариска Данилова, а справа не помню, хоть убейте

На весеннем коммунистическом субботнике ваш покорный слуга, Саня Смирнов и Андрей Торгашев

Школа №57, с которой для меня начинался Магнитогорск. Слева направо Вадим Костерин, Ринат Губайдуллин, Володя Шмаков, чуть сзади, Сергей Курносов, Слава Букин.

Слава Букин у меня на кухне по адресу Октябрьская, 6

Андрей Торгашов на задворках 53 школы

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

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

Ну, как оно?

Все фотографии, показанные здесь напечатаны на специальной фотобумаге, увеличителем, в тёмной ванной комнате при свете красного фонаря, путём экспонирования и последующей миграции проявитель → вода → закрепитель → вода → дальнейшая сушка на глянцевателе, не то, что сейчас — щёлкнул и получите. Цифровыми они стали лет через 40 после своего рождения, старичками уже, утратившими юношескую свежесть. Если над черно‑белыми фотографиями далёкого прошлого мы умиляемся низкому качеству, отдавая дань прошедшим годам, то в XXI веке хотелось бы лучше.

Я специально не делал ни какой предварительной обработки исходного материала — сие есть сейчас ручной, кропотливый, а самое неприятное в наше стремительное время, длительный процесс. А хотелось просто попробовать. Кроме того, в нейронной сетке не учтена балансировка цвета при восстановлении и это очень заметно, особенно с красным. Что делать? — знаю, но это дело будущего, когда будет время для подобных развлечений.

На сегодня хватит…

Материалы для пользы дела:

Print Friendly, PDF & Email

CC BY-NC 4.0 Вспомни молодость или как раскрасить черно‑белое фото из прошлого века, опубликовано К ВВ, лицензия — Creative Commons Attribution-NonCommercial 4.0 International.


Респект и уважуха

Добавить комментарий