5

I am developing android app that uses tesseract OCR to scan a text from image,
I heard that binarizing image before performing OCR on it will give better result,
So I start looking for a code that do the operation,

I found few but its actually in java and needs awt library ... so they don't work on android.
So can you help me to find one.
thank you

Firas Al Mannaa
  • 916
  • 1
  • 11
  • 30

5 Answers5

6

I have to do a similar task as part of a project for an asignment. I found in my workspace this piece of code, I think this is what you need:

Bitmap img = BitmapFactory.decodeResource(this.getResources(), drawable.testimage);
Paint paint = new Paint();

ColorMatrix cm = new ColorMatrix();
float a = 77f;
float b = 151f;
float c = 28f;
float t = 120 * -256f;
cm.set(new float[] { a, b, c, 0, t, a, b, c, 0, t, a, b, c, 0, t, 0, 0, 0, 1, 0 });
paint.setColorFilter(new ColorMatrixColorFilter(cm));
canvas.drawBitmap(img, 0, 0, paint);

Here I used ColorMatrix to generate a black and white image from a color one. Also I found this piece of code that I used to convert a color image to a gray scale image:

Bitmap result = Bitmap.createBitmap(destWidth, destHeight,Bitmap.Config.RGB_565);
RectF destRect = new RectF(0, 0, destWidth, destHeight);
Canvas canvas = new Canvas(result);
Paint paint = new Paint();
ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.setSaturation(0);
ColorFilter filter = new ColorMatrixColorFilter(colorMatrix);
paint.setColorFilter(filter);
canvas.drawBitmap(bitmap, sourceRect, destRect, paint); 

Hope this help you.

Diego
  • 1,531
  • 1
  • 15
  • 27
  • What was the problem? perhaps I can help you fixing it. – Diego May 07 '13 at 15:35
  • maybe I didn't do it right, but should I use the two codes you give above together o only one of them – Firas Al Mannaa May 07 '13 at 15:38
  • 2
    Only one of them, the firts one converts the image img to black and white, note that the first line assumes you have an image testimage in your resources. You need to replace the first line to load your image. The second piece of code converts an image to gray scale. Again, you need to load your own image and get the width and height (destWidth and destHeight) from it. Anything you need just let me know. – Diego May 07 '13 at 15:43
  • @Diego Hi, similarly I am also trying to binarise an image before ocr process, can I know where can I paste this code? I tried putting it in my codes and it could not resolve the symbol "canvas". I am running Android Studio 1.5.1 – Donovan Tan Feb 23 '16 at 03:10
4

A Simple Solution

In the following, I simply alter each pixel in an image based on the normal 3-dimension-space distance formula. I decide whether a pixel should be black or white based on how far it is from each of these colors. For example, (1,2,3) is closer to (0,0,0) than it is to (255,255,255) and therefore it is decided to be black. I'm sure there are more clever algorithms out there. This is just a simple one

MainActivity.java

package com.example.binarizeimage;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Environment;
import android.widget.ImageView;

import com.example.binarizeimage.R.drawable;

/**
 * @author Sherif elKhatib - shush
 *
 */
public class MainActivity extends Activity {
    /**
     * Boolean that tells me how to treat a transparent pixel (Should it be black?)
     */
    private static final boolean TRASNPARENT_IS_BLACK = false;
    /**
     * This is a point that will break the space into Black or white
     * In real words, if the distance between WHITE and BLACK is D;
     * then we should be this percent far from WHITE to be in the black region.
     * Example: If this value is 0.5, the space is equally split.  
     */
    private static final double SPACE_BREAKING_POINT = 13.0/30.0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //this is the original image
        Bitmap theOriginalImage = BitmapFactory.decodeResource(this.getResources(), drawable.ic_launcher);
        //this is the image that is binarized
        Bitmap binarizedImage = convertToMutable(theOriginalImage);
        // I will look at each pixel and use the function shouldBeBlack to decide 
        // whether to make it black or otherwise white
        for(int i=0;i<binarizedImage.getWidth();i++) {
            for(int c=0;c<binarizedImage.getHeight();c++) {
                int pixel = binarizedImage.getPixel(i, c);
                if(shouldBeBlack(pixel))
                    binarizedImage.setPixel(i, c, Color.BLACK);
                else
                    binarizedImage.setPixel(i, c, Color.WHITE);
            }
        }


