我们今天的目标:

二、SVD 背景消除 - 图1

加载和格式化数据

让我们使用 BMC 2012 背景建模挑战数据集中的真实视频 003

导入所需的库:

  1. import imageio
  2. imageio.plugins.ffmpeg.download()
  3. '''
  4. Imageio: 'ffmpeg.linux64' was not found on your computer; downloading it now.
  5. Try 1. Download from https://github.com/imageio/imageio-binaries/raw/master/ffmpeg/ffmpeg.linux64 (27.2 MB)
  6. Downloading: 8192/28549024 bytes (0.02260992/28549024 bytes (7.9%5455872/28549024 bytes (19.18790016/28549024 bytes (30.812189696/28549024 bytes (42.7%15687680/28549024 bytes (54.9%18898944/28549024 bytes (66.2%22134784/28549024 bytes (77.5%25518080/28549024 bytes (89.4%28549024/28549024 bytes (100.0%)
  7. Done
  8. File saved as /home/racheltho/.imageio/ffmpeg/ffmpeg.linux64.
  9. '''
  10. import moviepy.editor as mpe
  11. import numpy as np
  12. import scipy
  13. %matplotlib inline
  14. import matplotlib.pyplot as plt
  15. np.set_printoptions(precision=4, suppress=True)
  16. video = mpe.VideoFileClip("movie/Video_003.avi")
  17. video.subclip(0,50).ipython_display(width=300)
  18. '''
  19. 100%|█████████▉| 350/351 [00:00<00:00, 914.11it/s]
  20. '''
  1. video.duration
  2. # 113.57

辅助方法

  1. def create_data_matrix_from_video(clip, fps=5, scale=50):
  2. return np.vstack([scipy.misc.imresize(rgb2gray(clip.get_frame(i/float(fps))).astype(int),
  3. scale).flatten() for i in range(fps * int(clip.duration))]).T
  4. def rgb2gray(rgb):
  5. return np.dot(rgb[...,:3], [0.299, 0.587, 0.114])

格式化数据

一个时刻的图像是120像素乘160像素(缩放时)。 我们可以将该图片展开为一个很高的列。 因此,我们不是拥有120×160的 2D 图像,而是拥有1×19,200的一列。

这不是人类可读的,但它很方便,因为它可以让我们将来自不同时间的图像叠加在一起,将视频全部放入 1 个矩阵中。 如果我们拍摄视频 100 秒,每隔百分之一秒一张图像(所以有 10,000 个不同的图像,每个图像来自不同的时间点),我们将拥有10,000×19,200的矩阵,代表视频!

  1. scale = 0.50 # Adjust scale to change resolution of image
  2. dims = (int(240 * scale), int(320 * scale))
  3. fps = 60 # frames per second
  4. M = create_data_matrix_from_video(video.subclip(0,100), fps, scale)
  5. # M = np.load("movie/med_res_surveillance_matrix_60fps.npy")
  6. print(dims, M.shape)
  7. # (120, 160) (19200, 6000)
  8. plt.imshow(np.reshape(M[:,140], dims), cmap='gray');

二、SVD 背景消除 - 图2

由于create_data_from_matrix有点慢,我们将保存矩阵。 通常,只要你的预处理步骤较慢,最好保存结果以备将来使用。

  1. np.save("movie/med_res_surveillance_matrix_60fps.npy", M)
  2. plt.figure(figsize=(12, 12))
  3. plt.imshow(M[::3,:], cmap='gray')
  4. # <matplotlib.image.AxesImage at 0x7f92be09d710>

二、SVD 背景消除 - 图3

问题:那些黑波浪线是什么? 水平线是什么?

奇异值分解

SVD介绍

“将矩阵分解为我们关心的更简单,更有意义的片段的便捷方式” - 大卫奥斯汀

“我忘了学习的最重要的线性代数概念” - Daniel Lemire

SVD的应用:

  • 主成分分析
  • 数据压缩
  • 伪逆
  • 协同过滤
  • 主题建模
  • 背景消除
  • 删除损坏的数据
  1. U, s, V = np.linalg.svd(M, full_matrices=False)

这非常慢,因此你可能希望保存结果以便将来使用。

  1. np.save("movie/U.npy", U)
  2. np.save("movie/s.npy", s)
  3. np.save("move/V.npy", V)

将来,你只需加载已保存的内容:

  1. U = np.load("movie/U.npy")
  2. s = np.load("movie/s.npy")
  3. V = np.load("movie/V.npy")

U, s, V是什么样子?

  1. U.shape, s.shape, V.shape
  2. # ((19200, 6000), (6000,), (6000, 6000))

练习

检查它们是否是M的分解。

  1. # Exercise:
  2. # True

他们正是。

  1. np.allclose(M, reconstructed_matrix)
  2. # True
  3. np.set_printoptions(suppress=True, precision=0)

s的属性

  1. np.diag(s[:6])

