2015-04-17 128 views
3

我的.NET應用程序作爲表示以每秒30幀記錄的視頻的每幀的圖像的順序列表。基於視頻生成的新幀速率計算幀索引

00000001.png 
00000002.png 
00000003.png 
... 
99999999.png 

現在我要重新排序此列表,以便它可以生成基於以下參數視頻:

Start Frame Index: 100 
Direction:   Forward 
Output Speed:  100 FPS 
Duration:   10 seconds 

到目前爲止,我有這樣的事情:

var originalFrameRate = 30D; 
var originalFrameTime = 1D/originalFrameRate; 
var originalStartFrameIndex = 100; // 00000100.png. 
// Assume [originalFrames] will be filled with image file names from above. 
var originalFrames = new List<string> 
(new string [] { "0000003.png", "0000002.png", ..., "99999999.png", }); 

var targetFrameRate 100; // FPS. 
var targetDuration = TimeSpan.FromSeconds(10); 
var targetFrameCount = speed * targetDuration.Seconds; 
var targetFrames = new List<string>(); 

for (int i = 0; i < targetFrameCount; i++) 
{ 
    // How to map the original list from 30 FPS to 100 FPS? 
    targetFrames.Add(originalFrames [originalStartFrameIndex + ???]); 
} 

在上面的例子中,輸出將是基於變量名稱targetXXX填充相應文件名的targetFrames。

任何建議如何映射這將不勝感激。

編輯:我忘了提及輸出視頻將始終以原始幀速率生成。目標視頻的長度當然會改變。如果原始FPS低於目標,我們將重複幀。否則,我們將跳過它們。

+0

只是讓我明白了 - 你想跳過* *幀,這樣它匹配你想要的時間?或者......在重讀時,也許情況正好相反:複製幀? –

+1

'輸出速度'定義您如何跳過*或重複幀。 「持續時間」是*結束條件*。從「開始幀索引」開始,當targetFrames中的幀總數大於「持續時間/輸出速度」時停止。 FPS很棘手,對於'30 fps'中的'100 fps',你必須輸出每幀3.333333(3)'次(有時意味着4幀)。要檢查它是否是'4',再次計算當前時間並添加30 fps,看它是否超過1秒。 – Sinatr

+0

是的。如果原始FPS低於目標,我們將重複幀。否則,我們將跳過它們。 –

回答

1

我開始擴展文森特的答案來解決我注意到的一個問題:當從30fps縮放到100fps時,幀0重複一次第四次(0000 111 222 3333的幀格式)當我期待000 111 2222。沒什麼大不了的,因爲它可能只是一個偏好問題(不管你是希望在偶數幀還是奇數幀上發生小數「調整」),但是隨後我進入了兔子洞並構建了一個可以處理任何事情的迭代器類場景,包括分數幀率。

(使用一個通用的迭代具有不需要幀是string額外的獎勵 - 如果你想代表每一幀爲一類,你能做到這一點。)

public sealed class FramerateScaler<T> : IEnumerable<T> 
{ 
    private IEnumerable<T> _source; 
    private readonly double _inputRate; 
    private readonly double _outputRate; 
    private readonly int _startIndex; 

    public double InputRate { get { return _inputRate; } } 
    public double OutputRate { get { return _outputRate; } } 
    public int StartIndex { get { return _startIndex; } } 

    public TimeSpan InputDuration { 
     get { return TimeSpan.FromSeconds((1/_inputRate) * (_source.Count() - StartIndex)); } 
    } 

    public TimeSpan OutputDuration { 
     get { return TimeSpan.FromSeconds((1/_outputRate) * this.Count()); } 
    } 

    public FramerateScaler(
     double inputRate, double outputRate, 
     IEnumerable<T> source, int startIndex = 0) 
    { 
     _source = source; 
     _inputRate = inputRate; 
     _outputRate = outputRate; 
     _startIndex = startIndex; 
    } 

    public IEnumerator<T> GetEnumerator() 
    { 
     return new ScalingFrameEnumerator<T>(_inputRate, _outputRate, _source, _startIndex); 
    } 

    IEnumerator IEnumerable.GetEnumerator() 
    { 
     return (IEnumerator)GetEnumerator(); 
    } 

    private sealed class ScalingFrameEnumerator<T> : IEnumerator<T> 
    { 
     internal readonly double _inputRate; 
     internal readonly double _outputRate; 
     internal readonly int _startIndex; 

