Skip to content

Instantly share code, notes, and snippets.

@lukevanin
Created December 4, 2022 14:05
Show Gist options
  • Save lukevanin/64fdffc2f81759b48304ece4aa75c9b4 to your computer and use it in GitHub Desktop.
Save lukevanin/64fdffc2f81759b48304ece4aa75c9b4 to your computer and use it in GitHub Desktop.
Returns an image with vector field lines indicating the optical flow between two images.
func opticalFlow(referenceImage: UIImage, floatingImage: UIImage) -> UIImage? {
let request = VNGenerateOpticalFlowRequest(
targetedCGImage: referenceImage.cgImage!
)
request.computationAccuracy = .high
request.outputPixelFormat = kCVPixelFormatType_TwoComponent32Float
imageRequestHandler = VNImageRequestHandler(
cgImage: floatingImage.cgImage!
)
try! imageRequestHandler?.perform([request])
guard let results = request.results else {
print("No results")
return nil
}
guard let result = results.first else {
print("No result")
return nil
}
print("results", result)
let buffer = result.pixelBuffer
CVPixelBufferLockBaseAddress(buffer, .readOnly)
defer {
CVPixelBufferUnlockBaseAddress(buffer, .readOnly)
}
let pixelFormat = CVPixelBufferGetPixelFormatType(buffer)
precondition(pixelFormat == kCVPixelFormatType_TwoComponent32Float)
let bytesPerComponent = MemoryLayout<simd_packed_float2>.stride
precondition(bytesPerComponent == (4 * 2))
let isPlanar = CVPixelBufferIsPlanar(buffer)
precondition(isPlanar == false)
let width = CVPixelBufferGetWidth(buffer)
let height = CVPixelBufferGetHeight(buffer)
let bytesPerRow = CVPixelBufferGetBytesPerRow(buffer)
let bytes = bytesPerRow * height
let address = CVPixelBufferGetBaseAddress(buffer)!
let size = CGSize(width: width, height: height)
let bounds = CGRect(origin: .zero, size: size)
let grid = 50
let scale = Float32(1)
let backgroundColor = UIColor.black.withAlphaComponent(0.3)
let lineColor = UIColor.systemPink
let imageRenderer = UIGraphicsImageRenderer(bounds: bounds)
let image = imageRenderer.image { context in
let cgContext = context.cgContext
cgContext.saveGState()
cgContext.scaleBy(x: 1, y: -1)
cgContext.translateBy(x: 0, y: -bounds.height)
cgContext.draw(referenceImage.cgImage!, in: bounds)
cgContext.restoreGState()
cgContext.setFillColor(backgroundColor.cgColor)
cgContext.fill(bounds)
let sx = width / grid
let sy = height / grid
for y in 0 ..< sy {
for x in 0 ..< sx {
let dx = x * grid
let dy = y * grid
let origin = simd_packed_float2(x: Float32(dx), y: Float32(dy))
let offset = (dy * bytesPerRow) + (dx * bytesPerComponent)
let pointer = address.advanced(by: offset).assumingMemoryBound(to: simd_packed_float2.self)
let direction = pointer.pointee
let vector = direction * scale
let length = simd_length(direction)
guard length < 1000 else {
continue
}
let start = CGPoint(origin)
let end = CGPoint(origin + vector)
cgContext.move(to: start)
cgContext.addLine(to: end)
let percent = Double((y * sx) + x) / Double(sx * sy)
print(String(format: "%0.3f%% %0.3f %0.3f %0.3f", percent * 100, direction.x, direction.y, length))
}
}
cgContext.setLineWidth(3)
cgContext.setStrokeColor(lineColor.cgColor)
cgContext.strokePath()
}
return image
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment