iOS UIImage Binarization for OCR - handling images with varying luminance - ios

I had a C++ binarization routine that I used for later OCR operation.
However I found that it produced unnecessary slanting of text.
Searching for alternatives I found GPUImage of great value and it solved the slanting issue.
I am using GPUImage code like this to binarize my input images before applying OCR.
However the threshold value does not cover the range of images I get.
See two samples from my input images:
I can't handle both with same threshold value.
Low value seems to be fine with later, and higher value is fine with first one.
The second image seems to be of special complexity because I never get all the chars to be binarized right, irrespective of what value I set for threshold. On the other hand, my C++ binarization routine seems to do it right, but I don't have much insights to experiment into it like simplistic threshold value in GPUImage.
How should I handle that?
UPDATE:
I tried with GPUImageAverageLuminanceThresholdFilter with default multiplier = 1. It works fine with first image but the second image continues to be problem.
Some more diverse inputs for binarization:
UPDATE II:
After going through this answer by Brad, tried GPUImageAdaptiveThresholdFilter (also incorporating GPUImagePicture because earlier I was only applying it on UIImage).
With this, I got second image binarized perfect. However first one seems to have lot of noise after binarization when I set blur size is 3.0. OCR results in extra characters added. With lower value of blur size, second image loses precision.
Here it is:
+(UIImage *)binarize : (UIImage *) sourceImage
{
UIImage * grayScaledImg = [self toGrayscale:sourceImage];
GPUImagePicture *imageSource = [[GPUImagePicture alloc] initWithImage:grayScaledImg];
GPUImageAdaptiveThresholdFilter *stillImageFilter = [[GPUImageAdaptiveThresholdFilter alloc] init];
stillImageFilter.blurSize = 3.0;
[imageSource addTarget:stillImageFilter];
[imageSource processImage];
UIImage *imageWithAppliedThreshold = [stillImageFilter imageFromCurrentlyProcessedOutput];
// UIImage *destImage = [thresholdFilter imageByFilteringImage:grayScaledImg];
return imageWithAppliedThreshold;
}

For a pre processing step you need adaptive thresholding here.
I got these results using opencv grayscale and adaptive thresholding methods. Maybe with an addition of low pass noise filtering (gaussian or median) it should work like a charm.
I used provisia (its a ui to help you process images fast) to get the block size I need: 43 for the image you supplied here. The block size may change if you take photo from closer or further. If you want a generic algorithm, you need to develop one that should search for the best size (search until numbers are detected)
EDIT: I just saw the last image. It is untreatably small. Even if you apply the best pre-processing algorithm, you are not going to detect those numbers. Sampling up would not be solution since noises will come around.

I finally ended up exploring on my own, and here is my result with GPUImage filter:
+ (UIImage *) doBinarize:(UIImage *)sourceImage
{
//first off, try to grayscale the image using iOS core Image routine
UIImage * grayScaledImg = [self grayImage:sourceImage];
GPUImagePicture *imageSource = [[GPUImagePicture alloc] initWithImage:grayScaledImg];
GPUImageAdaptiveThresholdFilter *stillImageFilter = [[GPUImageAdaptiveThresholdFilter alloc] init];
stillImageFilter.blurSize = 8.0;
[imageSource addTarget:stillImageFilter];
[imageSource processImage];
UIImage *retImage = [stillImageFilter imageFromCurrentlyProcessedOutput];
return retImage;
}
+ (UIImage *) grayImage :(UIImage *)inputImage
{
// Create a graphic context.
UIGraphicsBeginImageContextWithOptions(inputImage.size, NO, 1.0);
CGRect imageRect = CGRectMake(0, 0, inputImage.size.width, inputImage.size.height);
// Draw the image with the luminosity blend mode.
// On top of a white background, this will give a black and white image.
[inputImage drawInRect:imageRect blendMode:kCGBlendModeLuminosity alpha:1.0];
// Get the resulting image.
UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return outputImage;
}
I achieve almost 90% using this - I am sure there must be better options but I tried with blurSize as far as I could and 8.0 is the value that works with most of my input images.
For anyone else, good luck with your trying!

