Skip to main content

Business Card Scanner using OCR API (Text Recognizer) in Kotlin Android



In this post,
I will explain about business card scanner using OCR API (Text Recognizer) in android.
Before that you need to read about OCR API released by google in following link,
https://developers.google.com/vision/text-overview
Step 1 :  We need to add following in dependencies
compile 'com.google.android.gms:play-services-vision:11.0.2'
Step 2 : Add Permissions for CAMERA, READ and WRITE_EXTERNAL_STORAGE Permissions for Capture and store image. After scan the image we will delete that stored image from storage. We need high quality image file for getting exact text, 's why we store the image.
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-feature android:name="android.hardware.camera"     android:required="false"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
Step 3 : For Handling run time permission, I user TedPermission library. It's very easy for handle accept and denied permissions. We need to add following line to dependencies,
compile 'gun0912.ted:tedpermission:1.0.0'
Step 4 : Add the following code for handle run time permission in your activity file
btnScanner.setOnClickListener {
    val permissionlistener = object : PermissionListener {
        override fun onPermissionGranted() {
            scanImage()
        }
        override fun onPermissionDenied(deniedPermissions: java.util.ArrayList<String>) {
            Toast.makeText(this@MainActivity, "Permission Denied\n" + 
deniedPermissions.toString(), Toast.LENGTH_SHORT).show()
        }
    }
    TedPermission(this@MainActivity)
            .setPermissionListener(permissionlistener)
            .setDeniedMessage("If you reject permission,you can not use this service\n\n
Please turn on permissions at [Settings] > [Permission]")
            .setPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE, 
Manifest.permission.CAMERA)
            .check()
}
Step 5 : We need to capture and store the image in internal storage with temporary name. So, We will add the following codes in ScanImage() function.
private fun scanImage() {
      val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
      if (takePictureIntent.resolveActivity(packageManager) != null) {
          val fileName = "temp.jpg"          val values = ContentValues()
          values.put(MediaStore.Images.Media.TITLE, fileName)
          mCapturedImageURI = contentResolver.insert
  (MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
          takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, mCapturedImageURI)
          startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE)
      }
}
Step 6: Handle Activity result and process the image in Asynctask
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
      super.onActivityResult(requestCode, resultCode, data)
      if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == Activity.RESULT_OK) {
          ProcessImageTask(resultCode).execute()
      }
}
Step 7: In Asynctask, we will get the captured image from storage and convert to bitmap. In doInBackground() function we convert and process the image.
inner class ProcessImageTask(requestCode: Int) : AsyncTask<Void, Void, String>() {
      private val resultCode = requestCode
      override fun onPreExecute() {
          super.onPreExecute()
          Alert.showProgress(progressDialog)
      }
      override fun doInBackground(vararg params: Void?): String? {
          if (REQUEST_IMAGE_CAPTURE == REQUEST_IMAGE_CAPTURE && resultCode == 
  Activity.RESULT_OK) {
              val projection = arrayOf(MediaStore.Images.Media.DATA)
              val cursor = managedQuery(mCapturedImageURI, projection, null, null, null)
              val column_index_data = cursor.getColumnIndexOrThrow
  (MediaStore.Images.Media.DATA)
              cursor.moveToFirst()
              picturePath = cursor.getString(column_index_data)
              val image = File(picturePath)
              val bmOptions = BitmapFactory.Options()
              val bitmap = BitmapFactory.decodeFile(image.absolutePath, bmOptions)
              cardBitmap = rotateImage(bitmap, 90f)
              processImage(cardBitmap)
          }
          return "finish"     
    }
      override fun onPostExecute(result: String?) {
          super.onPostExecute(result)
          Alert.hideProgress(progressDialog)
      }
 }
Step 8 : We process image using TextRecognizer method from bitmap
 private fun processImage(bitmap: Bitmap) {
    val textRecognizer = TextRecognizer.Builder(applicationContext).build()
    if (textRecognizer.isOperational) {
        val frame = Frame.Builder().setBitmap(bitmap).build()
        val items = textRecognizer.detect(frame)
        val stringBuilder = StringBuilder()
        for (i in 0 until items.size()) {
            val textBlock = items.valueAt(i)
            stringBuilder.append(textBlock.value)
            extractName(textBlock.value, false)
            extractEmail(textBlock.value)
            extractPhone(textBlock.value)
            stringBuilder.append("\n")
        }
        if (nameString.equals("")) {
            for (i in 0 until items.size()) {
                val textBlock = items.valueAt(i)
                stringBuilder.append(textBlock.value)
                extractName(textBlock.value, true)
                stringBuilder.append("\n")
            }
        }
        val fdelete = File(picturePath)
        if (fdelete.exists()) {
            if (fdelete.delete()) {
                println("file Deleted :" + picturePath)
            } else {
                println("file not Deleted :" + picturePath)
            }
        }
    } else {
        Toast.makeText(this@MainActivity, "Please rescan the card", Toast.LENGTH_SHORT).show()
    }
  }
