1

To improve my knowledge of imaging and get some experience working with the topics, I decided to create a license plate recognition algorithm on the Android platform.

The first step is detection, for which I decided to implement a recent paper titled "A Robust and Efficient Approach to License Plate Detection". The paper presents their idea very well and uses quite simple techniques to achieve detection. Besides some details lacking in the paper, I implemented the bilinear downsampling, converting to gray scale, and the edging + adaptive thresholding as described in Section 3A, 3B.1, and 3B.2. Unfortunately, I am not getting the output this paper presents in e.g. figure 3 and 6.

The image I use for testing is as follows:

colored image

The gray scale (and downsampled) version looks fine (see the bottom of this post for the actual implementation), I used a well-known combination of the RGB components to produce it (paper does not mention how, so I took a guess).

enter image description here

Next is the initial edge detection using the Sobel filter outlined. This produces an image similar to the ones presented in figure 6 of the paper.

enter image description here

And finally, the remove the "weak edges" they apply adaptive thresholding using a 20x20 window. Here is where things go wrong.

enter image description here

As you can see, it does not function properly, even though I am using their stated parameter values. Additionally I have tried:

  • Changing the beta parameter.
  • Use a 2d int array instead of Bitmap objects to simplify creating the integral image.
  • Try a higher Gamma parameter so the initial edge detection allows more "edges".
  • Change the window to e.g. 10x10.

Yet none of the changes made an improvement; it keeps producing images as the one above. My question is: what am I doing different than what is outlined in the paper? and how can I get the desired output?

Code

The (cleaned) code I use:

public int[][] toGrayscale(Bitmap bmpOriginal) {

    int width = bmpOriginal.getWidth();
    int height = bmpOriginal.getHeight();

    // color information
    int A, R, G, B;
    int pixel;

    int[][] greys = new int[width][height];

    // scan through all pixels
    for (int x = 0; x < width; ++x) {
        for (int y = 0; y < height; ++y) {
            // get pixel color
            pixel = bmpOriginal.getPixel(x, y);
            R = Color.red(pixel);
            G = Color.green(pixel);
            B = Color.blue(pixel);
            int gray = (int) (0.2989 * R + 0.5870 * G + 0.1140 * B);
            greys[x][y] = gray;
        }
    }
    return greys;
}

The code for edge detection:

private int[][] detectEges(int[][] detectionBitmap) {

    int width = detectionBitmap.length;
    int height = detectionBitmap[0].length;
    int[][] edges = new int[width][height];

    // Loop over all pixels in the bitmap
    int c1 = 0;
    int c2 = 0;
    for (int y = 0; y < height; y++) {
        for (int x = 2; x < width -2; x++) {
            // Calculate d0 for each pixel
            int p0 = detectionBitmap[x][y];
            int p1 = detectionBitmap[x-1][y];
            int p2 = detectionBitmap[x+1][y];
            int p3 = detectionBitmap[x-2][y];
            int p4 = detectionBitmap[x+2][y];


            int d0 = Math.abs(p1 + p2 - 2*p0) + Math.abs(p3 + p4 - 2*p0);
            if(d0 >= Gamma) {
                c1++;
                edges[x][y] = Gamma;
            } else {
                c2++;
                edges[x][y] = d0;
            }
        }
    }
    return edges;
}

The code for adaptive thresholding. The SAT implementation is taken from here:

private int[][] AdaptiveThreshold(int[][] detectionBitmap) {

    // Create the integral image
    processSummedAreaTable(detectionBitmap);

    int width = detectionBitmap.length;
    int height = detectionBitmap[0].length;

    int[][] binaryImage = new int[width][height];

    int white = 0;
    int black = 0;
    int h_w = 20; // The window size
    int half = h_w/2;

    // Loop over all pixels in the bitmap
    for (int y = half; y < height - half; y++) {
        for (int x = half; x < width - half; x++) {
            // Calculate d0 for each pixel
            int sum = 0;
            for(int k =  -half; k < half - 1; k++) {
                for (int j = -half; j < half - 1; j++) {
                    sum += detectionBitmap[x + k][y + j];
                }
            }

            if(detectionBitmap[x][y] >= (sum / (h_w * h_w)) * Beta) {
                binaryImage[x][y] = 255;
                white++;
            } else {
                binaryImage[x][y] =  0;
                black++;
            }
        }
    }
    return binaryImage;
}

/**
 * Process given matrix into its summed area table (in-place)
 * O(MN) time, O(1) space
 * @param matrix    source matrix
 */
