感觉这辈子,最深情绵长的注视,都给了手机。

视频是目前最热门的领域之一。
各平台争先推出便捷工具,自带滤镜和玩法,普通人也能轻松制作精美视频。
也有不少团队实现了批量视频制作,拥有更高产能,运营效率也更高。

现在手机上的剪映,电脑的爱剪辑,已经足够我们应付日常视频处理需求。再专业点,也可以用PR、FinalCut、Edius等软件处理。这些软件都提供可视化的编辑,可以边剪辑边预览。
但对于那些工作流程相对固定、产能要求高的操作,更适合机器处理。当人睡觉时,机器依旧在剪辑。
甚至如果需要支持多团队的视频处理,可以考虑把剪辑工作搬到云上,随时扩大处理能力。

视频可以看成是连续的图片,如果你看过翻书做的动画就能马上理解。

8、视频文件 - 图1

当连续的图片切换足够快时(主流认为每秒24帧),我们就会感受到平滑的视觉效果。

和音频类似,视频的清晰度,主要由帧率和每帧图像的清晰度(即分辨率)决定。

但需要注意的是:对于已有视频,提高帧率和分辨率,并不能让视频更清晰,因为信息无法凭空还原。

当然,这是人工智能的研究领域之一,俗称“AI修图”,把不清晰的变得更清晰。

比如前阵子火爆B站的老北京AI修复视频:

8、视频文件 - 图2

视频文件本身是个容器,内含音频、视频、字幕等信息,独立字幕是文本文件,音视频经编码后保存。

上一章已经介绍过ffmpeg,它是开源软件中处理视频的最佳选择,不少Python三方模块在处理音视频时,都会调用其编码和读写文件的能力。

Python处理视频主要有3类模块:

  1. opencv-python,由于视频本质上就是连续的图像,所以图像处理模块也能处理视频中的每一帧图像。最后对视频的编码和读写会依赖ffmpeg完成。
  2. ffmpeg-python,这类模块是对ffmpeg的命令包装,相当于用Python调用ffmepg的命令。
  3. moviepy,提供了便捷的视频处理接口,文件编码和读写也依赖ffmpeg

其中,moviepy使用门槛低,足够应付最常见的需求,如截取、拼接、简单转场和特效等。
模块安装:pip install moviepy

它的基本工作原理可以概括为:

  • 基于ffmpeg读写视频文件。
  • 基于numpyscipyopencvPIL处理内部图像数据。
  • 两大核心类:AudioClipVideoClip分别处理音频和视频。

8、视频文件 - 图3

如果要在视频中增加图形或文字,需要提前安装ImageMagick软件。
ImageMagick的安装在Mac上稍微复杂些,因为它基于X11框架。
分两步安装:

  1. 安装XQuartz:即X11框架的MacOS版实现。
  2. Homebrew安装软件:brew install imagemagick

本文将以moviepy为主介绍视频处理,图像特效等部分会兼用opencvskimage等模块。

视频处理的常见场景包括:

  • 分段截取:剪掉前几秒或后几秒,或取中间某段
  • 素材提取:音频提取,视频截图
  • 清晰调整:帧率、分辨率
  • 倍速播放:加速、减速
  • 格式转换:视频编码选择、GIF转换
  • 视频拼接:如添加片头、添加片尾
  • 视频剪裁:裁剪某个区域内容
  • 水印处理:加文字水印、加图片水印、加动画水印
  • 视频特效:镜像、滤镜、过长切换、遮照
  • 字幕处理:提取字幕,添加字幕
  • 智能处理:人脸追踪、马赛克、换脸

下面分成4个部分介绍:基本使用、拼接裁剪、效果水印、智能处理。

基本使用

