使用OpenCV创建GIF



本教程将向您展示如何使用OpenCV,Python和ImageMagick创建动画GIF。 然后结合这些方法,用OpenCV创建一个模因生成器!

我们所有人都需要时常大笑。 也许找到lulza的最好方法是使用模因。 我的一些最爱:

  • 青蛙青蛙(Kermit the Frog):“但这不关我的事”
  • 脾气暴躁的猫
  • 史诗失败
  • 好家伙格雷格

但是对我个人而言,这些模因无法与“与之达成协议”模因(“与之达成协议”或“自己理解”)相提并论,本文开头给出了一个例子。

通常在以下情况下使用:

  1. 作为对不同意您已完成/说过的事情的人的回答或反对(“与之达成协议”)
  2. 戴上眼镜就像您要离开一样,让一个人独自面对问题(“自己了解”)

几年前,我在作者的博客上读了一篇有趣的文章,我不记得如何使用计算机视觉生成此类模因。 上周我在任何地方都找不到此指南,因此,作为博客,计算机视觉专家和模因专家,我决定自己编写一个教程! (顺便说一句,如果您不小心知道了原始出处,请告诉我,以便感谢作者。UPD:刚刚从Kirk Kaiser的博客MakeArtWithPython中找到了原始文章)。

在OpenCV上开发模因生成器将教会我们许多宝贵的实践技能,包括:

  1. 使用深度学习技术的人脸检测
  2. 使用dlib库检测面部标志并提取眼睛区域
  3. 如何根据收到的信息计算眼睛之间的旋转角度
  4. 最后,如何使用OpenCV生成动画GIF(在ImageMagick的帮助下)

该指南应该既有趣又有趣-同时教会您在现实世界中有用的宝贵的计算机视觉编程技能。

使用OpenCV创建GIF


在指南的第一部分,我们将讨论该项目的必要条件和依赖关系,包括开发环境的正确配置。

然后考虑我们的OpenCV GIF生成器的项目/目录结构。

一旦了解了项目结构,我们将考虑:1)我们的配置文件; 2)Python脚本,负责使用OpenCV创建GIF。

最后,我们将根据流行的模因“ Deal With It”评估该计划的结果。

先决条件和依赖项



1.我们将使用OpenCV,dlib和ImageMagick创建GIF

Opencv和dlib


需要OpenCV来确定帧中的面孔和基本的图像处理。 如果系统上未安装OpenCV,请遵循我的一份OpenCV安装指南

我们使用Dlib来检测面部标志,这使我们能够在脸上找到两只眼睛并戴上太阳镜。 您可以使用此指令安装dlib

影像魔术师


如果您不熟悉ImageMagick ,那就白费了。 它是具有许多图像处理功能的跨平台命令行工具。

您是否要使用一个命令将PNG / JPG转换为PDF? 没问题

您需要从几张图像中制作多页PDF? 轻松一点

需要绘制多边形,直线和其他形状吗? 这是可能的。

如何使用一个命令对所有图片进行批处理颜色分级或调整其大小? 为此,您无需在Python中为OpenCV编写几行。

ImageMagick还可以从任何图像生成GIF。

要在Ubuntu(或Raspbian)上安装ImageMagick,只需使用apt:

使用OpenCVShell创建GIF

$ sudo apt-get install imagemagick 

在macOS上,您可以使用HomeBrew:

 $ brew install imagemagick 

不实用


在大多数文章,课程和书籍中,我都使用方便的imutils图像处理 。 它使用pip安装在系统或虚拟环境中:

 $ pip install imutils 

项目结构



2.项目结构包括两个目录,一个配置文件和一个Python脚本

我们的项目中有两个目录:

  • images/ :我们要为其制作动画GIF的输入图像的示例。 我发现了一些图像,但可以随意添加自己的图像。
  • assets/ :此文件夹包含我们的脸部检测器,脸部界标检测器以及所有图像和相关蒙版。 有了这些资产,我们将把点和文本放在第一个文件夹中的原始图像上。

