我正在尝试根据某些特征剪切视频。
我目前的策略导致每个帧的pandas
系列布尔值,按时间戳索引。 True
保留它,False
将其转储。
当我计划剪切视频时,我需要从此列表中提取边界,以便我可以告诉fmpeg我想从主视频中提取的部分的开头和结尾。
涂总结:
我有一个pandas
系列,如下所示:
acquisitionTs
0.577331 False
0.611298 False
0.645255 False
0.679218 False
0.716538 False
0.784453 True
0.784453 True
0.818417 True
0.852379 True
0.886336 True
0.920301 True
0.954259 False
...
83.393376 False
83.427345 False
dtype: bool
(由于出现原因而被截断,但TimeStamp通常从0开始)
我需要获得True
序列的边界,因此在此示例中,如果我[[t_0,t_1],[t_2,t_3]n, ... [t_2n-1,t_2n]]
,我应该t_0 = 0.784453
,t_1 = 0.920301
和n
我的熊猫系列中True
的不同序列。
现在这个问题看起来非常简单,实际上你可以把序列换成一个,然后在两者之间做一个xor来得到一个布尔列表,其中True
用于边界
e = df.shift(periods=1, freq=None, axis=0)^df
print(e[e].index)
(df
是熊猫系列)
还有一些工作要做,比如判断第一个元素是上升边还是下降边,但是这个黑客行之有效。
然而,这似乎不是非常pythonic。事实上,问题是如此简单我相信在pandas
,numpy
或甚至python
中必须有一个预先构建的函数,它可以很好地适用于单个函数调用而不是像上面的黑客。虽然groupby
函数似乎很有希望,但我之前从未使用它。
这样做的最佳方法是什么?
答案 0 :(得分:1)
您可以使用scipy.ndimage.label
来识别True
s:
In [102]: ts
Out[102]:
0.069347 False
0.131956 False
0.143948 False
0.224864 False
0.242640 True
0.372599 False
0.451989 False
0.462090 False
0.579956 True
0.588791 True
0.603638 False
0.625107 False
0.642565 False
0.708547 False
0.730239 False
0.741652 False
0.747126 True
0.783276 True
0.896705 True
0.942829 True
Name: keep, dtype: bool
In [103]: groups, nobs = ndimage.label(ts); groups
Out[103]: array([0, 0, 0, 0, 1, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3], dtype=int32)
获得groups
数组后,您可以使用groupby/agg
找到相关联的时间:
result = (df.loc[df['group'] != 0]
.groupby('group')['times']
.agg({'start':'first','end':'last'}))
例如,
import numpy as np
import pandas as pd
import scipy.ndimage as ndimage
np.random.seed(2016)
def make_ts(N, ngroups):
times = np.random.random(N)
times = np.sort(times)
idx = np.sort(np.random.randint(N, size=(ngroups,)))
arr = np.zeros(N)
arr[idx] = 1
arr = arr.cumsum()
arr = (arr % 2).astype(bool)
ts = pd.Series(arr, index=times, name='keep')
return ts
def find_groups(ts):
groups, nobs = ndimage.label(ts)
df = pd.DataFrame({'times': ts.index, 'group': groups})
result = (df.loc[df['group'] != 0]
.groupby('group')['times']
.agg({'start':'first','end':'last'}))
return result
ts = make_ts(20, 5)
result = find_groups(ts)
产量
start end
group
1 0.242640 0.242640
2 0.579956 0.588791
3 0.747126 0.942829
要获取您可以使用的列表列表的开始和结束时间:
In [125]: result.values.tolist()
Out[125]:
[[0.24264034406127022, 0.24264034406127022],
[0.5799564094638113, 0.5887908182432907],
[0.7471260123697537, 0.9428288694956402]]
使用ndimage.label
很方便,但请注意,也可以在没有scipy
的情况下进行计算:
def find_groups_without_scipy(ts):
df = pd.DataFrame({'times': ts.index, 'group': (ts.diff() == True).cumsum()})
result = (df.loc[df['group'] % 2 == 1]
.groupby('group')['times']
.agg({'start':'first','end':'last'}))
return result
此处的主要想法是使用True
查找(ts.diff() == True).cumsum()
s群集的标签。 ts.diff() == True
给出与ts.shift() ^ ts
相同的结果,但速度要快一些。取累积和(即调用cumsum
)将True
视为等于1,将False
视为等于0,因此每次遇到True
时累积和增加因此,每个群集都标有不同的数字:
In [111]: (ts.diff() == True).cumsum()
Out[111]:
0.069347 0
0.131956 0
0.143948 0
0.224864 0
0.242640 1
0.372599 2
0.451989 2
0.462090 2
0.579956 3
0.588791 3
0.603638 4
0.625107 4
0.642565 4
0.708547 4
0.730239 4
0.741652 4
0.747126 5
0.783276 5
0.896705 5
0.942829 5
Name: keep, dtype: int64
答案 1 :(得分:1)
我会使用Dataframe而不是Series(它实际上也适用于Series)。
df
acquisitionTs Value
0 0.577331 False
1 0.611298 False
2 0.645255 False
3 0.679218 False
4 0.716538 False
5 0.784453 True
6 0.784453 True
7 0.818417 False
8 0.852379 True
9 0.886336 True
10 0.920301 True
11 0.954259 False
我会这样做:
df[df.Value.diff().fillna(False)]
acquisitionTs Value
5 0.784453 True
7 0.818417 False
8 0.852379 True
11 0.954259 False
所以你知道这里的第一个值为False,你知道0-4是假的然后它会在每个索引处切换(5,7,8,11)
我认为groupby
函数对你没有帮助,因为它会松开你的True / False值的顺序(在我的例子中你将有2组,而不是5组)。
答案 2 :(得分:1)
这些都是很好的解决方案,但我认为可能有一个更简单、更普遍适用的选择。
本质上,您正在寻找一个值是否与前一个值不同。如果您将它与自身进行比较但移动了 1,您将得到您想要的结果。您还可以从快速比较操作中受益。
import pandas as pd
# Create a series
series_1 = pd.Series(['duck', 'duck', 'duck', 'duck', 'goose', 'goose', 'duck'])
# Create a copy of the series shifted by 1 space
series_2 = series_1.shift(1)
# Compare the original and shifted series to get a new "Is it an edge?" series
is_edge = series_1 != series_2
pd.DataFrame({'data': series_1, 'edge': is_edge})
或者如果包含比较列更清楚:
并使用数字索引和布尔值使这个示例直接说明您的问题:
series_1 = pd.Series({.1: True, .2: True, .3: False, .4: False, .5: True, .6: True})
series_2 = series_1.shift(1)
is_edge = series_1 != series_2
pd.DataFrame({'original': series_1, 'shifted': series_2, 'edge': is_edge})