Использование готовой библиотеки cvlib для использования предварительно обученных моделей

Это серия из 3 частей, в которой я играю с cvlib:

  1. Обнаружение людей за несколько минут
  2. Распознавание лиц за несколько минут
  3. Цензура лиц в видео

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

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

Цензура лица с помощью cvlib

Мы уже знаем, где находится лицо и ограничивающая рамка вокруг лица. Это означает, что мы уже знаем регион, который хотим подвергнуть цензуре. Но как мы собираемся это сделать?

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

Логика довольно проста, и код будет легко реализовать, но сначала, как и в упражнении с ограничивающей рамкой, давайте добавим возможность включать и выключать цензуру. А так как у нас теперь будет несколько переключателей, давайте добавим перечисление.

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

censoring = False
bounding = True

Теперь добавим перечисление после инициализации переменных.

class toggleOptions(Enum):
 FACE = 1
 CENSOR = 2
 ALL = 3

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

def toggle(option):
 print("Toggling {}".format(option.name))
global bounding
global censoring
if option == toggleOptions.CENSOR:
  censoring = not censoring
 
 if option == toggleOptions.FACE:
  bounding = not bounding
if option == toggleOptions.ALL:
  final = censoring & bounding
  final = not final
  censoring = final
  bounding = final

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

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

key = cv2.waitKey(1)
if key == ord('q'):
  break
elif key == ord('f'):
  toggle(toggleOptions.FACE)
elif key == ord('c'):
  toggle(toggleOptions.CENSOR)
elif key == 32:
  toggle(toggleOptions.ALL)

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

Мы хотим определить размер уменьшенного изображения при его пикселизации. Если слишком большое, все равно останется достаточно деталей, чтобы распознать, что находится за пикселями, поэтому давайте выберем меньшее число. Я выбрал 12 пикселей для ширины и высоты. Однако ширина и высота не обязательно должны быть одинаковыми, вы можете выбрать все, что захотите. Давайте объявим новые измерения в функции.

def faceDetect(detectImg, outputImg):
  pixelDim = (16,16)

Теперь давайте пикселизировать, только если censoring установлено на True. Давайте сделаем это после того, как мы извлекли координаты ограничивающей рамки.

   (startX, startY) = rectified_f[0], rectified_f[1]
   (endX, endY) = rectified_f[2], rectified_f[3]
#pixelate
   if censoring == True:

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

Так, например, изображение шириной 4 пикселя и высотой 5 пикселей можно представить следующим образом.

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

Итак, теперь мы это знаем, мы можем видеть, что извлечение региона — это просто вопрос навигации по массиву. Для этого мы можем использовать numpy, который действительно полезен для работы с массивами.

Предположим, что в приведенном выше представлении нам просто нужно изображение размером 2x3 пикселя, начиная с пикселя в (2,2).

С помощью numpy вы можете указать, что вам нужны столбцы со 2 по 3, и в каждом столбце вам нужны строки со 2 по 4. Обозначение будет [2: 3, 2: 4]. Если мы подумаем о координатах, один для верхнего левого угла и один для нижнего правого угла, мы увидим, что 2:3 представляет собой вектор между углами по оси Y, а 2:4 представляет собой вектор между углы по оси X.

Возвращаясь к коду, они у нас уже были.

(startX, startY) = rectified_f[0], rectified_f[1]
(endX, endY) = rectified_f[2], rectified_f[3]

Зная это, мы можем видеть, что запись для извлечения региона будет [StartY:EndY, StartX:EndX].

Давайте добавим извлечение региона в функцию и получим размеры, чтобы мы знали, к чему вернуться после изменения размера. Чтобы получить размеры, мы используем массив shape. 1 — ширина, 0 — высота.

#pixelate
if censoring == True:
  faceRect = outputImg[startY:endY,startX:endX]
  faceDim =  (faceRect.shape[1],faceRect.shape[0])

Теперь у нас есть извлеченная область, сохраненная в переменной faceRect.

Теперь мы можем изменить его размер до квадрата размером 12 пикселей, который мы определили ранее. Какой тип интерполяции в данном случае не имеет особого значения, так как мы все равно хотим потерять детали.

temp = cv2.resize(faceRect, (12,12), interpolation=cv2.INTER_NEAREST)

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

temp = cv2.resize(temp, faceDim, interpolation=cv2.INTER_NEAREST)

Теперь мы можем добавить эту область обратно к исходному изображению и позволить ей пройти этап ограничивающей рамки.

outputImg[startY:endY,startX:endX] = temp

Теперь это работает, но мы можем быстро улучшить сценарий.

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

Вы можете увидеть полный код ниже.