Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 714cba8

Browse files
committedJul 28, 2020
OCR implemented
1 parent bd40a91 commit 714cba8

32 files changed

+921
-0
lines changed
 

‎app/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build

‎app/build.gradle

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
apply plugin: 'com.android.application'
2+
apply plugin: 'kotlin-android'
3+
apply plugin: 'kotlin-android-extensions'
4+
5+
android {
6+
compileSdkVersion 29
7+
8+
defaultConfig {
9+
applicationId "com.chaione.c1scanner"
10+
minSdkVersion 21
11+
targetSdkVersion 29
12+
versionCode 1
13+
versionName "1.0"
14+
15+
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
16+
}
17+
18+
buildTypes {
19+
release {
20+
minifyEnabled false
21+
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
22+
}
23+
}
24+
25+
compileOptions {
26+
sourceCompatibility JavaVersion.VERSION_1_8
27+
targetCompatibility JavaVersion.VERSION_1_8
28+
}
29+
}
30+
31+
dependencies {
32+
implementation fileTree(dir: "libs", include: ["*.jar"])
33+
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
34+
implementation 'androidx.core:core-ktx:1.1.0'
35+
implementation 'androidx.appcompat:appcompat:1.1.0'
36+
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
37+
testImplementation 'junit:junit:4.12'
38+
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
39+
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
40+
// Face features
41+
implementation 'com.google.mlkit:face-detection:16.0.0'
42+
43+
// Text features
44+
implementation 'com.google.android.gms:play-services-mlkit-text-recognition:16.0.0'
45+
def camerax_version = "1.0.0-beta03"
46+
// CameraX core library using camera2 implementation
47+
implementation "androidx.camera:camera-camera2:$camerax_version"
48+
// CameraX Lifecycle Library
49+
implementation "androidx.camera:camera-lifecycle:$camerax_version"
50+
// CameraX View class
51+
implementation "androidx.camera:camera-view:1.0.0-alpha10"
52+
}

‎app/proguard-rules.pro

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Add project specific ProGuard rules here.
2+
# You can control the set of applied configuration files using the
3+
# proguardFiles setting in build.gradle.
4+
#
5+
# For more details, see
6+
# http://developer.android.com/guide/developing/tools/proguard.html
7+
8+
# If your project uses WebView with JS, uncomment the following
9+
# and specify the fully qualified class name to the JavaScript interface
10+
# class:
11+
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12+
# public *;
13+
#}
14+
15+
# Uncomment this to preserve the line number information for
16+
# debugging stack traces.
17+
#-keepattributes SourceFile,LineNumberTable
18+
19+
# If you keep the line number information, uncomment this to
20+
# hide the original source file name.
21+
#-renamesourcefileattribute SourceFile
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.chaione.c1scanner
2+
3+
import androidx.test.platform.app.InstrumentationRegistry
4+
import androidx.test.ext.junit.runners.AndroidJUnit4
5+
6+
import org.junit.Test
7+
import org.junit.runner.RunWith
8+
9+
import org.junit.Assert.*
10+
11+
/**
12+
* Instrumented test, which will execute on an Android device.
13+
*
14+
* See [testing documentation](http://d.android.com/tools/testing).
15+
*/
16+
@RunWith(AndroidJUnit4::class)
17+
class ExampleInstrumentedTest {
18+
@Test
19+
fun useAppContext() {
20+
// Context of the app under test.
21+
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22+
assertEquals("com.chaione.c1scanner", appContext.packageName)
23+
}
24+
}

