这个单应性矩阵H
描述如何将一个图像投影到另一图像的图像平面上。要将每个像素变换到其投影位置,您可以计算其投影位置x' = H * x
using 齐次坐标(基本上采用 2D 图像坐标,添加 1.0 作为第三个分量,应用矩阵H
,然后除以结果的第三个分量返回二维)。
对每个像素执行此操作的最有效方法是使用以下命令在均匀空间中编写此矩阵乘法核心图像. 核心图像提供多种着色器内核类型:CIColorKernel
, CIWarpKernel
and CIKernel
。对于这个任务,我们只想变换每个像素的位置,所以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
}
"""
)
请注意,着色器需要一个mat3
called homography
,这相当于着色语言simd_float3x3
matrix 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
对于图像,你必须告诉核心图像输出应该有多大。要合并扭曲图像和参考图像,输出应该足够大以覆盖整个投影and原始图像。我们可以通过将单应性应用到图像矩形的每个角来计算投影图像的大小(这次在 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])!
The roiCallback
有什么可说的核心图像需要输入图像的哪一部分来计算输出的某一部分。 CoreImage 使用它来逐块地将着色器应用于图像的各个部分,以便它可以处理巨大的图像。 (看创建自定义过滤器在苹果的文档中)。一个快速的破解方法是始终return CGRect.infinite
在这里,但是 CoreImage 无法执行任何块方面的魔法。
最后,创建参考图像和扭曲图像的合成图像:
let ciReferenceImage = CIImage(image: referenceImage)
let ciResultImage = ciWarpedImage.composited(over: ciReferenceImage)
let resultImage = UIImage(ciImage: ciResultImage)