karthik.Barcode.Barcode.java Source code

Java tutorial

Introduction

Here is the source code for karthik.Barcode.Barcode.java

Source

/*
 * Copyright (C) 2014 karthik
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package karthik.Barcode;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import org.opencv.core.*;
import org.opencv.highgui.Highgui;
import org.opencv.imgproc.Imgproc;

/**
 *
 * @author karthik
 */
public abstract class Barcode {
    // parent class containing common methods and definitions for 1D and 2D barcode searches

    public String getName() {
        return name;
    }

    // flag to indicate what kind of searches to perform on image to locate barcode
    protected int sizeFlag = TryHarderFlags.VERY_SMALL_MATRIX.value();
    protected boolean postProcessResizeBarcode = true;
    protected static double USE_ROTATED_RECT_ANGLE = 361;

    protected String name; // filename of barcode image file

    boolean DEBUG_IMAGES; // flag if we want to show intermediate steps for debugging

    SearchParameters searchParams; //various parameters and thresholds used during the search 
    protected ImageInfo img_details;
    protected int rows, cols;

    List<CandidateResult> candidateBarcodes = new ArrayList<CandidateResult>();

    static enum CodeType {

        LINEAR, MATRIX
    };

    Barcode(String filename, TryHarderFlags flag) throws IOException {
        name = filename;
        img_details = new ImageInfo(loadImage());

        rows = img_details.src_original.rows();
        cols = img_details.src_original.cols();

        setBarcodeSize(flag);

        DEBUG_IMAGES = false;
    }

    Barcode(Mat img, TryHarderFlags flag) throws IOException {
        // used in mobile implementation to avoid recreating Mat objects repeatedly
        img_details = new ImageInfo(img);

        rows = img_details.src_original.rows();
        cols = img_details.src_original.cols();

        setBarcodeSize(flag);
        DEBUG_IMAGES = false;
    }

    public static boolean updateImage(Barcode barcode, final Mat img, final String img_name) {
        barcode.name = img_name;
        return updateImage(barcode, img);
    }

    public static boolean updateImage(Barcode barcode, Mat img) {
        // used for video or camera feed when all images are the same size
        int orig_rows = barcode.img_details.src_original.rows();
        int orig_cols = barcode.img_details.src_original.cols();

        int new_rows = img.rows();
        int new_cols = img.cols();

        if ((orig_rows != new_rows) || (orig_cols != new_cols))
            return false;

        barcode.candidateBarcodes.clear();
        barcode.img_details.src_original = img;
        Imgproc.resize(barcode.img_details.src_original, barcode.img_details.src_scaled,
                barcode.img_details.src_scaled.size(), 0, 0, Imgproc.INTER_AREA);
        Imgproc.cvtColor(barcode.img_details.src_scaled, barcode.img_details.src_grayscale, Imgproc.COLOR_RGB2GRAY);

        return true;
    }

    public void setBarcodeSize(TryHarderFlags size) {
        // at least one of the size flags must be set so it chooses NORMAL as the default if nothing is set
        sizeFlag = size.value();
        setSearchParameters(size);
    }

    public void doPostProcessResizeBarcode(boolean postProcess) {
        postProcessResizeBarcode = postProcess;
    }

    protected void setSearchParameters(TryHarderFlags flags) {
        // should not be used when multiple size flags are set
        // it will set the search parameters to one of them and ignore the others

        if ((sizeFlag & TryHarderFlags.SMALL.value()) != 0)
            searchParams = SearchParameters.getSmallParameters();

        if ((sizeFlag & TryHarderFlags.LARGE.value()) != 0)
            searchParams = SearchParameters.getLargeParameters();

        if ((sizeFlag & TryHarderFlags.NORMAL.value()) != 0)
            searchParams = SearchParameters.getNormalParameters();

        if ((sizeFlag & TryHarderFlags.VERY_SMALL_MATRIX.value()) != 0)
            searchParams = SearchParameters.getVSmall_MatrixParameters();

        preprocess_image();
    }

    // actual locateBarcode algo must be implemented in child class
    public abstract List<CandidateResult> locateBarcode() throws IOException;

    protected void preprocess_image() {
        // pre-process image to convert to grayscale and do morph black hat
        // also resizes image if it is above a specified size and sets the search parameters
        // based on image size

        // shrink image if it is above a certain size   
        // it reduces image size for large images which helps with processing speed
        // and reducing sensitivity to barcode size within the image
        if (rows > searchParams.MAX_ROWS) {
            cols = (int) (cols * (searchParams.MAX_ROWS * 1.0 / rows));
            rows = searchParams.MAX_ROWS;
            img_details.src_scaled = new Mat(rows, cols, CvType.CV_32F);
            Imgproc.resize(img_details.src_original, img_details.src_scaled, img_details.src_scaled.size(), 0, 0,
                    Imgproc.INTER_AREA);
        }
        if (img_details.src_scaled == null)
            img_details.src_scaled = img_details.src_original.clone();

        searchParams.setImageSpecificParameters(rows, cols);
        // do pre-processing to increase contrast
        img_details.initializeMats(rows, cols, searchParams);

        Imgproc.cvtColor(img_details.src_scaled, img_details.src_grayscale, Imgproc.COLOR_RGB2GRAY);
    }

