Преобразование видео в мультфильм на python и cv2

5e37bdf150b1859880c8fc3379f53793

Возможно, последние несколько месяцев вы могли видеть некоторые генерации нейросетей наподобие этой:

К сожалению, не все обладают достаточным количеством времени, чтобы обрабатывать видео по одной картинке, а вычислительных мощностей многих компьютеров (например, моего ноутбука) не всегда хватает на использование stable diffusion.

Тогда попробуем сделать мультик из видео другим способом!

Сразу приведу готовый код, чтобы вы его не искали по всей статье, а по ходу буду его объяснять:

import cv2
import numpy as np

def color_quantization(img, k):
# Transform the image
  data = np.float32(img).reshape((-1, 3))

# Determine criteria
  criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 20, 0.001)

# Implementing K-Means
  ret, label, center = cv2.kmeans(data, k, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
  center = np.uint8(center)
  result = center[label.flatten()]
  result = result.reshape(img.shape)
  return result


def edge_mask(img, line_size, blur_value):
  gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  gray_blur = cv2.medianBlur(gray, blur_value)
  edges = cv2.adaptiveThreshold(gray_blur, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, line_size, blur_value)
  return edges


print("количество цветов на видео (рекомендуется 10-100)")
total_color = int(input())
line_size = 7
blur_value = 7
print("создание изображений...")
count = 1

vidcap = cv2.VideoCapture('vid.mp4')
success, image = vidcap.read()


while success:
  success, image = vidcap.read()

  edges = edge_mask(image, line_size, blur_value)
##  cv2.imshow('what',edges)

  image = color_quantization(image, total_color)
  ##cv2.imshow('what',img)

  blurred = cv2.bilateralFilter(image, d=7, sigmaColor=200,sigmaSpace=200)
  ##cv2.imshow('what',blurred)

  cartoon = cv2.bitwise_and(blurred, blurred, mask=edges)
  ##cv2.imshow('what',cartoon)

  cv2.imwrite(str(count) + '.jpg', cartoon)
  print("корректно сохранена " + str(count) + "-ая картинка")
  count += 1
import cv2
import moviepy.editor as moviepy
from time import sleep
import os

print("фотографий:")
count = int(input())
print("кадров в секунду:")
fps = int(input())

counter = 1
img_array = []
for i in range(count):
    img = cv2.imread(str(counter) + ".jpg")
    height, width, layers = img.shape
    size = (width,height)
    img_array.append(img)
    if os.path.isfile(str(counter) + ".jpg"):
        os.remove(str(counter) + ".jpg")
    
    counter += 1

    if counter - 1 % 10 == 0:
      print("успешно превращены в видео " + str(counter) + " из " + str(count) + " фотографий")


out = cv2.VideoWriter('cartoon.avi',cv2.VideoWriter_fourcc(*'DIVX'), fps, size)
 
for i in range(len(img_array)):
    out.write(img_array[i])
out.release()


print("готово видео в формате .avi, но вы его нигде не воспроизведёте,")
print("сейчас произойдёт конвертация в .mp4")
print("конвертация...")
print()
clip = moviepy.VideoFileClip("cartoon.avi")
clip.write_videofile("result.mp4")
print()

print("программа завершена! спасибо, что воспользовались cartooner 1.2!")
print("терминал автоматически закроется через 60 секунд")
sleep(60)

Для полноценного использования кода вы можете запускать .exe файл даже без установки python и библиотек, инструкция по использованию в конце статьи.

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

Обозначим основные этапы:

  1. Для каждого изображения из видео нужно превратить его в изображение типа «мультик»

  2. Соединить фото в видео

  3. Сделать видео воспроизводимым

Первое — импортировать необходимые библиотеки:

import cv2
import numpy as np

Далее необходимо создать функции:

def color_quantization(img, k):
# Transform the image
  data = np.float32(img).reshape((-1, 3))

# Determine criteria
  criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 20, 0.001)

