Why Tesseract OCR library (iOS) cannot recognize text at all? - ios

I'm trying to use Tesseract OCR library in my iOS application. I downloaded tesseract-ios library from github and when I tried to recognize a simple text image I got garbage instead. Here is an image of what I tried to recognize:
I got unreadable text:
T0I1101T0W KIR1 H1I1101T0W KIR1 H1I1101T0W CIBEPS H1 ES PBHY P306
EHH11 133I R1 11335 11I1H1 19 13S SYIL 3B19 M H300H1911 H1113 AIR1
J1 OIII 3I9SH5H133IS 13V9 I1 Q1H211 E015 19 W331 H1 111SW
Why Tesseract can't recognise even simple image? Here is code which I used to instantiate Tesseract:
Tesseract* tesseractObject = [[Tesseract alloc] initWithDataPath:#"tessdata" language:#"eng"];
[tesseractObject setVariableValue:#"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" forKey:#"tessedit_char_whitelist"];
[tesseractObject setImage:image];
[tesseractObject recognize];
NSLog(#"RECOGNISED= %#" , [tesseractObject recognizedText]);
Here is my project structure:
I added English testdata folder by reference. So what am I doing wrong? How can I fix this?

You are using the option tessedit_char_whitelist with the value "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" which limits the character recognition to this list only. However the image that you want to process contains lower case characters, if you want to use this option you will have to include lower cases char too.
[tesseractObject setVariableValue:#"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" forKey:#"tessedit_char_whitelist"];

Make sure you have the latest tessdata file from Google code
http://code.google.com/p/tesseract-ocr/downloads/list
This will provide you with a list of tessdata files that you need to download and include in your app if you haven't already. In your case you will need tesseract-ocr-3.02.eng.tar.gz as you are looking for the English language files
The following article will show you where you need to install it. I read through this tutorial when I built my first Tesseract project and found it really useful
http://lois.di-qual.net/blog/install-and-use-tesseract-on-ios-with-tesseract-ios/

Like Adam said, if you want good results, you'll have to do some image processing and configure some settings (white-listing certain characters, etc).
For anyone else stumbling upon this question, I've put together a sample project here that does some white-listing and image processing:https://github.com/mstrchrstphr/OCR-iOS-Example

and my output is
Solution :
tesseract.language = #"eng+fra";
tesseract.pageSegmentationMode = G8PageSegmentationModeAuto;
tesseract.engineMode = G8OCREngineModeTesseractCubeCombined;
tesseract.image = [image.image g8_blackAndWhite];
tesseract.maximumRecognitionTime = 60.0;
[tesseract recognize];
NSLog(#"%#", tesseract.recognizedText);
reco_area.text = [tesseract recognizedText];
for tessdata
click here

whatever # Adam Richardson explained is correct along with that add this 1) scaleimage method for increase size of the image(dimensions increase)
func scaleImage(image: UIImage, maxDimension: CGFloat) -> UIImage {
var scaledSize = CGSize(width: maxDimension, height: maxDimension)
var scaleFactor: CGFloat
if image.size.width > image.size.height {
scaleFactor = image.size.height / image.size.width
scaledSize.width = maxDimension
scaledSize.height = scaledSize.width * scaleFactor
} else {
scaleFactor = image.size.width / image.size.height
scaledSize.height = maxDimension
scaledSize.width = scaledSize.height * scaleFactor
}
UIGraphicsBeginImageContext(scaledSize)
image.draw(in: CGRect(x: 0, y: 0, width: scaledSize.width, height: scaledSize.height))
let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return scaledImage!
}
2) store this eng.traineddata language file in filemanager
func storeLanguageFile() throws{
var fileManager: FileManager = FileManager.default
let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
let docDirectory = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)[0] as NSString
let path: String = docDirectory.appendingPathComponent("/tessdata/eng.traineddata")
if fileManager.fileExists(atPath: path){
var data: NSData = NSData.dataWithContentsOfMappedFile((Bundle.main.resourcePath?.appending("/tessdata/eng.traineddata"))!)! as! NSData
var error: NSError
try FileManager.default.createDirectory(atPath: docDirectory.appendingPathComponent("/tessdata"), withIntermediateDirectories: true, attributes: nil)
data.write(toFile: path, atomically: true)
}
}
3) after that you can use https://github.com/BradLarson/GPUImage for increase clarity of the image
you can use this
func preprocessedImage(for tesseract: G8Tesseract!, sourceImage: UIImage!) -> UIImage! {
var stillImageFilter: GPUImageAdaptiveThresholdFilter = GPUImageAdaptiveThresholdFilter()
stillImageFilter.blurRadiusInPixels = 4.0
var filterImage: UIImage = stillImageFilter.image(byFilteringImage: sourceImage)
return filterImage
}
these 3 steps will help you to increase the accuracy of the tesseract upto 60 ~ 70 %

Related

Why app Terminates due to memory waring when convert pdf page to high quality image in ios, swift in real device

I'm trying to get the image(high quality) of each pdf page. I'm using below code running through a for loop until page count and it works.
guard let document = CGPDFDocument(pdfurl as CFURL) else { return }
guard let page = document.page(at: i) else { return }
let dpi: CGFloat = 300.0/72.0
let pagerect = page.getBoxRect(.mediaBox)
print(pagebounds)
print(pagerect)
let render = UIGraphicsImageRenderer(size: CGSize(width: pagerect.size.width * dpi, height: pagerect.size.height * dpi))
let imagedata = render.jpegData(withCompressionQuality: 0.5, actions: { cnv in
UIColor.white.set()
cnv.fill(pagerect)
cnv.cgContext.translateBy(x: 0.0, y: pagerect.size.height * dpi)
cnv.cgContext.scaleBy(x: dpi, y: -dpi)
cnv.cgContext.drawPDFPage(page)
})
let image = UIImage(data: imagedata)
I'm getting following issues with this ...
sometimes the image is nil.
When this runs, the usage of memory is very high.
With the page count(number of pages), usage of memory is very very high, and sometimes it goes to 1.4 GB and suddenly it crashes the app with the warning : Terminate due to memory waring . then I tried to run above code inside autoreleasepool. it did work but when the memory usage is more high (when it near to RAM size), again app crashes with above warning.
How can I avoid this memory warning and get the quality image form pdf page. hope any help. have a nice day.
If you are facing this issue then try this:
autoreleasepool {
guard let page = document.page(at: i) else { return }
// Fetch the page rect for the page we want to render.
let pageRect = page.getBoxRect(.mediaBox)
var dpi: CGFloat = 1.0
if pageRect.size.width > pageRect.size.height {
dpi = 3508.0 / pageRect.size.width
} else {
dpi = 3508.0 / pageRect.size.height
}
//dpi = 300
let format = UIGraphicsImageRendererFormat()
format.scale = 1
let renderer = UIGraphicsImageRenderer(size: CGSize(width: pageRect.size.width * dpi, height: pageRect.size.height * dpi), format: format)
let imagedata = renderer.jpegData(withCompressionQuality: 1.0, actions: { cnv in
UIColor.white.set()
cnv.fill(pageRect)
cnv.cgContext.translateBy(x: 0.0, y: pageRect.size.height * dpi)
cnv.cgContext.scaleBy(x: dpi, y: -dpi)
cnv.cgContext.drawPDFPage(page)
})
let image = UIImage(data: imagedata)
}
autoreleasepool - for permanent memory clearing
scale - so that images for different devices are not created, which increases their resolution by 2 or 3 times
changed the way to increase dpi as it can be initially more or less than 72
1) sometimes the image is nil.
Is there a reason that you are generating a jpeg data then converting to UIImage VS directly creating an uiimage (using func image(actions: (UIGraphicsImageRendererContext) -> Void) -> UIImage)?
If you really need to use the jpeg method, then don't directly instantiate from UIImage(data:), use CGImage's init?(jpegDataProviderSource source: CGDataProvider,
decode: UnsafePointer<CGFloat>?,
shouldInterpolate: Bool,
intent: CGColorRenderingIntent) then use UIImage(cgImage:) to get your UIImage instance
2) When this runs, the usage of memory is very high
Are you storing all images created per each page? If yes, then if you have a pdf of high number of pages then you will consume max memory at some point because of accumulated images. Why don't you store to disk each image created then releasing it afterwards so you don't accumulate the memory of storing all pages in memory.
Sharing the loop(assuming there is a loop) outside of this snippet could help solve your issue more

