お使いのブラウザは、バージョンが古すぎます。

このサイトは、Internet Explore8・Internet Explore9には対応しておりません。
恐れ入りますが、お使いのブラウザをバージョンアップしていただきますよう宜しくお願いいたします。

iPhoneで動作する映像フィルタをつくる:GPUImage+iOSのAVFoundationフレームワークその4

こんにちは、andyです。
今回は、フィルタを複数使用したカスタムフィルタをつくり、そのフィルタのパラメータを動的にスライダを使って変更してみたいと思います。


あまり複数のフィルタを使いすぎるとプレビュースピードが遅くなりますのでご注意を!

今回のコード

それでは今回もコードから。


ヘッダーファイルはこんな感じ。

#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#import <AssetsLibrary/AssetsLibrary.h>
#import "GPUImage.h"

@interface VideoFilterViewController : UIViewController

//解説-1
@property (strong, nonatomic) IBOutlet UIView *sepia;
@property (strong, nonatomic) IBOutlet UIView *gamma;
@property (strong, nonatomic) IBOutlet UIView *start;
@property (strong, nonatomic) IBOutlet UIView *end;

- (IBAction)exportToMovie:(id)sender;
- (IBAction)slider:(id)sender;

@end



実装部分は、こんな感じ

#import "VideoFilterViewController.h"

@interface VideoFilterViewController ()
{
    GPUImageMovieComposition *movieFile;

    GPUImageView *filterView;
    AVMutableComposition* composition;
    AVMutableVideoComposition* transformVideoComposition;
    
    //解説-2
    GPUImageOutput<GPUImageInput>* filter;
    GPUImageMovieWriter* movieWriter;
}

@end