由于大量的可配置参数,我决定创建一个JSON配置文件,该文件:1)将有助于参数的编辑; 2)将需要更少的命令行参数。 该项目所需的所有配置参数都包含在config.json

考虑config.jsoncreate_gif.py的内容。

注意事项 每。: 注册后将发布有关计算机视觉,机器学习和OpenCV的项目代码和17页的手册(镜像: 源代码manual )。

使用OpenCV生成GIF


因此,让我们继续并开始创建我们的OpenCV GIF生成器!

JSON配置文件内容


让我们从JSON配置文件开始,然后继续使用Python脚本。

打开一个新的config.json文件,并插入以下键/值对:

使用OpenCVPython创建GIF

 { "face_detector_prototxt": "assets/deploy.prototxt", "face_detector_weights": "assets/res10_300x300_ssd_iter_140000.caffemodel", "landmark_predictor": "assets/shape_predictor_68_face_landmarks.dat", 

这些是深度学习中的OpenCV人脸检测器模型文件。

最后一行是dlib人脸预测器的路径。

现在,我们有了一些图像文件的路径:

 "sunglasses": "assets/sunglasses.png", "sunglasses_mask": "assets/sunglasses_mask.png", "deal_with_it": "assets/deal_with_it.png", "deal_with_it_mask": "assets/deal_with_it_mask.png", 

这些是我们的太阳镜,文本和与之匹配的蒙版的路径,如下所示。

首先,花式太阳镜和口罩:


3.您不喜欢带像素的眼镜吗? 刚忍受


4.您不明白为什么需要戴太阳眼镜的口罩? 只需忍受它-或阅读本文的其余部分以获取答案。

现在我们的文字是“ DEAL WITH IT”和掩码:


5.您讨厌Helvetica Neue Condensed吗? 处理它


6:使用此遮罩可以在文本周围绘制边框。 哦,也许您不想,想要边界吗? 好吧,忍受它

需要使用遮罩才能将相应的图像覆盖在照片上:我们将在以后处理。

现在为模因生成器设置一些参数:

  "min_confidence": 0.5, "steps": 20, "delay": 5, "final_delay": 250, "loop": 0, "temp_dir": "temp" } 

以下是每个参数的定义:

  • min_confidence :所需的最小面部检测概率。
  • steps :最终动画中的帧数。 每个“步骤”都将太阳镜从上边界向下移动到目标(即,眼睛)。
  • delay :帧之间的延迟,以百分之一秒为单位。
  • final_delay :最后一帧的延迟,以百分之一秒为单位(在此情况下很有用,因为我们希望文本显示的时间比其余帧的显示时间长)。
  • loop :零值表示GIF永远重复,否则为动画的重复次数指定一个正整数。
  • temp_dir :创建最终GIF之前将在其中存储每个帧的临时目录。

模因,GIF和OpenCV


我们创建了JSON配置文件,现在让我们继续进行实际代码。

打开一个新文件,将其命名为create_gif.py并粘贴以下代码:

 #    from imutils import face_utils from imutils import paths import numpy as np import argparse import imutils import shutil import json import dlib import cv2 import sys import os 

在这里,我们导入必要的包。 特别是,我们将使用imutils,dlib和OpenCV。 要安装这些依赖项,请参阅上面的“先决条件和依赖项”部分。

现在该脚本具有必需的包,因此overlay_image定义overlay_image函数:

 def overlay_image(bg, fg, fgMask, coords): #     (, )  #    (sH, sW) = fg.shape[:2] (x, y) = coords #          #  ,   , **  # ,    overlay = np.zeros(bg.shape, dtype="uint8") overlay[y:y + sH, x:x + sW] = fg # - , **  ** # ,    ,    # ,       alpha = np.zeros(bg.shape[:2], dtype="uint8") alpha[y:y + sH, x:x + sW] = fgMask alpha = np.dstack([alpha] * 3) #  -   , #   - output = alpha_blend(overlay, bg, alpha) #   return output 

overlay_image函数在coords坐标坐标(x,y) )处在背景图像( bg )的顶部强加一个前景( fg ),从而在前景蒙版fgMask实现了alpha透明性。