How to load sprite sheets from web API in SpriteKit

I'm new to SpriteKit, and my question is how to load sprite sheets from web API.
Currently, I have an API returns a big PNG image, which contains all sprite sheets, and a json about individual frame information. (file and json are generated by TexturePacker) The API looks like this:
The format just likes a .atlasc folder, which contains a big image and a plist (XML) file.
I was thinking about downloading image and plist file and save it in the disk to load. However, SKTextureAtlas.init(named: String) can only load from app bundle.
In one word, I want to load a sprite animation from the web at runtime.
I have control of the API, so I can update the API to accomplish my goal.
The way I've figured out is downloading image, create a sourceTexture, like: let sourceTexture = SKTexture(image: image)
Then use the frame information in json to create individual textures with method init(rect rect: CGRect, inTexture texture: SKTexture)
Sample code is:
var textures: [SKTexture] = []
let sourceTexture = SKTexture(image: image)
for frame in spriteSheet.frames {
let rect = CGRect(
x: frame.frame.origin.x / spriteSheet.size.width,
y: 1.0 - (frame.frame.size.height / spriteSheet.size.height) - (frame.frame.origin.y / spriteSheet.size.height),
width: frame.frame.size.width / spriteSheet.size.width,
height: frame.frame.size.height / spriteSheet.size.height
)
let texture = SKTexture(rect: rect, inTexture: sourceTexture)
textures.append(texture)
}
basically same way with #Honghao Zhang 's answer, but I was little bit confused about the whole structure at first glance.
so I share my code snippet for later readers.
Happy coding :)
func getSpriteTextures() -> [SKTexture]? {
guard let spriteSheet = loadSpriteJson(name: "sprite_json_file", codable: SpriteJson.self) else { return nil }
let sourceImage = UIImage(named: "sprite_img_file.png")!
let sourceTexture = SKTexture(image: sourceImage)
var textures: [SKTexture] = []
let sourceWidth = spriteSheet.meta.size.w
let sourceHeight = spriteSheet.meta.size.h
let orderedFrameImgNames = spriteSheet.frames.keys.sorted()
for frameImgName in orderedFrameImgNames {
let frameMeta = spriteSheet.frames[frameImgName]!
let rect = CGRect(x: frameMeta.frame.x / sourceWidth,
y: 1.0
- (frameMeta.sourceSize.h / sourceHeight)
- (frameMeta.frame.y / sourceHeight),
width: frameMeta.frame.w / sourceWidth,
height: frameMeta.frame.h / sourceHeight)
let texture = SKTexture(rect: rect, in: sourceTexture)
textures.append(texture)
}
return textures
}

