Distorted CVImageBuffer to UIImage - ios

I have the following function that converts CVImageBugger to UIImage. The image that comes out is always a little bit distorted. I display the return value of this function in a UIImageView, which is set to 'aspect fill'. What gives?...
private func convert(buffer: CVImageBuffer) -> UIImage? {
let cmage: CIImage = CIImage(cvPixelBuffer: buffer)
let context: CIContext = CIContext(options: nil)
if let cgImage: CGImage = context.createCGImage(cmage, from: cmage.extent) {
return UIImage(cgImage: cgImage)
}
return nil
}

The CVImageBuffer doesn't contain the orientation information, maybe that's why the final UIImage is distorted.
The default orientation of CVImageBuffer is always Landscape(like the iPhone's Home button is at right side), no matter if you capture a video with portrait way or not.
So we need to add good orientation information to the image:
extension CIImage {
func orientationCorrectedImage() -> UIImage? {
var imageOrientation = UIImageOrientation.up
switch UIApplication.shared.statusBarOrientation {
case UIInterfaceOrientation.portrait:
imageOrientation = UIImageOrientation.right
case UIInterfaceOrientation.landscapeLeft:
imageOrientation = UIImageOrientation.down
case UIInterfaceOrientation.landscapeRight:
imageOrientation = UIImageOrientation.up
case UIInterfaceOrientation.portraitUpsideDown:
imageOrientation = UIImageOrientation.left
default:
break;
}
var w = self.extent.size.width
var h = self.extent.size.height
if imageOrientation == .left || imageOrientation == .right || imageOrientation == .leftMirrored || imageOrientation == .rightMirrored {
swap(&w, &h)
}
UIGraphicsBeginImageContext(CGSize(width: w, height: h));
UIImage.init(ciImage: self, scale: 1.0, orientation: imageOrientation).draw(in: CGRect(x: 0, y: 0, width: w, height: h))
let uiImage:UIImage? = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext();
return uiImage
}
}
Then use it with your code:
private func convert(buffer: CVImageBuffer) -> UIImage? {
let ciImage: CIImage = CIImage(cvPixelBuffer: buffer)
return ciImage.orientationCorrectedImage()
}

Related

Rotating UIImage for Google ML Vision framework on Swift 4

When an image gets captured it defaults to left orientation. So when you feed it into the textDetector inside the Google Vision framework, it comes all jumbled, unless you take the photo oriented left (home button on the right). I want my app to support both orientations.
let visionImage = VisionImage(image: image)
self.textDetector?.detect(in: visionImage, completion: { (features, error) in
//2
guard error == nil, let features = features, !features.isEmpty else {
print("Could not recognize any text")
self.dismiss(animated: true, completion: nil)
return
}
//3
print("Detected Text Has \(features.count) Blocks:\n\n")
for block in features {
//4
print("\(block.text)\n\n")
}
})
I have tried to recreate the Image with a new orientation and that won't change it.
Does anyone know what to do?
I have tried all of these suggestions
How to rotate image in Swift?
Try normalizing the UIImage object, using this function:
import UIKit
public extension UIImage {
public func normalizeImage() -> UIImage {
if self.imageOrientation == UIImageOrientation.up {
return self
}
UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale)
self.draw(in: CGRect(x: 0, y: 0, width: self.size.width, height: self.size.height))
let normalizedImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return normalizedImage
}
}
I hope this helps, cause I usually use this whenever there's an orientation problem.

Swift 3 - How do I improve image quality for Tesseract?