要熟悉OpenCV的基础知识(例如使用遮罩),请务必阅读本指南

要完成混合过程,请执行Alpha混合:

 def alpha_blend(fg, bg, alpha): #  ,    - #        [0, 1] fg = fg.astype("float") bg = bg.astype("float") alpha = alpha.astype("float") / 255 #  - fg = cv2.multiply(alpha, fg) bg = cv2.multiply(1 - alpha, bg) #     ,    output = cv2.add(fg, bg) #   return output.astype("uint8") 

该alpha混合实现也可以在LearnOpenCV博客上找到

本质上,我们将前景,背景和Alpha通道转换为[0,1]范围内的浮点数。 然后我们执行Alpha混合,添加前景和背景以获得返回到调用函数的结果。

我们还将创建一个辅助函数,该函数允许使用ImageMagick和convert命令从一组图像路径生成GIF:

 def create_gif(inputPath, outputPath, delay, finalDelay, loop): #        imagePaths = sorted(list(paths.list_images(inputPath))) #      lastPath = imagePaths[-1] imagePaths = imagePaths[:-1] #   imagemagick 'convert'  #  GIF      #   ( ) cmd = "convert -delay {} {} -delay {} {} -loop {} {}".format( delay, " ".join(imagePaths), finalDelay, lastPath, loop, outputPath) os.system(cmd) 

create_gif函数获取一组图像并将其收集到GIF动画中,并在帧和循环之间具有指定的延迟。 ImageMagick处理所有这一切-我们仅将convert命令包装在一个动态处理各种参数的函数中。

要查看可用的convert参数, 请参阅文档 。 在这里,您将看到这个团队有多少功能!

特别是在此功能中,我们:

  • 采取imagePaths
  • 选择最后一张图像的路径,它将有一个单独的延迟。
  • 重新分配imagePaths以排除最后一个路径。
  • 我们将命令与命令行参数组合在一起,然后指示操作系统进行convert以创建GIF动画。

为脚本分配自己的命令行参数:

 #      ap = argparse.ArgumentParser() ap.add_argument("-c", "--config", required=True, help="path to configuration file") ap.add_argument("-i", "--image", required=True, help="path to input image") ap.add_argument("-o", "--output", required=True, help="path to output GIF") args = vars(ap.parse_args()) 

我们有三个在运行时处理的命令行参数:

  • --config :JSON配置文件的路径。 我们在上一节中回顾了配置文件。
  • --image :创建动画所依据的输入图像的路径(即检测面部,戴墨镜,然后输入文本)。
  • --output :生成的GIF的路径。

在命令行/终端上运行脚本时,每个参数都是必需的。

下载配置文件以及眼镜和相应的掩码:

 #    JSON, #     config = json.loads(open(args["config"]).read()) sg = cv2.imread(config["sunglasses"]) sgMask = cv2.imread(config["sunglasses_mask"]) #    (  ),   #  ,  ,     #   GIF- shutil.rmtree(config["temp_dir"], ignore_errors=True) os.makedirs(config["temp_dir"]) 

在这里,我们加载配置文件(将来可能会作为Python词典提供)。 然后装入墨镜和口罩。

如果以前的脚本中仍然有其他内容,请删除临时目录,然后重新创建空的临时目录。 临时文件夹将包含GIF动画中的每个单独的帧。

现在将OpenCV深度学习人脸检测器加载到内存中:

 # load our OpenCV face detector and dlib facial landmark predictor print("[INFO] loading models...") detector = cv2.dnn.readNetFromCaffe(config["face_detector_prototxt"], config["face_detector_weights"]) predictor = dlib.shape_predictor(config["landmark_predictor"]) 

为此,请调用cv2.dnn.readNetFromCaffednn模块仅在OpenCV 3.3或更高版本中可用。 人脸检测器将检测图像中是否存在人脸:


7.使用OpenCV DNN进行面部检测器操作