@implementation VideoFilterViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
	[self showFilteringMovie];
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (void)showFilteringMovie
{
    NSURL *fileURL1 = [[NSBundle mainBundle] URLForResource:@"videoviewdemo" withExtension:@"mp4"];
    NSURL *fileURL2 = [[NSBundle mainBundle] URLForResource:@"sample3" withExtension:@"mp4"];
    
    composition = [AVMutableComposition composition];
    AVMutableCompositionTrack* videoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
    
    AVURLAsset* firstMovie = [AVURLAsset URLAssetWithURL:fileURL1 options:nil];
    AVURLAsset* secondMovie = [AVURLAsset URLAssetWithURL:fileURL2 options:nil];
    
    AVAssetTrack* firstMovieVideo = [[firstMovie tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
    AVAssetTrack* secondMovieVideo = [[secondMovie tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
    
    [videoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, firstMovie.duration)
                        ofTrack:firstMovieVideo
                         atTime:kCMTimeZero
                          error:nil];
    [videoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, secondMovie.duration)
                        ofTrack:secondMovieVideo
                         atTime:firstMovie.duration
                          error:nil];

    transformVideoComposition = [AVMutableVideoComposition videoComposition];
    NSMutableArray *inst = [NSMutableArray array];
    
    CGSize frameSize = CGSizeMake(640.0f, 480.0f);
    float scale = 0.7f;

    AVMutableVideoCompositionInstruction * firstInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
    firstInstruction.backgroundColor = [[UIColor blueColor] CGColor];
    firstInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, firstMovie.duration);

    AVMutableVideoCompositionLayerInstruction *firstlayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:firstMovieVideo];
    CGAffineTransform firstLayerScale = CGAffineTransformMakeScale(scale, scale);
    CGAffineTransform firstLayerMove = CGAffineTransformMakeTranslation((frameSize.width - firstMovieVideo.naturalSize.width * scale) / 2,
                                                              (frameSize.height-firstMovieVideo.naturalSize.height * scale) / 2);
    [firstlayerInstruction setTransform:CGAffineTransformConcat(firstLayerScale, firstLayerMove) atTime:kCMTimeZero];

    firstInstruction.layerInstructions = [NSArray arrayWithObjects:firstlayerInstruction,nil];;
    [inst addObject:firstInstruction];
    
    AVMutableVideoCompositionInstruction * secondInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
    secondInstruction.backgroundColor = [[UIColor blueColor] CGColor];
    secondInstruction.timeRange = CMTimeRangeMake(firstMovie.duration, secondMovie.duration);
    
    AVMutableVideoCompositionLayerInstruction *secondlayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:secondMovieVideo];
    CGAffineTransform secondLayerScale = CGAffineTransformMakeScale(scale, scale);
    CGAffineTransform secondLayerMove = CGAffineTransformMakeTranslation((frameSize.width - secondMovieVideo.naturalSize.width * scale) / 2,
                                                              (frameSize.height - secondMovieVideo.naturalSize.height * scale) / 2);
    [secondlayerInstruction setTransform:CGAffineTransformConcat(secondLayerScale, secondLayerMove) atTime:kCMTimeZero];
    
    secondInstruction.layerInstructions = [NSArray arrayWithObjects:secondlayerInstruction,nil];;
    [inst addObject:secondInstruction];
    
    transformVideoComposition.instructions = inst;

    transformVideoComposition.renderSize = frameSize;
    transformVideoComposition.frameDuration = CMTimeMake(1, 30);
    
    filterView = [[GPUImageView alloc] initWithFrame:CGRectMake(0, 0, 320, 240)];
    //filter = [[GPUImagePolkaDotFilter alloc] init];
    
    //解説-3
    filter = [[GPUImageFilterGroup alloc] init];
    
    GPUImageSepiaFilter *sepiaFilter = [[GPUImageSepiaFilter alloc] init];
    [(GPUImageFilterGroup *)filter addFilter:sepiaFilter];
    GPUImageGammaFilter *gammaFilter = [[GPUImageGammaFilter alloc] init];
    gammaFilter.gamma = 1.2;
    [(GPUImageFilterGroup *)filter addFilter:gammaFilter];
    GPUImageVignetteFilter* vignetteFilter = [[GPUImageVignetteFilter alloc] init];
    
    [sepiaFilter addTarget:gammaFilter];
    [gammaFilter addTarget:vignetteFilter];
    [(GPUImageFilterGroup *)filter setInitialFilters:[NSArray arrayWithObject:sepiaFilter]];
    [(GPUImageFilterGroup *)filter setTerminalFilter:vignetteFilter];


    //movieFile = [[GPUImageMovie alloc] initWithAsset:composition];

    movieFile = [[GPUImageMovieComposition alloc] initWithComposition:composition andVideoComposition:transformVideoComposition andAudioMix:nil];
    movieFile.playAtActualSpeed = YES;

    [movieFile addTarget:filter];
    [filter addTarget:filterView];
    [self.view addSubview:filterView];
    
    [NSTimer scheduledTimerWithTimeInterval:0.5f
                                     target:self
                                   selector: @selector(play:)
                                   userInfo:nil
                                    repeats:NO];
    
}

- (void)play:(id)sender
{
    __weak GPUImageMovie* weakMovieFile = movieFile;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [weakMovieFile startProcessing];
    });
}

- (IBAction)exportToMovie:(id)sender;
{
    if (filterView == nil) {
        filterView = [[GPUImageView alloc] initWithFrame:CGRectMake(0, 0, 320, 240)];
    }
    
    if (movieFile) {
        [movieFile cancelProcessing];
        [movieFile removeAllTargets];
        [filter removeAllTargets];
        movieFile = nil;
        [filterView removeFromSuperview];
    }

    movieFile = [[GPUImageMovieComposition alloc] initWithComposition:composition andVideoComposition:transformVideoComposition andAudioMix:nil];
    movieFile.playAtActualSpeed = NO;

    [movieFile addTarget:filter];
    [filter addTarget:filterView];
    [self.view addSubview:filterView];

    NSString* pathToMovie = [NSHomeDirectory() stringByAppendingPathComponent:@"tmp/tmp.mov"];
    unlink([pathToMovie UTF8String]);
    NSURL *movieURL = [NSURL fileURLWithPath:pathToMovie];

    CGSize frame = CGSizeMake(640, 480);

    movieWriter = [[GPUImageMovieWriter alloc] initWithMovieURL:movieURL size:frame fileType:AVFileTypeQuickTimeMovie outputSettings:nil];
    
    [filter addTarget:movieWriter];
    [movieFile enableSynchronizedEncodingUsingMovieWriter:movieWriter];
    
    [NSTimer scheduledTimerWithTimeInterval:1.0f
                                     target:self
                                   selector: @selector(exportStart:)
                                   userInfo:nil
                                    repeats:NO];
}

