Объединение изображений с помощью класса VNImageHomographicAlignmentObservation

Я пытаюсь объединить два изображения, используя VNImageHomographicAlignmentObservation, в настоящее время я получаю 3D-матрицу, которая выглядит так:

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

Но я не знаю, как использовать эти значения для объединения в одно изображение. Кажется, нет никакой документации о том, что означают эти значения. Я нашел некоторую информацию о матрицах преобразования здесь: Работа с матрицами.

Но пока мне ничего не помогло... Любые предложения?

Мой код:

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) }
    }
}

person Community    schedule 25.07.2018    source источник


Ответы (1)


Эта матрица гомографии H описывает, как спроецировать одно из ваших изображений на плоскость изображения другого изображения. Чтобы преобразовать каждый пиксель в его спроецированное местоположение, вы можете вычислить его спроецированное местоположение x' = H * x, используя однородные координаты ( в основном возьмите координату вашего 2D-изображения, добавьте 1.0 в качестве третьего компонента, примените матрицу H и вернитесь к 2D, разделив 3-й компонент результата).

Самый эффективный способ сделать это для каждого пикселя — записать это умножение матриц в однородном пространстве с помощью CoreImage. CoreImage предлагает несколько типов ядра шейдера: CIColorKernel, CIWarpKernel и CIKernel. Для этой задачи нам нужно преобразовать только расположение каждого пикселя, поэтому CIWarpKernel — это то, что вам нужно. Используя Core Image Shading Language, это будет выглядеть следующим образом:

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
    }
    """
)

Обратите внимание, что шейдеру требуется mat3 с именем homography, что является языковым эквивалентом затенения матрицы 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)

Теперь вы можете создать деформированную версию вашего плавающего изображения:

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 использует это для применения шейдера к частям изображения блок за блоком, чтобы он мог обрабатывать огромные изображения. (См. Создание пользовательских фильтров в документации Apple). Быстрый хак был бы всегда return CGRect.infinite здесь, но тогда CoreImage не может делать никакой блочной магии.

И, наконец, создайте составное изображение из эталонного изображения и искаженного изображения:

let ciReferenceImage = CIImage(image: referenceImage)
let ciResultImage = ciWarpedImage.composited(over: ciReferenceImage)
let resultImage = UIImage(ciImage: ciResultImage)
person Carsten Haubold    schedule 31.07.2018
comment
Возможно, вам нужно будет сделать некоторые преобразования координатного пространства до этого, так как мои деформированные изображения совсем не совпадают с моими обычными. - person joshLor; 22.11.2018