视频的基本处理包括:文件读写、分段截取、音量调整、素材提取、清晰度参数、倍速播放、格式转换。

  1. import pathlib
  2. from moviepy.editor import VideoFileClip
  3. path = list(pathlib.Path.cwd().parents)[1].joinpath('data/automate/008video')
  4. mp4_path = path.joinpath('input.mp4')
  5. vout_path = path.joinpath('008video_basic_video.mp4')
  6. vout15_path = path.joinpath('008video_basic_video_fps15.mp4')
  7. vout_scale_path = path.joinpath('008video_basic_video_scale.mp4')
  8. vout_speed2x_path = path.joinpath('008video_basic_video_speed2x.mp4')
  9. vout_speed05x_path = path.joinpath('008video_basic_video_speed05x.mp4')
  10. vout_webm_path = path.joinpath('008video_basic_video_format.webm')
  11. vout_gif_path = path.joinpath('008video_basic_video_gif.gif')
  12. aout_path = path.joinpath('008video_basic_audio.mp3')
  13. img_path = path.joinpath('008video_basic_images')
  14. clip = VideoFileClip(str(mp4_path))
  15. # 获取基本信息:时长、
  16. print('基本信息:')
  17. print(clip.duration, clip.size, clip.fps)
  18. # 截取前50秒视频
  19. clip = clip.subclip(0, 50)
  20. # 提取音频素材
  21. audio = clip.audio
  22. audio.write_audiofile(str(aout_path))
  23. # 视频截图
  24. ts = [5, 10, 20, 30, 40, 50] # 单位:秒
  25. for t in ts:
  26. clip.save_frame(str(img_path.joinpath(f'{t}.png')), t=t)
  27. # 调低音量
  28. clip.volumex(0.6)
  29. # 保存文件,audio_codec指定音频编码,默认视频编码为libx264
  30. clip.write_videofile(str(vout_path), audio_codec='aac')
  31. # 清晰度参数:帧率、分辨率
  32. clip_fps15 = clip.set_fps(15) # 调整帧率,并不会减少多少文件大小
  33. # 如果不指定audio,就会生成一个临时音频文件
  34. clip_fps15.write_videofile(str(vout15_path), audio_codec='aac')
  35. # 调整分辨率,可以很明显降低文件大小
  36. # clip_scale = clip.resize((clip.w//2, clip.h//2))
  37. clip_scale = clip.resize(0.5) # 等比缩放0.5
  38. clip_scale.write_videofile(str(vout_scale_path), audio_codec='aac')
  39. # 倍速播放
  40. clip_sp2x = clip.speedx(2)
  41. clip_sp2x.write_videofile(str(vout_speed2x_path), audio_codec='aac')
  42. clip_sp05x = clip.speedx(0.5)
  43. clip_sp05x.write_videofile(str(vout_speed05x_path), audio_codec='aac')
  44. # 格式转换,根据后缀选择编码器
  45. clip.write_videofile(str(vout_webm_path), audio=True)
  46. # 转GIF图
  47. clip.subclip(0,10).set_fps(1).write_gif(str(vout_gif_path))

视频拼接和裁剪

视频拼接是指在时间维度上,把多个视频段连起来,常见如每个视频的片头片尾。