I am using Swift 3 to build a mobile app that allows the user to take a picture and run Tesseract OCR over the resulting image.
However, I've been trying to increase the quality of scan and it doesn't seem to be working much. I've segmented the photo into a more "zoomed in" region that I want to recognize and even tried making it black and white. Are there any strategies for "enhancing" or optimizing the picture quality/size so that Tesseract can recognize it better? Thanks!
tesseract.image = // the camera photo here
tesseract.recognize()
print(tesseract.recognizedText)
I got these errors and have no idea what to do:
Error in pixCreateHeader: depth must be {1, 2, 4, 8, 16, 24, 32}
Error in pixCreateNoInit: pixd not made
Error in pixCreate: pixd not made
Error in pixGetData: pix not defined
Error in pixGetWpl: pix not defined
2017-03-11 22:22:30.019717 ProjectName[34247:8754102] Cannot convert image to Pix with bpp = 64
Error in pixSetYRes: pix not defined
Error in pixGetDimensions: pix not defined
Error in pixGetColormap: pix not defined
Error in pixClone: pixs not defined
Error in pixGetDepth: pix not defined
Error in pixGetWpl: pix not defined
Error in pixGetYRes: pix not defined
Please call SetImage before attempting recognition.Please call SetImage before attempting recognition.2017-03-11 22:22:30.026605 EOB-Reader[34247:8754102] No recognized text. Check that -[Tesseract setImage:] is passed an image bigger than 0x0.
ive been using tesseract fairly successfully in swift 3 using the following:
func performImageRecognition(_ image: UIImage) {
let tesseract = G8Tesseract(language: "eng")
var textFromImage: String?
tesseract?.engineMode = .tesseractCubeCombined
tesseract?.pageSegmentationMode = .singleBlock
tesseract?.image = imageView.image
tesseract?.recognize()
textFromImage = tesseract?.recognizedText
print(textFromImage!)
}
I also found pre-processing the image helped too. I added the following extension to UIImage
import UIKit
import CoreImage
extension UIImage {
func toGrayScale() -> UIImage {
let greyImage = UIImageView()
greyImage.image = self
let context = CIContext(options: nil)
let currentFilter = CIFilter(name: "CIPhotoEffectNoir")
currentFilter!.setValue(CIImage(image: greyImage.image!), forKey: kCIInputImageKey)
let output = currentFilter!.outputImage
let cgimg = context.createCGImage(output!,from: output!.extent)
let processedImage = UIImage(cgImage: cgimg!)
greyImage.image = processedImage
return greyImage.image!
}
func binarise() -> UIImage {
let glContext = EAGLContext(api: .openGLES2)!
let ciContext = CIContext(eaglContext: glContext, options: [kCIContextOutputColorSpace : NSNull()])
let filter = CIFilter(name: "CIPhotoEffectMono")
filter!.setValue(CIImage(image: self), forKey: "inputImage")
let outputImage = filter!.outputImage
let cgimg = ciContext.createCGImage(outputImage!, from: (outputImage?.extent)!)
return UIImage(cgImage: cgimg!)
}
func scaleImage() -> UIImage {
let maxDimension: CGFloat = 640
var scaledSize = CGSize(width: maxDimension, height: maxDimension)
var scaleFactor: CGFloat
if self.size.width > self.size.height {
scaleFactor = self.size.height / self.size.width
scaledSize.width = maxDimension
scaledSize.height = scaledSize.width * scaleFactor
} else {
scaleFactor = self.size.width / self.size.height
scaledSize.height = maxDimension
scaledSize.width = scaledSize.height * scaleFactor
}
UIGraphicsBeginImageContext(scaledSize)
self.draw(in: CGRect(x: 0, y: 0, width: scaledSize.width, height: scaledSize.height))
let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return scaledImage!
}
func orientate(img: UIImage) -> UIImage {
if (img.imageOrientation == UIImageOrientation.up) {
return img;
}
UIGraphicsBeginImageContextWithOptions(img.size, false, img.scale)
let rect = CGRect(x: 0, y: 0, width: img.size.width, height: img.size.height)
img.draw(in: rect)
let normalizedImage : UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return normalizedImage
}
}
And then called this before passing the image to performImageRecognition
func processImage() {
self.imageView.image! = self.imageView.image!.toGrayScale()
self.imageView.image! = self.imageView.image!.binarise()
self.imageView.image! = self.imageView.image!.scaleImage()
}
Hope this helps

CGImageCreateWithImageInRect() returning nil

I'm trying to crop an image into a square, but once I actually try to do the crop by using CGImageCreateWithImageInRect(), this line crashes. I set breakpoints and made sure that the arguments passed into this function are not nil.
I'm fairly new to programming and Swift, but have searched around and haven't found any solution to my problem.
The failure reason:
fatal error: unexpectedly found nil while unwrapping an Optional value
func cropImageToSquare(imageData: NSData) -> NSData {
let image = UIImage(data: imageData)
let contextImage : UIImage = UIImage(CGImage: image!.CGImage!)
let contextSize: CGSize = contextImage.size
let imageDimension: CGFloat = contextSize.height
let posY : CGFloat = (contextSize.height + (contextSize.width - contextSize.height)/2)
let rect: CGRect = CGRectMake(0, posY, imageDimension, imageDimension)
// error on line below: fatal error: unexpectedly found nil while unwrapping an Optional value
let imageRef: CGImageRef = CGImageCreateWithImageInRect(contextImage.CGImage, rect)!
let croppedImage : UIImage = UIImage(CGImage: imageRef, scale: 1.0, orientation: image!.imageOrientation)
let croppedImageData = UIImageJPEGRepresentation(croppedImage, 1.0)
return croppedImageData!
}
Your code uses a lot of force-unwrapping with !s. I would recommend avoiding this — the compiler is trying to help you write code that won't crash. Use optional chaining with ?, and if let / guard let, instead.
The ! on that particular line is hiding an issue where CGImageCreateWithImageInRect might return nil. The documentation explains that this happens when the rect is not correctly inside the image bounds. Your code works for images in portrait orientation, but not landscape.
Furthermore, there's a convenient function provided by AVFoundation which can automatically find the right rectangle for you to use, called AVMakeRectWithAspectRatioInsideRect. No need to do the calculations manually :-)
Here's what I would recommend:
import AVFoundation
extension UIImage
{
func croppedToSquare() -> UIImage
{
guard let cgImage = self.CGImage else { return self }
// Note: self.size depends on self.imageOrientation, so we use CGImageGetWidth/Height here.
let boundingRect = CGRect(
x: 0, y: 0,
width: CGImageGetWidth(cgImage),
height: CGImageGetHeight(cgImage))
// Crop to square (1:1 aspect ratio) and round the resulting rectangle to integer coordinates.
var cropRect = AVMakeRectWithAspectRatioInsideRect(CGSize(width: 1, height: 1), boundingRect)
cropRect.origin.x = ceil(cropRect.origin.x)
cropRect.origin.y = ceil(cropRect.origin.y)
cropRect.size.width = floor(cropRect.size.width)
cropRect.size.height = floor(cropRect.size.height)
guard let croppedImage = CGImageCreateWithImageInRect(cgImage, cropRect) else {
assertionFailure("cropRect \(cropRect) was not inside \(boundingRect)")
return self
}
return UIImage(CGImage: croppedImage, scale: self.scale, orientation: self.imageOrientation)
}
}
// then:
let croppedImage = myUIImage.croppedToSquare()

AVFoundation CaptureStillImage issue with orientation of result image