Step 9 : After converting the bitmap into text block, we add some pattern for find Name, email number and mobile number
private fun extractName(str: String, retryFetchSingleWord: Boolean) {
    if (nameString == "") {
        if (!retryFetchSingleWord) {
            val strList: List<String> = str.split("\n")
            if (strList.size == 2 || strList.size == 3) {
                if (isAlpha(strList[0])) {
                    nameString = strList[0]
                }
            }
            val NAME_REGEX = "^([A-Z]([a-z]*|\\.) *){1,2}([A-Z][a-z]+-?)+$"            
            val p = Pattern.compile(NAME_REGEX, Pattern.MULTILINE)
            val m = p.matcher(str)
            if (m.find()) {
                nameString = m.group()
            }
        } else {
            val strList: List<String> = str.split("\n")
            if (strList.count() == 1 && isAlpha(str)) {
                if (findWordsLength(str) < 3) {
                    nameString = str
                    return               
                }
            }
        }
    }
  }

  private fun extractEmail(str: String) {
    val EMAIL_REGEX = "(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.
[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\
x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*
[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|
[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:
(?:[\\x01-\\\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|
\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])"    
    val p = Pattern.compile(EMAIL_REGEX, Pattern.MULTILINE)
    val m = p.matcher(str)   // get a matcher object   
    if (m.find()) {
          emailString = m.group()
    }
  }

  private fun extractPhone(str: String) {
    if (mobileNumberString == "") {
        var numbericString = extractNumber(str)
        if (numbericString.length >= 10) {
            if (numbericString.length > 10) {
                numbericString = numbericString.substring(numbericString.length - 10)
            }
            println("Getting Phone Number")
            val PHONE_REGEX = "(?:^|\\D)(\\d{3})[)\\-. ]*?(\\d{3})[\\-. ]*?(\\d{4})(?:$|\\D)"            
            val p = Pattern.compile(PHONE_REGEX, Pattern.MULTILINE)
            val m = p.matcher(numbericString)   // get a matcher object            
            if (m.find()) {
                mobileNumberString = m.group()
            }
        }
    }
  }

  private fun extractNumber(str: String?): String {
    var str = str
    str = str!!.replace(" ".toRegex(), "")
    str = str.replace("-".toRegex(), "")
    if (str == null || str.isEmpty()) 
         return ""  
    val sb = StringBuilder()
    var found = false    for (c in str.toCharArray()) {
        if (Character.isDigit(c)) {
            sb.append(c)
            found = true      
       } else if (found) {
            // If we already found a digit before and this char is not a digit, stop looping          
           if (sb.length < 10) {
                sb.delete(0, sb.length)
            } else {
                if (sb.get(0).equals('9') || sb.get(0).equals('8') || sb.get(0).equals('7') || sb.get(0).equals('6')) {
                    break              
              } else {
                    sb.delete(0, sb.length)
                }
            }
        }
    }
    return</span><span style="font-family: "trebuchet ms" , sans-serif;"> sb.toString()
  }
Thank you :)

Comments

  1. First Class Quality PenPower WorldCard Pro Scanner in UAE, Business Card Scanner in UAE, English Scanner in UAE Visit now https://gccgamers.com/penpower-worldcard-pro-business-card-scanner-english-pt-wocpe.html

    ReplyDelete

Post a Comment

Popular posts from this blog

Convert image url into Multipart in kotlin android

In this post,        I added the code for how to convert the image url list to Multipart Body in kotlin android. Steps, Convert image url (String) to File   Create Request Body by using multipart/form-data MediaType from converted File Finally, Create MultiPart Body using MultipartBody.Part.createFormData private fun postImage(selectedUris: java.util.ArrayList<String>): ArrayList<MultipartBody.Part> { var multiParts: ArrayList<MultipartBody.Part> = ArrayList<MultipartBody.Part>() for (i in 0 until selectedUris.size) { // 1. Create File using image url (String) val file = File(selectedUris. get (i)) // 2. Create requestBody by using multipart/form-data MediaType from file val requestFile: RequestBody = RequestBody.create(MediaType.parse ( "multipart/form-data" ), file) // 3. Finally, Create MultipartBody using MultipartBody.Part.createFormData ...