I am using android camera X api to capture selfie image by analyzing the face detected.
The app captures more than 5 images if the face is inside rectangular frame show on the screen.
The issue is, camera preview freezes(few seconds) when capturing image for the first time.
I am using the below code to set up camera provider in my activity.
// preview use case
internal fun bindPreviewUseCase(previewView: PreviewView) {
Util.printDebugLog("Binding camera preview use case.")
if (previewUseCase != null) {
cameraProvider.unbind(previewUseCase)
}
previewUseCase = Preview.Builder()
.setTargetResolution(Size(480, 800))
.build()
previewUseCase!!.setSurfaceProvider(previewView.createSurfaceProvider())
cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, previewUseCase)
}
// analysis use case
internal fun bindAnalysisUseCase(graphicOverlay: GraphicOverlay) {
Util.printDebugLog("Binding camera analysis use case.")
if (analysisUseCase != null) {
cameraProvider.unbind(analysisUseCase)
}
imageProcessor?.stop()
try {
val faceDetectorOptions: FaceDetectorOptions = getFaceDetectorOptionsForLivePreview()
imageProcessor =
LiveFaceDetectorProcessor(context, faceDetectorOptions, faceFrameProcessListener)
} catch (e: Exception) {
Viola.listener.onFaceDetectionFailed(
FaceDetectionError.IMAGE_PROCESSOR_ERROR,
"Can not create image processor: ${e.localizedMessage}"
)
return
}
val builder = ImageAnalysis.Builder()
builder.setTargetResolution(Size(480, 800))
analysisUseCase = builder.build()
needUpdateGraphicOverlayImageSourceInfo = true
analysisUseCase!!.setAnalyzer(
ContextCompat.getMainExecutor(context),
ImageAnalysis.Analyzer { imageProxy: ImageProxy ->
if (needUpdateGraphicOverlayImageSourceInfo) {
val isImageFlipped =
lensFacing == CameraSelector.LENS_FACING_FRONT
val rotationDegrees = imageProxy.imageInfo.rotationDegrees
if (rotationDegrees == 0 || rotationDegrees == 180) {
graphicOverlay.setImageSourceInfo(
imageProxy.width, imageProxy.height, isImageFlipped
)
} else {
graphicOverlay.setImageSourceInfo(
imageProxy.height, imageProxy.width, isImageFlipped
)
}
needUpdateGraphicOverlayImageSourceInfo = false
}
try {
imageProcessor!!.processImageProxy(imageProxy, graphicOverlay)
} catch (e: MlKitException) {
Viola.listener.onFaceDetectionFailed(
FaceDetectionError.IMAGE_PROCESSOR_ERROR,
"Failed to process image: ${e.localizedMessage}"
)
}
}
)
cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, analysisUseCase)
}
// capture use case
internal fun bindCaptureUseCase(previewView: PreviewView) {
Util.printDebugLog("Binding camera capture use case.")
if (captureUseCase != null) {
cameraProvider.unbind(captureUseCase)
}
val rotation = previewView.display.rotation
captureUseCase = ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
.setTargetResolution(Size(960, 1280))
.setTargetRotation(rotation)
.build()
cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, captureUseCase)
}
//capture
internal fun takePicture(callback: CaptureCallback) {
Util.printDebugLog("Capturing current frame.")
val diff = System.currentTimeMillis() - lastCaptureTime
if (diff > captureDelay) {
if (!isCaptureInProgress) {
if (captureUseCase == null) {
return
}
isCaptureInProgress = true
lastCaptureTime = System.currentTimeMillis()
val executor = ContextCompat.getMainExecutor(context)
//TODO remove
val startTime = System.currentTimeMillis()
captureUseCase!!.takePicture(
executor,
object : ImageCapture.OnImageCapturedCallback() {
override fun onCaptureSuccess(image: ImageProxy) {
val timeElapsed = System.currentTimeMillis() - startTime
Util.printDebugLog("Image captured, producing bitmap from image proxy. $timeElapsed")
val bitmap: Bitmap =
BitmapUtil.getBitmap(image, image.imageInfo.rotationDegrees)!!
image.close()
callback.onCaptured(bitmap)
isCaptureInProgress = false
super.onCaptureSuccess(image)
}
override fun onError(exception: ImageCaptureException) {
super.onError(exception)
isCaptureInProgress = false
Util.printDebugLog("Unable to capture: ${exception.localizedMessage}")
}
})
}
} else {
Util.printDebugLog("Capture called before minimum delay.Ignoring capture call.")
}
}
// dependencies used
implementation "androidx.camera:camera-camera2:1.0.0-beta07"
implementation "androidx.camera:camera-view:1.0.0-alpha14"
implementation "androidx.camera:camera-lifecycle:1.0.0-beta07"
// time taken for each capture(in milliseconds)
image 1 -> 2396
image 2 -> 411
image 3 -> 356
image 4 -> 386
image 5 -> 345
Related
This is my stripped down sourcecode for barcode scanning
build.gradle
dependencies {
.....
// MLKit Dependencies
implementation 'com.google.android.gms:play-services-vision:20.1.3'
implementation 'com.google.mlkit:barcode-scanning:17.0.2'
def camerax_version = "1.1.0-beta01"
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-video:${camerax_version}"
......
}
ScanCameraFragment.kt
class ScanCameraFragment : BaseFragment() {
private lateinit var binding: FragmentScanCameraBinding
private lateinit var cameraExecutor: ExecutorService
//region Lifecycle Methods
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
binding = FragmentScanCameraBinding.inflate(inflater, container, false)
cameraExecutor = Executors.newSingleThreadExecutor()
startCamera()
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
cameraExecutor.shutdown()
}
companion object {
fun newInstance() = ScanCameraFragment().apply {}
}
private fun startCamera() {
context?.let { context ->
val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
cameraProviderFuture.addListener({
val cameraProvider = cameraProviderFuture.get()
// Preview
val preview = Preview.Builder()
.build()
.also {
it.setSurfaceProvider(binding.previewView.surfaceProvider)
}
// Image analyzer
val imageAnalyzer = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
.also {
it.setAnalyzer(cameraExecutor,
QrCodeAnalyzer(context, binding.barcodeBoxView,
binding.previewView.width.toFloat(),
binding.previewView.height.toFloat()
)
)
}
// Select back camera as a default
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
try {
// Unbind use cases before rebinding
cameraProvider.unbindAll()
// Bind use cases to camera
var camera = cameraProvider.bindToLifecycle(this, cameraSelector,
preview, imageAnalyzer)
} catch (exc: Exception) {
exc.printStackTrace()
}
}, ContextCompat.getMainExecutor(context))
}
}
}
QRCodeAnalyzer.kt
class QrCodeAnalyzer(private val context: Context,
private val barcodeBoxView: BarcodeBoxView, private val previewViewWidth: Float,
private val previewViewHeight: Float) : ImageAnalysis.Analyzer {
private var scaleX = 1f
private var scaleY = 1f
private fun translateX(x: Float) = x * scaleX
private fun translateY(y: Float) = y * scaleY
private fun adjustBoundingRect(rect: Rect) = RectF(
translateX(rect.left.toFloat()),
translateY(rect.top.toFloat()),
translateX(rect.right.toFloat()),
translateY(rect.bottom.toFloat())
)
#SuppressLint("UnsafeOptInUsageError")
override fun analyze(image: ImageProxy) {
val img = image.image
if (img != null) {
// Update scale factors
scaleX = previewViewWidth / img.height.toFloat()
scaleY = previewViewHeight / img.width.toFloat()
val inputImage = InputImage.fromMediaImage(img,
image.imageInfo.rotationDegrees)
// Process image searching for barcodes
val options = BarcodeScannerOptions.Builder()
.build()
val scanner = BarcodeScanning.getClient(options)
scanner.process(inputImage)
.addOnSuccessListener { barcodes ->
for (barcode in barcodes) {
barcode?.rawValue?.let {
if (it.trim().isNotBlank()) {
Scanner.updateBarcode(it)
barcode.boundingBox?.let { rect ->
barcodeBoxView.setRect(adjustBoundingRect(rect))
}
}
return#addOnSuccessListener
}
}
// coming here means no satisfiable barcode was found
barcodeBoxView.setRect(RectF())
}
.addOnFailureListener {
image.close()
}
.addOnFailureListener { }
}
image.close()
}
}
This code works and I am able to scan barcodes. But sometimes, the barcode detection is slow. The documentation says one way to increase performance is to limit the image resolution.
Don't capture input at the camera’s native resolution. On some
devices, capturing input at the native resolution produces extremely
large (10+ megapixels) images, which results in very poor latency with
no benefit to accuracy. Instead, only request the size from the camera
that's required for barcode detection, which is usually no more than 2
megapixels.
If scanning speed is important, you can further lower the image
capture resolution. However, bear in mind the minimum barcode size
requirements outlined above.
Unfortunately, the documentation doesn't specify how to reduce the image resolution. And some of my end users are using high end devices with powerful camera, so we assume the poor performance is because of the image size.
How can I reduce the resolution of the image to a fixed value (something like 1024 x 768) rather than the default camera resolution?
You can set it on the imageAnalyzer builder bij using
.setTargetResolution(Size)
val imageAnalysisUseCaseBuilder = ImageAnalysis.Builder()
imageAnalysisUseCaseBuilder.setTargetResolution(Size(1024, 768))
imageAnalysisUseCase = imageAnalysisUseCaseBuilder.build()
or in you case
val imageAnalyzer = ImageAnalysis.Builder()
.setTargetResolution(Size(1024, 768))
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
.also {
it.setAnalyzer(cameraExecutor,
QrCodeAnalyzer(context, binding.barcodeBoxView,
binding.previewView.width.toFloat(),
binding.previewView.height.toFloat()
)
)
}
User HarmenH's answer correctly tells how to set the image resolution, so I am not repeating it here.
As it turns out, the performance issue on my end was not because of image resolution. It seems I was closing the imageProxy prematurely.
override fun analyze(image: ImageProxy) {
val img = image.image
if (img != null) {
// Update scale factors
scaleX = previewViewWidth / img.height.toFloat()
scaleY = previewViewHeight / img.width.toFloat()
val inputImage = InputImage.fromMediaImage(img,
image.imageInfo.rotationDegrees)
// Process image searching for barcodes
val options = BarcodeScannerOptions.Builder()
.build()
val scanner = BarcodeScanning.getClient(options)
scanner.process(inputImage)
.addOnSuccessListener { barcodes - >
for (barcode in barcodes) {
barcode?.rawValue?.let {
if (it.trim().isNotBlank()) {
Scanner.updateBarcode(it)
barcode.boundingBox?.let { rect - >
barcodeBoxView.setRect(adjustBoundingRect(rect))
}
}
return #addOnSuccessListener
}
}
// coming here means no satisfiable barcode was found
barcodeBoxView.setRect(RectF())
}
.addOnFailureListener {
image.close()
}
.addOnFailureListener {
//added this here.
image.close()
}
}
//Removed this because we don't close the
//imageProxy before analysis completes
//image.close()
}
I'm working on an QR scanning application that is based on CameraX. The scanning works as expected on most devices except a few random devices. After debugging this issue for a long time, I found out that the cropped image is kind of shattered on the device in which scanning wasn't working.
Expected output (works on Pixel Device):
Current output (on the devices in which scanning is not working):
The analyze method that receives each frame (part of the CustomImageAnalyzer class)
override fun analyze(image: ImageProxy) {
val byteBuffer = image.planes[0].buffer
if (imageData.size != byteBuffer.capacity()) {
imageData = ByteArray(byteBuffer.capacity())
}
byteBuffer[imageData]
val iFact = if (mActivity.getOverlayView().width <= mActivity.getOverlayView().height) {
image.width / mActivity.getOverlayView().width.toDouble()
} else {
image.height / mActivity.getOverlayView().height.toDouble()
}
Log.i(TAG, "")
Log.i(TAG, "image.height" + image.height)
Log.i(TAG, "image.width" + image.width)
Log.i(TAG, "overlay.height" + mActivity.getOverlayView().height)
Log.i(TAG, "overlay.width" + mActivity.getOverlayView().width)
val size = mActivity.getOverlayView().size * iFact
Log.i(TAG, "Obtained size 1: " + mActivity.getOverlayView().size)
Log.i(TAG, "iFact: $iFact")
Log.i(TAG, "calculated size: $size")
val left = (image.width - size) / 2
val top = (image.height - size) / 2
Log.i(TAG, "left: $left")
Log.i(TAG, "top: $top")
val source = PlanarYUVLuminanceSource(
imageData,
image.width, image.height,
left.toInt(), top.toInt(),
size.toInt(), size.toInt(),
false
)
Log.i(TAG, "source.thumbnailHeight" + source.thumbnailHeight.toString())
Log.i(TAG, "source.thumbnailWidth" + source.thumbnailWidth.toString())
mActivity.runOnUiThread {
mActivity.showIntArray(source.renderThumbnail(), source.thumbnailHeight)
}
val binaryBitmap = BinaryBitmap(HybridBinarizer(source))
try {
val result = reader.decodeWithState(binaryBitmap)
listener.invoke(result.text)
} catch (e: ReaderException) {
} finally {
reader.reset()
}
// Compute the FPS of the entire pipeline
val frameCount = 10
if (++frameCounter % frameCount == 0) {
frameCounter = 0
val now = System.currentTimeMillis()
val delta = now - lastFpsTimestamp
val fps = 1000 * frameCount.toFloat() / delta
Log.d(TAG, "Analysis FPS: ${"%.02f".format(fps)}")
lastFpsTimestamp = now
}
image.close()
}
Code to start camera:
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
val preview = Preview.Builder()
.build()
.also {
it.setSurfaceProvider(contentFrame.surfaceProvider)
}
overlayView = findViewById(R.id.overlay)
val imageAnalysis = ImageAnalysis.Builder()
.setTargetResolution(Size(960, 960))
.build()
imageAnalysis.setAnalyzer(
executor,
QRCodeImageAnalyzer (this) { response ->
if (response != null) {
handleResult(response)
}
}
)
cameraProvider.unbindAll()
camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageAnalysis)
Also, I get the image from PlanarYUVLuminanceSource after the cropping is done.
Can someone please help me out with this issue?
some device have rotation special. you can use imageProxy.getImageInfo().getRotationDegrees() to get correct rotation of image. references with (ImageInfo) https://developer.android.com/reference/androidx/camera/core/ImageInfo#getRotationDegrees()
I am using CameraX https://developer.android.com/training/camerax to take some images. However, all my images come out the wrong rotation. They are all marked: ORIENTATION_ROTATE_90 Yes, I have checked to ensure that the orientation lock is not on and there is nothing in the manifest to lock the screen orientation nor am I overriding any orientation method.
No matter how I test it via simulator or real device, the orientation seems to be "locked". The only picture that turns out correctly rotated, is when the device is in portrait mode. However, it's still flagged as ORIENTATION_ROTATE_90
Can anyone see what I might be doing incorrectly?
private var imageCapture: ImageCapture? = null
private lateinit var cameraExecutor: ExecutorService
//.. other methods removed for brievity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Request camera permissions
cameraExecutor = Executors.newSingleThreadExecutor()
}
override fun onResume() {
super.onResume()
startCamera()
}
private fun startCamera() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
cameraProviderFuture.addListener({
// Used to bind the lifecycle of cameras to the lifecycle owner
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
// Preview
val preview = Preview.Builder()
.build()
.also {
it.setSurfaceProvider(binding.cameraPreview.surfaceProvider)
}
imageCapture = Builder().build()
// Doesn't work
// activity?.display.let { d ->
// d.let { imageCapture!!.targetRotation = d!!.rotation }
// }
// Select back camera as a default
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
try {
// Unbind use cases before rebinding
cameraProvider.unbindAll()
// Bind use cases to camera
cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture)
} catch (exc: Exception) {
Log.e(TAG, "Use case binding failed", exc)
}
}, ContextCompat.getMainExecutor(requireContext()))
}
private fun captureImage() {
// Get a stable reference of the modifiable image capture use case
val imageCapture = imageCapture ?: return
// this way the images will stay in order
val id = System.currentTimeMillis().toString()
// Make directory if it doesn't exist, and build a file for the new image to go into
val photoFile = File("${sharedViewModel.fileDirectory}/${id}").apply {
#Suppress("RECEIVER_NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
parentFile.mkdirs()
}
// Create output options object which contains file + metadata
val outputOptions = OutputFileOptions.Builder(photoFile).build()
// Set up image capture listener, which is triggered after photo has been taken
imageCapture.takePicture(
outputOptions, ContextCompat.getMainExecutor(requireContext()), object : OnImageSavedCallback {
override fun onError(exc: ImageCaptureException) {
Toast.makeText(requireContext(), "Photo capture failed: ${exc.message}", Toast.LENGTH_LONG).show()
Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
}
override fun onImageSaved(output: OutputFileResults) {
fixRotation(photoFile.path)
}
})
}
private fun fixRotation(imageUri: String) {
val bitmap = File(imageUri)
if (!bitmap.exists()) return
Uri.parse(imageUri)?.let{
CoroutineScope(Dispatchers.IO).launch { spinAndSave(it) }
}
}
private suspend fun spinAndSave(imageURI: Uri) = withContext(Dispatchers.IO) {
val options = BitmapFactory.Options()
options.inPreferredConfig = Bitmap.Config.ARGB_8888
imageURI.path?.let { path ->
ExifInterface(path).getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED).let { orientation ->
debugOrientation(orientation)
val rotatedBitmap = rotateBitmap( BitmapFactory.decodeFile(path, options), orientation)!!
FileOutputStream(imageURI.path).use { fos ->
rotatedBitmap.compress(Bitmap.CompressFormat.JPEG, 50, fos)
Log.i(TAG,"New Image Saved")
}
}
}
}
private fun debugOrientation(orientation: Int) {
val o = when (orientation) {
ExifInterface.ORIENTATION_NORMAL -> "ORIENTATION_NORMAL"
ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> "ORIENTATION_FLIP_HORIZONTAL"
ExifInterface.ORIENTATION_ROTATE_180 -> "ORIENTATION_ROTATE_180"
ExifInterface.ORIENTATION_FLIP_VERTICAL -> "ORIENTATION_FLIP_VERTICAL"
ExifInterface.ORIENTATION_TRANSPOSE -> "ORIENTATION_TRANSPOSE"
ExifInterface.ORIENTATION_ROTATE_90 -> "ORIENTATION_ROTATE_90"
ExifInterface.ORIENTATION_TRANSVERSE -> "ORIENTATION_TRANSVERSE"
ExifInterface.ORIENTATION_ROTATE_270 -> "ORIENTATION_ROTATE_270"
else -> "UKNONWN ORIENTATION"
}
Log.w(TAG,"ORIEntation int: $orientation is: $o")
}
private fun rotateBitmap(bitmap: Bitmap, orientation: Int): Bitmap? {
val matrix = Matrix()
when (orientation) {
ExifInterface.ORIENTATION_NORMAL -> return bitmap
ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> matrix.setScale(-1f, 1f)
ExifInterface.ORIENTATION_ROTATE_180 -> matrix.setRotate(180f)
ExifInterface.ORIENTATION_FLIP_VERTICAL -> {
matrix.setRotate(180f)
matrix.postScale(-1f, 1f)
}
ExifInterface.ORIENTATION_TRANSPOSE -> {
matrix.setRotate(90f)
matrix.postScale(-1f, 1f)
}
ExifInterface.ORIENTATION_ROTATE_90 -> matrix.setRotate(90f)
ExifInterface.ORIENTATION_TRANSVERSE -> {
matrix.setRotate(-90f)
matrix.postScale(-1f, 1f)
}
ExifInterface.ORIENTATION_ROTATE_270 -> matrix.setRotate(-90f)
else -> return bitmap
}
return try {
val bmRotated = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
bitmap.recycle()
bmRotated
} catch (e: OutOfMemoryError) {
e.printStackTrace()
null
}
}
use below of code to get exif and rotate of image file. Bitmap don't have exif.
public static Bitmap rotateImage(String path) throws IOException {
Bitmap bitmap = BitmapFactory.decodeFile(path);
int rotate = 0;
ExifInterface exif;
exif = new ExifInterface(path);
int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_NORMAL);
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_270:
rotate = 270;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
rotate = 180;
break;
case ExifInterface.ORIENTATION_ROTATE_90:
rotate = 90;
break;
}
Matrix matrix = new Matrix();
matrix.postRotate(rotate);
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
bitmap.getHeight(), matrix, true);
}
I build an app using CameraX codelab example, that is orking fine, but once my mobile gone into sleep and screen turned off, the CameraX transmittion had not been resumed after returning the mobile to normal status, and the CameraX screen remain white?
UPDATE
Sorry, it is not the Camera itself, The camera is invisible, and I've an image view, for which the image analyzer is displaying what is seen in the camera.
It look the val bitmap = view_finder.bitmap ?: return#Analyzer is returning null in my code below once the mobile goes in sleep.
private lateinit var viewFinder: TextureView
private fun startCamera() {
val previewConfig = PreviewConfig.Builder().apply {
setTargetAspectRatio(Rational(1, 1))
setTargetResolution(Size(640, 640))
}.build()
val preview = Preview(previewConfig)
preview.setOnPreviewOutputUpdateListener {
val parent = viewFinder.parent as ViewGroup
parent.removeView(viewFinder)
parent.addView(viewFinder, 0)
viewFinder.surfaceTexture = it.surfaceTexture
updateTransform()
}
val imageCaptureConfig = Builder()
.apply {
setTargetAspectRatio(Rational(1, 1))
setCaptureMode(ImageCapture.CaptureMode.MIN_LATENCY)
}.build()
val imageCapture = ImageCapture(imageCaptureConfig)
findViewById<ImageButton>(R.id.capture_button).setOnClickListener {
val file = File(externalMediaDirs.first(),
"${System.currentTimeMillis()}.jpg")
imageCapture.takePicture(file,
object : ImageCapture.OnImageSavedListener {
override fun onError(error: ImageCapture.UseCaseError,
message: String, exc: Throwable?) {
val msg = "Photo capture failed: $message"
Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
Log.e("CameraXApp", msg)
exc?.printStackTrace()
}
override fun onImageSaved(file: File) {
val msg = "Photo capture succeeded: ${file.absolutePath}"
Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
Log.d("CameraXApp", msg)
}
})
}
// Setup image analysis pipeline that computes average pixel luminance
val analyzerConfig = ImageAnalysisConfig.Builder().apply {
val analyzerThread = HandlerThread(
"LuminosityAnalysis").apply { start() }
setCallbackHandler(Handler(analyzerThread.looper))
setImageReaderMode(
ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE)
}.build()
val analyzerUseCase = ImageAnalysis(analyzerConfig).apply {
////// This is my own code that I added to the analyzer ///////
analyzer = ImageAnalysis.Analyzer { image, rotationDegrees ->
val bitmap = view_finder.bitmap ?: return#Analyzer
scope.launch(Dispatchers.Unconfined) {
val mat = Mat()
Utils.bitmapToMat(bitmap!!, mat)
val detectedFaces = FaceDetection.detectFaces(bitmap!!)
println("Detected Faces = $detectedFaces")
Toast.makeText(
this#MainActivity, "Detected Faces = ${detectedFaces.toArray().size}",
Toast.LENGTH_SHORT
).show()
if (detectedFaces.toArray().isNotEmpty()) {
val paint = Paint().apply {
isAntiAlias = true
style = Paint.Style.STROKE
color = Color.RED
strokeWidth = 10f
}
for (rect in detectedFaces.toArray()) {
bitmap?.let { Canvas(it) }?.apply {
drawRect(
rect.x.toFloat(), // faceRectangle.left,
rect.y.toFloat(), //faceRectangle.top,
rect.x.toFloat() + rect.width,
rect.y.toFloat() + rect.height,
paint
)
}
}
}
}
runOnUiThread { imageView.setImageBitmap(bitmap) }
}
}
CameraX.bindToLifecycle(
this, preview, imageCapture, analyzerUseCase)
}
I also had the same issue and this solution worked for me.
We can add the following line of code in onCreate(), to keep the device awake
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
Here is the reference link from docs
https://developer.android.com/training/scheduling/wakelock
When I try to switch the camera preview from BACK to FRONT my screen getting freeze and if I minimize the screen and restart the same then camera preview work perfectly.
below is the camera code.
private fun startCamera() {
CameraX.unbindAll()
val metrics = DisplayMetrics().also { viewFinder.display.getRealMetrics(it) }
val screenSize = Size(metrics.widthPixels, metrics.heightPixels)
val screenAspectRatio = Rational(metrics.widthPixels, metrics.heightPixels)
val previewConfig = PreviewConfig.Builder().apply {
setLensFacing(lensFacing)
setTargetResolution(screenSize)
setTargetAspectRatio(screenAspectRatio)
setTargetRotation(windowManager.defaultDisplay.rotation)
setTargetRotation(viewFinder.display.rotation)
}.build()
preview = Preview(previewConfig)
preview.setOnPreviewOutputUpdateListener {
viewFinder.surfaceTexture = it.surfaceTexture
updateTransform()
}
// Create configuration object for the image capture use case
val imageCaptureConfig = ImageCaptureConfig.Builder()
.apply {
setLensFacing(lensFacing)
setTargetAspectRatio(screenAspectRatio)
setTargetRotation(viewFinder.display.rotation)
setCaptureMode(ImageCapture.CaptureMode.MIN_LATENCY)
}.build()
// Build the image capture use case and attach button click listener
imageCapture = ImageCapture(imageCaptureConfig)
//for recording the video
val videoCaptureConfig = VideoCaptureConfig.Builder().apply {
setLensFacing(lensFacing)
setTargetAspectRatio(screenAspectRatio)
setTargetRotation(viewFinder.display.rotation)
}.build()
videoCapture = VideoCapture(videoCaptureConfig)
CameraX.bindToLifecycle(this, preview, imageCapture, videoCapture)
}
and the updateTransform code is
private fun updateTransform() {
val matrix = Matrix()
// Compute the center of the view finder
val centerX = viewFinder.width / 2f
val centerY = viewFinder.height / 2f
// Correct preview output to account for display rotation
val rotationDegrees = when (viewFinder.display.rotation) {
Surface.ROTATION_0 -> 0
Surface.ROTATION_90 -> 90
Surface.ROTATION_180 -> 180
Surface.ROTATION_270 -> 270
else -> return
}
matrix.postRotate(-rotationDegrees.toFloat(), centerX, centerY)
// Finally, apply transformations to our TextureView
viewFinder.setTransform(matrix)
}
I have try to switch between the camera preview is :
lensFacing = if (lensFacing == CameraX.LensFacing.BACK) {
CameraX.LensFacing.FRONT
} else
CameraX.LensFacing.BACK
try {
CameraX.getCameraWithLensFacing(lensFacing)
CameraX.unbind(preview, imageCapture, videoCapture)
startCamera()
} catch (e: Exception) {
e.printStackTrace()
}
and after calling the above code on button click preview getting freez.
You may need to again add textureView in onUpdated callback.
Refer this,
https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-master-dev/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java#168
Let me know if it works.