然后加载dlib人脸界标预测器 。 它使您可以定位单个结构:眼睛,眉毛,鼻子,嘴巴和下巴线:


8. dlib发现的地标叠加在我的脸上

在此脚本的后面,我们仅提取眼睛区域。

继续前进,让我们找到面孔:

 #       image = cv2.imread(args["image"]) (H, W) = image.shape[:2] blob = cv2.dnn.blobFromImage(cv2.resize(image, (300, 300)), 1.0, (300, 300), (104.0, 177.0, 123.0)) #        print("[INFO] computing object detections...") detector.setInput(blob) detections = detector.forward() #       ,  #  ,      i = np.argmax(detections[0, 0, :, 2]) confidence = detections[0, 0, i, 2] #    if confidence < config["min_confidence"]: print("[INFO] no reliable faces found") sys.exit(0) 

在此块中,我们执行以下操作:

  • 下载原始image
  • 我们构造一个blob以发送到神经网络的面部检测器。 本文介绍了OpenCV中的blobFromImage如何工作。
  • 执行面部检测程序。
  • 我们找到具有最高概率值的人,并将其与最小可接受概率阈值进行比较。 如果不符合条件,则退出脚本。 否则,请继续。

现在,我们将提取人脸并计算界标:

 #   (x, y)  #    box = detections[0, 0, i, 3:7] * np.array([W, H, W, H]) (startX, startY, endX, endY) = box.astype("int") #    dlib    #       rect = dlib.rectangle(int(startX), int(startY), int(endX), int(endY)) shape = predictor(image, rect) shape = face_utils.shape_to_np(shape) #        ,  #     (lStart, lEnd) = face_utils.FACIAL_LANDMARKS_IDXS["left_eye"] (rStart, rEnd) = face_utils.FACIAL_LANDMARKS_IDXS["right_eye"] leftEyePts = shape[lStart:lEnd] rightEyePts = shape[rStart:rEnd] 

要提取脸部并找到脸部界标,请执行以下操作:

  • 我们提取脸部周围的边界框的坐标。
  • 在dlib中创建一个rectangle对象并应用面本地化。
  • 我们分别获取leftEyePtsrightEyePts(x,y)坐标。

给定眼睛的坐标,您可以计算出放置太阳镜的位置和方式:

 #       leftEyeCenter = leftEyePts.mean(axis=0).astype("int") rightEyeCenter = rightEyePts.mean(axis=0).astype("int") #      dY = rightEyeCenter[1] - leftEyeCenter[1] dX = rightEyeCenter[0] - leftEyeCenter[0] angle = np.degrees(np.arctan2(dY, dX)) - 180 #      ,  #      sg = imutils.rotate_bound(sg, angle) #     **  ,    #   —       # 90%       sgW = int((endX - startX) * 0.9) sg = imutils.resize(sg, width=sgW) #      ( ,   #  ),      #     - —   #       , #     sgMask = cv2.cvtColor(sgMask, cv2.COLOR_BGR2GRAY) sgMask = cv2.threshold(sgMask, 0, 255, cv2.THRESH_BINARY)[1] sgMask = imutils.rotate_bound(sgMask, angle) sgMask = imutils.resize(sgMask, width=sgW, inter=cv2.INTER_NEAREST) 

首先,我们计算每只眼睛的中心,然后计算质心之间的角度。 框架中的面部水平对齐时执行相同的操作。

现在,您可以旋转眼镜并调整其大小。 请注意,我们使用不是rotate ,而是rotate 绑定函数 ,因此OpenCV不会修剪仿射变换后不可见的部分。

应用于眼镜的相同操作也适用于面罩。 但是首先,您需要将其转换为灰色阴影并进行二值化处理,因为遮罩始终是二进制的。 然后,我们以与眼镜相同的方式旋转并调整蒙版的大小。

注意:请注意,在调整遮罩大小时,我们对最近的相邻点使用插值,因为遮罩应该只有两个值(0和255)。 其他插值方法更美观,但不适用于蒙版。 在这里,您可以获得有关最近邻点插值的更多信息。