*** ERROR *** CGImageSource was created with data size: 22467 - current size is only: 6139

Getting this error while saving image to photos:
* ERROR * CGImageSource was created with data size: 22467 - current size is only: 6139
let canvasframe = self.canvasView.frame
self.canvasView.frame = CGRectMake(0, 0, self.size.width, self.size.height)
UIImageWriteToSavedPhotosAlbum(self.getUIImageFromThisUIView(self.canvasView),nil,nil,nil);
self.canvasView.frame = canvasframe
func getUIImageFromThisUIView(aUIView: UIView) -> UIImage {
UIGraphicsBeginImageContext(aUIView.bounds.size)
aUIView.layer.renderInContext(UIGraphicsGetCurrentContext()!)
let viewImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return viewImage
}
This problem is occurs when we trying to get png file from documents directory and we directly assign the content of file to local UIImage variable. Instead of doing it, First get data of content of file and then transform the data into UIImage. In short
Change this:
let img = UIImage(contentsOfFile: filename)
To This:
let img = NSData(contentsOfFile: filename)
let photo = UIImage(data: img!)!
This works for me. Hope this helps you
I had same problem when downloading an image.
I was presuming the file I have opening was a PNG but api was throwing a 500 and tmp/ NDData actually contained html error.
heres my answer in other SO on how to catch the bytesize

Load Animated gif in UIImageView IOS

