初衷
CoreImage系列是关于最近学习CoreImage处理图片和视频的一些总结,如果有高手看到有错误的地方请帮我指出来,免得误导了大家。谢谢。
CoreImage一瞥
CoreImage是苹果在iOS5出的一个对图片和视频的图像数据进行实时分析、加工的图像框架。通过运用CPU或GPU进行图片处理,开发者不用太关注于底层的OpenGL等技术就能进行强大的图片编辑。
它的核心类有以下几个:
- CIContext:CoreImage的上下文,这个上下文是框架真正工作的地方,它需要分配必要的内存,并编译和运行滤镜内核来执行图像处理。
- CIFilter:CoreImage进行图像的滤镜处理的对象。滤镜可以单独使用,也可以组成一个滤镜链来进行处理 ( 因为CoreImage并不是一个真正的图片对象,而是一个图片生成的"模板",所以在运用滤镜链的时候,并不会对一个图片进行多次滤镜,而是会把这些滤镜在底层的kernel进行像素处理混合,所以只会处理一次图片,效率大大增加 ) 。
- CIKernel:对图片进行处理的核心模块,负责进行像素变化等工作,最后返回加工完成的图片。开发者可以自定义Kernel进行滤镜开发。
。。。
当然,还有一些其他的类,在这里就不一一介绍,在接下来需要的时候会进行介绍。
1.运用CoreImage进行图片滤镜
CoreImage进行图片的滤镜开发其实还是挺简单的,最主要的是选择合适的滤镜,在CoreImage中提供了127个滤镜进行处理 ( 看今年的WWDC上的CoreImage session,好像今年又加入了很多滤镜,达到了196个 ,Amazing! ) 。
选择滤镜首先选择合适的滤镜分类,有些分类是用来处理图片修补的、有些是用来合并或者转场的等等。在这儿我们就进行简单的进行图片的处理就行了。
首先我们需要生成一个CIFilter,我们可以通过filterNamesInCategory或者filterNamesInCategories来获取到一个分类或多个分类的滤镜 ( 如果你想获取所有的 ,传入nil就可以了 )。
你拿到了filter的名字之后就可以用filterWithName来生成一个CIFilter:
CIFilter *filter = [CIFilter filterWithName:@"CIPhotoEffectMono"];
但是一般滤镜都有一些参数可以设置的,我们怎么知道这些参数喃?而且每个滤镜的参数都可能不一样啊!!
别着急,苹果爸爸肯定不会为难我们的。
我们可以获取filter的inputKeys和outputKeys来获取输入参数和输出参数。而且这些参数是接受什么参数也有标志,当然,苹果的官方文档也是有个,传送门。
接下来我们就可以传入图片了,传图片直接用KVC进行传入,key是kCIInputImageKey,value是CIImage类型的对象,你可以用NSData、CIColor、UIImage、CGImgae、CVImageBufferRef等等来创建。
这样就完成了所以准备,现在你只要去取outputImage就行了,它会传出一个CIImage对象,你可以用它来生成真正的Image进行显示。
2.进行视频滤镜
现在我们能够进行简单的图片处理了,现在我们更深入一点,进行视频处理。
因为我们需要实时的处理视频,所以需要拿到视频每一帧的图像,所以像UIImagePickerController这样的视频录制就不能满足我们的需求了,我们需要进行深度挖掘,我们就会用到AVCaptureAudioDataOutput,它能将我们录制的每一帧传出供我们处理,但是我们必须在一定的时间内进行处理,否则它就会将这一帧丢掉,从而出现卡顿现象,就像我们的UITableView一样。像这样实时的处理,如果我们用CPU的话显然是不行的,Core Graphics的效率我们都是知道的,嘻嘻。所以需要GPU出场,基于GPU处理,我们可以用GLKit和Metal来实现,这篇文章我们先用GLKit来实现,下一篇我会用Metal来实现。
对于GLKit,我就用官方的原话来介绍了
The GLKit framework provides functions and classes that reduce the effort required to create new shader-based apps or to port existing apps that rely on fixed-function vertex or fragment processing provided by earlier versions of OpenGL ES or OpenGL。
简单说就是基于OpenGL进行封装吧,让我们好用。
回归正题,我们首先需要建立视频连接,让我们能看到摄像头拍摄的东西,
//视频输入
AVCaptureDevice *video = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:video error:nil];
AVCaptureSession *session = [[AVCaptureSession alloc] init];
if ([session canAddInput:videoInput]) {
[session addInput:videoInput];
}
_session = session;
//视频输出
_queue = dispatch_queue_create("DataOutputQueue", DISPATCH_QUEUE_SERIAL);
_videoOutput = [AVCaptureVideoDataOutput new];
NSDictionary *videoSettings = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:kCVPixelFormatType_32BGRA], kCVPixelBufferPixelFormatTypeKey,
nil];
_videoOutput.videoSettings = videoSettings;
[_videoOutput setAlwaysDiscardsLateVideoFrames:YES];
[_videoOutput setSampleBufferDelegate:self queue:self.queue];
if ([session canAddOutput:_videoOutput]){
[session addOutput:_videoOutput];
}
AVCaptureConnection *connection = [_videoOutput connectionWithMediaType:AVMediaTypeVideo];
connection.videoOrientation = AVCaptureVideoOrientationPortrait;
[session startRunning];
这样我们就能在videoOutPut的代理回调
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
只不过拿到录制的视频帧了,就是CMSampleBufferRef对象。
然后我们需要创建滤镜相关的东西了,首先是GLKView的生成,它有一个方法来进行创建
- (instancetype)initWithFrame:(CGRect)frame context:(EAGLContext *)context;
但是这个EAGLContext又是什么喃?其实这个是GLKit相关的上下文对象,跳到EAGLContext的定义里面,我们能看到它的创建方式:
- (instancetype) init NS_UNAVAILABLE;
- (instancetype) initWithAPI:(EAGLRenderingAPI) api;
- (instancetype) initWithAPI:(EAGLRenderingAPI) api sharegroup:(EAGLSharegroup*) sharegroup NS_DESIGNATED_INITIALIZER;
第一种不能使用,二三种都有一个EAGLRenderingAPI的枚举,其实就是你要使用的OpenGL 的版本,sharegroup也只是一个用于debug时方便查看的对象,所以我们直接用第二种:[[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
因为我们需要对图像进行处理,所以我们需要关闭GLKView的自动渲染,.enableSetNeedsDisplay = NO;
有了GLKit的上下文,我们就能生成CIFilterd的上下文了,
[CIContext contextWithEAGLContext:_eaglContext options:@{kCIContextWorkingColorSpace : [NSNull null]} ]
因为CIContext的创建开销很大,所以我们很多时候都会复用一个context。
接下来我们生成一个滤镜就行了。
准备工作已经完成,接下来就是对实时视频帧进行处理了。
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
CIImage *sourceImage = [CIImage imageWithCVPixelBuffer:(CVPixelBufferRef)imageBuffer options:nil];
CIImage *filteredImage = RunFilter(sourceImage, self.filter);
[_videoPreviewView bindDrawable];
if (filteredImage)
[self.context drawImage:filteredImage inRect:CGRectMake(0, 0, self.videoPreviewView.drawableWidth, self.videoPreviewView.drawableHeight) fromRect:sourceImage.extent];
[_videoPreviewView display];
我们首先需要将CoreMedia的数据对象转换成图像数据,通过CMSampleBufferRef-> CVImageBufferRef-> CIImage,我们就拿到了我们需要的原始图像数据,接下来就是对图像进行滤镜处理,这里和上边处理图片一样的,就不贴代码了。
接下来我们就让滤镜上下文开始着色和让GLKView进行渲染。
这样,我们就实现了拍摄实时滤镜了,demo在此。
参考文章
ps
最近在学习视频相关的东西,本来想的是仿写一个VUE来试试的;恰逢今天纯银大大的新产品发布了,而且看那个准维密模特看得我一愣一愣的,准备参考一下猫饼来写一个,不知道纯银大大有没有意见啊,咳咳~
如果这篇还可以的话,接下来就再写一些Core Image的文章。如果我写的不好或者理解的不深甚至有错误的话,请帮忙指出。