- (void)exportStart:(id)sender
{
    __weak GPUImageMovieWriter* weakMovieWriter = movieWriter;
    __weak GPUImageMovie* weakMovieFile = movieFile;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [weakMovieWriter startRecording];
        [weakMovieFile startProcessing];
    });
}

//解説-4
- (IBAction)slider:(id)sender
{
    switch (((UISlider*)sender).tag) {
        case 100://sepia
            [(GPUImageSepiaFilter*)[(GPUImageFilterGroup *)filter filterAtIndex:0] setIntensity:[(UISlider *)sender value]];
            break;
            
        case 101://gammma
            [(GPUImageGammaFilter*)[(GPUImageFilterGroup *)filter filterAtIndex:1] setGamma:[(UISlider *)sender value]];
            break;
            
        case 102://start
            [(GPUImageVignetteFilter*)((GPUImageFilterGroup *)filter).terminalFilter setVignetteStart:[(UISlider *)sender value]];
            break;
            
        case 103://end
            [(GPUImageVignetteFilter*)((GPUImageFilterGroup *)filter).terminalFilter setVignetteEnd:[(UISlider *)sender value]];
            break;
    }
}

@end


解説

今回の解説はたったの4箇所です。
それでは始めます。

解説-1

まず始めにStoryboardでスライダを4つ画面に追加しておきます。そのスライダに100〜103までのタグを設定しておいてください。
今回使用するフィルタは、

  • GPUImageSepiaFilter
  • GPUImageGammaFilter、
  • GPUImageVignetteFilter

です。
GPUImageSepiaFilterでは、sepiaスライダを使いIntensityを可変します。
GPUImageGammaFilterでは、gammaスライダを使いgammaを可変します。
GPUImageVignetteFilterでは、start、endスライダを使いvignetteStartとvignetteEndを可変します。

解説-2

前回まではGPUImageSepiaFilterのインスタンスとしてfilterを扱っていましたが、今回はスーパークラスであるGPUImageOutputで取り扱います。

解説-3

カスタムフィルタを作っている部分です。フィルタを複数使用する場合にはGPUImageFilterGroupのインスタンスを作成し、フィルタを掛ける順番にそれぞれのフィルタインスタンスをグループに加えていきます。
それから、フィルタのアウトプットを順に後に続くフィルタのインプットに繋げていくという感じでしょうか。

解説-4

ここでスライダからのコールバックを受けています。
それぞれのスライダにタグが設定されているので、その値を元に処理を分岐し、それぞれのフィルタの設定値を可変できるようにしています。
フィルタグループからその中にある任意のフィルタを取得する場合には、NSArrayなどと同様に追加された順のインデックスで行います。ただし、一番最後に掛けたフィルタのみterminalFilterというプロパティから取得します。
後は、スライダからの戻り値をそれぞれの設定値にセットしているだけです。


わかってしまうと意外と簡単!
後は、数あるフィルタの中から自分なりにフィルタを複合してオリジナルフィルタを作るだけ。この辺はどちらかというと、映像制作に関わっている人の方がよくわかっている部分だと思います。


今回はここまで。
次回はどうしようか考え中。
出来たらクロマキー合成をやりたいんですが、動画同士のクロマキーがどうもうまく動かなくて悩んでおります。デバイス自体の性能も影響してるのかもしれませんが・・・


それではまた。

コメントをどうぞ

メールアドレスは公開されません。* が付いている欄は必須項目です。


お気軽にお問い合わせください。

日本VTR実験室では、お仕事のご依頼、ブログ・コラムのご感想などを受け付けております。
アプリ開発・コンテンツ制作でお困りでしたら、お気軽にご相談ください。
ご連絡お待ちしております。

お問い合わせはこちらから

03-3541-1230

info@nvtrlab.jp

電話受付対応時間:平日AM9:30〜PM6:00