diff --git a/app/src/main/java/unipd/se18/ocrcamera/BitmapBox.java b/app/src/main/java/unipd/se18/ocrcamera/BitmapBox.java new file mode 100644 index 00000000..428b038b --- /dev/null +++ b/app/src/main/java/unipd/se18/ocrcamera/BitmapBox.java @@ -0,0 +1,105 @@ +package com.example.imageprocessing; + +import android.graphics.Bitmap; +import com.example.imageprocessing.enumClasses.ProcessingResult; +import com.example.imageprocessing.interfaces.BitmapContainer; +import java.util.ArrayList; +import java.util.List; + + +/** + * Class used to manage the list of bitmaps and errors + * @author Thomas Porro (g1) + */ +class BitmapBox implements BitmapContainer { + private List container; + private int counter; + private ProcessingResult processingResult; + + + /** + * Constructor of the class that initialize the bitmaps's list and processingResult + * to a default value + */ + BitmapBox(){ + container = new ArrayList<>(); + counter = 0; + processingResult = ProcessingResult.PROCESSING_SUCCESSFUL; + } + + + /** + * Constructor of the class that initialize the bitmaps's list with a single image, + * and initialize processingResult with the desired value + */ + BitmapBox(Bitmap image, ProcessingResult value){ + container = new ArrayList<>(); + container.add(image); + counter = 0; + processingResult = value; + } + + + /** + * Constructor of the class that initialize the bitmaps's list and processingResult + * to the desired value + */ + BitmapBox(ProcessingResult value){ + container = new ArrayList<>(); + counter = 0; + processingResult = value; + } + + + /** + * Add a bitmap to the list + * @param croppedImage the image we want to add to the list + */ + void addBitmap(Bitmap croppedImage){ + container.add(croppedImage); + } + + + /** + * Set processingResult to the desired value + * @param value the new value of processingResult + */ + void setProcessingResult(ProcessingResult value){ + processingResult = value; + } + + + @Override + public ProcessingResult getProcessingResult(){ + return processingResult; + } + + @Override + public List getTextBitmaps() { + return container; + } + + @Override + public Bitmap getFirstBitmap(){ + if(this.hasNext()) { + return container.get(0); + } else { + return null; + } + } + + @Override + public Object next(){ + if(this.hasNext()) { + return container.get(counter++); + } else { + return null; + } + } + + @Override + public boolean hasNext(){ + return counter detectTextAreas(Mat filteredMat){ + List rectanglesList = new ArrayList<>(); + + //Saves all the contours in a list of MatOfPoint (multidimensional vector) + List contours = IPBuilder.doFindContours( + new IPBuilder.FindContoursBuilder(filteredMat) + .withMode(Imgproc.RETR_EXTERNAL) + .withMethod(Imgproc.CHAIN_APPROX_SIMPLE)); + + + //Fills rectanglesList with all the found rectangles of the image + MatOfPoint maxContour; + for (MatOfPoint contour : contours) { + maxContour = contour; + //Creates a rotated rectangle based on "max_contour + RotatedRect rect = Imgproc.minAreaRect(new MatOfPoint2f(maxContour.toArray())); + rectanglesList.add(rect); + } + + return rectanglesList; + } + + + /** + * Searches the rectangles in the matrix of an image to find + * the largest one, even if it's rotated + * @param filteredMat the path of the image you want to analyze + * @return A list of rectangles which contains the rectangle of text with the + * maximum area (it could be rotated) + * @author Thomas Porro (g1), Oscar Garrido (g1) + */ + private List detectMaxTextArea(Mat filteredMat){ + List rectanglesList = new ArrayList<>(); + + //Saves the contours in a list of MatOfPoint (multidimensional vector) + List contours = new ArrayList<>(); + Imgproc.findContours(filteredMat, contours, new Mat(), Imgproc.RETR_EXTERNAL, + Imgproc.CHAIN_APPROX_SIMPLE); + //The third parameter contains additional information that is unused + + /* + Finds the text rectangle with the largest area + and saves it in "max_contour" + */ + double maxArea = 0; + MatOfPoint maxContour = new MatOfPoint(); + for (MatOfPoint contour : contours) { + double area = Imgproc.contourArea(contour); + if (area > maxArea) { + maxArea = area; + maxContour = contour; + } + } + //Creates a rotated rectangle based on "max_contour + RotatedRect rect = Imgproc.minAreaRect(new MatOfPoint2f(maxContour.toArray())); + rectanglesList.add(rect); + + //Creates and return a rotated rectangle based on "max_contour" + return rectanglesList; + } + + + /** + * Crop the matrix with the given rectangle + * @param rectangle the part of the image you want to crop + * @param mat the matrix you want to crop + * @return a matrix that contains only the rectangle + * @author Thomas Porro(g1), Oscar Garrido (g1) + */ + private Mat crop(RotatedRect rectangle, Mat mat) { + + //Matrices we'll use + Mat croppedImg = new Mat(); + Mat rotatedImg = new Mat(); + Mat rotationMat; + + //Get angle and size from the bounding box + double angle = rectangle.angle; + Size rectSize = rectangle.size; + + //Thanks to http://felix.abecassis.me/2011/10/opencv-rotation-deskewing/ + if (rectangle.angle < -45.) { + angle += 90.0; + double width = rectSize.width; + double height = rectSize.height; + rectSize.width = height; + rectSize.height = width; + } + + //Creates the rotation matrix + rotationMat = getRotationMatrix2D(rectangle.center, angle, 1.0); + + //Perform the affine transformation (rotation) * + Imgproc.warpAffine(mat, rotatedImg, rotationMat, mat.size(), INTER_CUBIC); + + Log.d(TAG, "Channels = "+rotatedImg.channels()); + //Crop the resulting image + getRectSubPix(rotatedImg, rectSize, rectangle.center, croppedImg); + return croppedImg; + } + + /** + * See DetectTheTextMethods.java. + * @author Thomas Porro(g1) + */ + @Override + public TextRegions detectTextRegions(Bitmap image, DetectTheTextMethods method) { + TextAreas textContainer = new TextAreas(); + //Put the image into a matrix, if the conversion fails it return a textContainer + //with the full image + Mat img; + try { + img = IPUtils.conversionBitmapToMat(image); + } catch (ConversionFailedException e){ + Log.e(TAG, e.getErrorMessage()); + //Creates a Point object that head to the center of the full image + int centerHeight = image.getHeight()/2; + int centerWidth = image.getWidth()/2; + Point center = new Point(centerWidth, centerHeight); + + //Creates a Size object with the same size of the full image + Size imageDimensions = new Size(image.getHeight(), image.getWidth()); + + //Creates a RotatedRect containing the full image + double inclinationAngle=0; + RotatedRect fullImage = new RotatedRect(center, imageDimensions, inclinationAngle); + textContainer.addRegion(fullImage); + //This flag let the user know that occurred an error during the processing + textContainer.setProcessingResult(ProcessingResult.CONVERSION_FAILED); + return textContainer; + } + //Do the image Processing + Mat filteredMat = applyFilters(img); + //Add each element to the TextAreas's object + List rectanglesList = new ArrayList<>(); + switch(method){ + case DETECT_MAX_TEXT_AREA: rectanglesList = detectMaxTextArea(filteredMat); + break; + case DETECT_ALL_TEXT_AREAS: rectanglesList = detectTextAreas(filteredMat); + break; + } + for(RotatedRect rectangle : rectanglesList){ + textContainer.addRegion(rectangle); + } + return textContainer; + } + + /** + * See DetectTheTextMethods.java. + * @author Thomas Porro(g1) + */ + @Override + public BitmapContainer extractTextFromBitmap(Bitmap image, TextRegions textContainer) { + BitmapBox imgTextContainer = new BitmapBox(); + Mat img; + + //Converts the image into a matrix + try { + img = IPUtils.conversionBitmapToMat(image); + } catch (ConversionFailedException e){ + Log.e(TAG, e.getErrorMessage()); + imgTextContainer.addBitmap(image); + //This flag let the user know that occurred an error during the processing + imgTextContainer.setProcessingResult(ProcessingResult.CONVERSION_FAILED); + return imgTextContainer; + } + + /*Modifies the number of channel of the image so the matrix is compatible with + Imgproc.getRectSubPix method*/ + Imgproc.cvtColor(img, img, Imgproc.COLOR_BGRA2BGR); + + /*For each rectangle contained in textContainer extract the rectangle and saves it + into a bitmap*/ + RotatedRect rectangle; + try { + while (textContainer.hasNext()) { + rectangle = (RotatedRect) textContainer.next(); + Mat croppedMat = crop(rectangle, img); + //If the conversion failed return a List with only the original image + Bitmap croppedBitmap = IPUtils.conversionMatToBitmap(croppedMat); + imgTextContainer.addBitmap(croppedBitmap); + } + } catch (ConversionFailedException e) { + Log.e(TAG, e.getErrorMessage()); + BitmapBox imgTextContainerFailure = new BitmapBox(); + imgTextContainerFailure.addBitmap(image); + //This flag let the user know that occurred an error during the processing + imgTextContainerFailure.setProcessingResult(ProcessingResult.CONVERSION_FAILED); + return imgTextContainerFailure; + } + return imgTextContainer; + } +} diff --git a/app/src/main/java/unipd/se18/ocrcamera/IPBuilder.java b/app/src/main/java/unipd/se18/ocrcamera/IPBuilder.java new file mode 100644 index 00000000..95bb5e4d --- /dev/null +++ b/app/src/main/java/unipd/se18/ocrcamera/IPBuilder.java @@ -0,0 +1,432 @@ +package com.example.imageprocessing; + +import org.opencv.core.Mat; +import org.opencv.core.MatOfPoint; +import org.opencv.imgproc.Imgproc; +import org.opencv.core.MatOfInt4; +import android.graphics.Bitmap; +import java.util.ArrayList; +import java.util.List; + +/** + * Builder used to pass to openCV's methods the parameters + * @author Thomas Porro (g1), Oscar Garrido (g1) + */ +class IPBuilder { + + /* + Documentation of the Imgproc class available at: + https://docs.opencv.org/java/2.4.2/org/opencv/imgproc/Imgproc.html + + We referenced a previous instance of the documentation since + the newer one is still incomplete + */ + + /** + * Inner class to create an object CannyBuilder that contains + * all the variables needed from Imgproc.Canny method + * @author Thomas Porro (g1) + */ + static class CannyBuilder{ + + private Mat source; + private double minThreshold; + private double maxThreshold; + private int apertureSize; + private boolean l2gradient; + + /** + * Constructor that initialize the variables of the object + * with a default value + * @param src the source matrix + */ + CannyBuilder(Mat src){ + this.source = src; + this.minThreshold = 50; + this.maxThreshold = 200; + this.apertureSize = 3; + this.l2gradient = false; + } + + /** + * Set minThreshold with the passed value + * @param value the value you want it to take minThreshold + * @return returns the current object instance + */ + CannyBuilder withMinThreshold(double value){ + this.minThreshold = value; + return this; + } + + /** + * Set maxThreshold with the passed value + * @param value the value you want it to take maxThreshold + * @return returns the current object instance + */ + CannyBuilder withMaxThreshold(double value){ + this.maxThreshold = value; + return this; + } + + /** + * Set ApertureSize with the passed value + * @param value the value you want it to withApertureSize + * @return returns the current object instance + */ + CannyBuilder withApertureSize(int value){ + this.apertureSize = value; + return this; + } + + /** + * Set l2gradient with the passed value + * @param value the value you want it to l2gradient + * @return returns the current object instance + */ + CannyBuilder withL2gradient(boolean value){ + this.l2gradient = value; + return this; + } + } + + /** + * Inner class to create an object adaptiveThresholdBuilder that contains + * all the variables needed from Imgproc.adaptiveThreshold + */ + static class AdaptiveThresholdBuilder{ + private Mat source; + private double maxThreshold; + private int blockSize; + private double constant; + + /** + * Constructor that initialize the variables of the object + * with a default value + * @param src the source matrix + */ + AdaptiveThresholdBuilder(Mat src){ + this.source = src; + this.maxThreshold = 200; + this.blockSize = 3; + this.constant = 0; + } + + /** + * Set maxThreshold with the passed value + * @param value the value you want it to maxThreshold + * @return returns the current object instance + */ + AdaptiveThresholdBuilder withMaxThreshold(double value){ + this.maxThreshold = value; + return this; + } + + /** + * Set blockSize with the passed value + * @param value the value you want it to blockSize + * @return returns the current object instance + */ + AdaptiveThresholdBuilder withBlockSize(int value){ + this.blockSize = value; + return this; + } + + /** + * Set constant with the passed value + * @param value the value you want it to withApertureSize + * @return returns the current object instance + */ + AdaptiveThresholdBuilder withConstant(double value){ + this.constant = value; + return this; + } + } + + + + /** + * Inner class to create an object FindContoursBuilder that contains + * all the variables needed from Imgproc.findContours + * @author Oscar Garrido (g1) + */ + static class FindContoursBuilder{ + private Mat source; + private int mode; + private int method; + + /** + * Constructor that initialize the variables of the object + * with a default value + * @param src the source matrix + */ + FindContoursBuilder(Mat src){ + this.source = src; + this.mode = Imgproc.RETR_EXTERNAL; + this.method = Imgproc.CHAIN_APPROX_SIMPLE; + } + + /** + * Set mode with the passed value + * @param value the value you want it to take mode + * @return returns the current object instance + */ + FindContoursBuilder withMode(int value){ + this.mode = value; + return this; + } + + /** + * Set method with the passed value + * @param value the value you want it to take method + * @return returns the current object instance + */ + FindContoursBuilder withMethod(int value){ + this.method = value; + return this; + } + } + + /** + * Inner class to create an object HoughLinesPBuilder that contains + * all the variables needed from Imgproc.HoughLinesP + * @author Oscar Garrido (g1) + */ + static class HoughLinesPBuilder{ + private Mat source; + private double rho; + private double theta; + private int threshold; + private double minLineLength; + private double maxLineGap; + + /** + * Constructor that initialize the variables of the object + * with a default value + * @param img the source matrix + */ + HoughLinesPBuilder(Mat img){ + this.source = img; + this.rho = 1; + this.theta = Math.PI / 180; + this.threshold = 50; + this.minLineLength = 50; + this.maxLineGap = 10; + } + + /** + * Set rho with the passed value + * @param value the value you want it to rho + * @return returns the current object instance + */ + HoughLinesPBuilder withRho(double value){ + this.rho = value; + return this; + } + + /** + * Set theta with the passed value + * @param value the value you want it to theta + * @return returns the current object instance + */ + HoughLinesPBuilder withTheta(double value){ + this.theta = value; + return this; + } + + /** + * Set threshold with the passed value + * @param value the value you want it to threshold + * @return returns the current object instance + */ + HoughLinesPBuilder withThreshold(int value){ + this.threshold = value; + return this; + } + + /** + * Set minLineLength with the passed value + * @param value the value you want it to minLineLength + * @return returns the current object instance + */ + HoughLinesPBuilder withMinLineLength(double value){ + this.minLineLength = value; + return this; + } + + /** + * Set maxLineGap with the passed value + * @param value the value you want it to maxLineGap + * @return returns the current object instance + */ + HoughLinesPBuilder withMaxLineGap(double value){ + this.maxLineGap = value; + return this; + } + } + + /** + * Inner class to create an object GetPixelsBuilder that contains + * all the variables needed from getPixels of android.graphics.Bitmap + * @author Oscar Garrido (g1) + */ + static class GetPixelsBuilder{ + private Bitmap source; + private int offset; + private int stride; + private int x; + private int y; + private int width; + private int height; + + /** + * Constructor that initialize the variables of the object + * with a default value + * @param bmp the source image + */ + GetPixelsBuilder(Bitmap bmp){ + this.source = bmp; + this.offset = 0; + this.stride = 0; + this.x = 0; + this.y = 0; + this.width = 0; + this.height = 0; + } + + /** + * Set offset with the passed value + * @param value the value you want it to offset + * @return returns the current object instance + */ + GetPixelsBuilder withOffset(int value){ + this.offset = value; + return this; + } + + /** + * Set stride with the passed value + * @param value the value you want it to stride + * @return returns the current object instance + */ + GetPixelsBuilder withStride(int value){ + this.stride = value; + return this; + } + + /** + * Set x with the passed value + * @param value the value you want it to x + * @return returns the current object instance + */ + GetPixelsBuilder withX(int value){ + this.x = value; + return this; + } + + /** + * Set y with the passed value + * @param value the value you want it to y + * @return returns the current object instance + */ + GetPixelsBuilder withY(int value){ + this.y = value; + return this; + } + + /** + * Set width with the passed value + * @param value the value you want it to width + * @return returns the current object instance + */ + GetPixelsBuilder withWidth(int value){ + this.width = value; + return this; + } + + /** + * Set height with the passed value + * @param value the value you want it to height + * @return returns the current object instance + */ + GetPixelsBuilder withHeight(int value){ + this.height = value; + return this; + } + } + + /** + * Applies the openCV's method Imageproc.Canny, that detects the edges of an image + * @param builder the CannyBuilder that contains the parameters of the + * Imageproc.Canny method + * @return the matrix that contains the result of Imageproc.Canny + * @author Thomas Porro (g1) + */ + static Mat doCanny(CannyBuilder builder){ + Mat destination = new Mat(); + Imgproc.Canny(builder.source, destination, builder.minThreshold, builder.maxThreshold, + builder.apertureSize, builder.l2gradient); + return destination; + } + + + /** + * Applies the openCv's method Imagproc.adaptiveThreshold, that applies a threshold + * to an image + * @param builder the AdaptiveThresholdBuilder that contains the parameters of the + * Imageproc.adaptiveThreshold method + * @return the matrix that contains the result of Imageproc.adaptiveThreshold + * @author Thomas Porro (g1) + */ + static Mat doAdaptiveThreshold(AdaptiveThresholdBuilder builder) { + Mat destination = new Mat(); + Imgproc.adaptiveThreshold(builder.source, destination, builder.maxThreshold, + Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C, Imgproc.THRESH_BINARY, builder.blockSize, + builder.constant); + return destination; + } + + + /** + * Applies the openCv's method Imagproc.findContours, that finds + * contours in a binary image + * @param builder the FindContoursBuilder that contains the parameters of the + * Imageproc.findContours method + * @return the matrix that contains the result of Imageproc.adaptiveThreshold + * @author Oscar Garrido (g1) + */ + static List doFindContours(FindContoursBuilder builder) { + List contours = new ArrayList<>(); + Imgproc.findContours(builder.source, contours, new Mat(), builder.mode, builder.method); + //The third parameter contains additional information that is unused + return contours; + } + + /** + * Applies the openCv's method Imagproc.HoughLinesP, that finds line segments in a + * binary image using the probabilistic Hough transform + * @param builder the HoughLinesPBuilder that contains the parameters of the + * Imageproc.HoughLinesP method + * @return the matrix that contains the result of Imageproc.HoughLinesP + * @author Oscar Garrido (g1) + */ + static MatOfInt4 doHoughLinesP(HoughLinesPBuilder builder){ + MatOfInt4 lines = new MatOfInt4(); + Imgproc.HoughLinesP(builder.source, lines, builder.rho, builder.theta, + builder.threshold, builder.minLineLength, builder.maxLineGap); + return lines; + } + + /** + * Applies the android's Bitmap method getPixels, that gets the value of pixels into an array + * @param builder the GetPixelsBuilder that contains the parameters of the + * getPixels method + * @return an array containing a copy of the data in the bitmap + * @author Oscar Garrido (g1) + */ + static int[] doGetPixels(GetPixelsBuilder builder){ + int[] pixels = new int[builder.source.getHeight() * builder.source.getWidth()]; + builder.source.getPixels(pixels, builder.offset, builder.stride, builder.x, builder.y, + builder.width, builder.height); + return pixels; + } +} diff --git a/app/src/main/java/unipd/se18/ocrcamera/IPDebug.java b/app/src/main/java/unipd/se18/ocrcamera/IPDebug.java new file mode 100644 index 00000000..eec22630 --- /dev/null +++ b/app/src/main/java/unipd/se18/ocrcamera/IPDebug.java @@ -0,0 +1,86 @@ +package com.example.imageprocessing; + +import android.graphics.Bitmap; +import android.os.Environment; +import android.util.Log; +import com.example.imageprocessing.exceptions.ConversionFailedException; +import org.opencv.core.Mat; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.List; + +/** + * Class used for debugging + * @author Thomas Porro (g1) + */ +class IPDebug { + private final static String TAG = "IPDebug"; + private final static String DIRECTORY = Environment.getExternalStorageDirectory()+ + "/"+Environment.DIRECTORY_PICTURES+"/ImageProcessingTest/"; + + /** + * Class used to save a list of bitmap in a directory. If an image with the same + * name already exists in the directory it overwrites it + * @param bitmapList the list we want to save + */ + static void saveBitmapList(List bitmapList){ + int imageNumber = 0; + String imageName; + for(Bitmap currentImage : bitmapList){ + imageName = "imageNumber_n"+imageNumber+".jpg"; + saveImage(currentImage, DIRECTORY+imageName); + imageNumber++; + } + } + + + /** + * Method used to convert and save a matrix as a image in a predefined directory. + * If an image with the same name already exists in the directory it overwrites it + * @param matrix The matrix we want to save + * @param name The path where we want to save the image + */ + static void saveMatrix (Mat matrix, String name){ + Bitmap image; + try{ + image = IPUtils.conversionMatToBitmap(matrix); + } catch (ConversionFailedException e){ + Log.e(TAG, e.getErrorMessage()); + Log.e(TAG, "Matrix not saved"); + return; + } + saveImage(image, DIRECTORY+name); + } + + + /** + * Method used to save an image in a predefined directory. If an image with the + * same name already exists in the directory it overwrites it + * @param image the image we want to save + * @param path the path where we want to save the image + */ + private static void saveImage(Bitmap image, String path){ + File fileToSave = new File(path); + OutputStream outStream; + try{ + if (fileToSave.exists()) { + fileToSave.delete(); + fileToSave = new File(path); + } + try { + outStream = new FileOutputStream(fileToSave); + image.compress(Bitmap.CompressFormat.PNG, 100, outStream); + outStream.flush(); + outStream.close(); + Log.i(TAG, "outStream closed"); + } catch (FileNotFoundException fileNotFound) { + fileNotFound.printStackTrace(); + } + } catch (IOException fileNotCreated) { + fileNotCreated.printStackTrace(); + } + } +} diff --git a/app/src/main/java/unipd/se18/ocrcamera/IPUtils.java b/app/src/main/java/unipd/se18/ocrcamera/IPUtils.java new file mode 100644 index 00000000..deb8bd02 --- /dev/null +++ b/app/src/main/java/unipd/se18/ocrcamera/IPUtils.java @@ -0,0 +1,119 @@ +package com.example.imageprocessing; + +import android.graphics.Bitmap; +import android.util.Log; +import com.example.imageprocessing.exceptions.ConversionFailedException; +import com.example.imageprocessing.enumClasses.ErrorCodes; +import com.example.imageprocessing.exceptions.FailedToSave; +import com.example.imageprocessing.exceptions.MatrixEmptyException; +import org.opencv.android.Utils; +import org.opencv.core.Mat; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; + + +/** + * Utility class used in ExtractTheText + * @author Thomas Porro (g1), Oscar Garrido (g1) + */ +class IPUtils { + //Tag used to identify the log + private final static String TAG = "IPUtils"; + + /** + * Converts a matrix into a Bitmap and saves it in the default temp-file dir + * Used in case of debug. + * @param matrix the matrix to be converted + * @param tmpPrefix the name of the file being saved + * @param tmpSuffix the extension of the file being saved + * @author Thomas Porro(g1), Oscar Garrido(g1) + */ + static void save(Mat matrix, String tmpPrefix, String tmpSuffix) throws FailedToSave{ + + Bitmap image; + + try { + image = conversionMatToBitmap(matrix); + } catch (ConversionFailedException e){ + throw new FailedToSave(ErrorCodes.FAILED_TO_SAVE); + } + OutputStream outStream; + + try { + + //if not specified, the system-dependent default temporary-file directory will be used + File tmpFile = File.createTempFile(tmpPrefix, tmpSuffix); + + if (tmpFile.exists()) { + tmpFile.delete(); + tmpFile = File.createTempFile(tmpPrefix, tmpSuffix); + } + + try { + outStream = new FileOutputStream(tmpFile); + image.compress(Bitmap.CompressFormat.PNG, 100, outStream); + outStream.flush(); + outStream.close(); + Log.i(TAG, "outStream closed"); + } catch (FileNotFoundException fileNotFound) { + fileNotFound.printStackTrace(); + } + + } catch (IOException fileNotCreated) { + fileNotCreated.printStackTrace(); + } + } + + + /** + * Converts the matrix into a Bitmap + * @param matrix the matrix you want to convert + * @return the Bitmap corresponding to the matrix + * @throws ConversionFailedException if the conversion failed (see documentation of openCV's + * method "matToBitmap") + * @author Thomas Porro (g1) + */ + static Bitmap conversionMatToBitmap(Mat matrix) throws ConversionFailedException{ + //Create a Bitmap with a specific height, width and configuration + Bitmap image = Bitmap.createBitmap(matrix.width(), + matrix.height(), Bitmap.Config.ARGB_8888); + try { + Utils.matToBitmap(matrix, image); + } catch (Exception e){ + throw new ConversionFailedException(ErrorCodes.CONVERSION_FAILED); + } + return image; + } + + + /** + * Converts the Bitmap into a matrix + * @param image the Bitmap you want to convert + * @return the matrix corresponding to the Bitmap + * @throws ConversionFailedException if the conversion failed (see documentation of openCV's + * method "bitmapToMat") + * @throws MatrixEmptyException if the final matrix is empty + * @author Oscar Garrido (g1) + */ + static Mat conversionBitmapToMat(Bitmap image) + throws MatrixEmptyException, ConversionFailedException{ + + //Loads the grayscale image in a matrix + Mat img = new Mat(); + try{ + Utils.bitmapToMat(image, img, true); + } catch (Exception e) { + throw new ConversionFailedException(ErrorCodes.CONVERSION_FAILED); + } + + //Throw an Exception if "img" is empty + if (img.empty()) { + Log.e(TAG, "File not found"); + throw new MatrixEmptyException(ErrorCodes.MATRIX_EMPTY); + } + return img; + } +} \ No newline at end of file diff --git a/app/src/main/java/unipd/se18/ocrcamera/LibraryLoaderSingleton.java b/app/src/main/java/unipd/se18/ocrcamera/LibraryLoaderSingleton.java new file mode 100644 index 00000000..7ec6cc05 --- /dev/null +++ b/app/src/main/java/unipd/se18/ocrcamera/LibraryLoaderSingleton.java @@ -0,0 +1,32 @@ +package com.example.imageprocessing; + +import android.util.Log; + +/** + * Singletone used to avoid more than one library load + * @author Thomas Porro (g1) + */ +class LibraryLoaderSingleton { + + private final static String TAG = "Singleton openCV"; + private static LibraryLoaderSingleton myLibrary; + + /** + * Private constructor that load the library + */ + private LibraryLoaderSingleton(){ + System.loadLibrary("opencv_java3"); + Log.i(TAG, "Loaded the library"); + } + + /** + * Method called externally of the class to load the library + */ + static void loadLibrary(){ + if(myLibrary == null){ + myLibrary = new LibraryLoaderSingleton(); + } else { + Log.e(TAG, "Library already loaded"); + } + } +} diff --git a/app/src/main/java/unipd/se18/ocrcamera/PreProcessing.java b/app/src/main/java/unipd/se18/ocrcamera/PreProcessing.java new file mode 100644 index 00000000..0924f3ff --- /dev/null +++ b/app/src/main/java/unipd/se18/ocrcamera/PreProcessing.java @@ -0,0 +1,355 @@ +package com.example.imageprocessing; + +import android.graphics.Bitmap; +import android.graphics.Matrix; +import android.support.annotation.NonNull; +import android.util.Log; +import com.example.imageprocessing.enumClasses.BlurValue; +import com.example.imageprocessing.enumClasses.BrightnessValue; +import com.example.imageprocessing.enumClasses.ProcessingResult; +import com.example.imageprocessing.exceptions.ConversionFailedException; +import com.example.imageprocessing.interfaces.BitmapContainer; +import com.example.imageprocessing.interfaces.PreProcessingMethods; + +import org.opencv.core.Core; +import org.opencv.core.Mat; +import org.opencv.core.MatOfInt4; +import org.opencv.core.Scalar; +import org.opencv.imgproc.Imgproc; +import java.util.ArrayList; +import java.util.List; + +import static org.opencv.core.CvType.CV_8U; +import static org.opencv.core.CvType.CV_8UC1; + +/** + * Class used to process the image before passing the image to the OCR + */ +public class PreProcessing implements PreProcessingMethods { + + //Tag used to identify the log + private final String TAG = "PreProcessing"; + + /** + * Constructor of the class which initialize the openCV library + * @author Thomas Porro (g1) + */ + public PreProcessing() { + //Load the openCV library + LibraryLoaderSingleton.loadLibrary(); + } + + /** + * Calculate the angle between the text and the horizontal, and rotate the image + * @param image The image you want to analyze + * @return a BitmapContainer object that contain the image eventually rotated and the result + * of the process + * @author Thomas Porro (g1) + */ + private BitmapBox computeSkew(Bitmap image){ + + //Turns the image into a matrix + Mat img; + try{ + img = IPUtils.conversionBitmapToMat(image); + } catch (ConversionFailedException error){ + Log.e(TAG, error.getErrorMessage()); + return new BitmapBox(image, ProcessingResult.AUTOSKEW_FAILED); + } + + + Mat grayscale = new Mat(); + Imgproc.cvtColor(img, grayscale, Imgproc.COLOR_RGB2GRAY); + /* + Method used for debug + IPUtils.save(img, "grayScale", ".jpg"); + */ + + //Invert the colors of "img" onto itself + Core.bitwise_not(img, img); + + //Detect the edges in the image + Mat canny = IPBuilder.doCanny(new IPBuilder.CannyBuilder(grayscale) + .withMinThreshold(50) + .withMaxThreshold(200) + .withApertureSize(3) + .withL2gradient(false)); + + //Create a 4 dimensions vector using matrix + MatOfInt4 lines = IPBuilder.doHoughLinesP(new IPBuilder.HoughLinesPBuilder(canny) + .withRho(1) + .withTheta(Math.PI / 180) + .withThreshold(50) + .withMinLineLength(50) + .withMaxLineGap(10)); + + double meanAngle = 0; + Log.d(TAG, "rows = " + lines.cols() + "\ncols = " + lines.cols()); + + //Analyzes the text line per line + for (int i = 0; i < lines.rows(); i++) { + //Get points from the beginning and the ending of the line of text + double[] vec = lines.get(i, 0); + + //First point + double x1 = vec[0]; + double y1 = vec[1]; + + //Second point + double x2 = vec[2]; + double y2 = vec[3]; + + //Sum all the angle of the lines of text + meanAngle += Math.atan2(y2 - y1, x2 - x1); + } + + //Calculate the meanAngle by dividing it with the number of rows + meanAngle /= lines.rows(); + + //Transform the angle in degrees + double degreesAngle = Math.toDegrees(meanAngle); + Log.i(TAG, "Mean angle=" + degreesAngle); + Bitmap rotatedImage = rotateBitmap(image, degreesAngle); + return new BitmapBox(rotatedImage, ProcessingResult.AUTOSKEW_SUCCESSFUL); + } + + /** + * Rotate the given bitmap with the given angle + * @param original The image that we want to corrected, must not be null + * @param degrees The degrees of the angle we want to rotate + * @return the rotatedImage + * @author Thomas Porro(g1), Giovanni Fasan (g1), Oscar Garrido (g1) + */ + private static Bitmap rotateBitmap(@NonNull Bitmap original, double degrees) { + + //Obtain the dimen of the image + int width = original.getWidth(); + int height = original.getHeight(); + + //Prepare the rotation matrix. The minus before degrees allows us to rotate the + //image in the right way + Matrix matrix = new Matrix(); + matrix.preRotate((float)-degrees); + + //Rotate the Bitmap and returns it + return Bitmap.createBitmap(original, 0, 0, width, height, matrix, true); + + } + + + /** + * Detect if the image is bright + * @param imageMat the image we want to detect the brightness + * @return IMAGE_IS_OK if image is neither too bright nor too dark, + * IMAGE_IS_BRIGHT if image is too bright, + * IMAGE_IS_DARK if image is too dark. + * @author Thomas Porro(g1), Giovanni Fasan(g1), Oscar Garrido (g1) + */ + private BrightnessValue isBright(Mat imageMat){ + + Mat rgbImageMat = new Mat(); + + /* + Changes the format of the matrix into an RGB one, so we are now able to + split the color with the Core.split method + */ + Imgproc.cvtColor(imageMat, rgbImageMat, Imgproc.COLOR_RGBA2RGB); + + //Obtain 3 different matrix with the 3 elemental colors + + List imageColors = new ArrayList<>(); + Core.split(rgbImageMat, imageColors); + + + /*Each color is multiplied with his luminance. + The colors are in order RGB, so to access the che color I use the number 0, 1, 2 in order + For more informations see https://en.wikipedia.org/wiki/Relative_luminance + */ + final int RED = 0; + final int GREEN = 1; + final int BLUE = 2; + final double RED_LUMINANCE = 0.2126; + final double GREEN_LUMINANCE = 0.7152; + final double BLUE_LUMINANCE = 0.0722; + Mat redLuminance = new Mat(); + + Core.multiply(imageColors.get(RED), new Scalar(RED_LUMINANCE), redLuminance); + Mat greenLuminance= new Mat(); + Core.multiply(imageColors.get(GREEN), new Scalar(GREEN_LUMINANCE), greenLuminance); + Mat blueLuminance = new Mat(); + Core.multiply(imageColors.get(BLUE), new Scalar(BLUE_LUMINANCE), blueLuminance); + + //Sums the matrix of the colors into a single one + Mat tempLuminance = new Mat(); + Mat totalLuminance = new Mat(); + Core.add(redLuminance , greenLuminance , tempLuminance); //Red + Green = Temp + Core.add(tempLuminance , blueLuminance , totalLuminance); //Temp + Blue = Luminance + + //Calculate the sum of the values of all pixels + Scalar sum = Core.sumElems(totalLuminance); + + /*Calculate the percentage of the brightness. Since the value of the colors go + from 0 to 255 a pixel can contain the value 255 = 2^8-1*/ + final double PIXEL_MAX_VALUE = (Math.pow(2,8)-1); + double numberOfBits = PIXEL_MAX_VALUE * rgbImageMat.rows() * rgbImageMat.cols(); + double percentageBrightness = sum.val[0]/numberOfBits; + + Log.d(TAG, "Brightness:"+percentageBrightness); + + /*Bounds to define if the image is dark or bright. + The values were decided on the basis of various tests*/ + final double UPPER_BOUND = 0.45; + final double LOWER_BOUND = 0.2; + + if (percentageBrightness > UPPER_BOUND){ //Image is too bright + return BrightnessValue.IMAGE_TOO_BRIGHT; + } else if (percentageBrightness < LOWER_BOUND){ //Image is too dark + return BrightnessValue.IMAGE_TOO_DARK; + } else { //Image is neither too bright nor too dark + return BrightnessValue.IMAGE_IS_OK; + } + } + + /** + * Change the brightness of the image into an optimal one + * @param image the image we want to modify the brightness + * @return the image with the modified brightness + * @author Thomas Porro(g1), Giovanni Fasan(g1), Oscar Garrido(g1) + */ + private BitmapContainer editBright(Bitmap image){ + /*This variable is used to put a limit to the change of the image's brightness. + The value 240 is derived from the fact that in the for loop we try to modify + the value of all the pixels of a step, and being the maximum value = 255 (pixel's + color maximum value, we put the limit on 240*/ + final int STEP = 15; + + //Converts the image into a matrix + Mat imageMat; + try{ + imageMat = IPUtils.conversionBitmapToMat(image); + } catch (ConversionFailedException error){ + Log.e(TAG, error.getErrorMessage()); + return new BitmapBox(image, ProcessingResult.BRIGHTNESS_CONVERSION_ERROR); + } + + /*This variable is used to select the type of matrix we want to abtain in the + the convertTo method. If it's negative the type doesn't change*/ + final int MATRIX_TYPE = -1; + + /*This variable is used to change the contrast of the matrix, but we want + only modify the brightness so we put the value 1 because the method use + this formula from the documentation: + m(x,y) = saturate _ cast(alpha(*this)(x,y) + beta) + We called beta as STEP*/ + final int ALPHA = 1; + + //Call the internal method isBright to detect if the image is bright or dark + //and change the brightness according to the number obtained + while(isBright(imageMat) != BrightnessValue.IMAGE_IS_OK) { + switch (isBright(imageMat)) { + case IMAGE_TOO_BRIGHT: + Log.d(TAG, "Case==IMAGE_TOO_BRIGHT"); + /*Modify the values of all pixels with an alpha and beta value following + this formula m(x,y) = saturate _ cast(alpha(*this)(x,y) + beta)*/ + imageMat.convertTo(imageMat, MATRIX_TYPE, ALPHA, -STEP); + break; + case IMAGE_TOO_DARK: + Log.d(TAG, "Case==IMAGE_TOO_DARK"); + //The same as above + imageMat.convertTo(imageMat, MATRIX_TYPE, ALPHA, STEP); + break; + } + } + Log.d(TAG, "IMAGE_IS_OK"); + try { + Bitmap convertedImage = IPUtils.conversionMatToBitmap(imageMat); + return new BitmapBox(convertedImage, ProcessingResult.BRIGHTNESS_MODIFIED); + } catch (ConversionFailedException error) { + Log.e(TAG, error.getErrorMessage()); + return new BitmapBox(image, ProcessingResult.BRIGHTNESS_CONVERSION_ERROR); + } + } + + + /** + * @author Thomas Porro(g1), Oscar Garrido (g1), Giovanni Fasan(g1), Leonardo Pratesi(g1) + * See PreProcessingMethods.java + */ + @Override + public BlurValue isBlurred(Bitmap image) { + + //Total number of color of RGB: 256 each color, so 256^3 + int maxLap = -16777216; + + //Threshold above which the color is out of focus + final int OUT_OF_FOCUS_THRESHOLD = -6118750; + + //Converts the image into a matrix + Mat imageMat; + try { + imageMat = IPUtils.conversionBitmapToMat(image); + } catch (ConversionFailedException error){ + Log.e(TAG, error.getErrorMessage()); + return BlurValue.IMAGE_NOT_ANALYZED; + } + + //Turn the colored matrix into a grayscale matrix + Mat grayImageMat = new Mat(); + Imgproc.cvtColor(imageMat, grayImageMat, Imgproc.COLOR_BGR2GRAY); + + /*Use the openCV's Laplacian methods to apply a Laplacian filter, that allow us to detect + the image blurriness*/ + Mat laplacianMat = new Mat(); + Imgproc.Laplacian(grayImageMat, laplacianMat, CV_8U); + //Converts the matrix into another format used to detect the blur + Mat laplacianMat8Bit = new Mat(); + laplacianMat.convertTo(laplacianMat8Bit, CV_8UC1); + + //Create a Bitmap with the given matrix, and obtain all the pixels from it + Bitmap laplacianImage; + try{ + laplacianImage = IPUtils.conversionMatToBitmap(laplacianMat8Bit); + } catch (ConversionFailedException error){ + Log.e(TAG, error.getErrorMessage()); + return BlurValue.IMAGE_NOT_ANALYZED; + } + + //Extracts all the pixels of the laplacian image into the array + int[] pixels = IPBuilder.doGetPixels(new IPBuilder.GetPixelsBuilder(laplacianImage) + .withStride(laplacianImage.getWidth()) + .withWidth(laplacianImage.getWidth()) + .withHeight(laplacianImage.getHeight()) + ); + + //Searches the maximum value of the pixels in the Laplacin filtered image + for(int pixel : pixels){ + if(pixel > maxLap){ + maxLap = pixel; + } + } + + //Verify if the image is blurred + if(maxLap < OUT_OF_FOCUS_THRESHOLD){ + Log.d("Blur", "IS BLURRED"); + return BlurValue.IMAGE_BLURRED; + } else { + Log.d("Blur", "IS NOT BLURRED"); + return BlurValue.IMAGE_NOT_BLURRED; + } + } + + + /** + * @author Thomas Porro (g1), Giovanni Fasan (g1), Oscar Garrido (g1) + * See PreProcessingMethods.java + */ + @Override + public BitmapContainer doImageProcessing(Bitmap image, boolean autoSkew) { + //Call methods that perform the image processing + BitmapContainer modifiedBright = editBright(image); + if(autoSkew) { + modifiedBright = computeSkew(modifiedBright.getFirstBitmap()); + } + return modifiedBright; + } +} diff --git a/app/src/main/java/unipd/se18/ocrcamera/TextAreas.java b/app/src/main/java/unipd/se18/ocrcamera/TextAreas.java new file mode 100644 index 00000000..3c97b273 --- /dev/null +++ b/app/src/main/java/unipd/se18/ocrcamera/TextAreas.java @@ -0,0 +1,85 @@ +package com.example.imageprocessing; + +import com.example.imageprocessing.enumClasses.ProcessingResult; +import com.example.imageprocessing.interfaces.TextRegions; + +import java.util.ArrayList; +import java.util.List; +import org.opencv.core.RotatedRect; + +/** + * Class that contains all the regions with a text in an image + * @author Thomas Porro (g1) + */ +class TextAreas implements TextRegions { + private List detectedText; + private int counter; + private ProcessingResult processingResult; + + + /** + * Constructor that initialize the object + */ + TextAreas(){ + detectedText = new ArrayList<>(); + counter = 0; + processingResult = ProcessingResult.PROCESSING_SUCCESSFUL; + } + + + /** + * Constructor that initialize the object setting the value of processingResult to the + * desired one + */ + TextAreas(ProcessingResult value){ + detectedText = new ArrayList<>(); + counter = 0; + processingResult = value; + } + + + /** + * Add a text's region to the list + * @param region The region we want to add + */ + void addRegion(RotatedRect region){ + detectedText.add(region); + } + + + /** + * Set the value of the enum class + * @param value the new value of processingResult + */ + void setProcessingResult(ProcessingResult value){ + processingResult = value; + } + + + /** + * Get the value of the enum class + * @return the value of the enum class + */ + ProcessingResult getProcessingResult(){ + return processingResult; + } + + @Override + public Object next(){ + if(this.hasNext()) { + return detectedText.get(counter++); + } else { + return null; + } + } + + @Override + public boolean hasNext(){ + return counter getRegions() { + return detectedText; + } +} diff --git a/app/src/main/java/unipd/se18/ocrcamera/enumClasses/BlurValue.java b/app/src/main/java/unipd/se18/ocrcamera/enumClasses/BlurValue.java new file mode 100644 index 00000000..2305719e --- /dev/null +++ b/app/src/main/java/unipd/se18/ocrcamera/enumClasses/BlurValue.java @@ -0,0 +1,30 @@ +package com.example.imageprocessing.enumClasses; + +/** + * Enum class used to identify the blurriness + * @author Thomas Porro (g1) + */ +public enum BlurValue { + IMAGE_NOT_BLURRED(0), + IMAGE_BLURRED(1), + IMAGE_NOT_ANALYZED(2); + + private int value; + + /** + * Constructor + * @param value The number assigned to the constant + */ + BlurValue(int value){ + this.value = value; + } + + + /** + * Return the mode + * @return the mode of the constant + */ + int getValue(){ + return this.value; + } +} diff --git a/app/src/main/java/unipd/se18/ocrcamera/enumClasses/BrightnessValue.java b/app/src/main/java/unipd/se18/ocrcamera/enumClasses/BrightnessValue.java new file mode 100644 index 00000000..ce5c4394 --- /dev/null +++ b/app/src/main/java/unipd/se18/ocrcamera/enumClasses/BrightnessValue.java @@ -0,0 +1,29 @@ +package com.example.imageprocessing.enumClasses; + +/** + * Enum class used to identify the image's brightness + * @author Thomas Porro (g1) + */ +public enum BrightnessValue { + IMAGE_IS_OK(0), + IMAGE_TOO_BRIGHT(1), + IMAGE_TOO_DARK(2); + + private int value; + + /** + * Constructor + * @param value The number assigned to the constant + */ + BrightnessValue (int value){ + this.value = value; + } + + /** + * Return the value + * @return the value of the constant + */ + int getValue(){ + return this.value; + } +} diff --git a/app/src/main/java/unipd/se18/ocrcamera/enumClasses/DetectTheTextMethods.java b/app/src/main/java/unipd/se18/ocrcamera/enumClasses/DetectTheTextMethods.java new file mode 100644 index 00000000..5ca6e1c9 --- /dev/null +++ b/app/src/main/java/unipd/se18/ocrcamera/enumClasses/DetectTheTextMethods.java @@ -0,0 +1,29 @@ +package com.example.imageprocessing.enumClasses; + +/** + * Enum class used to identify the image's brightness + * @author Thomas Porro (g1) + */ +public enum DetectTheTextMethods { + DETECT_MAX_TEXT_AREA(0), + DETECT_ALL_TEXT_AREAS(1); + + private int mode; + + /** + * Constructor + * @param mode The number assigned to the constant + */ + DetectTheTextMethods(int mode){ + this.mode = mode; + } + + + /** + * Return the mode + * @return the mode of the constant + */ + int getMode(){ + return this.mode; + } +} diff --git a/app/src/main/java/unipd/se18/ocrcamera/enumClasses/ErrorCodes.java b/app/src/main/java/unipd/se18/ocrcamera/enumClasses/ErrorCodes.java new file mode 100644 index 00000000..f5d0c01e --- /dev/null +++ b/app/src/main/java/unipd/se18/ocrcamera/enumClasses/ErrorCodes.java @@ -0,0 +1,45 @@ +package com.example.imageprocessing.enumClasses; + +/** + * Enum class that contains all the error's definitions + * @author Thomas Porro(g1) + */ +public enum ErrorCodes{ + //List of all the error with their messages + MATRIX_EMPTY(0, "The matrix is empty, conversion failed"), + CONVERSION_FAILED(1, "Conversion failed"), + FAILED_TO_SAVE(2, "Failed to save the image"); + + //Variables of the class + private int errorCode; + private String errorMessage; + + + /** + * Constructor of the error object that assings a specific error's code and message + * @param id The error's code + * @param message The error's message + */ + ErrorCodes(int id, String message){ + this.errorCode = id; + this.errorMessage = message; + } + + + /** + * Returns the error's code + * @return The error's code + */ + public int getErrorCode(){ + return this.errorCode; + } + + + /** + * Returns the error's message + * @return The error's message + */ + public String getErrorMessage(){ + return this.errorMessage; + } +} diff --git a/app/src/main/java/unipd/se18/ocrcamera/enumClasses/ProcessingResult.java b/app/src/main/java/unipd/se18/ocrcamera/enumClasses/ProcessingResult.java new file mode 100644 index 00000000..c76dc72f --- /dev/null +++ b/app/src/main/java/unipd/se18/ocrcamera/enumClasses/ProcessingResult.java @@ -0,0 +1,47 @@ +package com.example.imageprocessing.enumClasses; + +/** + * Enum class that contains all the processing's result + * @author Thomas Porro(g1) + */ +public enum ProcessingResult{ + //List of all the error with their messages + CONVERSION_FAILED(0, "Conversion Failed"), + PROCESSING_SUCCESSFUL(1, "Processing successful"), + PROCESSING_FAILED(2, "Processing Failed"), + BRIGHTNESS_MODIFIED(3, "Brightness modified successfully"), + BRIGHTNESS_CONVERSION_ERROR(4, "Error while converting the image"), + AUTOSKEW_FAILED(5, "Error while converting the image"), + AUTOSKEW_SUCCESSFUL(6, "Image successfully rotated"); + + //Variables of the class + private int resultCode; + private String resultMessage; + + /** + * Constructor of the error object that assings a specific error's code and message + * @param value The number assigned to the constant + * @param message The result's message + */ + ProcessingResult(int value, String message){ + resultCode = value; + resultMessage = message; + } + + /** + * Returns the error's code + * @return The error's code + */ + public int getResultCode(){ + return this.resultCode; + } + + + /** + * Returns the error's message + * @return The error's message + */ + public String getResultMessage(){ + return this.resultMessage; + } +} diff --git a/app/src/main/java/unipd/se18/ocrcamera/exceptions/ConversionFailedException.java b/app/src/main/java/unipd/se18/ocrcamera/exceptions/ConversionFailedException.java new file mode 100644 index 00000000..2786a2ae --- /dev/null +++ b/app/src/main/java/unipd/se18/ocrcamera/exceptions/ConversionFailedException.java @@ -0,0 +1,43 @@ +package com.example.imageprocessing.exceptions; + +import com.example.imageprocessing.enumClasses.ErrorCodes; + +/** + * Exception threw in case that detectTextAreas have an invalid method as parameter + * @author Thomas Porro (g1) + */ +public class ConversionFailedException extends Exception { + + private int errorCode; + private String errorMessage; + + + /** + * Constructor of the exceptions + * @param code The code of the error + */ + public ConversionFailedException(ErrorCodes code){ + super(); + this.errorCode = code.getErrorCode(); + this.errorMessage = code.getErrorMessage(); + } + + + /** + * Returns the error's code + * @return the error's code + */ + public int getErrorCode(){ + return this.errorCode; + } + + + /** + * Returns the error's message + * @return the error's message + */ + public String getErrorMessage(){ + return this.errorMessage; + } + +} diff --git a/app/src/main/java/unipd/se18/ocrcamera/exceptions/FailedToSave.java b/app/src/main/java/unipd/se18/ocrcamera/exceptions/FailedToSave.java new file mode 100644 index 00000000..0e12efdd --- /dev/null +++ b/app/src/main/java/unipd/se18/ocrcamera/exceptions/FailedToSave.java @@ -0,0 +1,43 @@ +package com.example.imageprocessing.exceptions; + +import com.example.imageprocessing.enumClasses.ErrorCodes; + +/** + * Exception threw in case that detectTextAreas have an invalid method as parameter + * @author Thomas Porro (g1) + */ +public class FailedToSave extends Exception { + + private int errorCode; + private String errorMessage; + + + /** + * Constructor of the exceptions + * @param code The code of the error + */ + public FailedToSave(ErrorCodes code){ + super(); + this.errorCode = code.getErrorCode(); + this.errorMessage = code.getErrorMessage(); + } + + + /** + * Returns the error's code + * @return the error's code + */ + public int getErrorCode(){ + return this.errorCode; + } + + + /** + * Returns the error's message + * @return the error's message + */ + public String getErrorMessage(){ + return this.errorMessage; + } + +} diff --git a/app/src/main/java/unipd/se18/ocrcamera/exceptions/MatrixEmptyException.java b/app/src/main/java/unipd/se18/ocrcamera/exceptions/MatrixEmptyException.java new file mode 100644 index 00000000..8e2d297b --- /dev/null +++ b/app/src/main/java/unipd/se18/ocrcamera/exceptions/MatrixEmptyException.java @@ -0,0 +1,43 @@ +package com.example.imageprocessing.exceptions; + +import com.example.imageprocessing.enumClasses.ErrorCodes; + +/** + * Exception threw in case that detectTextAreas have an invalid method as parameter + * @author Thomas Porro (g1) + */ +public class MatrixEmptyException extends ConversionFailedException { + + private int errorCode; + private String errorMessage; + + + /** + * Constructor of the exceptions + * @param code The code of the error + */ + public MatrixEmptyException(ErrorCodes code){ + super(ErrorCodes.MATRIX_EMPTY); + this.errorCode = code.getErrorCode(); + this.errorMessage = code.getErrorMessage(); + } + + + /** + * Returns the error's code + * @return the error's code + */ + public int getErrorCode(){ + return this.errorCode; + } + + + /** + * Returns the error's message + * @return the error's message + */ + public String getErrorMessage(){ + return this.errorMessage; + } + +} diff --git a/app/src/main/java/unipd/se18/ocrcamera/interfaces/BitmapContainer.java b/app/src/main/java/unipd/se18/ocrcamera/interfaces/BitmapContainer.java new file mode 100644 index 00000000..c5f22bdb --- /dev/null +++ b/app/src/main/java/unipd/se18/ocrcamera/interfaces/BitmapContainer.java @@ -0,0 +1,31 @@ +package com.example.imageprocessing.interfaces; + +import android.graphics.Bitmap; +import com.example.imageprocessing.enumClasses.ProcessingResult; +import java.util.Iterator; +import java.util.List; + +/** + * Interface used to manage all the processing's operations + * @author Thomas Porro (g1) + */ +public interface BitmapContainer extends Iterator { + + /** + * Get the full list of bitmaps that contains some text or the image processed + * @return the list of bitmaps + */ + List getTextBitmaps(); + + /** + * Get the first element of a bitmap's list that contains some text or the image processed + * @return the Bitmap in the first place + */ + Bitmap getFirstBitmap(); + + /** + * Get the processingResult value + * @return the value of processingResult + */ + ProcessingResult getProcessingResult(); +} diff --git a/app/src/main/java/unipd/se18/ocrcamera/interfaces/DetectTheText.java b/app/src/main/java/unipd/se18/ocrcamera/interfaces/DetectTheText.java new file mode 100644 index 00000000..a5193cdb --- /dev/null +++ b/app/src/main/java/unipd/se18/ocrcamera/interfaces/DetectTheText.java @@ -0,0 +1,30 @@ +package com.example.imageprocessing.interfaces; + +import android.graphics.Bitmap; +import com.example.imageprocessing.enumClasses.DetectTheTextMethods; + +/** + * Interface used to detect the text in an image + * @author Thomas Porro (g1) + */ +public interface DetectTheText { + + /** + * Detects all the regions where there's some text in the image + * @param image The image we want to analyze. Not null. + * @param method The method used to extract the text area. See DetectTheTextMethods.java. + * @return The TextAreas object that contains the area where there's some text. If it fails + * return TextRegions containing the full image + */ + TextRegions detectTextRegions(Bitmap image, DetectTheTextMethods method); + + + /** + * Extract all the area where the text is detected + * @param image The image that contains the text. Not null. + * @param regions The object that contains the area where there's some text + * @return A list of bitmaps, each containing some text. If it fails return a List containing + * only the full image + */ + BitmapContainer extractTextFromBitmap(Bitmap image, TextRegions regions); +} diff --git a/app/src/main/java/unipd/se18/ocrcamera/interfaces/PreProcessingMethods.java b/app/src/main/java/unipd/se18/ocrcamera/interfaces/PreProcessingMethods.java new file mode 100644 index 00000000..6b4dad27 --- /dev/null +++ b/app/src/main/java/unipd/se18/ocrcamera/interfaces/PreProcessingMethods.java @@ -0,0 +1,29 @@ +package com.example.imageprocessing.interfaces; + +import android.graphics.Bitmap; +import com.example.imageprocessing.enumClasses.BlurValue; + +/** + * Interface used to call the processing methods + * @author Thomas Porro (g1), Giovanni Fasan(g1), OScar Garrido(g1) + */ +public interface PreProcessingMethods { + + /** + * Detect if the image is blurred + * @param image The image we want to discover if is blurred + * @return BlurValue type that contains the information of the image + * @author Thomas Porro (g1), Giovanni Fasan (g1), Oscar Garrido (g1) + */ + BlurValue isBlurred(Bitmap image); + + /** + * Does the image processing brightness adjustment and, if wanted to, it also auto rotates the image + * @param image The image to modify + * @param autoSkew auto rotates the image if true, does nothing if false + * @return a Bitmap with adjustment, auto cropping the image and the likes + * @author Thomas Porro (g1), Giovanni Fasan (g1), Oscar Garrido (g1) + */ + BitmapContainer doImageProcessing(Bitmap image, boolean autoSkew); + +} diff --git a/app/src/main/java/unipd/se18/ocrcamera/interfaces/TextRegions.java b/app/src/main/java/unipd/se18/ocrcamera/interfaces/TextRegions.java new file mode 100644 index 00000000..fe506299 --- /dev/null +++ b/app/src/main/java/unipd/se18/ocrcamera/interfaces/TextRegions.java @@ -0,0 +1,18 @@ +package com.example.imageprocessing.interfaces; + +import org.opencv.core.RotatedRect; +import java.util.Iterator; +import java.util.List; + +/** + * Interface used to contains RotatedRect objects + * @author Thomas Porro (g1) + */ +public interface TextRegions extends Iterator { + + /** + * Used to get the ArrayList containing the regions + * @return The arraylist that contains all the region with some text + */ + List getRegions(); +} diff --git a/app/src/main/java/unipd/se18/ocrcamera/removeMe.md b/app/src/main/java/unipd/se18/ocrcamera/removeMe.md deleted file mode 100644 index 6e2fe634..00000000 --- a/app/src/main/java/unipd/se18/ocrcamera/removeMe.md +++ /dev/null @@ -1,2 +0,0 @@ -*Remove me, I'm not useful anymore.* -_- PrandiniUniPD, frankplus_