I have a custom camera view controller that uses AVFoundation to capture a still image which you can see here:
My issue is that once I capture an image, the orientation is always sideways which you can see here
My code is as follows:
var captureInput: AVCaptureInput?
var stillImageOutput: AVCaptureStillImageOutput?
var previewLayer: AVCaptureVideoPreviewLayer?
var currentDevice: AVCaptureDevice?
var currentLayer: AVCaptureVideoPreviewLayer?
#IBAction func captureButtonTap(sender: UIButton) {
if(!capturingPhoto){
capturingPhoto = true
if let videoConnection = stillImageOutput!.connectionWithMediaType(AVMediaTypeVideo) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
videoConnection.videoOrientation = AVCaptureVideoOrientation.Portrait
self.stillImageOutput?.captureStillImageAsynchronouslyFromConnection(videoConnection, completionHandler: {(sampleBuffer, error) in
if (sampleBuffer != nil) {
var imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(sampleBuffer)
var image = UIImage(data: imageData)
var imageWidth = image?.size.width
var cropRect = CGRect(x: 0, y: 0, width: imageWidth!, height: imageWidth!)
var imageRef: CGImageRef = CGImageCreateWithImageInRect(image!.CGImage, cropRect)
var croppedImage: UIImage = UIImage(CGImage: imageRef)!
self.imagePreview.image = croppedImage
self.image = croppedImage
self.switchModeToPreview()
}
})
}
}
}
}
func initCameraSession(){
configureDevice()
NSLog(String(selectedCamera))
captureSession = AVCaptureSession()
captureSession!.sessionPreset = AVCaptureSessionPresetPhoto
if(selectedCamera == 0){
var backCamera = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)
currentDevice = backCamera
}else if(selectedCamera == 1){
var frontCamera = frontCameraIfAvalible()
currentDevice = frontCamera
}
if(currentDevice == nil){
NSException(name: "Error", reason: "Device Has No Camera.", userInfo: nil)
}
updateButtons()
var error: NSError?
var input = AVCaptureDeviceInput(device: currentDevice, error: &error)
if error == nil && captureSession!.canAddInput(input) {
captureSession!.addInput(input)
captureInput = input
stillImageOutput = AVCaptureStillImageOutput()
stillImageOutput!.outputSettings = [AVVideoCodecKey: AVVideoCodecJPEG]
if captureSession!.canAddOutput(stillImageOutput) {
captureSession!.addOutput(stillImageOutput)
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer!.videoGravity = AVLayerVideoGravityResizeAspectFill
previewLayer!.connection?.videoOrientation = AVCaptureVideoOrientation.Portrait
cameraPreview.layer.addSublayer(previewLayer)
previewLayer?.frame = CGRect(x: 0.0, y: 0.0, width: self.view.bounds.width, height: self.view.bounds.width + 139)
if(currentLayer != nil){
currentLayer!.removeFromSuperlayer()
}
currentLayer = previewLayer!
captureSession!.startRunning()
}
}
}
Please be aware I have left some code out on purpose and if you need further information, just request it. Thanks for the help.
Try using something like this, I use this to transform the buttons on my view when I rotate the device. This can be used with the image taken. Recall your standard transformation rotation matrices from linear algebra if you have ever taken that class. Basically, just alter this code to rotate the frame of the image you have captured and then make sure you set its frame bounds back to the view's frame in order to have it fit back correctly in the view. This may be more complicated than need be, But I have never had this issue before with images. Keep in mind this is being called due to a rotation change observer that has been registered, which you may not need to use. But this underlying concept is used A LOT
Any time I have rotation issues, I use this.
/**************************************************************************
DEVICE ORIENTATION DID CHANGE
**************************************************************************/
func deviceOrientationDidChange() {
println("DEVICE ORIENTATION DID CHANGE CALLED")
let orientation: UIDeviceOrientation = UIDevice.currentDevice().orientation
//------ IGNORE THESE ORIENTATIONS ------
if orientation == UIDeviceOrientation.FaceUp || orientation == UIDeviceOrientation.FaceDown || orientation == UIDeviceOrientation.Unknown || orientation == UIDeviceOrientation.PortraitUpsideDown || self.currentOrientation == orientation {
println("device orientation does not need to change --- returning...")
return
}
self.currentOrientation = orientation
//------ APPLY A ROTATION USING THE STANDARD ROTATION TRANSFORMATION MATRIX in R3 ------
/*
x y z
--- ---
x | cosø sinø 0 |
y | -sinø consø 0 |
z | 0 0 1 |
--- ---
*/
//----- PERFORM BUTTON AND VIDEO DATA BUFFER ROTATIONS ------
switch orientation {
case UIDeviceOrientation.Portrait:
println("Device Orientation Portrait")
if self.usingFrontCamera == true {
}
else {
self.playBackTransformation = CGAffineTransformMakeRotation(self.degrees0)
self.switchCaptureSession.transform = self.playBackTransformation!
self.switchCaptureSession.frame = self.view.bounds
self.flipCamera.transform = self.playBackTransformation!
self.flipCamera.frame = self.view.bounds
self.captureButton.transform = self.playBackTransformation!
self.captureButton.frame = self.view.bounds
}
break
case UIDeviceOrientation.LandscapeLeft:
println("Device Orientation LandScapeLeft")
if self.usingFrontCamera == true {
}
else {
self.playBackTransformation = CGAffineTransformMakeRotation(CGFloat(self.degrees90))
self.switchCaptureSession.transform = self.playBackTransformation!
self.switchCaptureSession.frame = self.view.bounds
self.flipCamera.transform = self.playBackTransformation!
self.flipCamera.frame = self.view.bounds
self.captureButton.transform = self.playBackTransformation!
self.captureButton.frame = self.view.bounds
}
break
case UIDeviceOrientation.LandscapeRight:
println("Device Orientation LandscapeRight")
if self.usingFrontCamera == true {
}
else {
self.playBackTransformation = CGAffineTransformMakeRotation(-self.degrees90)
self.switchCaptureSession.transform = self.playBackTransformation!
self.switchCaptureSession.frame = self.view.bounds
self.flipCamera.transform = self.playBackTransformation!
self.flipCamera.frame = self.view.bounds
self.captureButton.transform = self.playBackTransformation!
self.captureButton.frame = self.view.bounds
}
break
default:
break
}
Affine Transformation are great for rotations. There may be a function that sets the default image orientation when captured. Check Apple's references.

UIImage Orientation Swift

I have written this code to capture an image using the AVFoundation library in Swift:
#IBAction func cameraButtonWasPressed(sender: AnyObject) {
if let videoConnection = stillImageOutput.connectionWithMediaType(AVMediaTypeVideo){
stillImageOutput.captureStillImageAsynchronouslyFromConnection(videoConnection){
(imageSampleBuffer : CMSampleBuffer!, _) in
let imageDataJpeg = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(imageSampleBuffer)
var pickedImage: UIImage = UIImage(data: imageDataJpeg)!
let library = ALAssetsLibrary()
library.writeImageToSavedPhotosAlbum(pickedImage.CGImage,
metadata:nil,
completionBlock:nil)
}
}
}
It works fine, but when I go to the photo library the image shows rotated 90 degrees counter clockwise.
Can someone give me an hint on where to dig to fix this?
Maybe this Swift code can help you.
//correctlyOrientedImage.swift
import UIKit
extension UIImage {
public func correctlyOrientedImage() -> UIImage {
if self.imageOrientation == UIImageOrientation.Up {
return self
}
UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale)
self.drawInRect(CGRectMake(0, 0, self.size.width, self.size.height))
var normalizedImage:UIImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return normalizedImage;
}
}
I saw it somewhere in stack overflow and incorporated in my project as well.
You should be using a slightly different writeImage method:
(1) get the orientation from the UIImage imageOrientation property (an enum), and cast it to ALAssetOrientation (an enum with the same Int values as UIImageOrientation)
var orientation : ALAssetOrientation = ALAssetOrientation(rawValue:
pickedImage.imageOrientation.rawValue)!
(2) use a similar-but-different method on ALAssetLibrary
library.writeImageToSavedPhotosAlbum(
pickedImage.CGImage,
orientation: orientation,
completionBlock:nil)
This works for me in Objective-C ... I have had a quick go at translating to Swift (as above) but I am getting compiler warnings.
Cannot invoke 'writeImageToSavedPhotosAlbum' with an argument list of type '(CGImage!, orientation: ALAssetOrientation, completionBlock: NilLiteralConvertible)'
Perhaps you could try (I don't have the time to construct a full AVFoundation pipeline in Swift to test this definitively)
If you can't get that to work, the other solution is to extract the exif metadata from the sampleBuffer and pass it through to the method you are already using
library.writeImageToSavedPhotosAlbum(pickedImage.CGImage,
metadata:nil,
completionBlock:nil
Swift 5
extension UIImage {
public func correctlyOrientedImage() -> UIImage {
if self.imageOrientation == UIImage.Orientation.up {
return self
}
UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale)
self.draw(in: CGRect(x: 0, y: 0, width: self.size.width, height: self.size.height))
let normalizedImage = UIGraphicsGetImageFromCurrentImageContext()!;
UIGraphicsEndImageContext();
return normalizedImage;
}
}

Resources