    protected Mat scale_candidateBarcode(Mat candidate) {
        // resizes candidate image to have at least MIN_COLS columns and MIN_ROWS rows
        // called when RESIZE_BEFORE_DECODE is set - seems to help ZXing decode barcode

        int MIN_COLS = 200;
        int MIN_ROWS = 200;

        int num_rows = candidate.rows();
        int num_cols = candidate.cols();

        if ((num_cols > MIN_COLS) && (num_rows > MIN_ROWS))
            return candidate;

        if (num_cols < MIN_COLS) {
            num_rows = (int) (num_rows * MIN_COLS / (1.0 * num_cols));
            num_cols = MIN_COLS;
        }

        if (num_rows < MIN_ROWS) {
            num_cols = (int) (num_cols * MIN_ROWS / (1.0 * num_rows));
            num_rows = MIN_ROWS;
        }

        Mat result = Mat.zeros(num_rows, num_cols, candidate.type());

        Imgproc.resize(candidate, result, result.size(), 0, 0, Imgproc.INTER_CUBIC);
        return result;
    }

    protected void connectComponents() {
        // connect large components by doing morph close followed by morph open
        // use larger element size for erosion to remove small elements joined by dilation
        Mat small_elemSE, large_elemSE;

        if (searchParams.is_VSmallMatrix) {
            // test out slightly different process for small codes in a large image
            small_elemSE = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, searchParams.elem_size);
            large_elemSE = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, searchParams.large_elem_size);

            Imgproc.dilate(img_details.probabilities, img_details.probabilities, small_elemSE);
            Imgproc.erode(img_details.probabilities, img_details.probabilities, large_elemSE);

            Imgproc.erode(img_details.probabilities, img_details.probabilities, small_elemSE);
            Imgproc.dilate(img_details.probabilities, img_details.probabilities, large_elemSE);
            return;
        }

        small_elemSE = Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, searchParams.elem_size);
        large_elemSE = Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, searchParams.large_elem_size);

        Imgproc.dilate(img_details.probabilities, img_details.probabilities, small_elemSE);
        Imgproc.erode(img_details.probabilities, img_details.probabilities, large_elemSE);

        Imgproc.erode(img_details.probabilities, img_details.probabilities, small_elemSE);
        Imgproc.dilate(img_details.probabilities, img_details.probabilities, large_elemSE);
    }

    protected int calc_rect_sum(int[] integralArray, int numRows, int numCols, int top_row, int bottom_row,
            int left_col, int right_col) {
        // calculates sum of values within a rectangle from a given integral image
        // if the right col or bottom row falls outside the image bounds, sets it to max col and max row
        // in actuality, top_row - 1 and left_col - 1 are used - see p. 185 of Learning OpenCV ed. 1 by Gary Bradski for an explanation
        // if top_row or left_col are outside image boundaries, it uses 0 for their value
        // this is useful when one part of the rectangle lies outside the image bounds

        int top_left, top_right, bottom_left, bottom_right;
        int sum;

        // do bounds checking on provided parameters
        bottom_row = java.lang.Math.min(bottom_row, numRows);
        right_col = java.lang.Math.min(right_col, numCols);

        bottom_right = integralArray[bottom_row * numCols + right_col];
        top_right = (top_row < 0) ? 0 : integralArray[top_row * numCols + right_col];
        top_left = (left_col < 0 || top_row < 0) ? 0 : integralArray[top_row * numCols + left_col];
        bottom_left = (left_col < 0) ? 0 : integralArray[bottom_row * numCols + left_col];

        sum = (bottom_right - bottom_left - top_right + top_left);
        return sum;
    }

    protected static void write_Mat(String filename, Mat img) {
        // write the contents of a Mat object to disk
        try {
            PrintStream original = new PrintStream(System.out);
            PrintStream printStream = new PrintStream(new FileOutputStream(new File(filename)));
            System.setOut(printStream);
            System.out.println(img.dump());
            System.setOut(original);
        } catch (IOException ioe) {
        }

    }

    protected Mat loadImage() throws IOException {
        // reads the image file in the class variable name
        // Highgui produces an incomprehensible error message if the filename is incorrect so
        // we do the check ourselves first
        File f = new File(name);
        if (!f.isFile())
            throw new IOException("BarcodeLocalizer was called with an invalid filename " + name);
        return Highgui.imread(name, Highgui.CV_LOAD_IMAGE_COLOR);
    }

}