SWIFT3
SOLUTION 1
extension UIImage {
func doBinarize() -> UIImage? {
let grayScaledImg = self.grayImage()
let imageSource = GPUImagePicture(image: grayScaledImg)
let stillImageFilter = GPUImageAdaptiveThresholdFilter()
stillImageFilter.blurRadiusInPixels = 8.0
imageSource!.addTarget(stillImageFilter)
stillImageFilter.useNextFrameForImageCapture()
imageSource!.processImage()
guard let retImage: UIImage = stillImageFilter.imageFromCurrentFramebuffer(with: UIImageOrientation.up) else {
print("unable to obtain UIImage from filter")
return nil
}
return retImage
}
func grayImage() -> UIImage? {
UIGraphicsBeginImageContextWithOptions(self.size, false, 1.0)
let imageRect = CGRect(x: 0, y: 0, width: self.size.width, height: self.size.height)
self.draw(in: imageRect, blendMode: .luminosity, alpha: 1.0)
let outputImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return outputImage
}
}
The result would be
SOLUTION 2
use GPUImageLuminanceThresholdFilter to achieve 100% black and white effect whithout grey color
let stillImageFilter = GPUImageLuminanceThresholdFilter()
stillImageFilter.threshold = 0.9
For example I need to detect flash light and this works for me

Related

Colorwithpattern for coreimage color

