Эта матрица гомографии 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