swift - 使用 "VNImageHomographicAlignmentObservation"类合并图像

标签 swift matrix apple-vision

我正在尝试使用 VNImageHomographicAlignmentObservation 合并两个图像,我目前得到一个如下所示的 3d 矩阵:

simd_float3x3([ [0.99229, -0.00451023, -4.32607e-07)],  
                [0.00431724,0.993118, 2.38839e-07)],   
                [-72.2425, -67.9966, 0.999288)]], )

但我不知道如何使用这些值来合并成一张图像。似乎没有关于这些值甚至意味着什么的任何文档。我在这里找到了一些关于变换矩阵的信息:Working with matrices .

但到目前为止,没有任何其他帮助我...有什么建议吗?

我的代码:

func setup() {

    let floatingImage = UIImage(named:"DJI_0333")!
    let referenceImage = UIImage(named: "DJI_0327")!

    let request = VNHomographicImageRegistrationRequest(targetedCGImage: floatingImage.cgImage!, options: [:])

    let handler = VNSequenceRequestHandler()
    try! handler.perform([request], on: referenceImage.cgImage!)

    if let results = request.results as? [VNImageHomographicAlignmentObservation] {
        print("Perspective warp found: \(results.count)")
        results.forEach { observation in
        // A matrix with 3 rows and 3 columns.                         
        let matrix = observation.warpTransform
        print(matrix) }
    }
}

最佳答案

这个单应矩阵 H 描述了如何将您的一个图像投影到另一个图像的图像平面上。要将每个像素转换为其投影位置,您可以使用 homogeneous coordinates 计算其投影位置 x' = H * x (基本上采用 2D 图像坐标,添加 1.0 作为第三个分量,应用矩阵 H,然后通过除以结果的第三个分量返回到 2D)。

对每个像素执行此操作的最有效方法是使用 CoreImage 在齐次空间中编写此矩阵乘法。 CoreImage 提供多种着色器内核类型:CIColorKernelCIWarpKernelCIKernel。对于此任务,我们只想转换每个像素的位置,因此您需要一个 CIWarpKernel。使用核心图像着色语言,看起来如下:

import CoreImage
let warpKernel = CIWarpKernel(source:
    """
    kernel vec2 warp(mat3 homography)
    {
        vec3 homogen_in = vec3(destCoord().x, destCoord().y, 1.0); // create homogeneous coord
        vec3 homogen_out = homography * homogen_in; // transform by homography
        return homogen_out.xy / homogen_out.z; // back to normal 2D coordinate
    }
    """
)

请注意,着色器需要一个名为 homographymat3,它是 simd_float3x3 矩阵 H。调用着色器时,矩阵应存储在 CIVector 中,要对其进行转换,请使用:

let (col0, col1, col2) = yourHomography.columns
let homographyCIVector = CIVector(values:[CGFloat(col0.x), CGFloat(col0.y), CGFloat(col0.z),
                                             CGFloat(col1.x), CGFloat(col1.y), CGFloat(col1.z),
                                             CGFloat(col2.x), CGFloat(col2.y), CGFloat(col2.z)], count: 9)

当您将 CIWarpKernel 应用于图像时,您必须告诉 CoreImage 输出应该有多大。要合并变形图像和引用图像,输出应该足够大以覆盖整个投影原始图像。我们可以通过将单应性应用于图像矩形的每个角来计算投影图像的大小(这次在 Swift 中,CoreImage 将此矩形称为 extent):

/**
 * Convert a 2D point to a homogeneous coordinate, transform by the provided homography,
 * and convert back to a non-homogeneous 2D point.
 */
func transform(_ point:CGPoint, by homography:matrix_float3x3) -> CGPoint
{
  let inputPoint = float3(Float(point.x), Float(point.y), 1.0)
  var outputPoint = homography * inputPoint
  outputPoint /= outputPoint.z
  return CGPoint(x:CGFloat(outputPoint.x), y:CGFloat(outputPoint.y))
}

func computeExtentAfterTransforming(_ extent:CGRect, with homography:matrix_float3x3) -> CGRect
{
  let points = [transform(extent.origin, by: homography),
                transform(CGPoint(x: extent.origin.x + extent.width, y:extent.origin.y), by: homography),
                transform(CGPoint(x: extent.origin.x + extent.width, y:extent.origin.y + extent.height), by: homography),
                transform(CGPoint(x: extent.origin.x, y:extent.origin.y + extent.height), by: homography)]

  var (xmin, xmax, ymin, ymax) = (points[0].x, points[0].x, points[0].y, points[0].y)
  points.forEach { p in
    xmin = min(xmin, p.x)
    xmax = max(xmax, p.x)
    ymin = min(ymin, p.y)
    ymax = max(ymax, p.y)
  }
  let result = CGRect(x: xmin, y:ymin, width: xmax-xmin, height: ymax-ymin)
  return result
}

let warpedExtent = computeExtentAfterTransforming(ciFloatingImage.extent, with: homography.inverse)
let outputExtent = warpedExtent.union(ciFloatingImage.extent)

现在您可以创建 float 图像的扭曲版本:

let ciFloatingImage = CIImage(image: floatingImage)
let ciWarpedImage = warpKernel.apply(extent: outputExtent, roiCallback:
    {
        (index, rect) in
        return computeExtentAfterTransforming(rect, with: homography.inverse)
    },
    image: inputImage,
    arguments: [homographyCIVector])!

roiCallback 用于告诉 CoreImage 需要输入图像的哪一部分来计算输出的特定部分。 CoreImage 使用它来将着色器逐 block 应用于图像的某些部分,这样它就可以处理巨大的图像。 (请参阅 Apple 文档中的 Creating Custom Filters)。一个快速的破解方法是始终在此处返回 CGRect.infinite,但 CoreImage 无法执行任何 block 级魔法。

最后,创建引用图像和变形图像的合成图像:

let ciReferenceImage = CIImage(image: referenceImage)
let ciResultImage = ciWarpedImage.composited(over: ciReferenceImage)
let resultImage = UIImage(ciImage: ciResultImage)

关于swift - 使用 "VNImageHomographicAlignmentObservation"类合并图像,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51527754/

相关文章:

ios - 向上舍入到字典中最接近的整数键值 [Swift/iOS]

ios - 以编程方式获取 Swift 中当前播放轨道的名称和艺术家

ios - Swift 2 或 3 中的 Google Analytics 问题

arrays - MATLAB 2013a : sum + squeeze dimension inconsistencies

ios - 无法识别已登录的 Facebook 用户

python - 如何在 Python 中使用循环中的收敛标准来生成矩阵

android - 拖动、缩放或旋转 ImageView

swift - 如何在 Apple Vision 检测到的脸部上应用 3D 模型 "NO AR"

swift - 如何在 Keras 中生成 class_labels.txt 以用于 CoreML 模型?

ios - Swift - 使用 CoreML 删除图像背景