first commit

This commit is contained in:
NishantRajputRN
2026-04-16 15:23:13 +05:30
commit aa56e08ffd
523 changed files with 229267 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
/build
+77
View File
@@ -0,0 +1,77 @@
plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
}
android {
namespace = "com.cpm.india.cameraai"
compileSdk = 34
defaultConfig {
minSdk = 21
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
ndk {
abiFilters.add("armeabi-v7a")
abiFilters.add("arm64-v8a")
}
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
// compileOptions {
// sourceCompatibility = JavaVersion.VERSION_1_8
// targetCompatibility = JavaVersion.VERSION_1_8
// }
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
// jvmTarget = "1.8"
jvmTarget = "17"
// jvmToolchain(17) // Set this
}
buildFeatures {
viewBinding = true
}
}
dependencies {
//noinspection UseTomlInstead
implementation ("androidx.core:core-ktx:1.0.2")
implementation ("androidx.appcompat:appcompat:1.3.1")
implementation ("androidx.constraintlayout:constraintlayout:2.1.4")
implementation ("com.google.android.material:material:1.4.0")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.2.1")
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
///camerax
val camerax_version = "1.1.0-beta02"
// val camerax_version = "1.5.0-alpha01"
implementation ("androidx.camera:camera-core:$camerax_version")
implementation ("androidx.camera:camera-camera2:$camerax_version")
implementation ("androidx.camera:camera-lifecycle:$camerax_version")
implementation ("androidx.camera:camera-view:$camerax_version")
///for alert
//implementation ("org.jetbrains.anko:anko-common:0.10.4")
//for toast msg
implementation ("com.github.GrenderG:Toasty:1.4.2")
// mlkit face detection & gson
implementation ("com.google.mlkit:face-detection:16.1.7")
//google gson
implementation ("com.google.code.gson:gson:2.10.1")
implementation("org.opencv:opencv:4.9.0")
implementation ("androidx.activity:activity-ktx:1.8.0")
}
View File
+21
View File
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
@@ -0,0 +1,24 @@
package com.cpm.india.cameraai
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.cpm.india.cameraai.test", appContext.packageName)
}
}
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<uses-feature android:name="android.hardware.camera.any" />
<uses-feature android:name="android.hardware.camera" />
<uses-permission android:name="android.permission.MANAGE_DEVICE_POLICY_CAMERA" />
<uses-feature
android:name="android.hardware.camera.autofocus"
android:required="false" />
<application
android:supportsRtl="true"
android:allowBackup="false"
tools:replace="android:allowBackup">
<activity
android:name=".camera.CameraActivity"
android:screenOrientation="portrait"
tools:ignore="DiscouragedApi,LockedOrientationActivity"
android:exported="false" />
<activity
android:name=".preview.PreviewActivity"
android:screenOrientation="portrait"
tools:ignore="DiscouragedApi,LockedOrientationActivity"
android:exported="false" />
</application>
</manifest>
@@ -0,0 +1,421 @@
package com.cpm.india.cameraai.camera
import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.content.ContentValues.TAG
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.BitmapFactory
import android.os.Bundle
import android.os.Handler
import android.util.Log
import android.view.View
import android.view.animation.AlphaAnimation
import android.view.animation.Animation
//import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.camera.core.CameraSelector
import androidx.camera.core.ExperimentalGetImage
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY
import androidx.camera.core.ImageCapture.FLASH_MODE_AUTO
import androidx.camera.core.ImageCaptureException
import androidx.camera.core.ImageProxy
import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.cpm.india.cameraai.R
import com.cpm.india.cameraai.databinding.ActivityCameraBinding
import com.cpm.india.cameraai.preview.PreviewActivity
import com.cpm.india.cameraai.utils.GetProperImageRotation.checkBlinkAndMovement
import com.cpm.india.cameraai.utils.Utils.calculateBrightness
import com.cpm.india.cameraai.utils.Utils.checkBlurriness
import com.cpm.india.cameraai.utils.Utils.eyeBlink
import com.cpm.india.cameraai.utils.Utils.isFaceTooFar
import com.cpm.india.cameraai.utils.Utils.isValidHumanFace
import com.google.mlkit.vision.common.InputImage
import com.google.mlkit.vision.face.Face
import com.google.mlkit.vision.face.FaceDetection
import com.google.mlkit.vision.face.FaceDetectorOptions
import com.google.mlkit.vision.face.FaceLandmark
//import org.jetbrains.anko.toast
import android.widget.Toast
import org.opencv.android.OpenCVLoader
import java.io.File
import java.nio.ByteBuffer
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import kotlin.concurrent.thread
import kotlin.math.absoluteValue
class CameraActivity : AppCompatActivity() {
private var imageCapture: ImageCapture? = null
private lateinit var cameraExecutor: ExecutorService
private lateinit var binding: ActivityCameraBinding
private var flipX = true
private var focusFlag = 0
var cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA
private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA)
private val REQUEST_CODE_PERMISSIONS = 10
private var doubleBackToExitPressedOnce = false
private var mainThreadIsStopped: Boolean = true
private var parameterThread: Thread? = null
private var pictureThread: Thread? = null
private var filePath: String? = null
private var isGrid: Boolean? = false
private var isCheckFace: Boolean? = true
private var isFacingFront: Boolean? = true
private var isFlash = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// enableEdgeToEdge()
binding = ActivityCameraBinding.inflate(layoutInflater)
setContentView(binding.root)
ViewCompat.setOnApplyWindowInsetsListener(binding.main) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
if (!OpenCVLoader.initLocal()) {
Log.e("OpenCv", "OpenCV initialization failed")
} else {
Log.d("OpenCv", "OpenCV initialization succeeded")
}
eyeBlink = false
filePath = intent.getStringExtra("filePath")
isGrid = intent.getBooleanExtra("isGrid", false)
isCheckFace = intent.getBooleanExtra("isCheckFace", false)
isFacingFront = intent.getBooleanExtra("isFacingFront", true)
Log.e("filePath", filePath!!)
if (!isFacingFront!!) cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
if (isCheckFace!!) binding.capturebtnCameraActivity.visibility = View.INVISIBLE
switchCamera()
cameraExecutor = Executors.newSingleThreadExecutor()
if (allPermissionsGranted()) {
startCamera()
} else {
ActivityCompat.requestPermissions(
this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS
)
}
focusingContinuous()
requestPermissionsforApp()
calledOnClick()
}
private fun focusingContinuous() {
focusFlag = 1
this@CameraActivity.runOnUiThread {
runOnUiThread {
val anim = AlphaAnimation(0.0f, 1.0f)
anim.duration = 10//You can manage the time of the blink with this parameter
anim.startOffset = 300
anim.repeatMode = Animation.REVERSE
anim.repeatCount = Animation.INFINITE
}
}
}
private fun calledOnClick() {
Log.d("Jeevanpclick", filePath.toString())
binding.capturebtnCameraActivity.setOnClickListener {
val outputOptions = ImageCapture.OutputFileOptions.Builder(File(filePath!!)).build()
mainThreadIsStopped = true
imageCapture?.takePicture(
outputOptions,
ContextCompat.getMainExecutor(this),
object : ImageCapture.OnImageSavedCallback {
override fun onError(exception: ImageCaptureException) {
exception.printStackTrace()
}
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
mainThreadIsStopped = true
pictureThread = thread(start = true) {
val savedUri = outputFileResults.savedUri
Log.d("Jeevanp", savedUri.toString())
val bitmap = BitmapFactory.decodeFile(savedUri?.path)
val openCvResult = checkBlurriness(bitmap)
val brightnessResult = calculateBrightness(bitmap)
if (openCvResult == 1 && brightnessResult.absoluteValue > 0.20) {
Log.d("Jeevanp", "1")
val intent =
Intent(this@CameraActivity, PreviewActivity::class.java)
intent.putExtra("photoUri", File(filePath!!).absolutePath)
resultLauncher.launch(intent)
} else {
if (openCvResult != 1 && brightnessResult.absoluteValue <= 0.32) {
Log.d("Jeevanp", "2")
// runOnUiThread {
// toast("Image is blurry and dark. Please try again.")
// }
runOnUiThread {
Toast.makeText(applicationContext, "Image is blurry. and dark. Please try again", Toast.LENGTH_SHORT).show()
}
} else if (brightnessResult.absoluteValue <= 0.32) {
Log.d("Jeevanp", "3")
runOnUiThread {
// toast("Image is dark. Please try again.")
Toast.makeText(applicationContext, "Image is dark. Please try again.", Toast.LENGTH_SHORT).show()
}
} else {
Log.d("Jeevanp", "4")
runOnUiThread {
// toast("Image is blurry. Please try again.")
Toast.makeText(applicationContext, "Image is blurry. Please try again.", Toast.LENGTH_SHORT).show()
}
}
}
}
}
})
}
}
private var resultLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
// There are no request codes
val data: Intent? = result.data
if (data != null) {
val intent = Intent()
intent.putExtra("filePath", data.getStringExtra("filePath").toString())
setResult(RESULT_OK, intent)
finish()
}
}
}
private fun switchCamera() {
binding.switchCamera.setOnClickListener {
if (cameraSelector == CameraSelector.DEFAULT_BACK_CAMERA) {
cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA
flipX = true
} else {
cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
flipX = false
}
startCamera()
}
}
private fun startCamera() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
val preview = Preview.Builder().build()
.also {
it.setSurfaceProvider(binding.texture.surfaceProvider)
//it.surfaceProvider = binding.texture.surfaceProvider
}
imageCapture = ImageCapture.Builder()
.setCaptureMode(CAPTURE_MODE_MAXIMIZE_QUALITY)
.setFlashMode(FLASH_MODE_AUTO)
.setJpegQuality(100)
.build()
val imageAnalyzer = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
.also {
it.setAnalyzer(
cameraExecutor,
LuminosityAnalyzer(binding, isCheckFace!!, context = this)
)
}
try {
cameraProvider.unbindAll()
val cam = cameraProvider.bindToLifecycle(
this, cameraSelector, preview, imageCapture, imageAnalyzer
)
binding.ivFlash.setOnClickListener {
if (cam.cameraInfo.hasFlashUnit()) {
if (!isFlash) {
isFlash = true
cam.cameraControl.enableTorch(true) // or false
binding.ivFlash.setImageResource(R.mipmap.ic_flash_on_white_24dp)
} else {
isFlash = false
cam.cameraControl.enableTorch(false) // or false
binding.ivFlash.setImageResource(R.mipmap.ic_flash_off_white_24dp)
}
}
}
} catch (exc: Exception) {
Log.e(TAG, "Use case binding failed", exc)
}
}, ContextCompat.getMainExecutor(this))
}
private fun requestPermissionsforApp() {
ActivityCompat.requestPermissions(
this@CameraActivity,
arrayOf(Manifest.permission.CAMERA),
2
)
}
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<String>, grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == REQUEST_CODE_PERMISSIONS) {
if (allPermissionsGranted()) {
startCamera()
} else {
runOnUiThread {
// toast("Permissions not granted by the user.")
Toast.makeText(applicationContext, "Permissions not granted by the user.", Toast.LENGTH_SHORT).show()
}
this@CameraActivity.finish()
}
}
}
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
ContextCompat.checkSelfPermission(
baseContext, it
) == PackageManager.PERMISSION_GRANTED
}
public class LuminosityAnalyzer(
val binding: ActivityCameraBinding,
val isCheckFace: Boolean,
val context: Context
) :
ImageAnalysis.Analyzer {
private fun ByteBuffer.toByteArray(): ByteArray {
rewind() // Rewind the buffer to zero
val data = ByteArray(remaining())
get(data) // Copy the buffer into a byte array
return data // Return the byte array
}
@androidx.annotation.OptIn(ExperimentalGetImage::class)
override fun analyze(image: ImageProxy) {
val inputImage = InputImage.fromMediaImage(
image.image!!,
image.imageInfo.rotationDegrees
)
val options = FaceDetectorOptions.Builder()
.setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_ACCURATE)
.setLandmarkMode(FaceDetectorOptions.LANDMARK_MODE_ALL) // Detect landmarks (eyes, mouth, etc.)
.setClassificationMode(FaceDetectorOptions.CLASSIFICATION_MODE_ALL) // Enable face classification (e.g., smiling, eyes open)
.setMinFaceSize(0.1f) // Detect smaller faces in the image
.enableTracking() // Track faces across frames
.build()
val faceDetector = FaceDetection.getClient(options)
faceDetector.process(inputImage)
.addOnSuccessListener { faces: List<Face?>? ->
onSuccessListener(
faces,
inputImage,
binding,
isCheckFace,
context
)
}.addOnFailureListener {
}.addOnCompleteListener { image.close() }
}
@SuppressLint("ResourceType", "UseCompatLoadingForDrawables")
fun onSuccessListener(
faces: List<Face?>?,
inputImage: InputImage,
binding: ActivityCameraBinding,
isCheckFace: Boolean,
context: Context
) {
if (isCheckFace) {
if (!faces.isNullOrEmpty()) {
for (face in faces) {
val boundingBox = face?.boundingBox
// Ensure detected face meets conditions that typically indicate a human face
val leftEye = face?.getLandmark(FaceLandmark.LEFT_EYE)
val rightEye = face?.getLandmark(FaceLandmark.RIGHT_EYE)
val nose = face?.getLandmark(FaceLandmark.NOSE_BASE)
val mouthLeft = face?.getLandmark(FaceLandmark.MOUTH_LEFT)
val mouthRight = face?.getLandmark(FaceLandmark.MOUTH_RIGHT)
if (checkBlinkAndMovement(face!!)) {
eyeBlink = true
}
// Check if the face is too far based on the bounding box size
Log.e("eyeBlink", "$eyeBlink")
// Example condition: only consider it a human face if the eyes and mouth probabilities are detected
if (leftEye != null && rightEye != null && nose != null && mouthLeft != null && mouthRight != null) {
if (isValidHumanFace(face) && !isFaceTooFar(boundingBox) && eyeBlink) {
Log.e("facedata", "Face detected")
binding.tvImageBlurr.setText(R.string.face_detected)
binding.capturebtnCameraActivity.visibility = View.VISIBLE
} else {
Log.e("facedata", "No Face detected")
binding.tvImageBlurr.setText(R.string.no_face_detected)
binding.capturebtnCameraActivity.visibility = View.INVISIBLE
}
} else {
Log.e("facedata", "No Face detected")
binding.tvImageBlurr.setText(R.string.no_face_detected)
binding.capturebtnCameraActivity.visibility = View.INVISIBLE
}
}
} else {
Log.e("facedata", "No Face detected")
binding.tvImageBlurr.setText(R.string.no_face_detected)
binding.capturebtnCameraActivity.visibility = View.INVISIBLE
}
}
}
}
@SuppressLint("SetTextI18n")
override fun onResume() {
super.onResume()
focusFlag = 0
eyeBlink = false
mainThreadIsStopped = false
binding.capturebtnCameraActivity.isEnabled = true
parameterThread = thread(start = true) {
this@CameraActivity.runOnUiThread {
binding.infoPanel.text = "Please stand in a well lit area"
}
Thread.sleep(1000)
}
}
override fun onPause() {
super.onPause()
mainThreadIsStopped = true
}
override fun onDestroy() {
super.onDestroy()
Runtime.getRuntime().gc()
}
override fun onBackPressed() {
if (doubleBackToExitPressedOnce) {
super.onBackPressed()
return
}
this.doubleBackToExitPressedOnce = true
// toast("Please click BACK again to exit")
Toast.makeText(applicationContext, "Please click BACK again to exit", Toast.LENGTH_SHORT).show()
Handler().postDelayed({
doubleBackToExitPressedOnce = false
}, 2000)
}
}
@@ -0,0 +1,68 @@
package com.cpm.india.cameraai.preview
import android.content.Intent
import android.graphics.BitmapFactory
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.cpm.india.cameraai.databinding.ActivityPreviewBinding
import com.cpm.india.cameraai.utils.GetProperImageRotation
import java.io.File
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
class PreviewActivity : AppCompatActivity() {
private lateinit var cameraExecutor: ExecutorService
private lateinit var binding: ActivityPreviewBinding
private var photoUri: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityPreviewBinding.inflate(layoutInflater)
setContentView(binding.root)
cameraExecutor = Executors.newSingleThreadExecutor()
photoUri = intent.getStringExtra("photoUri")
val photoFile = photoUri?.let { File(it) }
if (photoUri != null) {
Log.d("uridata", photoUri!!)
}
val rotatedImageFile = GetProperImageRotation.getRotatedImageFile(photoFile, photoUri, this)
binding.capturedImageView.setImageBitmap(BitmapFactory.decodeFile(rotatedImageFile?.absolutePath))
binding.retakeLL.setOnClickListener {
finish() // Go back to the camera activity
}
binding.cancelLL.setOnClickListener {
deleteImageFile(photoFile!!.absolutePath)
finish() // Go back to the camera activity
}
binding.saveLL.setOnClickListener {
val intent = Intent()
intent.putExtra("filePath", photoUri!!)
setResult(RESULT_OK, intent)
finish()
}
}
private fun deleteImageFile(filePath: String): Boolean {
val file = File(filePath)
return if (file.exists()) {
val isDeleted = file.delete()
if (isDeleted) {
Log.d("FileDeletion", "File deleted successfully: $filePath")
} else {
Log.e("FileDeletion", "File deletion failed: $filePath")
}
isDeleted
} else {
Log.e("FileDeletion", "File does not exist: $filePath")
false
}
}
// Ensure to shut down the executor
override fun onDestroy() {
super.onDestroy()
cameraExecutor.shutdown()
}
}
@@ -0,0 +1,122 @@
package com.cpm.india.cameraai.utils
import android.content.Context
import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Matrix
import android.util.Log
import androidx.exifinterface.media.ExifInterface
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
import com.google.mlkit.vision.face.Face
import kotlin.math.absoluteValue
object GetProperImageRotation {
fun getRotatedImageFile(photoFile: File?, filePath: String?, context: Context?): File? {
val option = BitmapFactory.Options()
option.inSampleSize = 4
val convertedBitmap: Bitmap =
modifyOrientation(
BitmapFactory.decodeFile(photoFile!!.absolutePath, option),
photoFile.absolutePath, context
)
return saveImage(convertedBitmap, filePath)
}
private fun saveImage(image: Bitmap, filePath: String?): File? {
// Get the file object
val file = filePath?.let { File(it) }
val os = BufferedOutputStream(FileOutputStream(file))
image.compress(Bitmap.CompressFormat.JPEG, 100, os)
os.close()
return file
}
private fun modifyOrientation(
bitmap: Bitmap,
image_absolute_path: String,
context: Context?
): Bitmap {
val ei = ExifInterface(image_absolute_path)
val orientation: Int =
ei.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
Log.e("orintation", "" + getScreenOrientation(context))
when (orientation) {
ExifInterface.ORIENTATION_ROTATE_90 -> {
return rotate(bitmap, 90f)
}
ExifInterface.ORIENTATION_ROTATE_180 -> {
return rotate(bitmap, 180f)
}
ExifInterface.ORIENTATION_TRANSVERSE -> {
return rotate(bitmap, 270f)
}
ExifInterface.ORIENTATION_ROTATE_270 -> {
return rotate(bitmap, 270f)
}
ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> {
return flip(bitmap, true, vertical = false)
}
ExifInterface.ORIENTATION_FLIP_VERTICAL -> {
return flip(bitmap, false, vertical = true)
}
else -> {
return bitmap
}
}
}
private fun rotate(bitmap: Bitmap, degrees: Float): Bitmap {
val matrix = Matrix()
matrix.postRotate(degrees)
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
}
private fun flip(bitmap: Bitmap, horizontal: Boolean, vertical: Boolean): Bitmap {
val matrix = Matrix()
matrix.preScale(if (horizontal) (-1f) else 1f, if (vertical) (-1f) else 1f)
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true);
}
// Function to check for blink and movement
fun checkBlinkAndMovement(face: Face): Boolean {
// Step 1: Check blink detection
val leftEyeOpenProbability = face.leftEyeOpenProbability ?: -1f
val rightEyeOpenProbability = face.rightEyeOpenProbability ?: -1f
// If both eyes are open or closed, consider it a blink detection scenario
val isBlinking = leftEyeOpenProbability < 0.3 && rightEyeOpenProbability < 0.3
// Step 2: Check for facial movement (pose detection)
val headEulerAngleX = face.headEulerAngleX // Head tilt up/down
val headEulerAngleY = face.headEulerAngleY // Head turn left/right
val headEulerAngleZ = face.headEulerAngleZ // Head tilt sideways
// If the face poses (angles) change significantly between frames, consider it as movement
val isMoving =
headEulerAngleX.absoluteValue > 10 || headEulerAngleY.absoluteValue > 10 || headEulerAngleZ.absoluteValue > 10
// Return true if either blinking or movement is detected
return isBlinking && isMoving
}
fun getScreenOrientation(context: Context?): Int {
val configuration = context?.resources!!.configuration
// If the configuration returns ORIENTATION_UNDEFINED, manually check width/height
if (configuration.orientation == Configuration.ORIENTATION_UNDEFINED) {
val displayMetrics = context.resources!!.displayMetrics
return if (displayMetrics.widthPixels > displayMetrics.heightPixels) {
Configuration.ORIENTATION_LANDSCAPE
} else {
Configuration.ORIENTATION_PORTRAIT
}
}
return configuration.orientation
}
}
@@ -0,0 +1,182 @@
package com.cpm.india.cameraai.utils
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.Matrix
import android.graphics.Rect
import android.media.Image
import android.util.Log
import com.google.mlkit.vision.face.Face
import org.opencv.android.Utils
import org.opencv.core.Core
import org.opencv.core.Mat
import org.opencv.core.MatOfDouble
import org.opencv.imgproc.Imgproc
import java.nio.ReadOnlyBufferException
import kotlin.experimental.inv
object Utils {
var eyeBlink = false
private fun rotateBitmap(bitmap: Bitmap, rotationDegrees: Int, flipX: Boolean): Bitmap {
val matrix = Matrix()
// Rotate the image back to straight.
matrix.postRotate(rotationDegrees.toFloat())
// Mirror the image along the X or Y axis.
matrix.postScale(if (flipX) -1.0f else 1.0f, 1.0f)
val rotatedBitmap =
Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
// Recycle the old bitmap if it has changed.
if (rotatedBitmap != bitmap) {
bitmap.recycle()
}
return rotatedBitmap
}
private fun YUV_420_888toNV21(image: Image): ByteArray {
val width = image.width
val height = image.height
val ySize = width * height
val uvSize = width * height / 4
val nv21 = ByteArray(ySize + uvSize * 2)
val yBuffer = image.planes[0].buffer // Y
val uBuffer = image.planes[1].buffer // U
val vBuffer = image.planes[2].buffer // V
var rowStride = image.planes[0].rowStride
assert(image.planes[0].pixelStride == 1)
var pos = 0
if (rowStride == width) { // likely
yBuffer[nv21, 0, ySize]
pos += ySize
} else {
var yBufferPos = -rowStride.toLong() // not an actual position
while (pos < ySize) {
yBufferPos += rowStride.toLong()
yBuffer.position(yBufferPos.toInt())
yBuffer[nv21, pos, width]
pos += width
}
}
rowStride = image.planes[2].rowStride
val pixelStride = image.planes[2].pixelStride
assert(rowStride == image.planes[1].rowStride)
assert(pixelStride == image.planes[1].pixelStride)
if (pixelStride == 2 && rowStride == width && uBuffer[0] == vBuffer[1]) {
// maybe V an U planes overlap as per NV21, which means vBuffer[1] is alias of uBuffer[0]
val savePixel = vBuffer[1]
try {
vBuffer.put(1, savePixel.inv() as Byte)
if (uBuffer[0] == savePixel.inv() as Byte) {
vBuffer.put(1, savePixel)
vBuffer.position(0)
uBuffer.position(0)
vBuffer[nv21, ySize, 1]
uBuffer[nv21, ySize + 1, uBuffer.remaining()]
return nv21 // shortcut
}
} catch (ex: ReadOnlyBufferException) {
// unfortunately, we cannot check if vBuffer and uBuffer overlap
}
// unfortunately, the check failed. We must save U and V pixel by pixel
vBuffer.put(1, savePixel)
}
// other optimizations could check if (pixelStride == 1) or (pixelStride == 2),
// but performance gain would be less significant
for (row in 0 until height / 2) {
for (col in 0 until width / 2) {
val vuPos = col * pixelStride + row * rowStride
nv21[pos++] = vBuffer[vuPos]
nv21[pos++] = uBuffer[vuPos]
}
}
return nv21
}
private fun getResizedBitmap(bm: Bitmap): Bitmap {
val width = bm.width
val height = bm.height
val scaleWidth = (112f) / width
val scaleHeight = (112f) / height
// CREATE A MATRIX FOR THE MANIPULATION
val matrix = Matrix()
// RESIZE THE BIT MAP
matrix.postScale(scaleWidth, scaleHeight)
// "RECREATE" THE NEW BITMAP
val resizedBitmap = Bitmap.createBitmap(
bm, 0, 0, width, height, matrix, false
)
bm.recycle()
return resizedBitmap
}
fun isValidHumanFace(face: Face): Boolean {
val boundingBox = face.boundingBox
val faceWidth = boundingBox.width()
val faceHeight = boundingBox.height()
// Check face size or proportions (e.g., width to height ratio)
val aspectRatio = faceWidth.toFloat() / faceHeight.toFloat()
if (aspectRatio < 0.75 || aspectRatio > 1.3) {
return false // Unusual face shape, likely not a human face
}
// Additional checks can be added here
return true
}
// Function to check if the face is too far based on bounding box size
fun isFaceTooFar(boundingBox: Rect?): Boolean {
// Use width or height of the bounding box to determine if the face is too far
val faceWidth = boundingBox?.width()
// Set a threshold for face width (this value will depend on your camera and testing)
val farThreshold = 100 // Example threshold, adjust based on testing
// If the face width is smaller than the threshold, consider it too far
return faceWidth!! < farThreshold
}
fun checkBlurriness(result: Bitmap): Int {
val destination = Mat()
val image = Mat()
val matGray = Mat()
val std = MatOfDouble()
val median = MatOfDouble()
Utils.bitmapToMat(result, image)
Imgproc.cvtColor(image, matGray, Imgproc.COLOR_BGR2GRAY)
Imgproc.Laplacian(matGray, destination, 3)
Core.meanStdDev(destination, median, std)
val variance = Math.pow(std.get(0, 0)[0], 2.0)
Log.i("Variance : ", variance.toString())
return if (variance < 10) {
0
} else {
1
}
}
fun calculateBrightness(bitmap: Bitmap): Float {
return calculateBrightnessEstimate(bitmap, 1)
}
private fun calculateBrightnessEstimate(bitmap: Bitmap, pixelSpacing: Int): Float {
var r = 0
var g = 0
var b = 0
val height = bitmap.height
val width = bitmap.width
var n = 0
var lux = 0.0f
val pixels = IntArray(width * height)
bitmap.getPixels(pixels, 0, width, 0, 0, width, height)
var i = 0
while (i < pixels.size) {
val color = pixels[i]
r = Color.red(color)
g = Color.green(color)
b = Color.blue(color)
val luminance = (r * 0.2126f + g * 0.7152f + b * 0.0722f) / 255
lux += luminance
n++
i += pixelSpacing
}
return lux / n
}
}
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportWidth="512"
android:viewportHeight="512"
android:width="512dp"
android:height="512dp">
<group>
<clip-path
android:pathData="M0 0l512 0 0 512 -512 0z" />
<path
android:pathData="M512 256A256 256 0 0 1 256 512 256 256 0 0 1 0 256 256 256 0 0 1 256 0 256 256 0 0 1 512 256Z"
android:fillColor="#ffffff" />
<path
android:pathData="M511.5 256A255.5 255.5 0 0 1 256 511.5 255.5 255.5 0 0 1 0.5 256 255.5 255.5 0 0 1 256 0.5 255.5 255.5 0 0 1 511.5 256Z"
android:strokeWidth="1"
android:strokeColor="#707070" />
<group
android:translateX="45"
android:translateY="45">
<path
android:pathData="M422 211A211 211 0 0 1 211 422 211 211 0 0 1 0 211 211 211 0 0 1 211 0 211 211 0 0 1 422 211Z"
android:fillColor="#d2d9e0" />
</group>
</group>
</vector>
@@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@color/white">
<path
android:fillColor="@android:color/white"
android:pathData="M9,12c0,1.66 1.34,3 3,3s3,-1.34 3,-3s-1.34,-3 -3,-3S9,10.34 9,12zM13,12c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1s0.45,-1 1,-1S13,11.45 13,12z"/>
<path
android:fillColor="@android:color/white"
android:pathData="M8,10V8H5.09C6.47,5.61 9.05,4 12,4c3.72,0 6.85,2.56 7.74,6h2.06c-0.93,-4.56 -4.96,-8 -9.8,-8C8.73,2 5.82,3.58 4,6.01V4H2v6H8z"/>
<path
android:fillColor="@android:color/white"
android:pathData="M16,14v2h2.91c-1.38,2.39 -3.96,4 -6.91,4c-3.72,0 -6.85,-2.56 -7.74,-6H2.2c0.93,4.56 4.96,8 9.8,8c3.27,0 6.18,-1.58 8,-4.01V20h2v-6H16z"/>
</vector>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<corners android:radius="75dp" />
<solid
android:color="#99D2D9E0" />
<stroke
android:color="#99D2D9E0"
android:width="2dp"/>
</shape>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<stroke android:color="#FFF" android:width="1dp"/>
<corners android:radius="100dp" />
</shape>
@@ -0,0 +1,149 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".camera.CameraActivity">
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="130dp"
android:layout_marginBottom="124dp"
android:visibility="invisible"
app:layout_constraintBottom_toTopOf="@+id/infoPanel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintVertical_bias="1.0" />
<androidx.camera.view.PreviewView
android:id="@+id/texture"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0">
</androidx.camera.view.PreviewView>
<Space
android:id="@+id/space"
android:layout_width="16dp"
android:layout_height="1dp"
app:layout_constraintBottom_toBottomOf="@+id/capturebtn_camera_activity"
app:layout_constraintEnd_toStartOf="@+id/capturebtn_camera_activity"
app:layout_constraintTop_toTopOf="@+id/capturebtn_camera_activity" />
<View
android:id="@+id/transparent_layout"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#88676767"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/infoPanel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="70dp"
android:layout_marginEnd="70dp"
android:layout_marginBottom="240dp"
android:background="@drawable/rounded_corners"
android:padding="8dp"
android:visibility="invisible"
android:text="@string/need_all_green_dots_to_start_taking_pictures"
android:textAlignment="center"
android:textColor="#80000000"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ImageView
android:id="@+id/ivFlash"
android:layout_width="38dp"
android:layout_height="38dp"
android:layout_marginStart="50dp"
android:layout_marginBottom="100dp"
android:background="@drawable/roundedcorner"
android:src="@mipmap/ic_flash_off_white_24dp"
android:textAlignment="center"
android:scaleType="centerInside"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/switch_camera"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"/>
<ImageView
android:id="@+id/capturebtn_camera_activity"
android:layout_width="0dp"
android:layout_height="80dp"
android:layout_marginStart="128dp"
android:layout_marginEnd="32dp"
android:layout_marginBottom="80dp"
android:adjustViewBounds="true"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/camera_capture_button"
android:scaleType="centerInside"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/switch_camera"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:srcCompat="@drawable/ic_camera_icon" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
<LinearLayout
android:id="@+id/linearLayout_alpha"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="25dp"
android:layout_marginEnd="25dp"
android:layout_marginBottom="10dp"
android:orientation="vertical"
android:visibility="invisible"
app:layout_constraintBottom_toTopOf="@+id/infoPanel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
<TextView
android:id="@+id/tvImageBlurr"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="387dp"
android:text="@string/no_face_detected"
android:textColor="@color/red"
android:textSize="25sp"
android:gravity="center"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/texture" />
<ImageButton
android:id="@+id/switch_camera"
android:layout_width="38dp"
android:layout_height="38dp"
android:layout_marginBottom="100dp"
android:layout_marginEnd="50dp"
android:background="@drawable/roundedcorner"
android:contentDescription="@string/switch_camera"
android:src="@drawable/outline_flip_camera_android_24"
android:textAlignment="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -0,0 +1,103 @@
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:background="@color/black"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<ImageView
android:id="@+id/capturedImageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="?actionBarSize"
android:layout_centerHorizontal="true"
android:layout_marginStart="5dp"
android:layout_marginEnd="5dp"
android:layout_marginTop="5dp"
android:background="@color/black"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
android:orientation="horizontal"
android:background="@android:color/holo_blue_light"
android:layout_alignParentBottom="true"
android:weightSum="3">
<LinearLayout
android:id="@+id/cancelLL"
android:layout_width="0dp"
android:layout_height="match_parent"
android:orientation="horizontal"
android:gravity="center"
android:layout_weight="1"
>
<ImageView
android:layout_width="22dp"
android:layout_height="22dp"
android:src="@mipmap/close"
/>
<TextView
android:id="@+id/cancelTV"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-condensed-medium"
android:layout_marginLeft="20dp"
android:textSize="16sp"
android:text="Cancel"
android:textColor="@color/black"
/>
</LinearLayout>
<LinearLayout
android:id="@+id/retakeLL"
android:layout_width="0dp"
android:layout_height="match_parent"
android:orientation="horizontal"
android:gravity="center"
android:layout_weight="1"
>
<ImageView
android:layout_width="22dp"
android:layout_height="22dp"
android:src="@mipmap/retake"
/>
<TextView
android:id="@+id/retakeTV"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:fontFamily="sans-serif-condensed-medium"
android:textSize="16sp"
android:text="Retake"
android:textColor="@color/black"
/>
</LinearLayout>
<LinearLayout
android:id="@+id/saveLL"
android:layout_width="0dp"
android:layout_height="match_parent"
android:orientation="horizontal"
android:gravity="center"
android:layout_weight="1"
>
<ImageView
android:layout_width="22dp"
android:layout_height="22dp"
android:src="@mipmap/tick"
/>
<TextView
android:id="@+id/doneTV"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:fontFamily="sans-serif-condensed-medium"
android:textSize="16sp"
android:text="Ok"
android:textColor="@color/black" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 485 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 602 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 B

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="red">#FF0000</color>
</resources>
@@ -0,0 +1,7 @@
<resources>
<string name="switch_camera">switch camera</string>
<string name="face_detected">Face Detected</string>
<string name="no_face_detected">No Face Detected!</string>
<string name="camera_capture_button">Camera Capture Button</string>
<string name="need_all_green_dots_to_start_taking_pictures">Need all green dots to start taking pictures</string>
</resources>
@@ -0,0 +1,17 @@
package com.cpm.india.cameraai
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}