     private readonly List<T> _source; 

     private readonly double _rateScaleFactor; 
     private readonly int _totalOutputFrames; 
     private int _currentOutputFrame = 0; 

     public ScalingFrameEnumerator(
      double inputRate, double outputRate, 
      IEnumerable<T> source, int startIndex) 
     { 
      _inputRate = inputRate; 
      _outputRate = outputRate; 
      _source = source.ToList(); 
      _startIndex = startIndex; 

      _rateScaleFactor = _outputRate/_inputRate; 
      // Calculate total output frames from input duration 
      _totalOutputFrames = (int)Math.Round(
       (_source.Count - startIndex) * _rateScaleFactor, 0); 
     } 

     public T Current 
     { 
      get 
      { 
       return _source[_startIndex + 
        (int)Math.Ceiling(_currentOutputFrame/_rateScaleFactor) - 1]; 
      } 
     } 

     public void Dispose() 
     { 
      // Nothing unmanaged to dispose 
     } 

     object IEnumerator.Current 
     { 
      get { return Current; } 
     } 

     public bool MoveNext() 
     { 
      _currentOutputFrame++; 
      return ((_currentOutputFrame - 1) < _totalOutputFrames); 
     } 

     public void Reset() 
     { 
      _currentOutputFrame = 0; 
     } 
    } 
} 

而且一測試覆蓋冪等,擴大,縮小,以及分數幀率設置:

[TestClass] 
public class Test 
{ 
    private readonly List<string> _originalFrames = new List<string>(); 

    public Test() 
    { 
     // 30 FPS for 10 seconds 
     for (int f = 0; f < 300; f++) 
     { 
      _originalFrames.Add(string.Format("{0:0000000}.png", f)); 
     } 
    } 

    [TestMethod] 
    public void Should_set_default_values() 
    { 
     var scaler = new FramerateScaler<string>(30, 30, _originalFrames, 10); 

     Assert.AreEqual(30, scaler.InputRate); 
     Assert.AreEqual(30, scaler.OutputRate); 
     Assert.AreEqual(10, scaler.StartIndex); 
     Assert.AreEqual(_originalFrames.ElementAt(10), scaler.First()); 
    } 

    [TestMethod] 
    public void Scale_from_same_is_idempotent() 
    { 
     var scaler = new FramerateScaler<string>(30, 30, _originalFrames); 

     Assert.AreEqual(scaler.InputDuration, scaler.OutputDuration); 
     Assert.AreEqual(_originalFrames.Count, scaler.Count()); 
     Assert.IsTrue(_originalFrames.SequenceEqual(scaler)); 
    } 

    [TestMethod] 
    public void Scale_from_same_offset_by_half_is_idempotent() 
    { 
     var scaler = new FramerateScaler<string>(
      30, 30, _originalFrames, _originalFrames.Count/2); 

     Assert.AreEqual(150, scaler.Count()); 
     Assert.AreEqual(scaler.OutputDuration, scaler.InputDuration); 
     Assert.IsTrue(_originalFrames 
      .Skip(150) 
      .SequenceEqual(scaler)); 
    } 

    [TestMethod] 
    public void Scale_from_30_to_60() 
    { 
     var scaler = new FramerateScaler<string>(30, 60, _originalFrames); 

     Assert.AreEqual(600, scaler.Count()); 
     Assert.AreEqual(scaler.OutputDuration, scaler.InputDuration); 
     var result = scaler.ToList(); 
     Assert.IsTrue(_originalFrames 
      .Concat(_originalFrames) 
      .OrderBy(x => x) 
      .SequenceEqual(scaler)); 
    } 

    [TestMethod] 
    public void Scale_from_30_to_60_offset_by_half() 
    { 
     var scaler = new FramerateScaler<string>(
      30, 60, _originalFrames, _originalFrames.Count/2); 

     Assert.AreEqual(300, scaler.Count()); 
     Assert.AreEqual(scaler.OutputDuration, scaler.InputDuration); 
     Assert.IsTrue(_originalFrames 
      .Skip(150) 
      .Concat(_originalFrames.Skip(150)) 
      .OrderBy(x => x) 
      .SequenceEqual(scaler)); 
    } 

