如何优雅的制作App演示视频

February 13, 2020
OpenCVPython

这几天在家隔离实在是闲的无聊,所以准备把给Listify 做一个用户引导页面,因为随着一年的更新 Listify 的交互是一天比一天多但是可能很多用户还不知道那些交互,所以准备学着 iOS 的教学页面的设计做一个。因为我也发现要是在界面上直接放 App 截图或者录屏的话的话会非常的不好看。

于是这就有一个问题了,我想做一个 iPhone 套壳的交互视频,要是只是做一张带 iPhone 外壳的图片还好, Photoshop 或者 Sketch 就能搞定,但是如果是需要根据录屏来做一个视频的话还真的就不方便了,首先就是因为新版的 iPhone 和 iPad 的屏幕都不是规则的矩形了,iPhone 是一个异形的,而iPad Pro 是圆角矩形的,所以在剪辑软件上也要用遮罩操作一段时间,而且不管是 PR 还是 Final Cut Pro 授权费都很高,为了这么小一个需求也真是杀鸡焉用牛刀了。所以准备自己开发一个,能够给视频套上 iPhone 外壳的程序。

流程

大概的流程很简单

  1. 下载素材
  2. 使用 OpenCV 找到素材中 iPhone 的屏幕边缘
  3. 根据边缘生成一个遮罩
  4. 然后读取视频的每一帧,和遮罩进行运算把每一帧的图片中iPhone屏幕以外的部分裁掉
  5. 和素材中的iPhone拼成新的一帧
  6. 保存

下图是大概的流程

素材预处理

第一个问题就是素材了,为了方便我直接下载了 Apple 官方给的产品图像,所有型号都有,非常方便。
Marketing Resources and Identity Guidelines - App Store - Apple Developer

踩的第一个坑

一开始我把问题想得比较复杂,为了方便,我把 iPhone 的屏幕调成了白色 (在 PS 中,混合设置,颜色叠加),然后使用 OpenCV 做处理想要找到屏幕的边缘。

  • 二值化
    通过threshold进行二值化,这样可以吧原本的 iPhone 的细节消除,只留下黑色和白色,方便之后处理

  • 高斯模糊
    二值化过后手机的边框上还是有一些原本素材上反光效果所留下来的白色条带,为了降低这个噪音,我又做了一层高斯模糊。

  • Canny 算子
    之后使用 Canny 得到了素材中的边缘,从下图看出来效果还不错,大体上只剩下两个边缘,iPhone 的外圈和内圈,其中内圈使我想要的。

  • 边缘检测
    最后使用 OpenCV自带的 findContour 函数来得到边缘的一系列点,下图4这样的边缘使用 findContour的话会出来四个路径,因为每个边缘会有一个外圈和内圈,iPhone 屏幕的轨迹就是周长最小的那个。

这样的做法看似完美实则不然,上面的几个预处理步骤,除了二值化之外,都会受到素材大小的影响。高斯模糊的Kernel大小需要根据图片来找, Canny 两个threshold也会因为图片大小变动,这两个步骤的结果会直接影响到findContour的结果,所以在我百般尝试下还是效果不好,很多时候屏幕内圈的轨迹会断成好几段,要处理好的话非常麻烦,一点鲁棒性都没有。所以只好放弃

新的方案

之后我发现我一开始就把事情想得特别复杂了,很多拍电影的时候都是用绿幕作为背景,方便后期做特效合成,所以为啥我不这么弄。。。非得把屏幕调成白色背景然后各种边缘检测。

图片版权 https://www.quirkybyte.com/blog/2018/12/captain-america-civil-war-behind-the-scene/

于是这次预先在 PS 上吧 iPhone 屏幕的颜色调整了绿色。因为整个素材图片中只有屏幕区域是绿色,所以抠像也会变得简单很多,何必跟自己过不去。

那么怎么样去在 OpenCV 上找到这堆绿色的像素点呢?根据固定的RGB值来遍历并且筛选肯定不行,因为素材图片不是矢量图。在边缘上的绿色肯定是不一样的。不过到是可以根据绿色的一定RGB范围来筛选,这个方法没有试过,不知道准不准确。

实现

HSV空间抠图

我的方案是把图片的色彩空间转换为HSV空间来识别绿色。相比传统的红绿蓝RGB表示,HSV空间使用Hue, Saturation, Value(色调值、饱和度值、亮度值)来表示颜色。在RBG分量表示中,RGB对亮度很敏感,只要一个像素的亮度改变,RGB三个分量都会同时改变,所以不是很适合抠像应用。使用HSV更容易跟踪某种颜色的物体,常用于分割指定颜色的物体。