I am using CIAztecCodeGenerator to generate an Aztec code.
I'm trying to set a pattern instead of a solid color for the foreground color for it however it is rendering as blank/white I was wondering if anyone knew what I am doing wrong.
[colorFilter setValue:[CIColor colorWithCGColor:[[UIColor colorWithPatternImage:image] CGColor]] forKey:#"inputColor0"];
It's a bit more complicated, but you can use a custom pattern by combining and blending the images in a certain way:
import CoreImage.CIFilterBuiltins // needed for using the type-safe filter interface
// ...
let patternImage = UIImage(named: "<image_name>")!
var patternInput = CIImage(cgImage: patternImage.cgImage!)
// potentially scale the pattern image
patternInput = patternInput.transformed(by: CGAffineTransform(scaleX: 0.5, y: 0.5))
let codeFilter = CIFilter.aztecCodeGenerator()
codeFilter.message = "<message>".data(using: .utf8)!
var codeImage = codeFilter.outputImage!
// invert code so the actual code part is white
let colorInvertFilter = CIFilter.colorInvert()
colorInvertFilter.inputImage = codeImage
codeImage = colorInvertFilter.outputImage!
// potentially scale the barcode (using nearest sampling to retain sharp edges)
codeImage = codeImage.samplingNearest().transformed(by: CGAffineTransform(scaleX: 50, y: 50))
let blendFilter = CIFilter.blendWithMask()
blendFilter.inputImage = patternInput
// using white color as background here, but can be any (or transparent when alpha = 0)
blendFilter.backgroundImage = CIImage(color: CIColor.white).cropped(to: codeImage.extent)
blendFilter.maskImage = codeImage
let output = blendFilter.outputImage!
Objective-C version:
// the image containing the pattern you want to show over the aztec code
CIImage *patternImage = [CIImage imageWithData:imageData];
// potentially scale the pattern image, if necessary
patternImage = [patternImage imageByApplyingTransform:CGAffineTransformMakeScale(0.5, 0.5)];
// generate aztec code image
CIFilter *qrFilter = [CIFilter filterWithName:#"CIAztecCodeGenerator"];
[qrFilter setValue:stringData forKey:#"inputMessage"];
CIImage *codeImage = qrFilter.outputImage;
// invert code so the actual code part is white (used for masking below)
codeImage = [codeImage imageByApplyingFilter: #"CIColorInvert"];
// potentially scale the aztec code (using nearest sampling to retain sharp edges)
codeImage = [[codeImage imageBySamplingNearest] imageByApplyingTransform:CGAffineTransformMakeScale(50, 50)];
// the background color for your aztec code; basically a solid color image of the same size of the code
CIImage *background = [[CIImage imageWithColor:[CIColor whiteColor]] imageByCroppingToRect:codeImage.extent];
//
CIFilter *blendFilter = [CIFilter filterWithName:#"CIBlendWithMask"];
[blendFilter setValue:patternImage forKey:#"inputImage"]; // the pattern image is in the foreground
[blendFilter setValue:background forKey:#"backgroundImage"]; // solid color in the aztec code, but could be any color or image
[blendFilter setValue:codeImage forKey:#"maskImage"]; // use the aztec code as a mask for the pattern image over the background
CIImage *output = blendFilter.outputImage
Note that the numbers in the two scaling steps depend on how large you want to display the code and how the pattern image should be scaled above the code.

how can you apply a filter on a UIImage as if it is sprayed on a brick wall?

I want to blend an image with a background image.
As if it is sprayed on a wall.
How can i obtain a realistic blend.
I have tried alpha but not giving good results.
I am quit new in this CoreImage stuff.
Please help.
Something similiar to this.
http://designshack.net/wp-content/uploads/texturetricks-14.jpg
i googled some but no luck.
I even do not know what i am looking for exactly.
Not sure how to google it.
Here is how to blend two images together.
UIImage *bottomImage = [UIImage imageNamed:#"bottom.png"];
UIImage *image = [UIImage imageNamed:#"top.png"];
CGSize newSize = CGSizeMake(width, height);
UIGraphicsBeginImageContext( newSize );
// Use existing opacity as is
[bottomImage drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
// Apply supplied opacity
[image drawInRect:CGRectMake(0,0,newSize.width,newSize.height) blendMode:kCGBlendModeNormal alpha:0.8];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
This kind of works. I found a smiley face image - white on black - and a brick background.
The first job is to create CIImage versions of the pair. I pass the smiley face through a CIMaskToAlpha filter to make the black transparent:
let brick = CIImage(image: UIImage(named: "brick.jpg")!)!
let smiley = CIImage(image: UIImage(named: "smiley.jpg")!)!
.imageByApplyingFilter("CIMaskToAlpha", withInputParameters: nil)
Then composite the pair together with CISubtractBlendMode:
let composite = brick
.imageByApplyingFilter("CISubtractBlendMode",
withInputParameters: [kCIInputBackgroundImageKey: smiley])
The final result is almost there:
The subtract blend will only really work with white artwork.
Here's another approach: using CISoftLightBlendMode for the blend, bumping up the gamma to brighten the effect and then compositing that over the original brickwork using the smiley as a mask:
let composite = smiley
.imageByApplyingFilter("CISoftLightBlendMode",
withInputParameters: [kCIInputBackgroundImageKey: brick])
.imageByApplyingFilter("CIGammaAdjust",
withInputParameters: ["inputPower": 0.5])
.imageByApplyingFilter("CIBlendWithMask",
withInputParameters: [kCIInputBackgroundImageKey: brick, kCIInputMaskImageKey: smiley])
Which gives:
This approach is nice because you can control the paint whiteness with the gamma power.
Simon

GPUImage output image is missing in screen capture

I am trying to capture screen portion to post image on social media.
I am using following code to capture screen.
- (UIImage *) imageWithView:(UIView *)view
{
UIGraphicsBeginImageContextWithOptions(view.bounds.size, NO, 0.0);
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img;
}
Above code is perfect for capturing screen.
Problem :
My UIView contains GPUImageView with the filtered image. When I tries to capture screen using above code, that particular portion of GPUImageView does not contains the filtered image.
I am using GPUImageSwirlFilter with the static image (no camera). I have also tried
UIImage *outImage = [swirlFilter imageFromCurrentFramebuffer]
but its not giving image.
Note : Following is working code, which gives perfect output of swirl effect, but I want same image in UIImage object.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
GPUImageSwirlFilter *swirlFilter = [GPUImageSwirlFilter alloc] init];
swirlLevel = 4;
[swirlFilter setAngle:(float)swirlLevel/10];
UIImage *inputImage = [UIImage imageNamed:gi.wordImage];
GPUImagePicture *swirlSourcePicture = [[GPUImagePicture alloc] initWithImage:inputImage];
inputImage = nil;
[swirlSourcePicture addTarget:swirlFilter];
dispatch_async(dispatch_get_main_queue(), ^{
[swirlFilter addTarget:imgSwirl];
[swirlSourcePicture processImage];
// This works perfect and I have filtered image in my imgSwirl. But I want
// filtered image in UIImage to use at different place like posting
// on social media
sharingImage = [swirlFilter imageFromCurrentFramebuffer]; // This also
// returns nothing.
});
});
1) Am I doing something wrong with GPUImage's imageFromCurrentFramebuffer ?
2) And why does screen capture code is not including GPUImageView portion in output image ?
3) How do I get filtered image in UIImage ?
First, -renderInContext: won't work with a GPUImageView, because a GPUImageView renders using OpenGL ES. -renderinContext: does not capture from CAEAGLLayers, which are used to back views presenting OpenGL ES content.
Second, you're probably getting a nil image in the latter code because you've forgotten to set -useNextFrameForImageCapture on your filter before triggering -processImage. Without that, your filter won't hang on to its backing framebuffer long enough to capture an image from it. This is due to a recent change in the way that framebuffers are handled in memory (although this change did not seem to get communicated very well).

OpenCV:image processing,objective C/C++

My goal is to find golf ball using iPhone camera, So I did same steps in photoshop and
I want to achieve same steps in openCV on image/live video frame
start with the original picture.
then boost the satturation to get color into light areas
the use curves to cut off the edges of the spectrum
then convert the image to grayscale
use curves again to get to black/white
and finally - just for the look - apply a color
--Input Image:
--Output Image:
Would you please help me or give me some hints related image processing with OpenCV in iOS?
Thanks in advance!
Edit
I used following code and got the below output Image,
- (UIImage*) applyToneCurveToImage:(UIImage*)image
{
CIImage* ciImage = [[CIImage alloc] initWithImage:image];
CIFilter* filter =
[CIFilter filterWithName:#"CIToneCurve"
keysAndValues:
kCIInputImageKey, ciImage,
#"inputPoint0",[CIVector vectorWithX:0.00 Y:0.3]
,#"inputPoint1",[CIVector vectorWithX:0.25 Y:0.4]
,#"inputPoint2",[CIVector vectorWithX:0.50 Y:0.5]
,#"inputPoint3",[CIVector vectorWithX:0.75 Y:0.6]
,#"inputPoint4",[CIVector vectorWithX:1.00 Y:0.7]
,nil];
//CIFilter* filter2 = [filter copy];
//step1
filter = [CIFilter filterWithName:#"CIColorControls"
keysAndValues:kCIInputImageKey,
[filter valueForKey:kCIOutputImageKey], nil];
[filter setValue:[NSNumber numberWithFloat:0]
forKey:#"inputBrightness"];
[filter setValue:[NSNumber numberWithFloat:6]
forKey:#"inputContrast"];
CIImage* result = [filter valueForKey:kCIOutputImageKey];
CIContext *context = [CIContext contextWithOptions:nil];
CGImageRef cgImage = [context createCGImage:result
fromRect:[result extent]];
UIImage* filteredImage = [UIImage imageWithCGImage:cgImage];
CGImageRelease(cgImage);
ciImage=nil;
context=nil;
cgImage=nil;
result=nil;
return filteredImage;
}
- (void)didCaptureIplImage:(IplImage *)iplImage
{
#autoreleasepool
{
IplImage *orgimage = cvCreateImage(cvGetSize(iplImage), IPL_DEPTH_8U, 3);
orgimage=[self CreateIplImageFromUIImage:[self applyToneCurveToImage:[UIImage imageNamed:#"GolfImage.jpeg"] ] ];
Mat matRGB = Mat(orgimage);
//ipl imaeg is also converted to HSV; hue is used to find certain color
IplImage *imgHSV = cvCreateImage(cvGetSize(orgimage), 8, 3); //2
cvCvtColor(orgimage, imgHSV, CV_BGR2HSV);
IplImage *imgThreshed = cvCreateImage(cvGetSize(orgimage), 8, 1); //3
// cvInRangeS(imgHSV, cvScalar(_Hmin, _Smin, _Vmin), cvScalar(_Hmax , _Smax, _Vmax), imgThreshed);
cvInRangeS(imgHSV, cvScalar(0.00, 0.00, 34.82), cvScalar(180.00 , 202.54, 256.00), imgThreshed);
Originalimage=nil;
cvReleaseImage(&iplImage);
cvReleaseImage(&orgimage);
cvReleaseImage(&imgHSV);
[self didFinishProcessingImage:imgThreshed];
}
Output Image:
You don't need openCV for any of this. You should be able to get this result using Core Image
See this question How to change minimum or maximum value by CIFilter in Core image?
Where I give a fairly detailed answer on the manipulation of tone curves.
This will cover your steps 2 and 4. For step 1 (saturation) try CIColorControls. For step 3 (convert to grayscale) you could also use CIColorControls, but that would involove dropping saturation to 0, not what you want. Instead you can use CIMaximumComponentor CIMinimumComponent. For step 5, you could use the result of 1-4 as a mask with a flat colour.
OpenCV will allow you to pick out the ball from the rest of the image (I guess this is what you want to achieve, you didn't mention it in your question). You can refer to this question:
How does the HoughCircle function works? I can't get it to work properly which I answered with an accompanying demo project: https://github.com/foundry/OpenCVCircles. You can pass the result of your Core Image processing in to openCV by converting to the openCV Mat format from UIImage (that linked project shows you how to do this).

UIImage face detection

I am trying to write a routine that takes a UIImage and returns a new UIImage that contains just the face. This would seem to be very straightforward, but my brain is having problems getting around the CoreImage vs. UIImage spaces.
Here's the basics:
- (UIImage *)imageFromImage:(UIImage *)image inRect:(CGRect)rect {
CGImageRef sourceImageRef = [image CGImage];
CGImageRef newImageRef = CGImageCreateWithImageInRect(sourceImageRef, rect);
UIImage *newImage = [UIImage imageWithCGImage:newImageRef];
CGImageRelease(newImageRef);
return newImage;
}
-(UIImage *)getFaceImage:(UIImage *)picture {
CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeFace
context:nil
options:[NSDictionary dictionaryWithObject: CIDetectorAccuracyHigh forKey: CIDetectorAccuracy]];
CIImage *ciImage = [CIImage imageWithCGImage: [picture CGImage]];
NSArray *features = [detector featuresInImage:ciImage];
// For simplicity, I'm grabbing the first one in this code sample,
// and we can all pretend that the photo has one face for sure. :-)
CIFaceFeature *faceFeature = [features objectAtIndex:0];
return imageFromImage:picture inRect:faceFeature.bounds;
}
The image that is returned is from the flipped image. I've tried adjusting faceFeature.bounds using something like this:
CGAffineTransform t = CGAffineTransformMakeScale(1.0f,-1.0f);
CGRect newRect = CGRectApplyAffineTransform(faceFeature.bounds,t);
... but that gives me results outside the image.
I'm sure there's something simple to fix this, but short of calculating the bottom-down and then creating a new rect using that as the X, is there a "proper" way to do this?
Thanks!
It's much easier and less messy to just use CIContext to crop your face from the image. Something like this:
CGImageRef cgImage = [_ciContext createCGImage:[CIImage imageWithCGImage:inputImage.CGImage] fromRect:faceFeature.bounds];
UIImage *croppedFace = [UIImage imageWithCGImage:cgImage];
Where inputImage is your UIImage object and faceFeature object is of type CIFaceFeature that you get from [CIDetector featuresInImage:] method.
Since there doesn't seem to be a simple way to do this, I just wrote some code to do it:
CGRect newBounds = CGRectMake(faceFeature.bounds.origin.x,
_picture.size.height - faceFeature.bounds.origin.y - largestFace.bounds.size.height,
faceFeature.bounds.size.width,
faceFeature.bounds.size.height);
This worked a charm.
There is no simple way to achieve this, the problem is that the images from the iPhone camera are always in portrait mode, and metadata settings are used to get them to display correctly. You will also get better accuracy in your face detection call if you tell it the rotation of the image beforehand. Just to make things complicated, you have to pass it the image orientation in EXIF format.
Fortunately there is an apple sample project that covers all of this called Squarecam, i suggest you check it for details

Resources