在直播应用中添加Faceu效果

作者: 琨君 | 来源:发表于2016-05-22 22:09 被阅读15078次
       在我写的上篇文章 中,介绍了美颜滤镜的实现原理,已经能够体会到[GPUImage](https://github.com/BradLarson/GPUImage) 的强大。本文将要介绍的Faceu贴纸效果也是基于GPUImage实现的,demo我放在了[GitHub](https://github.com/Guikunzhi/YLFaceuDemo)上。
    

    1.核心原理

       Faceu贴纸效果其实就是在人脸上贴一些图片,同时这些图片是跟随着人脸的位置改变的。如果我们不强调贴图的位置,这就是一个简单的水印需求。
    
    Faceu原理.png
       根据人脸检测的结果动态调整水印贴纸的位置即可实现简单的Faceu效果。
    

    2.水印

       在GPUImage的官方demo中就已经有文字水印的实现:
    
                GPUImageFilter *filter = [[GPUImageFilter alloc] init];
                [self.videoCamera addTarget:filter];
                GPUImageAlphaBlendFilter *blendFilter = [[GPUImageAlphaBlendFilter alloc] init];
                blendFilter.mix = 1.0;
                
                NSDate *startTime = [NSDate date];
                
                UIView *temp = [[UIView alloc] initWithFrame:self.view.frame];
                UILabel *timeLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0, 0.0, 240.0f, 40.0f)];
                timeLabel.font = [UIFont systemFontOfSize:17.0f];
                timeLabel.text = @"Time: 0.0 s";
                timeLabel.textAlignment = UITextAlignmentCenter;
                timeLabel.backgroundColor = [UIColor clearColor];
                timeLabel.textColor = [UIColor whiteColor];
                [temp addSubview:timeLabel];
    
                uiElementInput = [[GPUImageUIElement alloc] initWithView:temp];
                [filter addTarget:blendFilter];
                [uiElementInput addTarget:blendFilter];
                
                [blendFilter addTarget:filterView];
    
                __unsafe_unretained GPUImageUIElement *weakUIElementInput = uiElementInput;
                
                [filter setFrameProcessingCompletionBlock:^(GPUImageOutput * filter, CMTime frameTime){
                    timeLabel.text = [NSString stringWithFormat:@"Time: %f s", -[startTime timeIntervalSinceNow]];
                    [weakUIElementInput update];
                }];
    
       要理解它的实现原理,需要搞懂GPUImageUIElement和GPUImageAlphaBlendFilter。GPUImageUIElement的作用是把一个视图的layer通过CALayer的renderInContext:方法把layer转化为image,然后作为OpenGL的纹理传给GPUImageAlphaBlendFilter。而GPUImageAlphaBlendFilter则是一个两输入的blend filter, 它的第一个输入是摄像头数据,第二个输入则是刚刚提到的GPUImageUIElement的数据,GPUImageAlphaBlendFilter将这两个输入做alpha blend,可以简单的理解为将第二个输入叠加到第一个的上面,更多关于[alpha blend](https://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending)在维基百科上有介绍。下图是整个加水印的过程:
    
    水印.png

    3.人脸检测

       利用CIDetector即可简单的实现人脸检测,首先是CIDetector的初始化:
    
            NSDictionary *detectorOptions = [[NSDictionary alloc] initWithObjectsAndKeys:CIDetectorAccuracyLow, CIDetectorAccuracy, nil];
            _faceDetector = [CIDetector detectorOfType:CIDetectorTypeFace context:nil options:detectorOptions];
    
       然后通过将摄像头数据CMSampleBufferRef转化为CIImage,对CIImage用CIDetector进行人脸检测:
    
        CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
        CFDictionaryRef attachments = CMCopyDictionaryOfAttachments(kCFAllocatorDefault, sampleBuffer, kCMAttachmentMode_ShouldPropagate);
        CIImage *convertedImage = [[CIImage alloc] initWithCVPixelBuffer:pixelBuffer options:(__bridge NSDictionary *)attachments];
        NSArray *features = [self.faceDetector featuresInImage:convertedImage options:imageOptions];
    
       上面得到的features数组里的每个元素都是CIFaceFeature对象,根据它就能计算出人脸的具体位置,从而调整中水印图像的位置,达到图像跟随人脸动的效果。
    
            for ( CIFaceFeature *faceFeature in featureArray) {
                // find the correct position for the square layer within the previewLayer
                // the feature box originates in the bottom left of the video frame.
                // (Bottom right if mirroring is turned on)
                //Update face bounds for iOS Coordinate System
                CGRect faceRect = [faceFeature bounds];
                
                // flip preview width and height
                CGFloat temp = faceRect.size.width;
                faceRect.size.width = faceRect.size.height;
                faceRect.size.height = temp;
                temp = faceRect.origin.x;
                faceRect.origin.x = faceRect.origin.y;
                faceRect.origin.y = temp;
                // scale coordinates so they fit in the preview box, which may be scaled
                CGFloat widthScaleBy = previewBox.size.width / clap.size.height;
                CGFloat heightScaleBy = previewBox.size.height / clap.size.width;
                faceRect.size.width *= widthScaleBy;
                faceRect.size.height *= heightScaleBy;
                faceRect.origin.x *= widthScaleBy;
                faceRect.origin.y *= heightScaleBy;
                
                faceRect = CGRectOffset(faceRect, previewBox.origin.x, previewBox.origin.y);
                
                //mirror
                CGRect rect = CGRectMake(previewBox.size.width - faceRect.origin.x - faceRect.size.width, faceRect.origin.y, faceRect.size.width, faceRect.size.height);
                if (fabs(rect.origin.x - self.faceBounds.origin.x) > 5.0) {
                    self.faceBounds = rect;
                }
            }
    
       上面则是计算人脸位置faceBounds的方法,我们再根据faceBounds来更新水印图像的位置:
    
        __weak typeof (self) weakSelf = self;
        [filter setFrameProcessingCompletionBlock:^(GPUImageOutput *output, CMTime time) {
            __strong typeof (self) strongSelf = weakSelf;
            // update capImageView's frame
            CGRect rect = strongSelf.faceBounds;
            CGSize size = strongSelf.capImageView.frame.size;
            strongSelf.capImageView.frame = CGRectMake(rect.origin.x +  (rect.size.width - size.width)/2, rect.origin.y - size.height, size.width, size.height);
            [strongSelf.element update];
        }];
    

    4.延伸

    • 问题1:上面用的人脸检测是基于CIDetector的,实际实验发现,当人脸在摄像头中捕获不全时,有可能检测不出人脸,也就没法更新水印图像的位置。因此,更加精准、快速、细致的人脸检测是很有必要的,后面我会尝试使用一些其他的人脸检测方法。
    • 问题2:上面的Faceu贴纸效果是静态图像的贴纸效果,如果要做动态效果的Faceu贴纸该怎么处理呢, Gif? CADisplayLink? 这个有待进一步研究,如果有这方面经验的朋友也欢迎在评论区留言,互相交流学习。

    相关文章

      网友评论

      • 0fb0420863cc:求教,我在官方demo中 尝试了GPUImageUIElement,直接使用label 显示的效果很清晰,但是如果在label外层加入view,label显示就会有模糊,楼主有解决办法吗?代码如下:
        UILabel *timeLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0, 0.0, 240.0f, 320.0f)];
        timeLabel.font = [UIFont systemFontOfSize:10.0f];
        timeLabel.text = @"Time: 0.0 s";
        timeLabel.textAlignment = UITextAlignmentCenter;
        timeLabel.backgroundColor = [UIColor clearColor];
        timeLabel.textColor = [UIColor whiteColor];
        timeLabel.backgroundColor = [UIColor redColor];

        #warning 添加
        UIView *tempView = [[UIView alloc] initWithFrame:self.view.bounds];
        tempView.backgroundColor = [UIColor clearColor];
        [tempView addSubview:timeLabel];
        #warning end

        uiElementInput = [[GPUImageUIElement alloc] initWithView:tempView];
      • 纯纯奶油酱:我以前做人脸识别使用的是OpenCV做的
      • 6310ffb20d0c:为啥fix设置为1的时候,水印看不到了。设置为0的时候,水印显示正常,但是视频不见了
      • 幻想无极:市面上的人脸检测是用的是讯飞还是苹果自带的哦
      • juefeiye:你好,我想问问,你研究过Faceu中的表情包功能的原理,如何把录制的视频转换gif的?难道取视频的每一帧的图片,合成的gif吗?
      • 那位小姐:demo 缺少GPUGPUImage/GPUImage.h> 这个库
      • ab930aaf4440:作者你好!我在运行你的demo的时候,会突然崩溃提示是Tried to overrelease a framebuffer, did you forget to call -useNextFrameForImageCapture before using -imageFromCurrentFramebuffer?请问这是为什么呢?还望指教
        Sunday_gao:老哥,问题解决了吗?我也碰到了
      • e0f6992dbc10:这种静态贴纸倒是简单 只要有了人脸定位数据 但是动态的那种就麻烦了 然后还有就是面部五官变形的有没有什么思路呢,例如眼睛很大 脸,嘴各种变化那种,还有那种3D贴纸(眼镜)貌似就更不好办了
        93ee6fbc4941:你们在做这个吗?这块有什么技术方向推荐?
      • 吴霸格07:ld: library not found for -lPods 怎么解决?打开不了
      • 倪灏:博主,你好,我在使用你 Demo 中类似的方法后,在低端手机上出现相机丢帧的现象,不知道你有没有什么解决方法,或者解决的思路呢?
      • akkkk47:我想几个视频跟图片 一起生成一个视频, 这种应该怎么做? 能否给个思路!感谢感谢!
      • brownfeng:这种方式如何跟踪头部转换呢. 如果用metaData方式来做可不可以呢
      • 刚刚2016:我的QQ:564702640 大家可以加我一起交流下faceu
      • 听弦不知音:CIDetector用起来CPU飙到一百多,抖动,贴图效果有时候冻在屏幕上,唉,是CPU占用的问题么?
      • 刚刚2016:在setFrameProcessingCompletionBlock内部更新贴纸位置,会偶然发生crash、dubug发现是因为此block为空,为什么?
        Sunday_gao:发现同样问题
      • evanzhou:请问针对抖动问题有没优化方案呢?
      • 刚刚2016:CPU飙升到百分之一百多,有什么解决方案吗?
      • 刚刚2016:CIDetector对于人脸关键点检测只有脸、左右眼睛、鼻等,对于直播开发中的faceu贴纸无法满足更多的需求,如何使用opencv来做关更多关键点检测呢?

        琨君:这个查看opencv文档就行了,调用不同的api就行
      • Dismon:请问,我用GPUImage 录制视频,然后添加实时的水印,但是发现添加上去的水印好模糊,还有锯齿,这是什么原因呢
        itzhangbao: @Dismon 视频分辨率太低
      • f607b547555d:请问GPUImage 支持webApp 吗
      • kaixinpi:同问,抖动情况有什么解决的办法吗?
        刚刚2016:两位大兄弟抖动问题解决了吗?
        iOSTbag:我的也是 有解决方案么
        Apples8023:@kaixinpi 同问啊,为什么图片一直抖动啊?
      • cd0c13afd056:想请问一下,在实际操作中,我定住手机不动,图片还是会一闪一闪,这是为啥呢?
      • 437aff410919:想请教一下,作者有没那种跟随着表情一起动的动态贴图的做法思路?就是脸部有一个表情 然后嘴巴动的时候贴图也会跟着动,就好像皮肤一样那个效果?
        faceowener:@Apples8023 OpenGL 纹理贴图
        437aff410919:@Apples8023 那种动态贴纸的思路就是OpenGL 来搞的...要算法支持...水比较深..已弃 :fearful:
        Apples8023:@Tosaka乐园 大兄弟,你有这方面的思路了吗?想出来怎么做没,我也有这方面的需求。。。
      • 奔跑的Max:可是应该还有抖动的问题吧?
      • userName:人脸识别 我一直使用AVCaptureMetadataOutput
        littleDad:@faceowener 不错
        faceowener:@userName 效果咋样
      • dulixiaonvzi:我是做的那个动态水印,这个会不会占用内存比较多,以及导致CPU的占用率比较高呢
        faceowener:GPUImage就是不贴纸,CPU占有率也要达到七八十
        刚刚2016:@dulixiaonvzi CPU飙升到百分之一百多。。。。
      • 57eb2448ebc1:关于动态贴纸,我参考了FLAnimatedImage的做法,获取GIF的每一张图片,然后根据视频帧的间隔确定当前需要贴哪一张,再贴到指定位置即可
        琨君:@loook 感谢分享,我再试试
      • 落影loyinglin:赞,学习了。
      • 493741bb515b:我现在就在做录像上面添加动态贴纸,我就是通过gif实现的,通过传一个URL获取CAKeyframeAnimation。将这个animation添加到layer上面,最后添加到View上实现的
        。不知道这样是否合理
        萧城x:@faceowener 有效果图 看下吗?
        faceowener:UIImage+animatedGIF,有这么一个类,支持UIImageView直接做gif,使用方法和普通image一样,我已经实现动态、静态、3D融合性贴纸,直接贴gif方案完全可行; 还有一种方法,采用OpenGL纹理绘制,不使用GPUImage也可以做到,但是比较复杂~
        小黄人写代码:@493741bb515b 我也试过这样,不行,请问你是怎么实现的?可否提供一下代码段?
      • 嗷大喵: :kissing_heart: 就是厉害 这么快就出demo了

      本文标题:在直播应用中添加Faceu效果

      本文链接:https://www.haomeiwen.com/subject/mwnorttx.html