‎app/src/main/AndroidManifest.xml

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3+
package="com.chaione.c1scanner">
4+
<uses-feature android:name="android.hardware.camera.any" />
5+
<uses-permission android:name="android.permission.CAMERA" />
6+
<application
7+
android:allowBackup="true"
8+
android:icon="@mipmap/ic_launcher"
9+
android:label="@string/app_name"
10+
android:roundIcon="@mipmap/ic_launcher_round"
11+
android:supportsRtl="true"
12+
android:theme="@style/AppTheme">
13+
<activity android:name=".MainActivity">
14+
<intent-filter>
15+
<action android:name="android.intent.action.MAIN" />
16+
17+
<category android:name="android.intent.category.LAUNCHER" />
18+
</intent-filter>
19+
</activity>
20+
</application>
21+
22+
</manifest>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
package com.chaione.c1scanner
2+
3+
import android.Manifest
4+
import android.annotation.SuppressLint
5+
import android.content.pm.PackageManager
6+
import android.net.Uri
7+
import android.opengl.Visibility
8+
import android.os.Bundle
9+
import android.util.Log
10+
import android.util.Size
11+
import android.widget.TextView
12+
import android.widget.Toast
13+
import androidx.appcompat.app.AppCompatActivity
14+
import androidx.camera.core.*
15+
import androidx.camera.lifecycle.ProcessCameraProvider
16+
import androidx.camera.view.PreviewView
17+
import androidx.core.app.ActivityCompat
18+
import androidx.core.content.ContextCompat
19+
import androidx.core.view.isVisible
20+
import com.google.android.gms.tasks.OnFailureListener
21+
import com.google.android.gms.tasks.OnSuccessListener
22+
import com.google.mlkit.vision.common.InputImage
23+
import com.google.mlkit.vision.text.TextRecognition
24+
import com.google.mlkit.vision.text.TextRecognizer
25+
import com.google.mlkit.vision.text.Text
26+
import kotlinx.android.synthetic.main.activity_main.*
27+
import java.io.File
28+
import java.text.SimpleDateFormat
29+
import java.util.*
30+
import java.util.concurrent.ExecutorService
31+
import java.util.concurrent.Executors
32+
33+
34+
typealias LumaListener = (luma: Double) -> Unit
35+
36+
class MainActivity : AppCompatActivity() {
37+
private var preview: Preview? = null
38+
private var imageCapture: ImageCapture? = null
39+
private var imageAnalyzer: ImageAnalysis? = null
40+
private var camera: Camera? = null
41+
private var results: TextView? = null
42+
43+
private lateinit var outputDirectory: File
44+
private lateinit var cameraExecutor: ExecutorService
45+
46+
override fun onCreate(savedInstanceState: Bundle?) {
47+
super.onCreate(savedInstanceState)
48+
setContentView(R.layout.activity_main)
49+
results = findViewById(R.id.results)
50+
// viewFinder = findViewById(R.id.viewFinder);
51+
52+
// Request camera permissions
53+
if (allPermissionsGranted()) {
54+
startCamera()
55+
} else {
56+
ActivityCompat.requestPermissions(
57+
this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)
58+
}
59+
60+
// Setup the listener for take photo button
61+
camera_capture_button.setOnClickListener { takePhoto() }
62+
63+
outputDirectory = getOutputDirectory()
64+
65+
cameraExecutor = Executors.newSingleThreadExecutor()
66+
}
67+
private fun startCamera() {
68+
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
69+
70+
cameraProviderFuture.addListener(Runnable {
71+
// Used to bind the lifecycle of cameras to the lifecycle owner
72+
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
73+
74+
// Preview
75+
preview = Preview.Builder().build()
76+
77+
imageCapture = ImageCapture.Builder()
78+
.setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
79+
.build()
80+
//
81+
imageAnalyzer = ImageAnalysis.Builder()
82+
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
83+
.build()
84+
.also {
85+
it.setAnalyzer(cameraExecutor, OcrAnalyzer())
86+
}
87+
88+
// Select back camera
89+
val cameraSelector = CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()
90+
91+
try {
92+
// Unbind use cases before rebinding
93+
cameraProvider.unbindAll()
94+
95+
// Bind use cases to camera
96+
camera = cameraProvider.bindToLifecycle(
97+
this, cameraSelector, preview, imageCapture, imageAnalyzer)
98+
preview?.setSurfaceProvider(viewFinder.createSurfaceProvider(camera?.cameraInfo))
99+
} catch(exc: Exception) {
100+
Log.e(TAG, "Use case binding failed", exc)
101+
}
102+
103+
}, ContextCompat.getMainExecutor(this))
104+
}
105+
106+
private fun takePhoto() {
107+
val imageCapture = imageCapture ?: return
108+
val photoFile = File(outputDirectory,SimpleDateFormat(FILENAME_FORMAT, Locale.US).format(System.currentTimeMillis()) + ".jpg")
109+
val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()
110+
111+
imageCapture.takePicture(outputOptions, ContextCompat.getMainExecutor(this), object: ImageCapture.OnImageSavedCallback{
112+
override fun onError(exception: ImageCaptureException) {
113+
Log.e(TAG, "Photo capture failed ${exception.message}", exception)
114+
}
115+
116+
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
117+
val savedUri = Uri.fromFile(photoFile)
118+
val msg = "Photo capture succeeded: $savedUri"
119+
Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
120+
Log.d(TAG, msg)
121+
122+
val file = InputImage.fromFilePath(baseContext, savedUri)
123+
runTextRecognition(file)
124+
}
125+
})
126+
}
127+
128+
private fun runTextRecognition(image: InputImage) {
129+
val recognizer: TextRecognizer = TextRecognition.getClient()
130+
recognizer.process(image)
131+
.addOnSuccessListener { texts ->
132+
processTextRecognitionResult(texts as Text)
133+
}
134+
.addOnFailureListener{ e -> // Task failed with an exception
135+
e.printStackTrace()
136+
}
137+
}
138+
private fun processTextRecognitionResult(texts: Text) {
139+
Log.d(TAG, "Processing...")
140+
val blocks: List<Text.TextBlock> = texts.getTextBlocks()
141+
if (blocks.size === 0) {
142+
Log.d(TAG, "No text found")
143+
return
144+
}
145+
var result = ""
146+
for (i in 0 until blocks.size) {
147+
val lines: List<Text.Line> = blocks[i].getLines()
148+
for (j in 0 until lines.size) {
149+
val elements: List<Text.Element> = lines[j].getElements()
150+
for (k in 0 until elements.size) {
151+
Log.d(TAG,"Text found: ${elements[k]}")
152+
result += elements[k].text + ", "
153+
// val textGraphic: Graphic = TextGraphic(mGraphicOverlay, elements[k])
154+
// mGraphicOverlay.add(textGraphic)
155+
}
156+
}
157+
}
158+
results?.text = result
159+
}
160+
161+
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
162+
if (requestCode == REQUEST_CODE_PERMISSIONS) {
163+
if (allPermissionsGranted()) {
164+
startCamera()
165+
} else {
166+
Toast.makeText(this,
167+
"Permissions not granted.",
168+
Toast.LENGTH_SHORT).show()
169+
finish()
170+
}
171+
}
172+
}
173+
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
174+
ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED
175+
}
176+
177+
fun getOutputDirectory(): File {
178+
val mediaDir = externalMediaDirs.firstOrNull()?.let {
179+
File(it, resources.getString(R.string.app_name)).apply { mkdirs() } }
180+
return if (mediaDir != null && mediaDir.exists())
181+
mediaDir else filesDir
182+
}
183+
184+
companion object {
185+
private const val TAG = "C1_Scanner"
186+
private const val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"
187+
private const val REQUEST_CODE_PERMISSIONS = 10
188+
private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA)
189+
}
190+
191+
private class OcrAnalyzer : ImageAnalysis.Analyzer {
192+
193+
@SuppressLint("UnsafeExperimentalUsageError")
194+
override fun analyze(image: ImageProxy) {
195+
val mediaImage = image.image
196+
if (mediaImage != null) {
197+
// val image = InputImage.fromMediaImage(mediaImage, image.imageInfo.rotationDegrees)
198+
// runTextRecognition(image);
199+
}
200+
image.close()
201+
}
202+
}
203+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
2+
xmlns:aapt="http://schemas.android.com/aapt"
3+
android:width="108dp"
4+
android:height="108dp"
5+
android:viewportWidth="108"
6+
android:viewportHeight="108">
7+
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
8+
<aapt:attr name="android:fillColor">
9+
<gradient
10+
android:endX="85.84757"
11+
android:endY="92.4963"
12+
android:startX="42.9492"
13+
android:startY="49.59793"
14+
android:type="linear">
15+
<item
16+
android:color="#44000000"
17+
android:offset="0.0" />
18+
<item
19+
android:color="#00000000"
20+
android:offset="1.0" />
21+
</gradient>
22+
</aapt:attr>
23+
</path>
24+
<path
25+
android:fillColor="#FFFFFF"
26+
android:fillType="nonZero"
27+
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
28+
android:strokeWidth="1"
29+
android:strokeColor="#00000000" />
30+
</vector>

0 commit comments

Comments
 (0)
Please sign in to comment.