        ImageView iv = (ImageView) findViewById(R.id.imageView1);
        ImageView ivb = (ImageView) findViewById(R.id.ImageView01);
        //show the original image
        iv.setImageBitmap(BitmapFactory.decodeResource(this.getResources(), drawable.ic_launcher));
        //show the binarized image
        ivb.setImageBitmap(binarizedImage);
    }
    /**
     * @param pixel the pixel that we need to decide on
     * @return boolean indicating whether this pixel should be black
     */
    private static boolean shouldBeBlack(int pixel) {
        int alpha = Color.alpha(pixel);
        int redValue = Color.red(pixel);
        int blueValue = Color.blue(pixel);
        int greenValue = Color.green(pixel);
        if(alpha == 0x00) //if this pixel is transparent let me use TRASNPARENT_IS_BLACK
            return TRASNPARENT_IS_BLACK;
        // distance from the white extreme
        double distanceFromWhite = Math.sqrt(Math.pow(0xff - redValue, 2) + Math.pow(0xff - blueValue, 2) + Math.pow(0xff - greenValue, 2));
        // distance from the black extreme //this should not be computed and might be as well a function of distanceFromWhite and the whole distance
        double distanceFromBlack = Math.sqrt(Math.pow(0x00 - redValue, 2) + Math.pow(0x00 - blueValue, 2) + Math.pow(0x00 - greenValue, 2));
        // distance between the extremes //this is a constant that should not be computed :p
        double distance = distanceFromBlack + distanceFromWhite;
        // distance between the extremes
        return ((distanceFromWhite/distance)>SPACE_BREAKING_POINT);
    }
    /**
     * @author Derzu
     * 
     * @see http://stackoverflow.com/a/9194259/833622
     * 
     * Converts a immutable bitmap to a mutable bitmap. This operation doesn't allocates
     * more memory that there is already allocated.
     * 
     * @param imgIn - Source image. It will be released, and should not be used more
     * @return a copy of imgIn, but muttable.
     */
    public static Bitmap convertToMutable(Bitmap imgIn) {
        try {
            //this is the file going to use temporally to save the bytes. 
            // This file will not be a image, it will store the raw image data.
            File file = new File(Environment.getExternalStorageDirectory() + File.separator + "temp.tmp");

            //Open an RandomAccessFile
            //Make sure you have added uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
            //into AndroidManifest.xml file
            RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");

            // get the width and height of the source bitmap.
            int width = imgIn.getWidth();
            int height = imgIn.getHeight();
            Config type = imgIn.getConfig();

            //Copy the byte to the file
            //Assume source bitmap loaded using options.inPreferredConfig = Config.ARGB_8888;
            FileChannel channel = randomAccessFile.getChannel();
            MappedByteBuffer map = channel.map(MapMode.READ_WRITE, 0, imgIn.getRowBytes()*height);
            imgIn.copyPixelsToBuffer(map);
            //recycle the source bitmap, this will be no longer used.
            imgIn.recycle();
            System.gc();// try to force the bytes from the imgIn to be released

            //Create a new bitmap to load the bitmap again. Probably the memory will be available. 
            imgIn = Bitmap.createBitmap(width, height, type);
            map.position(0);
            //load it back from temporary 
            imgIn.copyPixelsFromBuffer(map);
            //close the temporary file and channel , then delete that also
            channel.close();
            randomAccessFile.close();

            // delete the temp file
            file.delete();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } 

        return imgIn;
    }
}