剩下的三个代码块为GIF动画创建框架:

 #    ,   #  N       #    steps = np.linspace(0, rightEyeCenter[1], config["steps"], dtype="int") # start looping over the steps for (i, y) in enumerate(steps): #      #  ,    **    #  ,       #   shiftX = int(sg.shape[1] * 0.25) shiftY = int(sg.shape[0] * 0.35) y = max(0, y - shiftY) # add the sunglasses to the image output = overlay_image(image, sg, sgMask, (rightEyeCenter[0] - shiftX, y)) 

眼镜从图像顶部掉落。 在每帧上,它们都靠近脸部显示,直到遮住眼睛为止。 使用JSON配置文件中的"steps"变量,我们为每个框架生成y坐标。 为此,我们无需花费太多精力即可使用NumPy的linspace函数。

从左到右稍微偏移的线条可能看起来有些奇怪,但是需要确保眼镜能覆盖整个眼睛,而不仅仅是移动到眼睛中心的位置。 我凭经验确定百分比以计算沿每个轴的偏移量。 下一行确保没有负值。

使用overlay_image函数, overlay_image生成最终的output帧。

现在,使用另一个蒙版应用文本“ DEAL WITH IT”:

  #    ,    #  "DEAL WITH IT"   if i == len(steps) - 1: #   "DEAL WITH IT"  , #   dwi = cv2.imread(config["deal_with_it"]) dwiMask = cv2.imread(config["deal_with_it_mask"]) dwiMask = cv2.cvtColor(dwiMask, cv2.COLOR_BGR2GRAY) dwiMask = cv2.threshold(dwiMask, 0, 255, cv2.THRESH_BINARY)[1] #       80%   #  oW = int(W * 0.8) dwi = imutils.resize(dwi, width=oW) dwiMask = imutils.resize(dwiMask, width=oW, inter=cv2.INTER_NEAREST) #  ,   ,  #   oX = int(W * 0.1) oY = int(H * 0.8) output = overlay_image(output, dwi, dwiMask, (oX, oY)) 

在最后一步,我们强加了文本,实际上是另一幅图像。

我决定使用图像,因为OpenCV字体的渲染功能非常有限。 另外,我想在文本周围添加阴影和边框,这也是OpenCV不知道的方法。

在此代码的其余部分中,我们将同时加载图像和蒙版,然后执行alpha混合以生成最终结果。

剩下的只是将每个帧保存到磁盘,并随后创建GIF动画:

  #      p = os.path.sep.join([config["temp_dir"], "{}.jpg".format( str(i).zfill(8))]) cv2.imwrite(p, output) #      ,     #   GIF- print("[INFO] creating GIF...") create_gif(config["temp_dir"], args["output"], config["delay"], config["final_delay"], config["loop"]) #  --    print("[INFO] cleaning up...") shutil.rmtree(config["temp_dir"], ignore_errors=True) 

我们将结果写入磁盘。 生成所有帧后,我们调用create_gif函数来创建GIF动画文件。 请记住,这是一个将参数传递给ImageMagick convert命令行工具的外壳。

最后,删除临时输出目录和单个图像文件。

结果


现在最有趣的部分:让我们看看我们的模因生成器创建了什么!

确保下载源代码,示例图像和深度学习模型。 然后打开一个终端并运行以下命令:

 $ python create_gif.py --config config.json --image images/adrian.jpg \ --output adrian_out.gif [INFO] loading models... [INFO] computing object detections... [INFO] creating GIF... [INFO] cleaning up... 


图9.使用此Python脚本由OpenCV和ImageMagick生成的GIF动画

在这里,您可以看到使用OpenCV和ImageMagick创建的GIF。 在其上执行以下操作:

  1. 正确检测我的脸。
  2. 眼睛的定位及其中心的计算。
  3. 眼镜正确落在脸上。

我博客的读者知道我是侏罗纪公园的一个大书呆子,经常在我的书,课程和学习指南中提到它。

不喜欢侏罗纪公园吗?

