Jun 22

小记:iOS15的personSegmentationFilter正确使用方法

Lrdcq , 2021/06/22 01:30 , 程序 , 閱讀(1926) , Via 本站原創
WWDC20221讲到iOS15在相机部分新增了不少识别相关的功能,一种有一项就是人物聚焦——对应背景模糊。同时,我们排查SDK变化时,注意到CIFilter新增了一个personSegmentationFilter,看起来就是对应这个功能的实现了。不过翻阅文档https://developer.apple.com/documentation/coreimage/cifilter/3750390-personsegmentationfilter?language=objc,苹果真是啥都没有讲。尝试了之后,我得到了能实现人物聚焦(背景虚化)的效果的CIFilter使用方式。

过程

首先提供的原图是如下,刚才随手拍的同事:
點擊在新視窗中瀏覽此圖片

1. 第一步即通过personSegmentationFilter处理得到识别图像,并缩放到和原图相同的大小。
        CIFilter<CIPersonSegmentation> *personFilter = [CIFilter personSegmentationFilter];
        personFilter.inputImage = sourceImage;
        personFilter.qualityLevel = 128;
    
        CIFilter<CILanczosScaleTransform> *personResizeFilter = [CIFilter lanczosScaleTransformFilter];
        personResizeFilter.inputImage = personFilter.outputImage;
        personResizeFilter.scale = sourceImage.extent.size.height / personResizeFilter.inputImage.extent.size.height;
        personResizeFilter.aspectRatio = sourceImage.extent.size.width / (personResizeFilter.inputImage.extent.size.width * personResizeFilter.scale);
    
        CIImage *personResizeImg = personResizeFilter.outputImage;

这里用了两个CIFilter:

a. 其中第一个即personSegmentationFilter,它有一个参数是qualityLevel,没有文档,尝试后发现这个参数控制的识别精度,而识别精度决定了输出的识别图像的分辨率,一张普通的相册图片,按512这样的值去识别甚至能得到1w+像素的结果,当然这个值也影响到识别速度了。多次尝试后,128应该是一个精度足够高的合适的值。

b. 既然第一步说personSegmentationFilter丢出来的图像尺寸是质量决定的,因此补充一个lanczosScaleTransformFilter,把输出的识别图像resize到源氏图像大小。

结果得到:
點擊在新視窗中瀏覽此圖片

2. 第二步,通过maskedVariableBlurFilter,对非人物区域进行模糊。这个filter可以根据一个mask入参来决定模糊的区域与大小,文档:https://developer.apple.com/library/archive/documentation/GraphicsImaging/Reference/CoreImageFilterReference/index.html#//apple_ref/doc/filter/ci/CIMaskedVariableBlur

因此涉及到2步:mask生成与模糊执行。

a. 根据如上文档,mask是一个灰度图,其中黑色代表不进行模糊,白色是100%模糊。这里使用一个简单的CIKernel来实现personSegmentationFilter转换得到mask:
        NSString *maskKernelString =
            @"kernel vec4 color( __sample s ) { \n"
            @"  float r = 1.0 - s.r; \n"
            @"  return vec4(r, r, r, 1.0); \n"
            @"}";
        CIKernel *maskKernel = [CIKernel kernelWithString:maskKernelString];
        CIImage *maskImage = [maskKernel applyWithExtent:personResizeImg.extent roiCallback:^CGRect(int index, CGRect rect) {
                return personResizeImg.extent;
            } arguments:@[personResizeImg]];

其伪shader逻辑其实就是把红色图转换为反转的红色值的灰度图即可。
點擊在新視窗中瀏覽此圖片

b.然后执行maskedVariableBlurFilter即可
        CIFilter<CIMaskedVariableBlur> *peopleImgFilter = [CIFilter maskedVariableBlurFilter];
        peopleImgFilter.inputImage = ciImage;
        peopleImgFilter.mask = maskImage;
        peopleImgFilter.radius = 10.0;

點擊在新視窗中瀏覽此圖片

3. 第三步有一定后期处理。问题包括:a. maskedVariableBlurFilter会得到一张区域拓展的带模糊延展区域的图像,需要裁减,另外模糊会导致图片边缘半透明。
因此todo明确了:在图像下面垫上原始图像,再裁减到原始图像大小即可:
        CIFilter<CICompositeOperation> *peopleAddedFilter = [CIFilter sourceOverCompositingFilter];
        peopleAddedFilter.inputImage = peopleImgFilter.outputImage;
        peopleAddedFilter.backgroundImage = sourceImage;
        
        CIImage *peopleBlurImg = [peopleAddedFilter.outputImage imageByCroppingToRect:CGRectMake(0, 0, sourceImage.extent.size.width, sourceImage.extent.size.height)];

这样就得到最终的效果图了。
點擊在新視窗中瀏覽此圖片

完整流水线为:
點擊在新視窗中瀏覽此圖片

实际效果判断

1. 经过多次各种网络图片与自己拍摄的图片测试,只要分辨率足够(人物大小在500px+),有相当高的识别率。不过人物身上的物品识别还不够准确,比如上图同事的眼镜被裁减掉了。而且配合模糊效果,由于识别边界过于清晰,反倒是不自然,有可能需要对mask再做一次模糊。

2. 虽然是gpu执行的,如上整个过程性能还是比较慢,肯定不建议实时使用。目前多次测速,每次这个流水线执行时间约0.045s左右,再包括流的输入输出处理,肯定远超过0.05s了,也就是说如果实时使用,fps至少低于20,体验还是比较差的。
logo