在 CMSampleBuffer 中获取非矩形轮廓内的 RGB 平均值的最快方法

Fastest way to get the RGB average inside of a non-rectangular contour in the CMSampleBuffer

我正在尝试从 AVCaptureVideoDataOutput 获取在帧中面部标志区域(将其视为面部轮廓)上生成的非矩形多边(闭合)轮廓内的 RGB 平均值。我目前有以下代码,

        let landmarkPath = CGMutablePath()
        let landmarkPathPoints = landmark.normalizedPoints
            .map({ landmarkPoint in
                CGPoint(
                    x: landmarkPoint.y * faceBoundingBox.height + faceBoundingBox.origin.x,
                    y: landmarkPoint.x * faceBoundingBox.width + faceBoundingBox.origin.y)
            })
        landmarkPath.addLines(between: landmarkPathPoints)
        landmarkPath.closeSubpath()

        let averageFilter = CIFilter(name: "CIAreaAverage", parameters: [kCIInputImageKey: frame, kCIInputExtentKey: landmarkPath])!
        let outputImage = averageFilter.outputImage!

但是,它目前抛出 *** Terminating app due to uncaught exception 'NSInvalidArgumentException',原因:'-[__NSCFType CGRectValue]: unrecognized selector sent to instance 0x283a57a80' terminating with uncaught exception of输入 NSException。我怀疑这是因为 kCIInputExtentKey 不是一个合适的 CIVector 矩形对象。有没有什么办法解决这一问题?如何为 CIAreaAverage 过滤器定义非矩形区域?如果不可能,获得整个感兴趣区域的平均 RGB 的最有效方法是什么?

提前致谢!

如果您可以使轮廓外的所有像素透明,那么您可以使用 CIKmeans 过滤器,其中 inputCount 等于 1 并且 inputExtent 设置为获取轮廓内区域平均颜色的帧(过滤器的输出将包含 1 像素图像,像素的颜色就是您要查找的颜色)。

现在,要使轮廓外的所有像素透明,您可以这样做:

  1. 创建蒙版图像,但将轮廓内部的所有像素设置为白色,外部设置为黑色(将背景设置为黑色并用白色填充路径)。
  2. 使用 CIBlendWithMask 过滤器,其中:
    • inputBackgroundImage是完全透明(清晰)的图像
    • inputImage为原帧
    • inputMaskImage 是你上面创建的遮罩

该过滤器的输出将为您提供轮廓外所有像素完全透明的图像。现在您可以按照开头所述使用 CIKMeans 过滤器。

顺便说一句,如果您想使用 230 个过滤器中的每一个,请查看此应用程序:https://apps.apple.com/us/app/filter-magic/id1594986951

更新:

CIFilters 只能与 CIImages 一起使用。所以蒙版图像也必须是 CIImage。 一种方法是从包含遮罩的 CAShapeLayer 创建一个 CGImage,然后从中创建 CIImage。代码如下所示:

// Create the closed contour path from points
let path = CGMutablePath()
path.addLines(between: points)
path.closeSubpath()

// Create CAShapeLayer matching the dimensions of the input frame
let layer = CAShapeLayer()
layer.frame = frame.extent // Assuming frame is the input CIImage with the face

// Set background and fill color and set the path
layer.fillColor = UIColor.white.cgColor
layer.backgroundColor = UIColor.black.cgColor
layer.path = path

// Render the contents of the CAShapeLayer to CGImage
let width = Int(layer.bounds.width)
let height = Int(layer.bounds.height)
let context = CGContext(data: nil,
                        width: width,
                        height: height,
                        bitsPerComponent: 8,
                        bytesPerRow: 4 * width,
                        space: CGColorSpaceCreateDeviceRGB(),
                        bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)!

layer.render(in: context)
let cgImage = context.makeImage()!

// Create CIImage out of it
let ciImage = CIImage(cgImage: cgImage)

// To create clear background CIImage just do this:
let bgImage = CIImage.clear.cropped(to: frame.extent)