如何使用GPUImage或其他库在iOS上创建VHS效果

时间:2017-07-27 08:36:54

标签: ios video gpuimage effect

我正在尝试为iOS应用制作VHS效果,就像在此视频中一样: https://www.youtube.com/watch?v=8ipML-T5yDk

我想实现这种效果,可以减少产生较少的CPU费用。

基本上我需要的是提高色彩水平以创建“色差”,更改锐化参数,并添加一些高斯模糊+添加一些噪音。

我正在使用GPUImage。对于锐化和高斯模糊,易于应用。

我遇到两个问题: 1)对于“色差”,他们这样做的方式通常是复制视频的三倍,并在一个视频上将红色设置为0,在另一个视频上将蓝色设置为0,在最后一个视频上将绿色设置为0,并将其混合为0他们在一起(就像在教程中一样)。但是在iPhone中执行此操作会占用大量CPU资源。

  

任何想法如何实现相同的效果,而不必复制视频并混合它=

2)我还想添加一些噪音,但不知道要使用哪种GPUImage效果。对此也有任何想法吗?

非常感谢,

塞巴斯蒂安

1 个答案:

答案 0 :(得分:3)

(我不是iOS开发人员,但我希望这可以对某人有所帮助。)

我在Windows上编写了VHS过滤器,这就是我所做的:

  1. 将视频帧的宽高比设为4:3,并将分辨率降低至360 * 270。
  2. 降低色彩饱和度,并应用颜色矩阵以将绿色降低到93%(这样视频将显示为紫色)。
  3. 应用卷积矩阵以定向锐化视频帧。这是我使用的内核:
    0    -0.5    0      0
 -0.5     2.9    0   -0.5
    0    -0.5    0      0
  1. 将真实的空白VHS素材混合到视频中以消除噪音(在YouTube上搜索“ VHS叠加”)。

视频:Before After
截图:Before After

CPU和GPU消耗正常。我将此滤镜应用于旧版Windows Phone(使用Snapdragon 808)上的实时摄像头预览,效果很好。

代码(C#,使用Win2D库进行GPU加速,实现Windows.Media.Effects.IBasicVideoEffect接口):

public void ProcessFrame(ProcessVideoFrameContext context)    //This method is called each frame
{
    int outputWidth = 360;    //Output Resolution
    int outputHeight = 270;

    IDirect3DSurface inputSurface = context.InputFrame.Direct3DSurface;
    IDirect3DSurface outputSurface = context.OutputFrame.Direct3DSurface;

    using (CanvasBitmap inputFrame = CanvasBitmap.CreateFromDirect3D11Surface(canvasDevice, inputSurface))    //The video frame to be processed
    using (CanvasRenderTarget outputFrame = CanvasRenderTarget.CreateFromDirect3D11Surface(canvasDevice, outputSurface))    //The video frame after processing
    using (CanvasDrawingSession outputFrameDrawingSession = outputFrame.CreateDrawingSession())
    using (CanvasRenderTarget croppedFrame = new CanvasRenderTarget(canvasDevice, outputWidth, outputHeight, outputFrame.Dpi))    
    using (CanvasDrawingSession croppedFrameDrawingSession = croppedFrame.CreateDrawingSession())
    using (CanvasBitmap overlay = Task.Run(async () => { return await CanvasBitmap.LoadAsync(canvasDevice, overlayFrames[new Random().Next(0, overlayFrames.Count - 1)]); }).Result)    //"overlayFrames" is a list containing video frames from https://youtu.be/SHhRFU2Jyfs, here we just randomly pick one frame for blend
    {
        double inputWidth = inputFrame.Size.Width;
        double inputHeight = inputFrame.Size.Height;
        Rect ractangle;

        //Crop the inputFrame to 360*270, save it to "croppedFrame"
        if (3 * inputWidth > 4 * inputHeight)
        {
            double x = (inputWidth - inputHeight / 3 * 4) / 2;
            ractangle = new Rect(x, 0, inputWidth - 2 * x, inputHeight);
        }
        else
        {
            double y = (inputHeight - inputWidth / 4 * 3) / 2;
            ractangle = new Rect(0, y, inputWidth, inputHeight - 2 * y);
        }
        croppedFrameDrawingSession.DrawImage(inputFrame, new Rect(0, 0, outputWidth, outputHeight), ractangle, 1, CanvasImageInterpolation.HighQualityCubic);

        //Apply a bunch of effects (mentioned in step 2,3,4) to "croppedFrame"
        BlendEffect vhsEffect = new BlendEffect
        {
            Background = new ConvolveMatrixEffect
            {
                Source = new ColorMatrixEffect
                {
                    Source = new SaturationEffect
                    {
                        Source = croppedFrame,
                        Saturation = 0.4f
                    },
                    ColorMatrix = new Matrix5x4
                    {
                        M11 = 1f,
                        M22 = 0.93f,
                        M33 = 1f,
                        M44 = 1f
                    }
                },
                KernelHeight = 3,
                KernelWidth = 4,
                KernelMatrix = new float[]
                {
                        0,   -0.5f,    0,     0,
                    -0.5f,    2.9f,    0, -0.5f,
                        0,   -0.5f,    0,     0,
                }
            },
            Foreground = overlay,
            Mode = BlendEffectMode.Screen
        };

        //And draw the result to "outputFrame"
        outputFrameDrawingSession.DrawImage(vhsEffect, ractangle, new Rect(0, 0, outputWidth, outputHeight));
    }
}