好的,这是我的答案:

 $ python create_gif.py --config config.json --image images/adrian_jp.jpg \ --output adrian_jp_out.gif [INFO] loading models... [INFO] computing object detections... [INFO] creating GIF... [INFO] cleaning up... 


10. OpenCV GIF动画基于最近侏罗纪世界2放映中的照片

在这里,我身着主题T恤参加了展览《侏罗纪世界:2》,上面放着一杯光,还有一本收藏书。

有趣的故事:

五六年前,我和我的妻子参观了佛罗里达迪斯尼世界的Epcot中心主题公园。

我们决定出差,以摆脱康涅狄格州严酷的冬季和急需的阳光。

不幸的是,在佛罗里达州一直下着雨,温度几乎不超过10°C。

在加拿大花园附近,特丽莎(Trisha)拍了一张我的照片:她说我看上去像个吸血鬼,皮肤苍白,深色衣服和兜帽,背后是郁郁葱葱的花园:

 $ python create_gif.py --config config.json --image images/vampire.jpg \ --output vampire_out.gif [INFO] loading models... [INFO] computing object detections... [INFO] creating GIF... [INFO] cleaning up... 


11.使用OpenCV和Python,您可以制作此模因或其他动画GIF

当天晚上,Trisha在社交网络上发布了一张照片-我不得不忍受。

参加了PyImageConf 2018的那些人( 阅读评论 )知道我总是很喜欢开玩笑。 这是一个例子:

问题:为什么公鸡过马路?

 $ python create_gif.py --config config.json --image images/rooster.jpg \ --output rooster_out.gif [INFO] loading models... [INFO] computing object detections... [INFO] creating GIF... [INFO] cleaning up... 


12.即使对比度低也可以识别出脸部,OpenCV会正确处理照片并

放下 墨镜答案:我不会说答案-对此表示赞同。

最后,我们以良好的模因总结了本指南。

大约六年前,我和父亲收养了一只小猎犬吉玛。

在这里,您可以看到Gemma在我的肩膀上:

 $ python create_gif.py --config config.json --image images/pupper.jpg \ --output pupper_out.gif [INFO] loading models... [INFO] computing object detections... [INFO] creating GIF... [INFO] cleaning up... 


13. Gemma很好吃。你不这样认为吗 然后“顺其自然!”

不同意她是可爱的吗?处理它。

有AttributeError错误吗?


不用担心!

如果看到此错误:

 $ python create_gif.py --config config.json --image images/adrian.jpg \ --output adrian_out.gif ... Traceback (most recent call last): File "create_gif.py", line 142, in <module> (lStart, lEnd) = face_utils.FACIAL_LANDMARKS_IDXS["left_eye"] AttributeError: module 'imutils.face_utils' has no attribute 'FACIAL_LANDMARKS_IDXS' 

...那么您只需要更新imutils软件包:

 $ pip install --upgrade imutils Collecting imutils ... Successfully installed imutils-0.5.1 

怎么了

默认情况下,它imutils.face_utils使用内置于dlib中的68点地标检测器(如本文所述)。一个更快的5点检测器,现在也可以与imutils一起使用。我最近更新了imutils以支持两个检测器(因此您可能会看到错误)。

总结


在今天的教程中,您学习了如何使用OpenCV创建GIF。

为了使课程有趣,我们使用OpenCV生成了GIF动画“ Deal With It”,这是一个流行的模因(也是我的最爱),在几乎每个社交网站上都以一种或另一种形式出现。

在此过程中,我们使用计算机视觉和深度学习解决了一些实际问题:

  • 身份识别
  • 预测脸上的地标
  • 识别面部区域(在这种情况下为眼睛)
  • 计算眼睛之间的角度以对齐脸部
  • 使用Alpha混合创建透明混合

最后,我们拍摄了一组生成的图像,并使用OpenCV和ImageMagick创建了动画GIF。

希望您喜欢今天的教程!

如果您喜欢它,请发表评论,让我知道。

好吧,如果您不喜欢它,没关系,就忍受它。 ;)

Source: https://habr.com/ru/post/zh-CN429024/


All Articles