# Implementing K-Means
  ret, label, center = cv2.kmeans(data, k, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
  center = np.uint8(center)
  result = center[label.flatten()]
  result = result.reshape(img.shape)
  return result


def edge_mask(img, line_size, blur_value):
  gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  gray_blur = cv2.medianBlur(gray, blur_value)
  edges = cv2.adaptiveThreshold(gray_blur, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, line_size, blur_value)
  return edges

Объяснение их работы вы найдёте в статье. Это главный ресурс, который я использовал для создания итоговой программы.
То же самое можно сказать про код сохранения и обработки:

print("количество цветов на видео (рекомендуется 10-100)")
total_color = int(input())
line_size = 7
blur_value = 7
print("создание изображений...")
count = 1

vidcap = cv2.VideoCapture('vid.mp4')
success, image = vidcap.read()

while success:
  success, image = vidcap.read()

  edges = edge_mask(image, line_size, blur_value)
  image = color_quantization(image, total_color)
  blurred = cv2.bilateralFilter(image, d=7, sigmaColor=200,sigmaSpace=200)
  cartoon = cv2.bitwise_and(blurred, blurred, mask=edges)

  cv2.imwrite(str(count) + '.jpg', cartoon)
  print("корректно сохранена " + str(count) + "-ая картинка")
  count += 1

Важно описать то, что есть в этом коде, но чего нет в вышеупомянутой статье. Это строка 26, отвечающая за сохранение изображения, а также 8, 9 и 12 строки, отвечающие за извлечение изображений из видео. Это — первая часть программы.

Второй файл отвечает за сборку фото в видео и удаление полученных фото. Впрочем, добавлена функция сборки с начала до определённой фотографии.

Как обычно, импорт библиотек:

import cv2
import moviepy.editor as moviepy
from time import sleep
import os

Приём данных от пользователя:

print("фотографий:")
count = int(input())
print("кадров в секунду:")
fps = int(input())

Далее — составление массива с фото и удаление этих фотографий, иначе вы получите полтысячи ненужных фотографий:

counter = 1
img_array = []
for i in range(count):
    img = cv2.imread(str(counter) + ".jpg")
    height, width, layers = img.shape
    size = (width,height)
    img_array.append(img)
    if os.path.isfile(str(counter) + ".jpg"):
        os.remove(str(counter) + ".jpg")
    
    counter += 1

    if counter - 1 % 10 == 0:
      print("успешно превращены в видео " + str(counter) + " из " + str(count) + " фотографий")

Если вы хотите использовать полученные изображения, сотрите 8 и 9 строки.

Сборка видео из массива:

out = cv2.VideoWriter('cartoon.avi',cv2.VideoWriter_fourcc(*'DIVX'), fps, size)
 
for i in range(len(img_array)):
    out.write(img_array[i])
out.release()

print("готово видео в формате .avi, но вы его нигде не воспроизведёте,")

Собственно, конвертация и сохранение:

print("сейчас произойдёт конвертация в .mp4")
print("конвертация...")
print()
clip = moviepy.VideoFileClip("cartoon.avi")
clip.write_videofile("result.mp4")
print()

print("программа завершена! спасибо, что воспользовались cartooner 1.2!")
print("терминал автоматически закроется через 60 секунд")
sleep(60)

Вот и всё!

Теперь проведём небольшое исследование модели, и получим видео:

https://disk.yandex.ru/d/fhhrK0KRp5UDTQ

Заметно, что чем больше цветов, тем лучше, но и время генерации увеличивается пропорционально.

Теперь рассмотрим использование файлов .exe из Яндекс диска. Скачиваем файлы, помещаем в отдельную папку. В эту же папку помещаем видео для превращения, переименовываем в vid.mp4, запускаем cartooner.exe, следуем инструкции. После окончания выполнения программы запускаем videoizer.exe и снова следуем инструкции. После выполнения программы вы получите: видео в формате avi, видео в формате mp4, возможно, несколько фотографий. Видео result.mp4 и есть результат работы программы. Помните, что при повторной генерации видео результата заместится новым.

© Habrahabr.ru