你看到s的顺序了吗?

  1. s[0:2000:50]
  2. '''
  3. array([ 1341720., 10528., 6162., 4235., 3174., 2548.,
  4. 2138., 1813., 1558., 1346., 1163., 1001.,
  5. 841., 666., 0., 0., 0., 0.,
  6. 0., 0., 0., 0., 0., 0.,
  7. 0., 0., 0., 0., 0., 0.,
  8. 0., 0., 0., 0., 0., 0.,
  9. 0., 0., 0., 0.])
  10. '''
  11. len(s)
  12. # 6000
  13. s[700]
  14. # 3.2309523518534773e-10
  15. np.set_printoptions(suppress=True, precision=4)

U是个巨大的矩阵,所以仅仅查看一小部分:

  1. U[:5,:5]
  2. '''
  3. array([[-0.0083, -0.0009, -0.0007, 0.003 , -0.0002],
  4. [-0.0083, -0.0013, -0.0005, 0.0034, -0.0001],
  5. [-0.0084, -0.0012, 0.0002, 0.0045, -0.0003],
  6. [-0.0085, -0.0011, 0.0001, 0.0044, -0. ],
  7. [-0.0086, -0.0013, -0.0002, 0.004 , 0.0001]])
  8. '''

寻找背景

  1. U.shape, s.shape, V.shape
  2. # ((19200, 6000), (6000,), (6000, 6000))
  3. low_rank = np.expand_dims(U[:,0], 1) * s[0] * np.expand_dims(V[0,:], 0)
  4. plt.figure(figsize=(12, 12))
  5. plt.imshow(low_rank, cmap='gray')
  6. # <matplotlib.image.AxesImage at 0x7f1cc3e2c9e8>

二、SVD 背景消除 - 图4

  1. plt.imshow(np.reshape(low_rank[:,0], dims), cmap='gray');

二、SVD 背景消除 - 图5

如何得到里面的人?

  1. plt.imshow(np.reshape(M[:,0] - low_rank[:,0], dims), cmap='gray');

二、SVD 背景消除 - 图6

高分辨率版本。

  1. plt.imshow(np.reshape(M[:,140] - low_rank[:,140], dims), cmap='gray');

二、SVD 背景消除 - 图7

制作视频

我受到了 fast.ai 学生萨米尔·穆萨(Samir Moussa)的启发来制作人物视频。

  1. from moviepy.video.io.bindings import mplfig_to_npimage
  2. def make_video(matrix, dims, filename):
  3. mat_reshaped = np.reshape(matrix, (dims[0], dims[1], -1))
  4. fig, ax = plt.subplots()
  5. def make_frame(t):
  6. ax.clear()
  7. ax.imshow(mat_reshaped[...,int(t*fps)])
  8. return mplfig_to_npimage(fig)
  9. animation = mpe.VideoClip(make_frame, duration=int(10))
  10. animation.write_videofile('videos/' + filename + '.mp4', fps=fps)
  11. make_video(M - low_rank, dims, "figures2")
  12. '''
  13. [MoviePy] >>>> Building video videos/figures2.mp4
  14. [MoviePy] Writing video videos/figures2.mp4
  15. 100%|█████████▉| 600/601 [00:39<00:00, 15.22it/s]
  16. [MoviePy] Done.
  17. [MoviePy] >>>> Video ready: videos/figures2.mp4
  18. '''

二、SVD 背景消除 - 图8

  1. mpe.VideoFileClip("videos/figures2.mp4").subclip(0,10).ipython_display(width=300)
  2. # 100%|█████████▉| 600/601 [00:00<00:00, 858.48it/s]

SVD 分解不同尺寸矩阵的速度

  1. import timeit
  2. import pandas as pd
  3. m_array = np.array([100, int(1e3), int(1e4)])
  4. n_array = np.array([100, int(1e3), int(1e4)])
  5. index =
  6. pd.MultiIndex.from_product([m_array, n_array], names=['# rows', '# cols'])
  7. pd.options.display.float_format = '{:,.3f}'.format
  8. df = pd.DataFrame(index=m_array, columns=n_array)
  9. # %%prun
  10. for m in m_array:
  11. for n in n_array:
  12. A = np.random.uniform(-40,40,[m,n])
  13. t = timeit.timeit('np.linalg.svd(A, full_matrices=False)', number=3, globals=globals())
  14. df.set_value(m, n, t)
  15. df/3
100 1000 10000
100 0.006 0.009 0.043
1000 0.004 0.259 0.992
10000 0.019 0.984 218.726

一个视频中的两个背景

我们现在使用 BMC 2012 背景建模挑战数据集中的真实视频 008,以及上面使用的 003。

  1. from moviepy.editor import concatenate_videoclips
  2. video2 = mpe.VideoFileClip("movie/Video_008.avi")
  3. concat_video = concatenate_videoclips([video2.subclip(0,20), video.subclip(0,10)])
  4. concat_video.write_videofile("movie/concatenated_video.mp4")
  5. '''
  6. [MoviePy] >>>> Building video movie/concatenated_video.mp4
  7. [MoviePy] Writing video movie/concatenated_video.mp4
  8. 100%|█████████▉| 300/301 [00:00<00:00, 481.76it/s]
  9. [MoviePy] Done.
  10. [MoviePy] >>>> Video ready: movie/concatenated_video.mp4
  11. '''
  12. concat_video = mpe.VideoFileClip("movie/concatenated_video.mp4")