private void processSummedAreaTable(int[][] matrix) {
    int rowSize = matrix.length;
    int colSize = matrix[0].length;
    for (int i=0; i<rowSize; i++) {
        for (int j=0; j<colSize; j++) {
            matrix[i][j] = getVal(i, j, matrix);
        }
    }
}
/**
 * Helper method for processSummedAreaTable
 * @param row       current row number
 * @param col       current column number
 * @param matrix    source matrix
 * @return      sub-matrix sum
 */
private int getVal (int row, int col, int[][] matrix) {
    int leftSum;                    // sub matrix sum of left matrix
    int topSum;                     // sub matrix sum of top matrix
    int topLeftSum;                 // sub matrix sum of top left matrix
    int curr = matrix[row][col];    // current cell value
    /* top left value is itself */
    if (row == 0 && col == 0) {
        return curr;
    }
    /* top row */
    else if (row == 0) {
        leftSum = matrix[row][col - 1];
        return curr + leftSum;
    }
    /* left-most column */
    if (col == 0) {
        topSum = matrix[row - 1][col];
        return curr + topSum;
    }
    else {
        leftSum = matrix[row][col - 1];
        topSum = matrix[row - 1][col];
        topLeftSum = matrix[row - 1][col - 1]; // overlap between leftSum and topSum
        return curr + leftSum + topSum - topLeftSum;
    }
}
Gooey
  • 4,740
  • 10
  • 42
  • 76
  • You do know that [one of the authors](http://www.wzou.eu/) has the corresponding source code online? TL;DR: We will not read the whole paper along with your source code to debug this for you. This is simply too time-intensive for a platform like SO. Sorry pal, you are on your own on this one. – Turing85 Dec 30 '17 at 20:31
  • I don't see how `for (int y = half; y < height - half; y++){...` in the adaptive thresholding would loop through the whole image. But then again I don't know how `half` ( or `h_w `) gets initialized. – Mick Mnemonic Dec 30 '17 at 20:47
  • @Turing85 They do not. They have executable code online which is a .msi and .exe. The source code is unavailable unfortunately. Else I would've taken a look there obviously (and not having to guess things like how they grayscaled their images). – Gooey Dec 31 '17 at 13:42
  • @MickMnemonic My bad, I forgot to include the global variables. I added `h_w` to the code. The reason I loop from `half` to `height - half` is to not get out of bounds when running the 20x20 window in the adaptive threshold step. – Gooey Dec 31 '17 at 14:13
  • 1
    Your adaptive threshold algorithm seems ok. Remove the call to `processSummedAreaTable`, I think it computes the integral image? You are trying to threshold its output, which is not going to work. – Cris Luengo Jan 01 '18 at 16:24
  • 1
    You can use the integral image to speed up computation of the local threshold value (avoid the loops over k and j), but you need to threshold the gradient image, not its integral. – Cris Luengo Jan 01 '18 at 16:26

1 Answers1

0

Marvin provides an approach to find text regions. Perhaps it can be a start point for you:

Find Text Regions in Images: http://marvinproject.sourceforge.net/en/examples/findTextRegions.html

This approach was also used in this question:
How do I separates text region from image in java

Using your image I got this output: enter image description here

Source Code:

package textRegions;

import static marvin.MarvinPluginCollection.findTextRegions;

import java.awt.Color;
import java.util.List;

import marvin.image.MarvinImage;
import marvin.image.MarvinSegment;
import marvin.io.MarvinImageIO;

public class FindVehiclePlate {

    public FindVehiclePlate() {
        MarvinImage image = MarvinImageIO.loadImage("./res/vehicle.jpg");
        image = findText(image, 30, 20, 100, 170);
        MarvinImageIO.saveImage(image, "./res/vehicle_out.png");
    }

    public MarvinImage findText(MarvinImage image, int maxWhiteSpace, int maxFontLineWidth, int minTextWidth, int grayScaleThreshold){
        List<MarvinSegment> segments = findTextRegions(image, maxWhiteSpace, maxFontLineWidth, minTextWidth, grayScaleThreshold);

        for(MarvinSegment s:segments){
            if(s.height >= 10){
                s.y1-=20;
                s.y2+=20;
                image.drawRect(s.x1, s.y1, s.x2-s.x1, s.y2-s.y1, Color.red);
                image.drawRect(s.x1+1, s.y1+1, (s.x2-s.x1)-2, (s.y2-s.y1)-2, Color.red);
                image.drawRect(s.x1+2, s.y1+2, (s.x2-s.x1)-4, (s.y2-s.y1)-4, Color.red);
            }
        }
        return image;
    }

    public static void main(String[] args) {
        new FindVehiclePlate();
    }
}
Gabriel Archanjo
  • 4,547
  • 2
  • 34
  • 41