    [TestMethod] 
    public void Scale_from_30_to_100() 
    { 
     var scaler = new FramerateScaler<string>(30, 100, _originalFrames); 

     Assert.AreEqual(1000, scaler.Count()); 
     Assert.AreEqual(scaler.OutputDuration, scaler.InputDuration); 
     // 000 - 111 - 2222 ... 
     Assert.IsTrue(scaler.PatternIs(0, 0, 0, 1, 1, 1, 2, 2, 2, 2)); 
    } 

    [TestMethod] 
    public void Scale_from_30_to_100_offset_by_half() 
    { 
     var scaler = new FramerateScaler<string>(
      30, 100, _originalFrames, _originalFrames.Count/2); 

     Assert.AreEqual(500, scaler.Count()); 
     Assert.AreEqual(scaler.OutputDuration, scaler.InputDuration); 
     // 000 - 111 - 2222 ... 
     Assert.IsTrue(scaler.PatternIs(0, 0, 0, 1, 1, 1, 2, 2, 2, 2)); 
    } 

    [TestMethod] 
    public void Scale_from_24p_to_ntsc() 
    { 
     var scaler = new FramerateScaler<string>(23.967, 29.97, _originalFrames); 

     Assert.AreEqual(375, scaler.Count()); 
     Assert.AreEqual(
      scaler.OutputDuration.TotalMilliseconds, 
      scaler.InputDuration.TotalMilliseconds, delta: 4); 
     // 0 - 1 - 2 - 33 ... 
     Assert.IsTrue(scaler.PatternIs(0, 1, 2, 3, 3)); 
    } 

    [TestMethod] 
    public void Scale_from_30_to_15() 
    { 
     var scaler = new FramerateScaler<string>(30, 15, _originalFrames); 

     Assert.AreEqual(150, scaler.Count()); 
     Assert.AreEqual(scaler.OutputDuration, scaler.InputDuration); 
     Assert.IsTrue(_originalFrames 
      .Where((item, index) => index % 2 == 1) 
      .SequenceEqual(scaler)); 
    } 

    [TestMethod] 
    public void Scale_from_30_to_15_offset_by_half() 
    { 
     var scaler = new FramerateScaler<string>(30, 15, _originalFrames, 150); 

     Assert.AreEqual(75, scaler.Count()); 
     Assert.AreEqual(scaler.OutputDuration, scaler.InputDuration); 
     Assert.IsTrue(_originalFrames 
      .Skip(150) 
      .Where((item, index) => index % 2 == 1) 
      .SequenceEqual(scaler)); 
    } 
} 

static class Extensions 
{ 
    public static bool PatternIs<T>(this IEnumerable<T> source, params int[] pattern) 
    { 
     foreach (var chunk in source.Chunkify(pattern.Length)) 
     { 
      for (var i = 0; i < chunk.Length; i++) 
       if (!chunk.ElementAt(i).Equals(
        chunk.Distinct().ElementAt(pattern[i]))) 
        return false; 
     } 

     return true; 
    } 

    // http://stackoverflow.com/a/3210961/3191599 
    public static IEnumerable<T[]> Chunkify<T>(this IEnumerable<T> source, int size) 
    { 
     if (source == null) throw new ArgumentNullException("source"); 
     if (size < 1) throw new ArgumentOutOfRangeException("size"); 
     using (var iter = source.GetEnumerator()) 
     { 
      while (iter.MoveNext()) 
      { 
       var chunk = new T[size]; 
       chunk[0] = iter.Current; 
       for (int i = 1; i < size && iter.MoveNext(); i++) 
       { 
        chunk[i] = iter.Current; 
       } 
       yield return chunk; 
      } 
     } 
    } 
} 
+0

這是一個詳盡的答案。謝謝。我喜歡在這裏使用統計員。 –

+0

沒問題。還有什麼你需要它做的,成爲一個被接受的答案? –

+0

該解決方案適用於單個測試用例。我正在努力使它一個接一個地產生多個序列。在考慮時間作爲輸入時,問題就出現了。所以用戶可以說從30FPS到60FPS的縮放4秒,然後縮放到15FPS 2秒。我怎麼能跟蹤哪些幀已經被處理,並在枚舉器中設置了時間限制? –

1

targetFrames.Add(originalFrames [originalStartFrameIndex + (int)(i * targetFrameRate/originalFrameRate) ]

應該做的伎倆。添加一些錯誤驗證(檢查零除以及超出數組的邊界):)