现在回到我们的背景消除问题

  1. concat_video.ipython_display(width=300, maxduration=160)
  2. # 100%|█████████▉| 300/301 [00:00<00:00, 533.88it/s]
  1. scale = 0.5 # 调整比例来更改图像的分辨率
  2. dims = (int(240 * scale), int(320 * scale))
  3. N = create_data_matrix_from_video(concat_video, fps, scale)
  4. # N = np.load("low_res_traffic_matrix.npy")
  5. np.save("med_res_concat_video.npy", N)
  6. N.shape
  7. # (19200, 1800)
  8. plt.imshow(np.reshape(N[:,200], dims), cmap='gray');

二、SVD 背景消除 - 图9

  1. U_concat, s_concat, V_concat = np.linalg.svd(N, full_matrices=False)

这很慢,因此你可能希望保存结果以便将来使用。

  1. np.save("movie/U_concat.npy", U_concat)
  2. np.save("movie/s_concat.npy", s_concat)
  3. np.save("movie/V_concat.npy", V_concat)

将来,你只需加载已保存的内容:

  1. U_concat = np.load("movie/U_concat.npy")
  2. s_concat = np.load("movie/s_concat.npy")
  3. V_concat = np.load("movie/V_concat.npy")
  4. low_rank = U_concat[:,:10] @ np.diag(s_concat[:10]) @ V_concat[:10,:]

U的最小主成分:

  1. plt.imshow(np.reshape(U_concat[:, 1], dims), cmap='gray')
  2. # <matplotlib.image.AxesImage at 0x7f92bcf47da0>

二、SVD 背景消除 - 图10

  1. plt.imshow(np.reshape(U_concat[:, 2], dims), cmap='gray')
  2. # <matplotlib.image.AxesImage at 0x7f92bc691a90>

二、SVD 背景消除 - 图11

  1. plt.imshow(np.reshape(U_concat[:, 3], dims), cmap='gray')
  2. # <matplotlib.image.AxesImage at 0x7f92bc5aa240>

二、SVD 背景消除 - 图12

背景移除:

  1. plt.imshow(np.reshape((N - low_rank)[:, -40], dims), cmap='gray')
  2. # <matplotlib.image.AxesImage at 0x7f92bc540908>

二、SVD 背景消除 - 图13

  1. plt.imshow(np.reshape((N - low_rank)[:, 240], dims), cmap='gray')
  2. # <matplotlib.image.AxesImage at 0x7f92bc4d7f28>

二、SVD 背景消除 - 图14

数据压缩旁注

假设我们采用 700 个奇异值(记住,总共有 10000 个奇异值)。

  1. s[0:1000:50]
  2. '''
  3. array([ 1341719.6552, 10527.5148, 6162.0638, 4234.9367,
  4. 3174.0389, 2548.4273, 2138.1887, 1812.9873,
  5. 1557.7163, 1345.805 , 1163.2866, 1000.5186,
  6. 841.4604, 665.7271, 0. , 0. ,
  7. 0. , 0. , 0. , 0. ])
  8. '''
  9. k = 700
  10. compressed_M = U[:,:k] @ np.diag(s[:k]) @ V[:k,:]
  11. plt.figure(figsize=(12, 12))
  12. plt.imshow(compressed_M, cmap='gray')
  13. # <matplotlib.image.AxesImage at 0x7fefa0076ac8>

二、SVD 背景消除 - 图15

  1. plt.imshow(np.reshape(compressed_M[:,140], dims), cmap='gray');

二、SVD 背景消除 - 图16

  1. np.allclose(compressed_M, M)
  2. # True
  3. np.linalg.norm(M - compressed_M)
  4. # 2.864899899979104e-09
  5. U[:,:k].shape, s[:k].shape, V[:k,:].shape
  6. # ((19200, 700), (700,), (700, 6000))

节省的空间为对于 700 个奇异值的U, s, V中的数据比原始矩阵。

  1. ((19200 + 1 + 6000) * 700) / (19200 * 6000)
  2. # 0.1531310763888889

我们只需要存储 15.3% 的数据,并且可以将精度保持在1e-5! 很棒!

很漂亮!!!但…
SVD 的运行时复杂度为O(min(m^2 n, m n^2))
缺点:这真的很慢(同样,我们摒弃了很多计算)。

  1. %time u, s, v = np.linalg.svd(M, full_matrices=False)
  2. '''
  3. CPU times: user 5min 38s, sys: 1.53 s, total: 5min 40s
  4. Wall time: 57.1 s
  5. '''
  6. M.shape
  7. # (19200, 6000)