CapturedImage in ARFrame to portrait/vertical
There's an API for that.
ARFrame.
displayTransform(for:viewportSize:)
gives you a transform matrix that rotates from the image's sensor-native orientation to whatever UI orientation you specify, and provides a scale factor to fit a viewport whose aspect ratio doesn't necessarily match that of the camera.
You can then use that transform in whatever display pipeline you're working with to show the rotated image. For example:
check the Xcode project template for a Metal-based ARKit app to see how they feed that transform matrix to a vertex shader.
create a Core Image
CIImage
from the pixel buffer, then usetransformed(by:)
to get a rotated image, which you can then convert toUIImage
and display inUIImageView
.
CapturedImage in ARFrame to portrait/vertical
There's an API for that.
ARFrame.
displayTransform(for:viewportSize:)
gives you a transform matrix that rotates from the image's sensor-native orientation to whatever UI orientation you specify, and provides a scale factor to fit a viewport whose aspect ratio doesn't necessarily match that of the camera.
You can then use that transform in whatever display pipeline you're working with to show the rotated image. For example:
check the Xcode project template for a Metal-based ARKit app to see how they feed that transform matrix to a vertex shader.
create a Core Image
CIImage
from the pixel buffer, then usetransformed(by:)
to get a rotated image, which you can then convert toUIImage
and display inUIImageView
.
iOS how to correctly handle orientation when capturing video using AVAssetWriter
Video orientation is handled by the AVAssetWriterInput.transform
, looks like the getVideoTransform()
implementation is not correct - CGAffineTransform
expects the rotation angle to be in radians, so need to change to something like this:
private func getVideoTransform() -> CGAffineTransform {
switch UIDevice.current.orientation {
case .portrait:
return .identity
case .portraitUpsideDown:
return CGAffineTransform(rotationAngle: .pi)
case .landscapeLeft:
return CGAffineTransform(rotationAngle: .pi/2)
case .landscapeRight:
return CGAffineTransform(rotationAngle: -.pi/2)
default:
return .identity
}
}
From Apple Technical Q&A:
https://developer.apple.com/library/archive/qa/qa1744/_index.html
If you are using an
AVAssetWriter
object to write a movie file, you
can use thetransform
property of the associatedAVAssetWriterInput
to
specify the output file orientation. This will write a display
transform property into the output file as the preferred
transformation of the visual media data for display purposes. See the
AVAssetWriterInput.h
interface file for the details.
iOS ARKit how to save ARFrame .capturedImage to file?
There are a couple of issues I encountered when trying to pull a screenshot from an ARFrame:
- The
CVPixelBuffer
underlined in the ARFrame is rotated to the left, which means that you will need to rotate the image. - The frame itself is much wider than what you see in the camera. This is done because AR needs a wider range of image to be able to see beyond what you see for it's scene.
E.G:
This is an image taken from a raw ARFrame
The following below may assist you to handle those two steps easily:
https://github.com/Rightpoint/ARKit-CoreML/blob/master/Library/UIImage%2BUtilities.swift
The problem with these are that this code uses UIGraphicsBeginImageContextWithOptions
, which is heavy util.
But if performance are not your main issue(Let's say for an AR Experience) these might be suffice.
If performance is an issue, you can do the mathematical calculation to understand the conversion you will need to do, and simply cut the image needed from the original picture and rotate that.
Hope this will assist you!
Convert ARFrame's captured image to UIImage orientation issue
1. Is there another way to convert the image with the correct orientation?
You may try to use snapshot()
of ARSCNView
(inherited from SCNView
), which:
Draws the contents of the view and returns them as a new image object
so if you have an object like:
@IBOutlet var arkitSceneView:ARSCNView!
you only need to do so:
let imageFromArkitScene:UIImage? = arkitSceneView.snapshot()
2. Why does this happen?
It's because the CVPixelBuffer
comes from ARFrame
, which is :
captured (continuously) from the device camera, by the running AR session.
Well, since the camera orientation does not change with the rotation of the device (they are separate), to be able to adjust the orientation of your frame to the current view, you should re-orient the image captured from your camera applying the affine transform extracted with displayTransform(for:viewportSize:)
:
Returns an affine transform for converting between normalized image coordinates and a coordinate space appropriate for rendering the camera image onscreen.
you may find good documentation here, usage example:
let orient = UIApplication.shared.statusBarOrientation
let viewportSize = yourSceneView.bounds.size
let transform = frame.displayTransform(for: orient, viewportSize: viewportSize).inverted()
var finalImage = CIImage(cvPixelBuffer: pixelBuffer).transformed(by: transform)
Stream a display, transform it, and display it in a window?
It looks like you can do most of the work with Core Image:
- The handler of the
CGDisplayStream
gives you anIOSurface
. You can wrap that inside aCIImage
withCIImage(ioSurface:)
. - Apply any (or multiple)
CGAffineTransform
to theCIImage
usingimage.transformed(by:)
. - Use a
CIContext
to render the resulting image into anMTKView
. Maybe you can get some inspiration from my example here.
Note: I'm not 100% sure if Core Image would properly retain the IOSurface
for you when you wrap it in a CIImage
. You should double-check if you still need to perform any of the actions described in the handler documentation.
How to rotate JPEG images based on the orientation metadata?
If you want to rotate your images, I would suggest to use the metadata extractor library http://code.google.com/p/metadata-extractor/. You can get the image information with the following code:
// Inner class containing image information
public static class ImageInformation {
public final int orientation;
public final int width;
public final int height;
public ImageInformation(int orientation, int width, int height) {
this.orientation = orientation;
this.width = width;
this.height = height;
}
public String toString() {
return String.format("%dx%d,%d", this.width, this.height, this.orientation);
}
}
public static ImageInformation readImageInformation(File imageFile) throws IOException, MetadataException, ImageProcessingException {
Metadata metadata = ImageMetadataReader.readMetadata(imageFile);
Directory directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
JpegDirectory jpegDirectory = metadata.getFirstDirectoryOfType(JpegDirectory.class);
int orientation = 1;
try {
orientation = directory.getInt(ExifIFD0Directory.TAG_ORIENTATION);
} catch (MetadataException me) {
logger.warn("Could not get orientation");
}
int width = jpegDirectory.getImageWidth();
int height = jpegDirectory.getImageHeight();
return new ImageInformation(orientation, width, height);
}
Then given the orientation you retrieve, you can rotate and/or flip the image to the right orientation. The Affine transform for the EXIF orientation is given by the following method:
// Look at http://chunter.tistory.com/143 for information
public static AffineTransform getExifTransformation(ImageInformation info) {
AffineTransform t = new AffineTransform();
switch (info.orientation) {
case 1:
break;
case 2: // Flip X
t.scale(-1.0, 1.0);
t.translate(-info.width, 0);
break;
case 3: // PI rotation
t.translate(info.width, info.height);
t.rotate(Math.PI);
break;
case 4: // Flip Y
t.scale(1.0, -1.0);
t.translate(0, -info.height);
break;
case 5: // - PI/2 and Flip X
t.rotate(-Math.PI / 2);
t.scale(-1.0, 1.0);
break;
case 6: // -PI/2 and -width
t.translate(info.height, 0);
t.rotate(Math.PI / 2);
break;
case 7: // PI/2 and Flip
t.scale(-1.0, 1.0);
t.translate(-info.height, 0);
t.translate(0, info.width);
t.rotate( 3 * Math.PI / 2);
break;
case 8: // PI / 2
t.translate(0, info.width);
t.rotate( 3 * Math.PI / 2);
break;
}
return t;
}
The rotation of the image would be done by the following method:
public static BufferedImage transformImage(BufferedImage image, AffineTransform transform) throws Exception {
AffineTransformOp op = new AffineTransformOp(transform, AffineTransformOp.TYPE_BICUBIC);
BufferedImage destinationImage = op.createCompatibleDestImage(image, (image.getType() == BufferedImage.TYPE_BYTE_GRAY) ? image.getColorModel() : null );
Graphics2D g = destinationImage.createGraphics();
g.setBackground(Color.WHITE);
g.clearRect(0, 0, destinationImage.getWidth(), destinationImage.getHeight());
destinationImage = op.filter(image, destinationImage);
return destinationImage;
}
In a server environment, don't forget to run with -Djava.awt.headless=true
Related Topics
Extract Reality Composer Scene for Arquicklook
Core Data Appears to Lose Data After Xcode Upgrade
How to Increment a Swift Int Enumeration
What's The Difference Between Nsusernotification and Unusernotification in Macos
Setadvertisertrackingenabled Is Not Found in Settings Class of Fbsdk
Why Is This Predicate Format Being Turned into '= Nil'
Is There a Simple Way to Test If You Match One of a Set of Enumerations
Core Data with Swiftui Mvvm Feedback
Check If Avaudioplayer Is Playing
Swift Set UIbutton Setbordercolor in Storyboard
Dynamictype of Optional Chaining Not The Same as Assignment
Dynamically Set Properties from Dictionary<String, Any> in Swift
Decoding Different Type with and Without Array
Swiftui - How to Change The Button's Image on Click
Swiftsupport Folder Not Included in The IPA When Generating Build from Script
How to Make UItabbar Image Rounded in Swift Programmatically