基于视频生成的新帧速率计算帧索引

时间:2015-04-17 13:23:26

标签: c# .net video frame-rate

我的.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填充适当文件名的targetFrame。

任何关于如何映射这一点的建议都将受到赞赏。

编辑:我忘了提到输出视频将始终以原始帧速率生成。目标视频的长度当然会改变。如果原始FPS低于目标,我们将重复帧。否则我们会跳过它们。

2 个答案:

答案 0 :(得分:1)

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

应该做的伎俩。添加一些错误验证(检查除以零并超出数组的边界):)

答案 1 :(得分:1)

我开始扩展文森特的答案来解决我注意到的问题:当从30fps缩放到100fps时,第0帧重复第4次(0000 111 222 3333的帧模式)当我期待{{1}时}。没什么大不了的,因为它可能只是一个偏好问题(你是否想要在偶数或奇数帧上进行分数“调整”),但后来我走下兔洞并构建了一个迭代器类,可以处理任何场景,包括分数帧率。

(使用通用迭代器还有额外的好处,即不要求帧为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;
        }
    }
}