视频裁剪是指在屏幕上划出一个区域当成新的视频。

  1. import pathlib
  2. from moviepy.editor import VideoFileClip, TextClip
  3. from moviepy.editor import vfx
  4. from moviepy.editor import CompositeVideoClip, concatenate_videoclips
  5. from moviepy.video.tools.drawing import circle
  6. from moviepy.video.tools.credits import credits1
  7. path = list(pathlib.Path.cwd().parents)[1].joinpath('data/automate/008video')
  8. mp4s_path = path.joinpath('008video_concat')
  9. vout_final_path = path.joinpath('008video_concat_final.mp4')
  10. vout_cropped_path = path.joinpath('008video_concat_cropped.mp4')
  11. font_path = path.joinpath('SourceHanSansCN-Bold.otf')
  12. # 设置一个简单片头片尾
  13. the_start = TextClip('英语"不可能"怎么说?\n"No Way"', font=font_path,
  14. color='white', fontsize=70).set_duration(2).set_pos('center')
  15. the_end = TextClip('By 程一初', font=font_path,
  16. color='white', fontsize=70).set_duration(2).set_pos('center')
  17. clip_list = [ the_start ]
  18. # 把所有文件夹下的视频都读取出来
  19. mp4_list = [ f for f in mp4s_path.iterdir() if f.is_file() ]
  20. for mp4 in mp4_list:
  21. clip_list.append(VideoFileClip(str(mp4)))
  22. clip_list.append(the_end)
  23. # 拼接,'compose'表示不管各种视频大小,以最大为基础
  24. final = concatenate_videoclips(clip_list, method='compose')
  25. final.write_videofile(str(vout_final_path), audio_codec='aac')
  26. # 裁剪,取中间一块
  27. W, H = final.size
  28. cropped = final.crop(x_center=W//2, y_center=H//2, width=400, height=300)
  29. cropped.write_videofile(str(vout_cropped_path), audio_codec='aac')

效果处理和水印

对视频中的每一帧图像应用滤镜,就是对视频应用滤镜。
滤镜可以是变换色彩风格,也可以是应用遮照。
所以视频水印原理与图像一致,可以加文字、图片和动画水印。
此外,在视频片段间连接时,可以增加一些淡入淡出的过场效果。

moviepy最核心的3个方法:

  1. fl_image:处理每一帧图像,比如添加元素、应用遮照。
  2. fl_time:处理时间相关特效,比如动态变速。
  3. fl:同时处理时间和每一帧图像。

在使用时,优先用前两个,有时会加快渲染速度。
此外moviepy通过vfx包提供了很多内置特效功能。

效果处理

  1. import pathlib
  2. from PIL import Image, ImageDraw
  3. import numpy as np
  4. from moviepy.editor import VideoFileClip, ImageClip, TextClip
  5. from moviepy.editor import vfx, clips_array, CompositeVideoClip
  6. from moviepy.video.tools.drawing import circle
  7. path = list(pathlib.Path.cwd().parents)[1].joinpath('data/automate/008video')
  8. mp4_path = path.joinpath('input.mp4')
  9. vout_path = path.joinpath('008video_effect.mp4')
  10. clip = VideoFileClip(str(mp4_path)).subclip(0, 10).margin(10)
  11. # 水平镜像,变亮,增加进场效果
  12. clip_x = clip.fx(vfx.mirror_x).fx(vfx.colorx, 2).fx(vfx.fadein, 1.5)
  13. # 垂直镜像,变暗
  14. clip_y = clip.fx(vfx.mirror_y).fx(vfx.colorx, 0.5)
  15. # 上下左右对称,增加淡入淡出过场效果
  16. clip_yx = clip_y.fx(vfx.mirror_x).fx(vfx.fadein, 1.5).fx(vfx.fadeout, 1.5)
  17. # 任意角度
  18. clip_90 = clip.fx(vfx.rotate, angle=90)
  19. # 遮照: 用Image画个圆形遮照
  20. img = Image.new('RGB', clip.size, (0,0,0))
  21. draw = ImageDraw.Draw(img)
  22. r = min(clip.w, clip.h)
  23. x, y = (clip.w-r)/2, (clip.h-r)/2
  24. draw.ellipse((x,y,x+r,y+r), fill=(255,255,255))
  25. mask = ImageClip(np.array(img), ismask=True)
  26. clip_mask = CompositeVideoClip([clip.set_mask(mask)])
  27. # 输出整个效果系列
  28. final_clip = clips_array([[clip, clip_x],
  29. [clip_y, clip_yx],
  30. [clip_90, clip_mask]])
  31. final_clip.write_videofile(str(vout_path), audio_codec='aac')

关于动态遮照,目前官方代码moviepy.video.tools.drawing.color_gradient有点小问题。
动态遮照的本质,是对每一帧图像应用动态生成的遮照。
由于moviepy内部使用numpy.ndarray格式存储数据,我们可以选择opencvscikit-image来处理动态的遮照图像。
这里就以scikit-image来演示,模块安装:pip install scikit-image

scikit-image基本画图方法

先看下scikit-image的基本图形绘制方法:

  • 线:skimage.draw.line
  • 实心圆:skimage.draw.circle
  • 空心圆:skimage.draw.circle_perimeter
  • 多边形:skimage.draw.polygon
  • 椭圆:skimage.draw.ellipse
  • 空心椭圆:skimage.draw.ellipse_perimeter
  • 贝塞尔曲线:skimage.draw.bezier_curve

具体参数官方都有详细解释,就不列了。

动态遮照

  1. import pathlib
  2. from skimage import draw
  3. from skimage import img_as_float
  4. import cv2
  5. import numpy as np
  6. from moviepy.editor import VideoFileClip, TextClip
  7. from moviepy.editor import clips_array, CompositeVideoClip
  8. path = list(pathlib.Path.cwd().parents)[1].joinpath('data/automate/008video')
  9. mp4_path = path.joinpath('input.mp4')
  10. vout_path = path.joinpath('008video_effect_dynamic.mp4')
  11. clip = VideoFileClip(str(mp4_path)).subclip(0, 5).margin(10)
  12. # 开场,圆形打开效果
  13. clip_start = clip.add_mask()
  14. # 结束,圆形关闭效果,出现“The End”
  15. clip_end = clip.add_mask()
  16. w, h = clip.size
  17. r = max(h, w)/2
  18. def make_circle_ski_start(t):
  19. # 注意w和h,cy和cx的顺序
  20. arr = np.zeros((h,w), np.uint8)
  21. rr, cc = draw.circle(clip.h/2, clip.w/2, radius=min(r*2, int(200*t)), shape=arr.shape)
  22. arr[rr, cc] = 1
  23. return arr
  24. def make_circle_ski_end(t):
  25. arr = np.zeros((h,w), np.uint8)
  26. rr, cc = draw.circle(clip.h/2, clip.w/2, radius=max(0, int(r-200*t)), shape=arr.shape)
  27. arr[rr, cc] = 1
  28. return arr
  29. def make_circle_cv2(t):
  30. arr = np.zeros((h,w), np.uint8)
  31. cv2.circle(arr, (clip.w//2, clip.h//2), max(0, int(r-200*t)), 255, -1)
  32. # 如果要用opencv,返回值需要转为[0, 1]范围(也是skimage采用格式)
  33. return img_as_float(arr)
  34. clip_start.mask.get_frame = make_circle_ski_start
  35. clip_end.mask.get_frame = make_circle_ski_end
  36. # clip_end.mask.get_frame = make_circle_cv2
  37. txt_end = TextClip('The End', font='Amiri-bold', color='white',
  38. fontsize=20).set_duration(clip.duration).set_pos('center')
  39. clip_end = CompositeVideoClip([txt_end, clip_end], size=clip.size)
  40. final_clip = clips_array([[clip_start, clip_end],])
  41. final_clip.write_videofile(str(vout_path), audio_codec='aac')

水印处理

视频水印的处理,可以把原视频和水印用CompositeVideoClip方法合并。

  1. import pathlib
  2. from moviepy.editor import VideoFileClip, ImageClip, TextClip
  3. from moviepy.editor import clips_array, CompositeVideoClip
  4. path = list(pathlib.Path.cwd().parents)[1].joinpath('data/automate/008video')
  5. font_path = path.joinpath('SourceHanSansCN-Bold.otf')
  6. mp4_path = path.joinpath('input.mp4')
  7. avatar_path = path.joinpath('avatar.jpg')
  8. gif_path = path.joinpath('wm.gif')
  9. vout_path = path.joinpath('008video_watermark.mp4')
  10. clip = VideoFileClip(str(mp4_path)).subclip(0, 10).margin(10)
  11. # 文字水印
  12. txt_clip = TextClip('By 程一初', font=font_path, fontsize=20,
  13. color='white').set_duration(
  14. clip.duration).margin(
  15. mar=10, color=(96,96,96), opacity=0.5).set_opacity(0.5)
  16. txt_clip = txt_clip.set_position((clip.w-txt_clip.w, clip.h-txt_clip.h))
  17. txt_clip = CompositeVideoClip([clip, txt_clip])
  18. # 图片水印
  19. img_clip = ImageClip(str(avatar_path)).set_duration(
  20. clip.duration).resize(0.1).margin(
  21. mar=10, color=(96,96,96), opacity=0.5).set_opacity(0.5)
  22. img_clip = img_clip.set_position((clip.w-img_clip.w, clip.h-img_clip.h))
  23. img_clip = CompositeVideoClip([clip, img_clip])
  24. # 动画水印
  25. gif_clip = VideoFileClip(str(gif_path)).loop().set_duration(
  26. clip.duration).margin(
  27. mar=10, color=(96,96,96), opacity=0.5).set_opacity(0.5)
  28. gif_clip = gif_clip.set_position((clip.w-gif_clip.w, clip.h-gif_clip.h))
  29. gif_clip = CompositeVideoClip([clip, gif_clip])
  30. # 输出整个效果系列
  31. final_clip = clips_array([[clip, txt_clip],
  32. [img_clip, gif_clip]])
  33. final_clip.write_videofile(str(vout_path), audio_codec='aac')

关于去水印的主要4种思路参考:

  1. 通过裁剪,把包含水印部分去除,最简单但会丢失部分信息。
  2. 把水印部分模糊化,或另一个水印覆盖原水印,相当于涂抹。
  3. 拿到水印原文件,尝试透明度反向减除,不能100%但有时可做到肉眼不可见。
  4. 基于算法消除,目前大部分速度很慢,一张图都得几十秒,更不用说视频。

智能处理

视频相关的智能处理,可以分解到对字幕、图像、音频的处理。
如:生成字幕、人脸追踪、视频分类等。

字幕提取

关于字幕提取的3个思路:

  1. 字幕如果是嵌入在视频文件中,就可以通过ffmpeg命令直接提取字幕srt文件。
  2. 更多时候字幕和视频渲染在一起,即所谓“硬字幕”,这时就需要靠算法识别。
  3. 算法识别字幕有两种方式:从音频里提取(即上一章的STT),或从图像里提取(即OCR技术)。

OCR技术中较出名的如Google的tesseract项目,它能识别100多种语言。之前介绍过的百度paddlehub也有文字识别的模型。

从效果上看,paddlehub在图像的中文识别方面更优。

处理方式也很简单:

  1. 从视频里抽取图像。
  2. 调用paddlehub识别图片里的文字。
  1. import pathlib
  2. import paddlehub as hub
  3. module = hub.Module(name='chinese_ocr_db_crnn_mobile')
  4. path = list(pathlib.Path.cwd().parents)[1].joinpath('data/automate/008video')
  5. img_path = path.joinpath('008video_seqimages')
  6. img_path_list = sorted([ str(f) for f in img_path.iterdir() if f.is_file() ])
  7. results = module.recognize_text(paths=img_path_list, visualization=True)
  8. for result in results:
  9. print(result)

注意还需要安装2个模块:pip install shapely pyclipper

在实战中更推荐STT方式提取字幕。除了之前推荐的云平台之外,平时也可以使用如网易见外、讯飞听见等在线应用。

人脸追踪

2019年ZAO换脸曾风靡一时,它就是人脸追踪的一种应用,而且实现了追踪后替换融合的效果。

此外我们经常看到一些新闻里会对人脸动态打马赛克,其基本原理如下:

  1. 找到每一帧图像中的人脸位置,记录下数据。
  2. 处理每一帧图像,对人脸打马赛克。

我们通过结合moviepypaddlehub可以很容易实现。

  1. import pathlib
  2. import numpy as np
  3. import cv2
  4. from moviepy.editor import VideoFileClip, ImageSequenceClip
  5. import paddlehub as hub
  6. path = list(pathlib.Path.cwd().parents)[1].joinpath('data/automate/008video')
  7. mp4_path = path.joinpath('input.mp4')
  8. out_path = path.joinpath('008video_paddlehub_headblur_fl.mp4')
  9. out_path_frm = path.joinpath('008video_paddlehub_headblur_frm.mp4')
  10. snd_path = path.joinpath('008video_snd.mp3')
  11. clip = VideoFileClip(str(mp4_path)).subclip(0,10)
  12. module = hub.Module(name='ultra_light_fast_generic_face_detector_1mb_640')
  13. def mask_frame(im):
  14. h, w, d = im.shape
  15. results = module.face_detection(images=[im])
  16. face_data = results[0]['data']
  17. # 模糊每个人脸
  18. for d in face_data:
  19. x = int((d['left']+d['right'])//2)
  20. y = int((d['top']+d['bottom'])//2)
  21. r_zone = int((d['right']-d['left'])//2) # 半径
  22. r_blur = int(2*r_zone/3) # 模糊范围
  23. x1, x2 = max(0, x - r_zone), min(x + r_zone, w)
  24. y1, y2 = max(0, y - r_zone), min(y + r_zone, h)
  25. region_size = y2 - y1, x2 - x1
  26. mask = np.zeros(region_size).astype('uint8')
  27. cv2.circle(mask, (r_zone, r_zone), r_zone, 255, -1, lineType=cv2.CV_AA)
  28. mask = np.dstack(3 * [(1.0 / 255) * mask])
  29. orig = im[y1:y2, x1:x2]
  30. blurred = cv2.blur(orig, (r_blur, r_blur))
  31. im[y1:y2, x1:x2] = mask * blurred + (1 - mask) * orig
  32. return im
  33. def fl_fun(im):
  34. # im是只读数据,需要重新创建一个可修改的ndarray
  35. frame = np.array(im)
  36. return mask_frame(frame)
  37. clip_blur = clip.fl_image(fl_fun)
  38. clip_blur.write_videofile(str(out_path), audio_codec='aac')

如果想要实现类似ZAO换脸一样的效果,除了定位人脸,还得实现图像融合。

比较热门的一个开源项目faceswap,实现了换脸算法。有兴趣可以看我Notebook的记录,对硬件要求较高,需要训练自己的模型,速度很慢。

也可以用paddlehub来识别人脸,它提供了不少训练模型,如:

  • 人体结构标注:ace2p
  • 人脸识别:ultra_light_fast_generic_face_detector_1mb_640
  • 人脸结构标注:face_landmark_localization

可以比较容易识别出人脸模型,但想要融合,则需要借助额外的模型,如图像风格迁移StarGAN

视频分割属于人工智能的前沿研究领域,尤其是视频软分割 (video matting) 算法,感兴趣的可以关注人工智能顶级会议:CVPR,每年都会有一些新算法被提出。

总结

本文介绍了视频文件的处理方法,主要介绍了moviepy在日常视频处理中的应用,以及借助人工智能算法实现一些特殊的效果,如人脸追踪打马赛克、字幕提取等。

paddlehub项目里还有不少有趣的训练模型,我们可以借助它结合moviepy玩出很多新奇的效果。

比如,复制视频里的人物、生成艺术风格视频等。

8、视频文件 - 图4

入群学习

8、视频文件 - 图5