I am trying to add a text scanning feature to my app where the user can scan any business card and extract the information out of it.
Then I’ll be saving that info to firebase in a table.
For this purpose I am using the Google Machine Learning Kit and Camera X APIs.
Here, my problem is the regex validations.
I have created these structures to fetch the info out of it.
val emailPattern = "[a-zA-Z0-9._-]+@[a-z]+\.[a-z]+"
val phonePattern = "(\+\d{1,3}[- ]?)?" + // Country code (optional, + followed by 1 to 3 digits)
"(\d{1,4}[- ]?)?" + // Area code (optional, 1 to 4 digits)
"(\(\d{1,4}\)[- ]?)?" + // Area code in parentheses (optional, 1 to 4 digits)
"(\d{3,4}[- ]?)?" + // First part of the number (3 to 4 digits)
"(\d{4})" // Second part of the number (4 digits)
val namePattern = "^([a-zA-Z]{2,}\s[a-zA-Z]{1,}'?-?[a-zA-Z]{2,}\s?([a-zA-Z]{1,})?)"
val jobTitlePattern = "^([a-zA-Z]{2,}\s[a-zA-Z]{1,}'?-?[a-zA-Z]{2,}\s?([a-zA-Z]{1,})?)"
val companyPattern = "\b[A-Z][a-zA-Z]+(?:\s+[A-Z][a-zA-Z]+)*\b"
I need to filter out the text on email, name, job title, phone number, company name and save this info to firebase.
class ScannerFragment : Fragment() {
private lateinit var binding: FragmentScannerBinding
private lateinit var previewView: PreviewView
private var imageCapture: ImageCapture? = null
private lateinit var cameraExecutor: ExecutorService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentScannerBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val captureButton = binding.btnCapture
previewView = binding.previewView
cameraExecutor = Executors.newSingleThreadExecutor()
captureButton.setOnClickListener { takePhoto() }
startCamera()
}
private fun startCamera() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(requireActivity())
cameraProviderFuture.addListener({
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
bindPreview(cameraProvider)
}, ContextCompat.getMainExecutor(requireActivity()))
}
private fun bindPreview(cameraProvider: ProcessCameraProvider) {
val preview = Preview.Builder().build()
imageCapture = ImageCapture.Builder().build()
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
preview.setSurfaceProvider(previewView.surfaceProvider)
try {
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture)
} catch (exc: Exception) {
Log.d(TAG, "Use case binding failed", exc)
}
}
private fun takePhoto() {
val imageCapture = imageCapture ?: return
imageCapture.takePicture(ContextCompat.getMainExecutor(requireActivity()), object : ImageCapture.OnImageCapturedCallback() {
override fun onCaptureSuccess(image: ImageProxy) {
processImageProxy(image)
}
override fun onError(exception: ImageCaptureException) {
Log.d(TAG, "Photo capture failed: ${exception.message}", exception)
}
})
}
@SuppressLint("UnsafeOptInUsageError")
private fun processImageProxy(imageProxy: ImageProxy) {
val mediaImage = imageProxy.image ?: return
val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
val recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)
recognizer.process(image)
.addOnSuccessListener { text ->
saveTextToUI(text)
}
.addOnFailureListener { e ->
Log.e(TAG, "Text recognition error", e)
Toast.makeText(requireContext(), "Failed to recognize text", Toast.LENGTH_SHORT).show()
}
.addOnCompleteListener {
imageProxy.close()
}
}
private fun saveTextToUI(text: Text) {
val recognizedText = text.text
val emailPattern = "[a-zA-Z0-9._-]+@[a-z]+\.[a-z]+"
val phonePattern = "(\+\d{1,3}[- ]?)?" + // Country code (optional, + followed by 1 to 3 digits)
"(\d{1,4}[- ]?)?" + // Area code (optional, 1 to 4 digits)
"(\(\d{1,4}\)[- ]?)?" + // Area code in parentheses (optional, 1 to 4 digits)
"(\d{3,4}[- ]?)?" + // First part of the number (3 to 4 digits)
"(\d{4})" // Second part of the number (4 digits)
val namePattern = "^([a-zA-Z]{2,}\s[a-zA-Z]{1,}'?-?[a-zA-Z]{2,}\s?([a-zA-Z]{1,})?)"
val jobTitlePattern = "^([a-zA-Z]{2,}\s[a-zA-Z]{1,}'?-?[a-zA-Z]{2,}\s?([a-zA-Z]{1,})?)"
val companyPattern = "\b[A-Z][a-zA-Z]+(?:\s+[A-Z][a-zA-Z]+)*\b"
// business card criteria
if (isBusinessCard(recognizedText, emailPattern, phonePattern, namePattern, jobTitlePattern, companyPattern)) {
binding.tvResultant.text = recognizedText
Log.d(TAG, "saving text: $recognizedText")
val name = extractPattern(recognizedText, namePattern)
val jobTitle = extractPattern(recognizedText, jobTitlePattern)
val company = extractPattern(recognizedText, companyPattern)
val phoneNumber = extractPattern(recognizedText, phonePattern)
val email = extractPattern(recognizedText, emailPattern)
binding.tiName.setText(name ?: "Name not found")
binding.tiJobTitle.setText(jobTitle ?: "Job Title not found")
binding.tiCompany.setText(company ?: "Company not found")
binding.tiPhoneNumber.setText(phoneNumber ?: "Phone Number not found")
binding.tiEmail.setText(email ?: "Email not found")
} else {
Toast.makeText(requireContext(), "Not a business card", Toast.LENGTH_SHORT).show()
}
}
private fun isBusinessCard(text: String, vararg patterns: String): Boolean {
var matchCount = 0
patterns.forEach { pattern ->
val compiledPattern = Pattern.compile(pattern)
val matcher = compiledPattern.matcher(text)
if (matcher.find()) {
matchCount++
}
}
return matchCount >= 3
}
private fun extractPattern(text: String, pattern: String): String? {
val compiledPattern = Pattern.compile(pattern)
val matcher = compiledPattern.matcher(text)
return if (matcher.find()) matcher.group() else null
}
override fun onDestroy() {
super.onDestroy()
cameraExecutor.shutdown()
}
companion object {
val TAG = "ScannerFragment"
}
}
I want to create such a validation with regex that filters out the name, email, job title, company, phone number.
But right now most of the times my regex struct doesnt pick right data, although the scanned data contains the name, email, job title etc other fields.