*activity_main.xml*

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/textView2"
        android:layout_centerHorizontal="true"
        android:text="Original Image" />

    <ImageView
        android:id="@+id/imageView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/textView1"
        android:layout_centerHorizontal="true"
        android:src="@drawable/ic_launcher" />

    <TextView
        android:id="@+id/TextView02"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/textView1"
        android:layout_below="@+id/imageView1"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="28dp"
        android:text="YES/NO Image" />

    <ImageView
        android:id="@+id/ImageView01"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/TextView02"
        android:layout_centerHorizontal="true"
        android:src="@drawable/ic_launcher" />

</RelativeLayout>
Sherif elKhatib
  • 45,786
  • 16
  • 89
  • 106
  • I will try it and come again, thaannnnnnnnxxxxx HABIB ALBY – Firas Al Mannaa May 14 '13 at 14:03
  • hi, I am also finding a solution to this. How can I implement the image to your codes? I tried creating a new project and pasting your codes in but the application does not launch on the device when i run it. Or can you take a look at my question: http://stackoverflow.com/questions/35568362/binarize-image-before-ocr-scan ? – Donovan Tan Feb 23 '16 at 05:23
2

I had a similar project involving colors, although on another platform.

Although they might be other better algorithms, I used a function (GetColorDistance) to calculate the distances between two colors, in the 3D RGB space, by using the Pythagorean theorem. GetNewColor calculates whether the a color is closer to white or black, and then returns a black or white accordingly. Finally, the GetBitmapBinary function processes the pixels on the bitmap and converts them into black & white.

private Bitmap GetBinaryBitmap(Bitmap bitmap_src)
    {
        Bitmap bitmap_new=bitmap_src.copy(bitmap_src.getConfig(), true);



    for(int x=0; x<bitmap_new.getWidth(); x++)
    {
        for(int y=0; y<bitmap_new.getHeight(); y++)
        {
            int color=bitmap_new.getPixel(x, y);
            color=GetNewColor(color);
            bitmap_new.setPixel(x, y, color);
        }
    }

    return bitmap_new;
}


private double GetColorDistance(int c1, int c2)
{
    int db=Color.blue(c1)-Color.blue(c2);
    int dg=Color.green(c1)-Color.green(c2);
    int dr=Color.red(c1)-Color.red(c2);


    double d=Math.sqrt(  Math.pow(db, 2) + Math.pow(dg, 2) +Math.pow(dr, 2)  );
    return d;
}

private int GetNewColor(int c)
{
    double dwhite=GetColorDistance(c,Color.WHITE);
    double dblack=GetColorDistance(c,Color.BLACK);

    if(dwhite<=dblack)
    {
        return Color.WHITE;

    }
    else
    {
        return Color.BLACK;
    }


}

You can modify the GetNewColor function for better results in different light densities. For example, you may multiply dblack by 1.5 so that darker pixels can become white, in a dark environment.

ntrstd11
  • 153
  • 5
1

Wouldn't be to hard to port this from java to android:

/**
 * Image binarization - Otsu algorithm
 *
 * Author: Bostjan Cigan (http://zerocool.is-a-geek.net)
 *
 */

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;

public class OtsuBinarize {

    private static BufferedImage original, grayscale, binarized;

    public static void main(String[] args) throws IOException {

        File original_f = new File(args[0]+".jpg");
        String output_f = args[0]+"_bin";
        original = ImageIO.read(original_f);
        grayscale = toGray(original);
        binarized = binarize(grayscale);
        writeImage(output_f);         

    }

    private static void writeImage(String output) throws IOException {
        File file = new File(output+".jpg");
        ImageIO.write(binarized, "jpg", file);
    }

    // Return histogram of grayscale image
    public static int[] imageHistogram(BufferedImage input) {

        int[] histogram = new int[256];

        for(int i=0; i<histogram.length; i++) histogram[i] = 0;

        for(int i=0; i<input.getWidth(); i++) {
            for(int j=0; j<input.getHeight(); j++) {
                int red = new Color(input.getRGB (i, j)).getRed();
                histogram[red]++;
            }
        }

        return histogram;

    }

