怎么快速为裸体美女视频无马赛克添加静态马赛克

怎么给视频打马赛克啊
谢谢_ae吧_百度贴吧
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&签到排名:今日本吧第个签到,本吧因你更精彩,明天继续来努力!
本吧签到人数:0成为超级会员,使用一键签到本月漏签0次!成为超级会员,赠送8张补签卡连续签到:天&&累计签到:天超级会员单次开通12个月以上,赠送连续签到卡3张
关注:205,211贴子:
怎么给视频打马赛克啊
还在学习中
求解啊 谢谢
你这叫在特效里搜索马赛克
有这个效果的
参数调节也不难
添加一个调节层打上遮罩,然后添加马赛克
还要有动态跟踪
用动态跟踪跟踪出要马赛克的区域的运动范围.然后添加固态层,马赛克
静态视频---直接风格化-马赛克移动视频---先追踪。 马赛克
最简单的方法,复制视频层,给上面视频加遮罩。。。
视频层复制一层 在上面的视频层上画遮罩 然后托马赛克效果进去就好了。
第一步,你需要了解AE基础知识第二步,你需要学习如何使用运动跟踪第三步,你需要了解如何做跟踪表达式以上三步约一万字,自己百度脑补。第四步,看下图
登录百度帐号推荐应用
为兴趣而生,贴吧更懂你。或播放列表加载中...
正在载入...
分享视频:
嵌入代码:
拍下二维码,随时随地看视频
怎样给视频加静态马赛克
上 传 者:
内容介绍:
怎样给视频加静态马赛克
Channel Me 精选
我来说点啥
版权所有 CopyRight
| 京网文[0号 |
| 京公网安备:
互联网药品信息服务资格证:(京)-非经营性- | 广播电视节目制作经营许可证:(京)字第403号
<img src="" width="34" height="34"/>
<img src=""/>
<li data-vid="">
<img src=""/><i data-vid="" class="ckl_plays">
<li data-vid="">
<img src=""/><i data-vid="" class="ckl_plays">
<img src="///img/blank.png" data-src=""/>
<img src="///img/blank.png" data-src="http://"/>
<li data-vid="" class="cfix">
src="///img/blank.png" data-src=""/>
<i data-vid="" class="ckl_plays">
<li data-vid="" class="cfix">
src="///img/blank.png" data-src=""/><i data-vid="" class="ckl_plays">
没有数据!
{upload_level_name}
粉丝 {fans_count}
{video_count}
{description}加载中,请稍候...
加载中,请稍候...
商品编号:
京 东 价:
[定价:¥]
白条分期:
在线客服:
服务支持:
7天精通会声会影X5视频短片编辑和特效(全彩)(含DVD光盘1张)
加载中,请稍候...
商品介绍加载中...
扫一扫,精彩好书免费看
服务承诺:
京东平台卖家销售并发货的商品,由平台卖家提供发票和相应的售后服务。请您放心购买!
注:因厂家会在没有任何提前通知的情况下更改产品包装、产地或者一些附件,本司不能确保客户收到的货物与商城图片、产地、附件说明完全一致。只能确保为原厂正货!并且保证与当时市场上同样主流新品一致。若本商城没有及时更新,请大家谅解!
权利声明:京东上的所有商品信息、客户评价、商品咨询、网友讨论等内容,是京东重要的经营资源,未经许可,禁止非法转载使用。
注:本站商品信息均来自于合作方,其真实性、准确性和合法性由信息拥有者(合作方)负责。本站不提供任何保证,并不承担任何法律责任。
加载中,请稍候...
加载中,请稍候...
加载中,请稍候...
加载中,请稍候...
加载中,请稍候...
加载中,请稍候...
加载中,请稍候...
浏览了该商品的用户还浏览了
加载中,请稍候...
iframe(src='///ns.html?id=GTM-T947SH', height='0', width='0', style='display: visibility:')7691人阅读
iOS8 Core Image In Swift:视频实时滤镜在Core Image之前,我们虽然也能在视频录制或照片拍摄中对图像进行实时处理,但远没有Core Image使用起来方便,我们稍后会通过一个Demo回顾一下以前的做法,在此之前的例子都可以在模拟器和真机中测试,而这个例子因为会用到摄像头,所以只能在真机上测试。视频采集我们要进行实时滤镜的前提,就是对摄像头以及UI操作的完全控制,那么我们将不能使用系统提供的Controller,需要自己去绘制一切。先建立一个Single View Application工程(我命名名RealTimeFilter),还是在Storyboard里关掉Auto Layout和Size Classes,然后放一个Button进去,Button的事件连到VC的openCamera方法上,然后我们给VC加两个属性:class&ViewController:&UIViewController&,&AVCaptureVideoDataOutputSampleBufferDelegate&{& &&var&captureSession:&AVCaptureSession!& &&var&previewLayer:&CALayer!......一个previewLayer用来做预览窗口,还有一个AVCaptureSession则是重点。除此之外,我还对VC实现了AVCaptureVideoDataOutputSampleBufferDelegate协议,这个会在后面说。要使用AV框架,必须先引入库:import AVFoundation在viewDidLoad里实现如下:override&func&viewDidLoad() {& &&super.viewDidLoad()&& && &&previewLayer&=&CALayer()& &&previewLayer.bounds&=&CGRectMake(0,&0,&self.view.frame.size.height,&self.view.frame.size.width);& &&previewLayer.position&=&CGPointMake(self.view.frame.size.width&/&2.0,&self.view.frame.size.height&/&2.0);& &&previewLayer.setAffineTransform(CGAffineTransformMakeRotation(CGFloat(M_PI&/&2.0)));&& && &&self.view.layer.insertSublayer(previewLayer, atIndex:&0)&& && &&setupCaptureSession()}这里先对previewLayer进行初始化,注意bounds的宽、高和设置的旋转,这是因为AVFoundation产出的图像是旋转了90度的,所以这里预先调整过来,然后把layer插到最下面,全屏显示,最后调用初始化captureSession的方法:func&setupCaptureSession() {& &&captureSession&=&AVCaptureSession()& & captureSession.beginConfiguration()& &&captureSession.sessionPreset&=&AVCaptureSessionPresetLow&& && &&let&captureDevice =&AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)&& && &&let&deviceInput =&AVCaptureDeviceInput.deviceInputWithDevice(captureDevice, error:&nil)&as&AVCaptureDeviceInput& &&if&captureSession.canAddInput(deviceInput) {& & & &&captureSession.addInput(deviceInput)& & }&& && &&let&dataOutput =&AVCaptureVideoDataOutput()& & dataOutput.videoSettings&= [kCVPixelBufferPixelFormatTypeKey&:&kCVPixelFormatType_420YpCbCr8BiPlanarFullRange]& & dataOutput.alwaysDiscardsLateVideoFrames&=&true&& && &&if&captureSession.canAddOutput(dataOutput) {& & & &&captureSession.addOutput(dataOutput)& & }&& && &&let&queue =&dispatch_queue_create(&VideoQueue&,&DISPATCH_QUEUE_SERIAL)& & dataOutput.setSampleBufferDelegate(self, queue: queue)& & captureSession.commitConfiguration()}从这个方法开始,就算正式开始了。首先实例化一个AVCaptureSession对象,AVFoundation基于会话的概念,会话(session)被用于控制输入到输出的过程beginConfiguration与commitConfiguration总是成对调用,当后者调用的时候,会批量配置session,且是线程安全的,更重要的是,可以在session运行中执行,总是使用这对方法是一个好的习惯然后设置它的采集质量,除了AVCaptureSessionPresetLow以外还有很多其他选项,感兴趣可以自己看看。获取采集设备,默认的摄像设备是后置摄像头。把上一步获取到的设备作为输入设备添加到当前session中,先用canAddInput方法判断一下是个好习惯。添加完输入设备后再添加输出设备到session中,我在这里添加的是AVCaptureVideoDataOutput,表示视频里的每一帧,除此之外,还有AVCaptureMovieFileOutput(完整的视频)、AVCaptureAudioDataOutput(音频)、AVCaptureStillImageOutput(静态图)等。关于videoSettings属性设置,可以先看看文档说明:后面有写到虽然videoSettings是指定一个字典,但是目前只支持kCVPixelBufferPixelFormatTypeKey,我们用它指定像素的输出格式,这个参数直接影响到生成图像的成功与否,由于我打算先做一个实时灰度的效果,所以这里使用kCVPixelFormatType_420YpCbCr8BiPlanarFullRange的输出格式,关于这个格式的详细说明,可以看最后面的参数资料3(YUV的维基)。后面设置了alwaysDiscardsLateVideoFrames参数,表示丢弃延迟的帧;同样用canAddInput方法判断并添加到session中。最后设置delegate回调(AVCaptureVideoDataOutputSampleBufferDelegate协议)和回调时所处的GCD队列,并提交修改的配置。我们现在完成一个session的建立过程,但这个session还没有开始工作,就像我们访问数据库的时候,要先打开数据库---然后建立连接---访问数据---关闭连接---关闭数据库一样,我们在openCamera方法里启动session:&@IBAction&func&openCamera(sender:&UIButton) {& & sender.enabled&=&false& &&captureSession.startRunning()}session启动之后,不出意外的话,回调就开始了,并且是实时回调(这也是为什么要把delegate回调放在一个GCD队列中的原因),我们处理optional func captureOutput(captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, fromConnection connection: AVCaptureConnection!)这个回调就可以了:Core Image之前的方式func&captureOutput(captureOutput:&AVCaptureOutput!,& & & & & & & & & & didOutputSampleBuffer sampleBuffer:&CMSampleBuffer!,& & & & & & & & & & fromConnection connection:&AVCaptureConnection!) {& &&let&imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)& &&CVPixelBufferLockBaseAddress(imageBuffer,&0)& &&let&width =&CVPixelBufferGetWidthOfPlane(imageBuffer,&0)& &&let&height =&CVPixelBufferGetHeightOfPlane(imageBuffer,&0)& &&let&bytesPerRow =&CVPixelBufferGetBytesPerRowOfPlane(imageBuffer,&0)& &&let&lumaBuffer =&CVPixelBufferGetBaseAddressOfPlane(imageBuffer,&0)&& && &&let&grayColorSpace =&CGColorSpaceCreateDeviceGray()& &&let&context =&CGBitmapContextCreate(lumaBuffer, width, height,&8, bytesPerRow, grayColorSpace,&CGBitmapInfo.allZeros)& &&let&cgImage =&CGBitmapContextCreateImage(context)&& && &&dispatch_sync(dispatch_get_main_queue(), {& & & &&self.previewLayer.contents = cgImage& & })}当数据缓冲区的内容更新的时候,AVFoundation就会马上调这个回调,所以我们可以在这里收集视频的每一帧,经过处理之后再渲染到layer上展示给用户。首先这个回调给我们了一个CMSampleBufferRef类型的sampleBuffer,这是Core Media对象,我们可以通过CMSampleBufferGetImageBuffer方法把它转成Core Video对象。然后我们把缓冲区的base地址给锁住了,锁住base地址是为了使缓冲区的内存地址变得可访问,否则在后面就取不到必需的数据,显示在layer上就只有黑屏,更详细的原因可以看这里:接下来从缓冲区取图像的信息,包括宽、高、每行的字节数等因为视频的缓冲区是YUV格式的,我们要把它的luma部分提取出来我们为了把缓冲区的图像渲染到layer上,需要用Core Graphics创建一个颜色空间和图形上下文,然后通过创建的颜色空间把缓冲区的图像渲染到上下文中cgImage就是从缓冲区创建的Core Graphics图像了(CGImage),最后我们在主线程把它赋值给layer的contents予以显示现在在真机上编译、运行,应该能看到如下的实时灰度效果:(这张图是通过手机截屏获取的,容易手抖,所以不是很清晰)用Core Image处理通过以上几步可以看到,代码不是很多,没有Core Image也能处理,但是比较费劲,难以理解、不好维护,如果想多增加一些效果(这仅仅是一个灰度效果),代码会变得非常臃肿,所以拓展性也不好。事实上,我们想通过Core Image改造上面的代码也很简单,先从添加CIFilter和CIContext开始,这是Core Image的核心内容。在VC上新增两个属性:var&filter:&CIFilter!lazy&var&context:&CIContext&= {& &&let&eaglContext =&EAGLContext(API:&EAGLRenderingAPI.OpenGLES2)& &&let&options = [kCIContextWorkingColorSpace&:&NSNull()]& &&return&CIContext(EAGLContext: eaglContext, options: options)}()申明一个CIFilter对象,不用实例化;懒加载一个CIContext,这个CIContext的实例通过contextWithEAGLContext:方法构造,和我们之前所使用的不一样,虽然通过contextWithOptions:方法也能构造一个GPU的CIContext,但前者的优势在于:渲染图像的过程始终在GPU上进行,并且永远不会复制回CPU存储器上,这就保证了更快的渲染速度和更好的性能。实际上,通过contextWithOptions:创建的GPU的context,虽然渲染是在GPU上执行,但是其输出的image是不能显示的,只有当其被复制回CPU存储器上时,才会被转成一个可被显示的image类型,比如UIImage。我们先创建了一个EAGLContext,再通过EAGLContext创建一个CIContext,并且通过把working color space设为nil来关闭颜色管理功能,颜色管理功能会降低性能,而且只有当对颜色保真度要求很高的时候才需要颜色管理功能,在其他情况下,特别是实时处理中,颜色保真都不是特别重要(性能第一,视频帧延迟很高的app大家都不会喜欢的)。然后我们把session的配置过程稍微修改一下,只修改一处代码即可:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange替换为kCVPixelFormatType_32BGRA我们把上面那个难以理解的格式替换为BGRA像素格式,大多数情况下用此格式即可。再把session的回调进行一些修改,变成我们熟悉的方式,就像这样:func&captureOutput(captureOutput:&AVCaptureOutput!,& & & & & & & & & & didOutputSampleBuffer sampleBuffer:&CMSampleBuffer!,& & & & & & & & & & fromConnection connection:&AVCaptureConnection!) {& &&let&imageBuffer =&CMSampleBufferGetImageBuffer(sampleBuffer)&& & & & & & & & & & & && &&// CVPixelBufferLockBaseAddress(imageBuffer, 0)& &&// let width = CVPixelBufferGetWidthOfPlane(imageBuffer, 0)& &&// let height = CVPixelBufferGetHeightOfPlane(imageBuffer, 0)& &&// let bytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 0)& &&// let lumaBuffer = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0)& &&//& &&// let grayColorSpace = CGColorSpaceCreateDeviceGray()& &&// let context = CGBitmapContextCreate(lumaBuffer, width, height, 8, bytesPerRow, grayColorSpace, CGBitmapInfo.allZeros)& &&// let cgImage = CGBitmapContextCreateImage(context)&& && &&var&outputImage =&CIImage(CVPixelBuffer: imageBuffer)&& && &&if&filter&!=&nil&{& & & &&filter.setValue(outputImage, forKey:&kCIInputImageKey)& & & & outputImage =&filter.outputImage& & }&& && &&let&cgImage =&context.createCGImage(outputImage, fromRect: outputImage.extent())&& && &&dispatch_sync(dispatch_get_main_queue(), {& & & &&self.previewLayer.contents&= cgImage& & })}这是一段拓展性、维护性都比较好的代码了:先拿到缓冲区,看从缓冲区直接取到一张CIImage如果指定了滤镜,就应用到图像上;反之则显示原图通过context创建CGImage的实例在主队列中显示到layer上在此基础上,我们只用添加一些滤镜就可以了。先在Storyboard上添加一个UIView,再以这个UIView作容器,往里面加四个button,从0到3设置button的tag,并把button们的事件全部连接到VC的applyFilter方法上,UI看起来像这样:把这个UIView(buttons的容器)连接到VC的filterButtonsContainer上,再添加一个字符串数组,存储一些滤镜的名字,最终VC的所有属性如下:class&ViewController:&UIViewController&,&AVCaptureVideoDataOutputSampleBufferDelegate&{& &&@IBOutlet&var&filterButtonsContainer:&UIView!& &&var&captureSession:&AVCaptureSession!& &&var&previewLayer:&CALayer!& &&var&filter:&CIFilter!& &&lazy&var&context:&CIContext&= {& & & &&let&eaglContext =&EAGLContext(API:&EAGLRenderingAPI.OpenGLES2)& & & &&let&options = [kCIContextWorkingColorSpace&:&NSNull()]& & & &&return&CIContext(EAGLContext: eaglContext, options: options)& & }()& &&lazy&var&filterNames: [String] = {& & & &&return&[&CIColorInvert&,&CIPhotoEffectMono&,&CIPhotoEffectInstant&,&CIPhotoEffectTransfer&]& & }()......在viewDidLoad方法中先隐藏滤镜按钮们的容器:&......filterButtonsContainer.hidden&=&true......修改openCamera方法,最终实现如下:@IBAction&func&openCamera(sender:&UIButton) {& & sender.enabled&=&false& &&captureSession.startRunning()& &&self.filterButtonsContainer.hidden&=&false}最后applyFilter方法的实现:@IBAction&func&applyFilter(sender:&UIButton) {& &&var&filterName =&filterNames[sender.tag]& &&filter&=&CIFilter(name: filterName)}至此,我们就大功告成了,赶紧在真机上编译、运行看看吧:保存到图库接下来我们添加拍照功能。首先我们在VC上添加一个名为“拍照”的button,连接到VC的takePicture方法上,在实现方法之前,有几步改造工作要先做完。首先就是图像元数据的问题,一张图像可能包含定位信息、图像格式、方向等元数据,而方向是我们最关心的部分,在上面的viewDidLoad方法中,我是通过将previewLayer进行旋转使我们看到正确的图像,但是如果直接将图像保存在图库或文件中,我们会得到一个方向不正确的图像,为了最终获取方向正确的图像,我把previewLayer的旋转去掉:......previewLayer&=&CALayer()// previewLayer.bounds = CGRectMake(0, 0, self.view.frame.size.height, self.view.frame.size.width);// previewLayer.position = CGPointMake(self.view.frame.size.width / 2.0, self.view.frame.size.height / 2.0);// previewLayer.setAffineTransform(CGAffineTransformMakeRotation(CGFloat(M_PI / 2.0)));previewLayer.anchorPoint&=&CGPointZeropreviewLayer.bounds&=&view.bounds......设置layer的anchorPoint是为了把bounds的顶点从中心变为左上角,这正是UIView的顶点。现在你运行的话看到的将是方向不正确的图像。然后我们把方向统一放到captureSession的回调中处理,修改之前写的实现:......var&outputImage =&CIImage(CVPixelBuffer: imageBuffer)&& & & & & & & & & &let&orientation =&UIDevice.currentDevice().orientationvar&t:&CGAffineTransform!if&orientation&==&UIDeviceOrientation.Portrait&{& & t =&CGAffineTransformMakeRotation(CGFloat(-M_PI&/&2.0))}&else&if&orientation&==&UIDeviceOrientation.PortraitUpsideDown&{& & t =&CGAffineTransformMakeRotation(CGFloat(M_PI&/&2.0))}&else&if&(orientation&==&UIDeviceOrientation.LandscapeRight) {& & t =&CGAffineTransformMakeRotation(CGFloat(M_PI))}&else&{& & t =&CGAffineTransformMakeRotation(0)}outputImage = outputImage.imageByApplyingTransform(t)if&filter&!=&nil&{& &&filter.setValue(outputImage, forKey:&kCIInputImageKey)& & outputImage =&filter.outputImage}......在获取outputImage之后并在使用滤镜之前调整outputImage的方向,这样一下,四个方向都处理了。运行之后看到的效果和之前就一样了。方向处理完后我们还要用一个实例变量保存这个outputImage,因为这里面含有图像的元数据,我们不会丢弃它:给VC添加一个CIImage的属性:&var&ciImage:&CIImage!在captureSession的回调里保存CIImage:......if&filter&!=&nil&{& &&filter.setValue(outputImage, forKey:&kCIInputImageKey)& & outputImage =&filter.outputImage}let&cgImage =&context.createCGImage(outputImage, fromRect: outputImage.extent())ciImage&= outputImage......滤镜处理完后,就将这个CIImage存起来,它可能被应用过滤镜,也可能是干干净净的原图。最后是takePicture的方法实现:@IBAction&func&takePicture(sender:&UIButton) {& & sender.enabled&=&false& &&captureSession.stopRunning()& &&var&cgImage =&context.createCGImage(ciImage, fromRect:&ciImage.extent())& &&ALAssetsLibrary().writeImageToSavedPhotosAlbum(cgImage, metadata:&ciImage.properties())& & & & { (url:&NSURL!, error :NSError!) -& Void&in& & & & & &&if&error ==&nil&{& & & & & & & &&println(&保存成功&)& & & & & & & &&println(url)& & & & & & }&else&{& & & & & & & &&let&alert =&UIAlertView(title:&&错误&,&
& & &&message: error.localizedDescription,&
& & &delegate:&nil,&
& &&cancelButtonTitle:&&确定&)& & & & & & & & alert.show()& & & & & & }& & & & & &&self.captureSession.startRunning()& & & & & & sender.enabled&=&true& & }}&先将按钮禁用,session停止运行,再用实例变量ciImage绘制一张CGImage,最后连同元数据一同存进图库中。这里需要导入AssetsLibrary库:import AssetsLibrary。writeImageToSavedPhotosAlbum方法的回调block用到了尾随闭包语法。在真机上编译、运行看看吧。注:由于我是用layer来做预览容器的,它没有autoresizingMask这样的属性,你会发现横屏的时候就显示不正常了,在iOS 8gh,你可以通过重写VC的以下方法来兼容横屏:override&func&viewWillTransitionToSize(size:&CGSize, withTransitionCoordinator&
coordinator:&UIViewControllerTransitionCoordinator) {& &&previewLayer.bounds.size&= size}录制视频前期配置这篇文章并不会详解AVFoundation框架,但为了完成Core Image的功能,我们多多少少会说一些。我们在VC上添加一个名为“开始录制”的按钮,把按钮本身连接到VC的recordsButton属性上,并把它的事件连接到record方法上,UI看起来像这样:为了愉快地进行下去,我先把为VC新增的所有属性列出来:......// Video Records@IBOutlet&var&recordsButton:&UIButton!var&assetWriter:&AVAssetWriter?var&assetWriterPixelBufferInput:&AVAssetWriterInputPixelBufferAdaptor?var&isWriting =&falsevar&currentSampleTime:&CMTime?var&currentVideoDimensions:&CMVideoDimensions?......这些就是为了实现视频录制会用到的所有属性,我们简单说一下:recordsButton,为了方便的获取录制按钮的实例而增加的属性assetWriter,这是一个AVAssetWriter对象的实例,这个类的工作方式很像AVCaptureSession,也是为了控制输入输出的流程而存在的assetWriterPixelBufferInput,一个AVAssetWriterInputPixelBufferAdaptor对象,这个属性的作用如同它的名字,它允许我们不断地增加像素缓冲区到assetWriter对象里isWriting,如果我们当前正在录制视频,则会用这个实例变量记录下来currentSampleTime,这是一个时间戳,在AVFoundation框架里,每一块添加的数据(视频或音频等)除了data部分外,还需要一个当前的时间,每一帧的时间都不同,这就形成了每一帧的持续时间(时间间隔)currentVideoDimensions,这个属性描述了视频尺寸,虽然这个属性并不重要,但是我更加懒得把尺寸写死,它的单位是像素接下来我们先完成两个工具方法:movieURL和checkForAndDeleteFile。func&movieURL() -&&NSURL&{& &&var&tempDir =&NSTemporaryDirectory()& &&let&urlString = tempDir.stringByAppendingPathComponent(&tmpMov.mov&)& &&return&NSURL(fileURLWithPath: urlString)}这个方法做的事情很简单,只是构建一个临时目录里的文件URL。func&checkForAndDeleteFile() {& &&let&fm =&NSFileManager.defaultManager()& &&var&url =&movieURL()& &&let&exist = fm.fileExistsAtPath(movieURL().path!)&& && &&var&error:&NSError?& &&if&exist {& & & & fm.removeItemAtURL(movieURL(), error: &error)& & & &&println(&删除之前的临时文件&)& & & &&if&let&errorDescription = error?.localizedDescription&{& & & & & &&println(errorDescription)& & & & }& & }}这个方法检查了文件是否已存在,如果已存在就删除旧文件,之所以要增加这个方法是因为AVAssetWriter不能在已有的文件URL上写文件,如果文件已存在就会报错。还有一点需要注意:我在iOS 7上判断文件是否存在时用的是URL的absoluteString方法,结果导致AVAssetWriter没报错,但是后面的缓冲区出错了,排查了很久,把absoluteString换成path就好了。。二个工具方法完成后,我们就开始写最主要的方法,即createWriter方法:func&createWriter() {& &&self.checkForAndDeleteFile()&& && &&var&error:&NSError?& &&assetWriter&=&AVAssetWriter(URL:&movieURL(), fileType:&AVFileTypeQuickTimeMovie, error: &error)& &&if&let&errorDescription = error?.localizedDescription&{& & & &&println(&创建writer失败&)& & & &&println(errorDescription)& & & &&return& & }& &&let&outputSettings = [& & & &&AVVideoCodecKey&:&AVVideoCodecH264,& & & &&AVVideoWidthKey&:&Int(currentVideoDimensions!.width),& & & &&AVVideoHeightKey&:&Int(currentVideoDimensions!.height)& & ]& &&let&assetWriterVideoInput =&AVAssetWriterInput(mediaType:&AVMediaTypeVideo, outputSettings: outputSettings)& & assetWriterVideoInput.expectsMediaDataInRealTime&=&true& & assetWriterVideoInput.transform&=&CGAffineTransformMakeRotation(CGFloat(M_PI&/&2.0))& &&let&sourcePixelBufferAttributesDictionary = [& & & &&kCVPixelBufferPixelFormatTypeKey&:&kCVPixelFormatType_32BGRA,& & & &&kCVPixelBufferWidthKey&:&Int(currentVideoDimensions!.width),& & & &&kCVPixelBufferHeightKey&:&Int(currentVideoDimensions!.height),& & & &&kCVPixelFormatOpenGLESCompatibility&:&kCFBooleanTrue& & ]& &&assetWriterPixelBufferInput&=&AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: assetWriterVideoInput,& & & & & & & & & & & & & & & & & & & & & & sourcePixelBufferAttributes: sourcePixelBufferAttributesDictionary)&& && &&if&assetWriter!.canAddInput(assetWriterVideoInput) {& & & &&assetWriter!.addInput(assetWriterVideoInput)& & }&else&{& & & &&println(&不能添加视频writer的input&\(assetWriterVideoInput)&)& & }}&这个方法主要是配置项很多。首先检查了文件是否存在,如果存在的话就删除旧的临时文件,不然AVAssetWriter会因无法写入文件而报错实例化一个AVAssetWriter对象,把需要写的文件URL和文件类型传递给它,再给它一个存储错误信息的指针,方便在出错的时候排查创建一个outputSettings的字典应用到AVAssetWriterInput对象上,这个对象之前没有提到,但也是相当重要的一个对象,它表示了一个输入设备,比如视频、音频的输入等,不同的设备拥有不同的参数和配置,并不复杂,我们这里就不考虑音频输入了。在这个视频的配置里,我们配置了视频的编码,以及用获取到的当前视频设备尺寸(单位像素)初始化了宽、高设置expectsMediaDataInRealTime为true,这是从摄像头捕获的源中进行实时编码的必要参数设置了视频的transform,主要也是为了解决方向问题创建另外一个属性字典去实例化一个AVAssetWriterInputPixelBufferAdaptor对象,我们在视频采集的过程中,会不断地通过这个缓冲区往AVAssetWriter对象里添加内容,实例化的参数中还有AVAssetWriterInput对象,属性字典标识了缓冲区的大小与格式。最后判断一下能否添加这个输入设备,虽然大多数情况下判断一定为真,而且为假的情况我们也没办法考虑了,但预先判断还是一个好的编码习惯处理每一帧上面这些基本性的配置工作完成后,在正式开始录制视频之前,我们还有最后一步要处理,那就是处理视频的每一帧。其实在之前我们就已经尝试过处理每一帧了,因为我们做过拍照的实时滤镜功能,现在我们只需要修改AVCaptureSession的回调就行了。由于之前在captureOutput:didOutputSampleBuffer:这个回调方法中,我们是先对图像的方向进行处理,然后再对其应用滤镜,而录制视频的时候我们不需要对方向进行处理,因为在配置AVAssetWriterInput对象的时候我们已经处理过了,所以我们先将应用滤镜和方向调整的代码互换一下,变成先应用滤镜,再处理方向,然后在他们中间插入处理录制视频的代码:......if&self.filter&!=&nil&{& &&self.filter.setValue(outputImage, forKey:&kCIInputImageKey)& & outputImage =&self.filter.outputImage}//&处理录制视频let&formatDescription =&CMSampleBufferGetFormatDescription(sampleBuffer)self.currentVideoDimensions&=&CMVideoFormatDescriptionGetDimensions(formatDescription)self.currentSampleTime&=&CMSampleBufferGetOutputPresentationTimeStamp(sampleBuffer)if&self.isWriting&{& &&if&self.assetWriterPixelBufferInput?.assetWriterInput.readyForMoreMediaData&==&true&{& & & &&var&newPixelBuffer:&Unmanaged&CVPixelBuffer&? =&nil& & & &&CVPixelBufferPoolCreatePixelBuffer(nil,&self.assetWriterPixelBufferInput?.pixelBufferPool, &newPixelBuffer)&& & & && & & &&self.context.render(outputImage,& & & & & & & & & & & & & & toCVPixelBuffer: newPixelBuffer?.takeUnretainedValue(),& & & & & & & & & & & & & & bounds: outputImage.extent(),& & & & & & & & & & & & & & colorSpace:&nil)&& & & && & & &&let&success =&self.assetWriterPixelBufferInput?.appendPixelBuffer(newPixelBuffer?.takeUnretainedValue(),& & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & withPresentationTime:&self.currentSampleTime!)&& & & && & & & newPixelBuffer?.autorelease()&& & & && & & &&if&success ==&false&{& & & & & &&println(&Pixel Buffer没有append成功&)& & & & }& & }}let&orientation =&UIDevice.currentDevice().orientationvar&t:&CGAffineTransform!......在对图像应用完滤镜之后,我们做了这些事情:获取尺寸和时间,这两个值在后面会用到。强调一下,时间这个参数是很重要的,当你有一系列的帧的时候,assetWriter必须知道何时显示他们,我们除了通过CMSampleBufferGetOutputPresentationTimeStamp函数获取之外,也可以手动创建一个时间,比如把每个缓冲区的时间设置为比上一个缓冲区时间多1/30秒,这就相当于创建一个每秒30帧的视频,但是这不能保证视频时序的真实情况,因为某些滤镜(或者其他操作)可能会耗时过长当前是否需要录制视频,录制视频其实就是写文件的一个过程判断assetWriter是否已经准备好输入数据了一切都准备好后,我们就先配置一个缓冲区。用CVPixelBufferPoolCreatePixelBuffer函数能创建基于池的缓冲区,它的好处是在创建缓冲区的时候会把之前对assetWriterPixelBufferInput对象的配置项应用到新的缓冲区上,这样就避免了你重新对新的缓冲区进行配置。有一点需要注意,如果我们的assetWriter还未开始工作,那么当我们调用assetWriterPixelBufferInput的pixelBufferPool时候会得到一个空指针,缓冲区当然也就创建不了了我们把缓冲区准备好后,就利用context把图像渲染到里面把缓冲区写入到临时文件中,同时得到是否写入成功的返回值由于在Swift里CVPixelBufferPoolCreatePixelBuffer函数需要的是一个手动管理引用计数的对象(Unmanaged对象),所以需要自己把它处理一下如果第6步失败的话就输出一下之前的代码还是保留,因为我们还是需要将每一帧绘制到屏幕上。由于这个方法用到了很多对象,而且比较占用内存,所以我在进入这个方法的时候还手动增加了自动释放池:autoreleasepool&{& &&// ....}&保存视频到图库我们之前就加入了recordsButton,并把它连接到了record方法上,现在来实现它:@IBAction&func&record() {& &&if&isWriting&{& & & &&self.isWriting&=&false& & & &&assetWriterPixelBufferInput&=&nil& & & &&recordsButton.enabled&=&false& & & &&assetWriter?.finishWritingWithCompletionHandler({[unowned&self] () -& Void&in& & & & & &&println(&录制完成&)& & & & & &&self.recordsButton.setTitle(&处理中...&, forState:&UIControlState.Normal)& & & & & &&self.saveMovieToCameraRoll()& & & & })& & }&else&{& & & &&createWriter()& & & &&recordsButton.setTitle(&停止录制...&, forState:&UIControlState.Normal)& & & &&assetWriter?.startWriting()& & & &&assetWriter?.startSessionAtSourceTime(currentSampleTime!)& & & &&isWriting&=&true& & }}首先是不是在录制,如果是的话就停止录制、保存视频,并清理资源。如果还没有开始录制,就创建AVAssetWriter并配置好,然后调用startWriting方法使assetWriter开始工作,不然在回调里取pixelBufferPool的时候取不到,除此之外,还要调用startSessionAtSourceTime方法,调用后者是为了在回调中拿到最新的时间,即currentSampleTime。如果不调用这两个方法,在appendPixelBuffer的时候就会有问题,就算最后能保存,也只能得到一个空的视频文件。当视频录制的过程开始后,就只有调用finishWriting方法才能停止,我们通过saveMovieToCameraRoll方法把视频写入到图库中,不然这视频也就没机会展示了:func&saveMovieToCameraRoll() {& &&ALAssetsLibrary().writeVideoAtPathToSavedPhotosAlbum(movieURL(), completionBlock: { (url:&NSURL!, error:&NSError?) -& Void&in& & & &&if&let&errorDescription = error?.localizedDescription&{& & & & & &&println(&写入视频错误:\(errorDescription)&)& & & & }&else&{& & & & & &&self.checkForAndDeleteFile()& & & & & &&println(&写入视频成功&)& & & & }& & & &&self.recordsButton.enabled&=&true& & & &&self.recordsButton.setTitle(&开始录制&, forState:&UIControlState.Normal)& & })}&之前在拍照并保存的时候,我们使用了尾随闭包语法,这里使用的是完整语法的闭包。保存成功后就可以删除临时文件了。编译、运行吧:局部滤镜上面的滤镜都是对整张图像应用滤镜,我们也可以只对部分区域应用滤镜,例如把滤镜应用到视频中的面部上。不同于,AVFoundation框架内置了检测人脸的功能,所以我们不需要使用CIDetector。标记人脸我们先简单的用一个Layer把人脸的区域标记出来,给VC增加一个属性://&标记人脸var&faceLayer:&CALayer?修改setupCaptureSession方法,在captureSession调用commitConfiguration方法之前加入以下代码:......//&为了检测人脸let&metadataOutput =&AVCaptureMetadataOutput()metadataOutput.setMetadataObjectsDelegate(self, queue:&dispatch_get_main_queue())if&captureSession.canAddOutput(metadataOutput) {& &&captureSession.addOutput(metadataOutput)& &&println(metadataOutput.availableMetadataObjectTypes)& & metadataOutput.metadataObjectTypes&= [AVMetadataObjectTypeFace]}......这里加入了一个元数据的output对象,添加到captureSession后我们就能在回调中得到图像的元数据,包括检测到的人脸。给metadataObjectTypes属性赋值是为了申明要检测的类型,这句要在增加到captureSession之后调用。因为我们要在回调中直接操作Layer的显示,所以我把回调放在主队列中。实现AVCaptureMetadataOutput的回调方法:// MARK: - AVCaptureMetadataOutputObjectsDelegatefunc&captureOutput(captureOutput:&AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [AnyObject]!, fromConnection connection:&AVCaptureConnection!) {& &&// println(metadataObjects)& &&if&metadataObjects.count&&&0&{& & & &&//识别到的第一张脸& & & &&var&faceObject = metadataObjects.first&as&AVMetadataFaceObject&& & & && & & &&if&faceLayer&==&nil&{& & & & & &&faceLayer&=&CALayer()& & & & & &&faceLayer?.borderColor&=&UIColor.redColor().CGColor& & & & & &&faceLayer?.borderWidth&=&1& & & & & &&view.layer.addSublayer(faceLayer)& & & & }& & & &&let&faceBounds = faceObject.bounds& & & &&let&viewSize =&view.bounds.size& & & &&faceLayer?.position&=&CGPoint(x: viewSize.width&*&(1 -&faceBounds.origin.y&-&faceBounds.size.height&/&2),& & & & & & & & & & & & & & & & & & & y: viewSize.height&*&(faceBounds.origin.x&+&faceBounds.size.width&/&2))&& & & && & & &&faceLayer?.bounds.size&=&CGSize(width: faceBounds.size.width&*&viewSize.height,& & & & & & & & & & & & & & & & & & & & height: faceBounds.size.height&*&viewSize.width)& & & &&print(faceBounds.origin)& & & &&print(&###&)& & & &&print(faceLayer!.position)& & & &&print(&###&)& & & &&print(faceLayer!.bounds)& & }}&简单说明下上述代码的作用:参数中的metadataObjects数组就是AVFoundation框架给我们的关于图像的所有元数据,由于我只设置了需要人脸检测,所以简单判断是否为空后,取出其中的数据即可。在这里我只对第一张脸进行了处理接下来初始化Layer,并设置边框取到的faceObject对象虽然包含了bounds属性,但并不能直接使用,因为从AVFoundation视频中取到的bounds,是一个0~1之间的数,是相对于图像的百分比,所以我们在设置position时,做了两步:把x、y颠倒,修正方向等问题,我只是简单地适配了Portrait方向,此处能达到目的即可。再和view的宽、高相乘,其实是和Layer的父Layer的宽、高相乘。设置size也如上做的事情比较简单,只是单纯地初始化一个Layer,然后不停地修改它的postion和size就行了。编译、运行后应该能看到如下效果:使用滤镜上面用Layer只是简单的先显示一下人脸的区域,我们没有调整图像输出时的CIImage,所以并不能被录制到视频或被保存图片到图库中。接下来我们就修改之前的代码,使其能同时支持整体滤镜和部分滤镜。首先把VC中记录的属性改一下:&......//&标记人脸// var faceLayer: CALayer?var&faceObject:&AVMetadataFaceObject?......我们就不用Layer作人脸范围的标记了,而是直接把滤镜应用到输出的CIImage上,为此,我们需要在AVCaptureMetadataOutput对象的delegate回调方法中记录识别到的脸部元数据:// MARK: - AVCaptureMetadataOutputObjectsDelegatefunc&captureOutput(captureOutput:&AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [AnyObject]!, fromConnection connection:&AVCaptureConnection!) {& &&// println(metadataObjects)& &&if&metadataObjects.count&&&0&{& & & &&//识别到的第一张脸& & & &&faceObject&= metadataObjects.first&as?&AVMetadataFaceObject&& & & && & & &&/*& & & & if faceLayer == nil {& & & & & & faceLayer = CALayer()& & & & & & faceLayer?.borderColor = UIColor.redColor().CGColor& & & & & & faceLayer?.borderWidth = 1& & & & & & view.layer.addSublayer(faceLayer)& & & & }& & & & let faceBounds = faceObject.bounds& & & & let viewSize = view.bounds.size& & & & faceLayer?.position = CGPoint(x: viewSize.width * (1 - faceBounds.origin.y - faceBounds.size.height / 2),& & & & & & & & & & & & & & & & & & & y: viewSize.height * (faceBounds.origin.x + faceBounds.size.width / 2))&& & & && & & & faceLayer?.bounds.size = CGSize(width: faceBounds.size.height * viewSize.width,& & & & & & & & & & & & & & & & & & & & height: faceBounds.size.width * viewSize.height)& & & & print(faceBounds.origin)& & & & print(&###&)& & & & print(faceLayer!.position)& & & & print(&###&)& & & & print(faceLayer!.bounds)& & & & */& & }}&之前的Layer相关代码都注释掉,只简单地把识别到的第一张脸记录在VC的属性中。然后修改AVCaptureSession的delegate回调,在录制视频的代码之前,全局滤镜的代码之后,添加脸部处理代码:......if&self.filter&!=&nil&{ & &// 之前做的全局滤镜&& &&self.filter.setValue(outputImage, forKey:&kCIInputImageKey)& & outputImage =&self.filter.outputImage}if&self.faceObject&!=&nil&{ & &// 脸部处理& & outputImage =&self.makeFaceWithCIImage(outputImage, faceObject:&self.faceObject!)}......&我们写了个makeFaceWithImage的方法来专门为脸部应用滤镜,应用的效果是中提到的马赛克效果。makeFaceWithCIImage的方法实现:func&makeFaceWithCIImage(inputImage:&CIImage, faceObject:&AVMetadataFaceObject) -&&CIImage&{& &&var&filter =&CIFilter(name:&&CIPixellate&)& & filter.setValue(inputImage, forKey:&kCIInputImageKey)& &&// 1.& & filter.setValue(max(inputImage.extent().size.width, inputImage.extent().size.height)&/&60, forKey:&kCIInputScaleKey)&& && &&let&fullPixellatedImage = filter.outputImage& &&var&maskImage:&CIImage!& &&let&faceBounds = faceObject.bounds&& && &&// 2.& &&let&centerX = inputImage.extent().size.width&*&(faceBounds.origin.x&+&faceBounds.size.width&/&2)& &&let&centerY = inputImage.extent().size.height&*&(1&-&faceBounds.origin.y&-&faceBounds.size.height&/&2)& &&let&radius = faceBounds.size.width&*&inputImage.extent().size.width&/&2& &&let&radialGradient =&CIFilter(name:&&CIRadialGradient&,& & & & withInputParameters: [& & & & & &&&inputRadius0&&: radius,& & & & & &&&inputRadius1&&: radius&+&1,& & & & & &&&inputColor0&&:&CIColor(red:&0, green:&1, blue:&0, alpha:&1),& & & & & &&&inputColor1&&:&CIColor(red:&0, green:&0, blue:&0, alpha:&0),& & & & & &&kCIInputCenterKey&:&CIVector(x: centerX, y: centerY)& & & & ])& &&let&radialGradientOutputImage = radialGradient.outputImage.imageByCroppingToRect(inputImage.extent())& &&if&maskImage ==&nil&{& & & & maskImage = radialGradientOutputImage& & }&else&{& & & &&println(radialGradientOutputImage)& & & & maskImage =&CIFilter(name:&&CISourceOverCompositing&,& & & & & & withInputParameters: [& & & & & & & &&kCIInputImageKey&: radialGradientOutputImage,& & & & & & & &&kCIInputBackgroundImageKey&: maskImage& & & & & & ]).outputImage& & }&& && &&let&blendFilter =&CIFilter(name:&&CIBlendWithMask&)& & blendFilter.setValue(fullPixellatedImage, forKey:&kCIInputImageKey)& & blendFilter.setValue(inputImage, forKey:&kCIInputBackgroundImageKey)& & blendFilter.setValue(maskImage, forKey:&kCIInputMaskImageKey)&& && &&return&blendFilter.outputImage}&这上面的代码基本是复制里的代码,改的地方只有两处:把马赛克的效果变大,kCIInputScaleKey默认值为0.5,你可以把这行代码注释掉后看效果计算脸部的中心点和半径,计算方法和之前didOutputMetadataObjects这个delegate回调中的计算方法一样,复制过来就行了如果你看到我的上一篇《》的话,这里面的实现方式应该就很清楚了。到此,对脸部的滤镜也处理好了,编译、运行,可以得到这样的结果:我在GitHub上会保持更新。参考资料:1.&2.&3.&
版权声明:如需转载,请注明出处,谢谢!
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:788349次
积分:9734
积分:9734
排名:第711名
原创:120篇
评论:296条
如果有任何问题欢迎随时与我沟通联系
或是CSDN私信
传送门:,如果有什么问题,我会尽力解答,还望共同学习
如需转载,请注明出处,谢谢!
欢迎Star、欢迎PR
文章:24篇
阅读:155374
文章:52篇
阅读:365890
阅读:48067
(3)(5)(8)(1)(5)(2)(4)(5)(5)(14)(6)(1)(3)(2)(3)(1)(5)(2)(1)(8)(8)(4)(1)(4)(17)(9)(1)(1)(1)

我要回帖

更多关于 视频马赛克去除工具 的文章

 

随机推荐