├── .gitignore ├── LICENSE.txt ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── joshuabutton │ │ └── documentscanner │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── joshuabutton │ │ │ └── documentscanner │ │ │ ├── OpenCVCallback.java │ │ │ ├── activities │ │ │ └── MainActivity.java │ │ │ └── views │ │ │ └── QuadrilateralSelectionImageView.java │ └── res │ │ ├── layout │ │ ├── activity_main.xml │ │ └── dialog_document_scan_result.xml │ │ ├── menu │ │ └── activity_main_menu.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── ids.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── joshuabutton │ └── documentscanner │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── opencv ├── build.gradle ├── lint.xml └── src │ └── main │ ├── AndroidManifest.xml │ ├── aidl │ └── org │ │ └── opencv │ │ └── engine │ │ └── OpenCVEngineInterface.aidl │ ├── java │ └── org │ │ └── opencv │ │ ├── android │ │ ├── AsyncServiceHelper.java │ │ ├── BaseLoaderCallback.java │ │ ├── Camera2Renderer.java │ │ ├── CameraBridgeViewBase.java │ │ ├── CameraGLRendererBase.java │ │ ├── CameraGLSurfaceView.java │ │ ├── CameraRenderer.java │ │ ├── FpsMeter.java │ │ ├── InstallCallbackInterface.java │ │ ├── JavaCameraView.java │ │ ├── LoaderCallbackInterface.java │ │ ├── OpenCVLoader.java │ │ ├── StaticHelper.java │ │ └── Utils.java │ │ ├── calib3d │ │ ├── Calib3d.java │ │ ├── StereoBM.java │ │ ├── StereoMatcher.java │ │ └── StereoSGBM.java │ │ ├── core │ │ ├── Algorithm.java │ │ ├── Core.java │ │ ├── CvException.java │ │ ├── CvType.java │ │ ├── DMatch.java │ │ ├── KeyPoint.java │ │ ├── Mat.java │ │ ├── MatOfByte.java │ │ ├── MatOfDMatch.java │ │ ├── MatOfDouble.java │ │ ├── MatOfFloat.java │ │ ├── MatOfFloat4.java │ │ ├── MatOfFloat6.java │ │ ├── MatOfInt.java │ │ ├── MatOfInt4.java │ │ ├── MatOfKeyPoint.java │ │ ├── MatOfPoint.java │ │ ├── MatOfPoint2f.java │ │ ├── MatOfPoint3.java │ │ ├── MatOfPoint3f.java │ │ ├── MatOfRect.java │ │ ├── Point.java │ │ ├── Point3.java │ │ ├── Range.java │ │ ├── Rect.java │ │ ├── RotatedRect.java │ │ ├── Scalar.java │ │ ├── Size.java │ │ └── TermCriteria.java │ │ ├── features2d │ │ ├── DescriptorExtractor.java │ │ ├── DescriptorMatcher.java │ │ ├── FeatureDetector.java │ │ └── Features2d.java │ │ ├── imgcodecs │ │ └── Imgcodecs.java │ │ ├── imgproc │ │ ├── CLAHE.java │ │ ├── Imgproc.java │ │ ├── LineSegmentDetector.java │ │ ├── Moments.java │ │ └── Subdiv2D.java │ │ ├── ml │ │ ├── ANN_MLP.java │ │ ├── Boost.java │ │ ├── DTrees.java │ │ ├── EM.java │ │ ├── KNearest.java │ │ ├── LogisticRegression.java │ │ ├── Ml.java │ │ ├── NormalBayesClassifier.java │ │ ├── RTrees.java │ │ ├── SVM.java │ │ ├── StatModel.java │ │ └── TrainData.java │ │ ├── objdetect │ │ ├── BaseCascadeClassifier.java │ │ ├── CascadeClassifier.java │ │ ├── HOGDescriptor.java │ │ └── Objdetect.java │ │ ├── photo │ │ ├── AlignExposures.java │ │ ├── AlignMTB.java │ │ ├── CalibrateCRF.java │ │ ├── CalibrateDebevec.java │ │ ├── CalibrateRobertson.java │ │ ├── MergeDebevec.java │ │ ├── MergeExposures.java │ │ ├── MergeMertens.java │ │ ├── MergeRobertson.java │ │ ├── Photo.java │ │ ├── Tonemap.java │ │ ├── TonemapDrago.java │ │ ├── TonemapDurand.java │ │ ├── TonemapMantiuk.java │ │ └── TonemapReinhard.java │ │ ├── utils │ │ └── Converters.java │ │ ├── video │ │ ├── BackgroundSubtractor.java │ │ ├── BackgroundSubtractorKNN.java │ │ ├── BackgroundSubtractorMOG2.java │ │ ├── DenseOpticalFlow.java │ │ ├── DualTVL1OpticalFlow.java │ │ ├── KalmanFilter.java │ │ └── Video.java │ │ └── videoio │ │ ├── VideoCapture.java │ │ ├── VideoWriter.java │ │ └── Videoio.java │ └── res │ └── values │ └── attrs.xml ├── screenshots ├── 1.png ├── 2.png ├── 3.png └── 4.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea/ 3 | .DS_Store 4 | 5 | # Built application files 6 | *.apk 7 | *.ap_ 8 | 9 | # Files for the Dalvik VM 10 | *.dex 11 | 12 | # Java class files 13 | *.class 14 | 15 | # Generated files 16 | bin/ 17 | gen/ 18 | 19 | # Gradle files 20 | .gradle/ 21 | build/ 22 | */build/ 23 | 24 | # Local configuration file (sdk path, etc) 25 | local.properties 26 | 27 | # Proguard folder generated by Eclipse 28 | proguard/ 29 | 30 | # Log Files 31 | *.log 32 | 33 | # Android Studio Navigation editor temp files 34 | .navigation/ 35 | 36 | # Android Studio captures folder 37 | captures/ 38 | 39 | # Compiled libs 40 | *.a 41 | *.so 42 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Joshua Button 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple Document Scanner 2 | 3 | A simple Android app which allows for scanning of documents similar to apps like [GeniusScan](http://thegrizzlylabs.com). 4 | 5 | ## Screenshots 6 | 7 | ![Screenshot](https://raw.githubusercontent.com/jbttn/SimpleDocumentScanner-Android/master/screenshots/1.png) 8 | ![Screenshot](https://raw.githubusercontent.com/jbttn/SimpleDocumentScanner-Android/master/screenshots/4.png) 9 | ![Screenshot](https://raw.githubusercontent.com/jbttn/SimpleDocumentScanner-Android/master/screenshots/2.png) 10 | ![Screenshot](https://raw.githubusercontent.com/jbttn/SimpleDocumentScanner-Android/master/screenshots/3.png) 11 | 12 | ## Details 13 | 14 | I needed this type of functionality inside of an app I was building recently and I was unable to find exactly what I was looking for anywhere online, so I put something together myself. 15 | 16 | I should note that this is not a library. This is just an example meant to demonstrate how to implement a feature like this in your own app. 17 | 18 | ### OpenCV 19 | 20 | This app relies on OpenCV for image processing, but integrating it was not a simple task. For this project, I followed [these steps](http://stackoverflow.com/a/35135495). This works fine, but having an extra module in your app may be undesirable. If that's the case, simply build the app and add the compiled AAR into your own app as a dependency. 21 | 22 | By default, the app will use OpenCV Manager (availiable on Google Play) in order to link to the OpenCV binary. This is the recommended way to use OpenCV on Android by the OpenCV developers, but I find this a clunky approach and prefer to statically link OpenCV at build time. To do this you will need to follow the following steps: 23 | 24 | 1. Download the [OpenCV SDK](http://sourceforge.net/projects/opencvlibrary/files/opencv-android/3.1.0/OpenCV-3.1.0-android-sdk.zip/download) 25 | 2. Unzip and then copy all folders from *OpenCV SDK Dir*/sdk/native/libs to *this project*/opencv/src/main/jniLibs (You may need to create the jniLibs folder). 26 | 3. Rebuild 27 | 28 | At this point, OpenCV Manager shouldn't be necessary anymore. 29 | 30 | ### TODO 31 | 32 | I leave these as an exercise for the reader. 33 | 34 | * Camera support 35 | * Save the resulting bitmap 36 | 37 | ## Resources 38 | 1. [Integrating OpenCV with Android Studio](http://stackoverflow.com/a/35135495) 39 | 2. [OpenCV Manager](http://stackoverflow.com/a/20259621) 40 | 3. [OpenCV as an AAR](http://steveliles.github.io/building_opencv_as_an_aar_for_android.html) 41 | 4. [OpenCV Perspective Transform](http://www.pyimagesearch.com/2014/08/25/4-point-opencv-getperspective-transform-example/) 42 | 5. [Document Scanning Algorithm](http://www.pyimagesearch.com/2014/09/01/build-kick-ass-mobile-document-scanner-just-5-minutes/) 43 | 6. [If you attempt to include the generated AAR from a module your app depends on, this might be helpful](http://stackoverflow.com/a/31558348) 44 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.3" 6 | 7 | defaultConfig { 8 | applicationId "com.joshuabutton.documentscanner" 9 | minSdkVersion 21 10 | targetSdkVersion 23 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | testCompile 'junit:junit:4.12' 25 | 26 | compile project(':opencv') 27 | 28 | compile 'com.android.support:appcompat-v7:23.4.0' 29 | compile 'com.android.support:design:23.4.0' 30 | compile 'com.android.support:support-v4:23.4.0' 31 | 32 | compile 'com.afollestad.material-dialogs:core:0.8.5.9' 33 | compile 'com.github.chrisbanes:PhotoView:1.2.6' 34 | compile 'com.jakewharton.timber:timber:4.1.2' 35 | } 36 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/Shared/android-sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/joshuabutton/documentscanner/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.joshuabutton.documentscanner; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/joshuabutton/documentscanner/OpenCVCallback.java: -------------------------------------------------------------------------------- 1 | package com.joshuabutton.documentscanner; 2 | 3 | import android.app.Activity; 4 | import android.support.annotation.NonNull; 5 | 6 | import com.afollestad.materialdialogs.DialogAction; 7 | import com.afollestad.materialdialogs.MaterialDialog; 8 | 9 | import org.opencv.android.InstallCallbackInterface; 10 | import org.opencv.android.LoaderCallbackInterface; 11 | 12 | import timber.log.Timber; 13 | 14 | /** 15 | * This class is used to display custom messaging when using OpenCV via the initAsync method. 16 | * 17 | * Created by jbttn on 5/24/16. 18 | */ 19 | public abstract class OpenCVCallback implements LoaderCallbackInterface { 20 | Activity mContext; 21 | 22 | public OpenCVCallback(Activity context) { 23 | mContext = context; 24 | } 25 | 26 | public void onManagerConnected(int status) { 27 | switch (status) { 28 | case LoaderCallbackInterface.SUCCESS: { 29 | break; 30 | } 31 | 32 | case LoaderCallbackInterface.MARKET_ERROR: { 33 | Timber.e("Package installation failed, there is no market app."); 34 | showMessage("Unable to open Google Market", "The document scanner depends on the OpenCV Manager application available on Google Play. We were unable to launch the Google Play Market on your device."); 35 | break; 36 | } 37 | 38 | case LoaderCallbackInterface.INSTALL_CANCELED: { 39 | Timber.d("OpenCV library installation was canceled by the user"); 40 | finish(); 41 | break; 42 | } 43 | 44 | case LoaderCallbackInterface.INCOMPATIBLE_MANAGER_VERSION: { 45 | Timber.d("OpenCV Manager Service is incompatible with this app!"); 46 | showMessage("OpenCV Manager Outdated", "The OpenCV Manager installed on this device is incompatible with this app. Try to update it via Google Play."); 47 | break; 48 | } 49 | 50 | default: { 51 | Timber.e("OpenCV loading failed!"); 52 | showMessage("OpenCV Error", "OpenCV is unable initialize! Document scanner is unavailable."); 53 | break; 54 | } 55 | } 56 | } 57 | 58 | public void onPackageInstall(final int operation, final InstallCallbackInterface callback) { 59 | switch (operation) { 60 | case InstallCallbackInterface.NEW_INSTALLATION: { 61 | new MaterialDialog.Builder(mContext) 62 | .title("OpenCV Not Found") 63 | .content("The document scanner feature relies on OpenCV. In order to continue, you must download OpenCV from Google Play Market. Would you like to download it now?") 64 | .cancelable(false) 65 | .positiveText("Yes") 66 | .negativeText("No") 67 | .onPositive(new MaterialDialog.SingleButtonCallback() { 68 | @Override 69 | public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { 70 | callback.install(); 71 | } 72 | }) 73 | .onNegative(new MaterialDialog.SingleButtonCallback() { 74 | @Override 75 | public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { 76 | callback.cancel(); 77 | } 78 | }) 79 | .show(); 80 | break; 81 | } 82 | 83 | case InstallCallbackInterface.INSTALLATION_PROGRESS: { 84 | new MaterialDialog.Builder(mContext) 85 | .title("OpenCV is not ready") 86 | .content("Installation is in progress. Wait or exit?") 87 | .cancelable(false) 88 | .positiveText("Wait") 89 | .negativeText("Exit") 90 | .onPositive(new MaterialDialog.SingleButtonCallback() { 91 | @Override 92 | public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { 93 | callback.wait_install(); 94 | } 95 | }) 96 | .onNegative(new MaterialDialog.SingleButtonCallback() { 97 | @Override 98 | public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { 99 | callback.cancel(); 100 | } 101 | }) 102 | .show(); 103 | break; 104 | } 105 | } 106 | } 107 | 108 | void showMessage(String title, String content) { 109 | new MaterialDialog.Builder(mContext) 110 | .title(title) 111 | .content(content) 112 | .cancelable(false) 113 | .positiveText("OK") 114 | .onPositive(new MaterialDialog.SingleButtonCallback() { 115 | @Override 116 | public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { 117 | finish(); 118 | } 119 | }) 120 | .show(); 121 | } 122 | 123 | void finish() { 124 | mContext.finish(); 125 | } 126 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 |