图片来源:RGB (left) and HSV (right) color spaces. | Download Scientific Diagram

下图中是我查到的一个表,描述了常用颜色的 HSV 值区间。

图片来源 【OpenCV】HSV颜色识别-HSV基本颜色分量范围_Taily老段的专栏-CSDN博客

在 Python 中使用 OpenCV可以很方便的把图像转换成HSV空间,并且根据三个通道的范围进行筛选生成遮罩。

  1. import cv2
  2. import numpy as np
  3. def load_mask(background_hsv):
  4. mask = cv2.inRange(background_hsv, (35, 43, 46), (77, 255, 255))
  5. mask_i = np.where(mask == 255)
  6. return mask
  7. background = cv2.cvtColor(cv2.imread(“color_spectrum.png”),cv2.COLOR_BGR2RGB)
  8. background_hsv = cv2.cvtColor(background, cv2.COLOR_RGB2HSV)
  9. load_mask(background_hsv)

我使用了一张颜色频谱图来看一下质直观的效果, 下图中第一个图为原图,第二个图为绿色的遮罩,可以看到的确所有的绿色都被盖住了,并且效果很好。

图片处理代码实现

接下来先试一试生成单张的图片。大概流程如下

  • 根据iPhone 素材中的屏幕的绿色生成图二样子的遮罩
  • 根据 iPhone 素材的尺寸和遮罩位置大小生成图三
  • 将遮罩与图三运算得到图四
  • 将第一幅图与遮罩运算来移除绿色
  • 将移除绿色的第一幅图与图四想加运算的到最终结果图五

下面是部分核心的代码,可以看出来实现没什么难度。

  1. # 生成遮罩,返回遮罩与遮罩位置与大小
  2. def load_mask(background_hsv):
  3. mask = cv2.inRange(background_hsv, (35, 43, 46), (77, 255, 255))
  4. mask_i = np.where(mask == 255)
  5. y = np.min(mask_i[0])
  6. x = np.min(mask_i[1])
  7. w = np.max(mask_i[1]) - np.min(mask_i[1])
  8. h = np.max(mask_i[0]) - np.min(mask_i[0])
  9. return mask, (x, y, w, h)
  10. # 得到最终图片
  11. def generate_masked_frame(background, mask, mask_rect, frame):
  12. x, y, w, h = mask_rect
  13. # 调整 App 截图大小为遮罩的长宽
  14. frame = cv2.resize(frame, (w, h))
  15. # 生成图三
  16. masked_frame = np.zeros(background.shape, np.uint8)
  17. masked_frame[y:y+h, x:x+w] = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
  18. padded_frame = masked_frame.copy()
  19. # 生成图四
  20. masked_frame[mask != 255] = [0, 0, 0]
  21. # 生成图5
  22. crop_background = background.copy()
  23. crop_background[mask == 255] = [0, 0, 0]
  24. final_image = crop_background + masked_frame
  25. return padded_frame, masked_frame, final_image

视频生成

有了上面的代码之后生成视频也只是把原视频逐帧按照上一节的流程处理再生成新的视频就好了。OpenCV对视频处理的支持也是非常好,使用 VideoWriter 和 VideoCapture 类就能实现,之前的文章 从零打造树莓派家庭监控 (二): 监控模块与前后端 有使用过。

值得注意的是,OpenCV目前我找的方法只能处理恒定帧率的视频,在 iOS 13 中默认的录制功能是恒定的 60帧没有问题,但是如果想要在 Xcode 的 iPhone 模拟器上直接录制在拿来套壳就不是很好了,iPhone模拟器的屏幕录制帧率是变化的,交互的时候是60帧,但是屏幕没有变动的时候则是0帧,这就导致 OpenCV 读出来的帧数是一个平均帧数,所以生成的视频效果很不好,会一卡一卡的。解决这个问题可以先使用 FFmpeg 转码成固定帧率再进行处理。不过这么麻烦的话还不如直接拿自己的 iPad 和 iPhone 直接录制。

详细的代码可以直接到我的 GitHub 上查看。
GitHub - Yigang0622/AppDemoVideoGenerator

Comments

作者
July 21, 2018 at 10:52 am

There are no comments

keyboard_arrow_up