    // The luminance method
    private static BufferedImage toGray(BufferedImage original) {

        int alpha, red, green, blue;
        int newPixel;

        BufferedImage lum = new BufferedImage(original.getWidth(), original.getHeight(), original.getType());

        for(int i=0; i<original.getWidth(); i++) {
            for(int j=0; j<original.getHeight(); j++) {

                // Get pixels by R, G, B
                alpha = new Color(original.getRGB(i, j)).getAlpha();
                red = new Color(original.getRGB(i, j)).getRed();
                green = new Color(original.getRGB(i, j)).getGreen();
                blue = new Color(original.getRGB(i, j)).getBlue();

                red = (int) (0.21 * red + 0.71 * green + 0.07 * blue);
                // Return back to original format
                newPixel = colorToRGB(alpha, red, red, red);

                // Write pixels into image
                lum.setRGB(i, j, newPixel);

            }
        }

        return lum;

    }

    // Get binary treshold using Otsu's method
    private static int otsuTreshold(BufferedImage original) {

        int[] histogram = imageHistogram(original);
        int total = original.getHeight() * original.getWidth();

        float sum = 0;
        for(int i=0; i<256; i++) sum += i * histogram[i];

        float sumB = 0;
        int wB = 0;
        int wF = 0;

        float varMax = 0;
        int threshold = 0;

        for(int i=0 ; i<256 ; i++) {
            wB += histogram[i];
            if(wB == 0) continue;
            wF = total - wB;

            if(wF == 0) break;

            sumB += (float) (i * histogram[i]);
            float mB = sumB / wB;
            float mF = (sum - sumB) / wF;

            float varBetween = (float) wB * (float) wF * (mB - mF) * (mB - mF);

            if(varBetween > varMax) {
                varMax = varBetween;
                threshold = i;
            }
        }

        return threshold;

    }

    private static BufferedImage binarize(BufferedImage original) {

        int red;
        int newPixel;

        int threshold = otsuTreshold(original);

        BufferedImage binarized = new BufferedImage(original.getWidth(), original.getHeight(), original.getType());

        for(int i=0; i<original.getWidth(); i++) {
            for(int j=0; j<original.getHeight(); j++) {

                // Get pixels
                red = new Color(original.getRGB(i, j)).getRed();
                int alpha = new Color(original.getRGB(i, j)).getAlpha();
                if(red > threshold) {
                    newPixel = 255;
                }
                else {
                    newPixel = 0;
                }
                newPixel = colorToRGB(alpha, newPixel, newPixel, newPixel);
                binarized.setRGB(i, j, newPixel); 

            }
        }

        return binarized;

    }

    // Convert R, G, B, Alpha to standard 8 bit
    private static int colorToRGB(int alpha, int red, int green, int blue) {

        int newPixel = 0;
        newPixel += alpha;
        newPixel = newPixel << 8;
        newPixel += red; newPixel = newPixel << 8;
        newPixel += green; newPixel = newPixel << 8;
        newPixel += blue;

        return newPixel;

    }

}
Richard
  • 14,427
  • 9
  • 57
  • 85
1

simple clean and first first convert image to grayscale(if u don't you'll get an input image error) after conversion use the adaptive threshold method to complete task code:

 Mat tmp = new Mat(bitmap.getWidth(), bitmap.getHeight(), CvType.CV_8UC1);
                // Convert
                Utils.bitmapToMat(bitmap, tmp);

                Mat gray = new Mat(bitmap.getWidth(), bitmap.getHeight(),     CvType.CV_8UC1);
                // Conver the color
                Imgproc.cvtColor(tmp, gray, Imgproc.COLOR_RGB2GRAY);
                // Convert back to bitmap


                Mat destination = new Mat(gray.rows(),gray.cols(),gray.type());

                Imgproc.adaptiveThreshold(gray, destination, 255,
                        Imgproc.ADAPTIVE_THRESH_MEAN_C, Imgproc.THRESH_BINARY_INV, 15, 4);

                Utils.matToBitmap(destination, bitmap);
                imv_binary.setImageBitmap(bitmap);
r_allela
  • 792
  • 11
  • 23