Transforming an image in three dimensions
Create and use a projective transformation to apply a perspective warp to an image.
Overview
The vImage library provides a set of functions that allow you create projective-transformation structures and apply them to images. The following image shows the effect of a projective transformation that derives from the four corner points of a perspective distorted rectangle. The image demonstrates how the projective transformation warps the image to match the empty billboard rectangle.
[Image]
Create the vImage buffers that represent the source images
Create a vImage_CGImageFormat structure that describes the four-channel, 8-bit-per-channel images that this example uses.
var format = vImage_CGImageFormat(
bitsPerComponent: 8,
bitsPerPixel: 8 * 4,
colorSpace: CGColorSpaceCreateDeviceRGB(),
bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.noneSkipFirst.rawValue))!Use separate buffers to store the background and foreground images.
let backgroundImage = imageLiteral(resourceName: "background.jpg").cgImage(
forProposedRect: nil,
context: nil,
hints: nil)!
let backgroundBuffer = try vImage.PixelBuffer<vImage.Interleaved8x4>(
cgImage: backgroundImage,
cgImageFormat: &format)
let foregroundImage = imageLiteral(resourceName: "foreground.jpg").cgImage(
forProposedRect: nil,
context: nil,
hints: nil)!
let foregroundBuffer = try vImage.PixelBuffer<vImage.Interleaved8x4>(
cgImage: foregroundImage,
cgImageFormat: &format)
Because the perspective warp doesn’t work in place (that is, the source and destination buffers need to point to different underlying memory), create a third destination buffer.
let warpedBuffer = vImage.PixelBuffer<vImage.Interleaved8x4>(
size: backgroundBuffer.size)Use the Vision framework to find the rectangle corner points
The Vision framework allows you to find the corner points of the target rectangle. The code below is a simplified example. See Detecting Objects in Still Images for additional information.
let imageRequestHandler = VNImageRequestHandler(cgImage: backgroundImage,
options: [:])
let requests: [VNRequest] = [VNDetectRectanglesRequest()]
try imageRequestHandler.perform(requests)
guard let observation = requests.first?.results?.first as? VNRectangleObservation else {
throw vImage.Error.internalError
}On return, observation contains the four corner points of the target rectangle.
Create the source and destination points
Use the values in the Vision VNRectangleObservation instance to create a set of points for the vImage warp function. The Vision framework returns normalized coordinates in the range 0...1 with 0 at the bottom-left of the image. The vImage warp function requires coordinates that represent pixel values with 0 at the top-left of the image.
The destination points refer to the corner points of the target rectangle.
typealias vImagePoint = (Float, Float)
let dstPoints: [vImagePoint] = {
func scalePoint(_ point: CGPoint) -> vImagePoint {
return (Float(point.x) * Float(backgroundImage.width),
Float(point.y) * Float(backgroundImage.height))
}
let dstTopLeft: vImagePoint = scalePoint(observation.topLeft)
let dstTopRight: vImagePoint = scalePoint(observation.topRight)
let dstBottomLeft: vImagePoint = scalePoint(observation.bottomLeft)
let dstBottomRight: vImagePoint = scalePoint(observation.bottomRight)
return [dstTopLeft, dstTopRight, dstBottomLeft, dstBottomRight]
}()
The source points refer to the bounding box of the foreground image.
let srcPoints: [vImagePoint] = {
let foregroundWidth = Float(foregroundImage.width)
let foregroundHeight = Float(foregroundImage.height)
let srcTopLeft: (Float, Float) = (0, foregroundHeight)
let srcTopRight: (Float, Float) = (foregroundWidth, foregroundHeight)
let srcBottomLeft: (Float, Float) = (0, 0)
let srcBottomRight: (Float, Float) = (foregroundWidth, 0)
return [srcTopLeft, srcTopRight, srcBottomLeft, srcBottomRight]
}()
Calculate the projective transformation
Call vImageGetPerspectiveWarp(_:_:_:_:) to calculate the projective transformation to translate the foreground image to the target rectangle.
var transform = vImage_PerpsectiveTransform()
vImageGetPerspectiveWarp(srcPoints, dstPoints, &transform, 0)
On return, transform contains the projective transformation to warp the source points to the destination points.
Apply the perspective warp
Call vImagePerspectiveWarp_ARGB8888(_:_:_:_:_:_:_:) to warp the foreground image to the destination rectangle’s shape and position.
foregroundBuffer.withUnsafePointerToVImageBuffer { src in
warpedBuffer.withUnsafePointerToVImageBuffer { dst in
var bgColor: [UInt8] = [0, 0, 0, 0]
vImagePerspectiveWarp_ARGB8888(
src, dst, nil,
&transform,
vImage_WarpInterpolation(kvImageInterpolationLinear),
&bgColor,
vImage_Flags(kvImageBackgroundColorFill))
}
}
Composite the warped foreground over the background image
Finally, composite the warped foreground image over the background image.
backgroundBuffer.alphaComposite(.nonpremultiplied,
topLayer: warpedBuffer,
destination: backgroundBuffer)
let result = backgroundBuffer.makeCGImage(cgImageFormat: format)