I have this gif into my assets file
The name of the assets is loading_apple but when I add the following code I get a nullpointer exception error:
let img = UIImage (named: "loading_apple")
So how can I show this gif ? :(
Hope someone can help me.
I would recommend using FLAnimatedImage https://github.com/Flipboard/FLAnimatedImage
I would recommend breaking out the frames of that gif and use animatedImageNamed:duration: - you can name them all the similar name with a number change at the end. For instance:
loading-1.png
loading-2.png
loading-3.png etc.
Xcode will recognize you want multiple images and will play those through in order.
Look at THIS
instead of storing the gif file, why don't u make use of CAReplicatorLayer refer this link and this link in objective c, i took same code in second like and with some modification,
func spinTheCustomSpinner() -> Void {
let aBar:CALayer = CALayer.init()
aBar.bounds = CGRectMake(0, 0, 8, 25);
aBar.cornerRadius = 4; //(8/2)
aBar.backgroundColor = UIColor.blackColor().CGColor
aBar.position = CGPointMake(150.0, 150.0 + 35)
let replicatorLayer:CAReplicatorLayer = CAReplicatorLayer.init()
replicatorLayer.bounds = CGRectMake(0, 0,300,300)
replicatorLayer.cornerRadius = 10.0
replicatorLayer.backgroundColor = UIColor.whiteColor().CGColor
replicatorLayer.position = CGPointMake(CGRectGetMidX(self.view!.bounds), CGRectGetMidY(self.view!.bounds))
let angle:CGFloat = CGFloat (2.0 * M_PI) / 12.0
let transform:CATransform3D = CATransform3DMakeRotation(angle, 0, 0, 1.0)
replicatorLayer.instanceCount = 12
replicatorLayer.instanceTransform = transform
replicatorLayer .addSublayer(aBar)
self.view!.layer .addSublayer(replicatorLayer)
aBar.opacity = 0.0
let animationFade:CABasicAnimation = CABasicAnimation(keyPath: "opacity")
animationFade.fromValue = NSNumber(float: 1.0)
animationFade.toValue = NSNumber(float: 0.0)
animationFade.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
animationFade.repeatCount = HUGE
animationFade.duration = 1.0
let aBarAnimationDuration:Double = 1.0/12.0
replicatorLayer.instanceDelay = aBarAnimationDuration
aBar .addAnimation(animationFade, forKey: "fadeAnimation")
}
if u use above with a view, show this view while loading and hide after loading, this is really cool and handy and there is no headache of storing the gif file and using a image view to load.
and by changing the properties of aBar layer u get different effects.
For this specific gif you may want to use UIActivityIndicatorView and make it animating with startAnimating()

Calculating size (in bytes) of an image in memory

I am writing a small app in Swift to resize an image. I would like to calculate the size of the resized image (in bytes/KB). How do I do that?
Here is the piece of code I am working on:
var assetRepresentation : ALAssetRepresentation = asset.defaultRepresentation()
self.originalImageSize = assetRepresentation.size()
selectedImageSize = self.originalImageSize
// now scale the image
let image = selectedImage
let hasAlpha = false
let scale: CGFloat = 0.0 // Automatically use scale factor of main screen
UIGraphicsBeginImageContextWithOptions(sizeChange, !hasAlpha, scale)
image.drawInRect(CGRect(origin: CGPointZero, size: sizeChange))
let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
self.backgroundImage.image = scaledImage
Since scaledImage is not yet saved, how do I go about calculating its size?
Since you're looking to display the size of the file to your user, NSByteCountFormatter is a good solution. It takes NSData, and can output a String representing the size of the data in a human readable format (like 1 KB, 2 MB, etc).
Since you're dealing with a UIImage though, you'll have to convert the UIImage to NSData to use this, which for example, can be done using UIImagePNGRepresentation() or UIImageJPEGRepresentation(), which returns NSData representative of the image in the specified format. A usage example could look something like this:
let data = UIImagePNGRepresentation(scaledImage)
let formatted = NSByteCountFormatter.stringFromByteCount(
Int64(data.length),
countStyle: NSByteCountFormatterCountStyle.File
)
println(formatted)
Edit: If as suggested by your title, you're looking to show this information with a specific unit of measurement (bytes), this can also be achieved with NSByteCountFormatter. You just have to create an instance of the class and set its allowedUnits property.
let data = UIImagePNGRepresentation(scaledImage)
let formatter = NSByteCountFormatter()
formatter.allowedUnits = NSByteCountFormatterUnits.UseBytes
formatter.countStyle = NSByteCountFormatterCountStyle.File
let formatted = formatter.stringFromByteCount(Int64(data.length))
println(formatted)
I used this to create my image:
var imageBuffer: UnsafeMutablePointer<UInt8> = nil
let ctx = CGBitmapContextCreate(imageBuffer, UInt(width), UInt(height), UInt(8), bitmapBytesPerRow, colorSpace, bitmapInfo)
imageBuffer is allocated automatically (see according documentation).

Resources