-
Notifications
You must be signed in to change notification settings - Fork 5
feature/region-of-interest #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,8 +2,8 @@ package ai.cyberlabs.yoonit.facefy | |
|
|
||
| import ai.cyberlabs.yoonit.facefy.model.FaceDetected | ||
| import ai.cyberlabs.yoonit.facefy.model.FacefyOptions | ||
| import android.graphics.RectF | ||
| import com.google.mlkit.vision.common.InputImage | ||
| import java.lang.Exception | ||
|
|
||
| class Facefy { | ||
|
|
||
|
|
@@ -27,7 +27,37 @@ class Facefy { | |
| field = value | ||
| } | ||
|
|
||
| fun detect(image: InputImage, onFaceDetected: (FaceDetected) -> Unit, onFaceUndetected: (Exception) -> Unit) { | ||
| facefyController.detect(image, onFaceDetected, onFaceUndetected) | ||
| var roiEnable: Boolean = FacefyOptions.faceROI.enable | ||
| set(value) { | ||
| FacefyOptions.faceROI.enable = value | ||
| field = value | ||
| } | ||
|
|
||
| var roiRect: RectF = FacefyOptions.faceROI.rectOffset | ||
| set(value) { | ||
| FacefyOptions.faceROI.rectOffset = value | ||
|
||
| field = value | ||
| } | ||
|
|
||
| var roiDetectMinSize: Float = FacefyOptions.faceROI.minimumSize | ||
| set(value) { | ||
| FacefyOptions.faceROI.minimumSize = value | ||
|
||
| field = value | ||
| } | ||
|
|
||
| var detectMinSize: Float = FacefyOptions.faceCaptureMinSize | ||
| set(value) { | ||
| FacefyOptions.faceCaptureMinSize = value | ||
|
||
| field = value | ||
| } | ||
|
|
||
| var detectMaxSize: Float = FacefyOptions.faceCaptureMaxSize | ||
| set(value) { | ||
| FacefyOptions.faceCaptureMaxSize = value | ||
|
||
| field = value | ||
| } | ||
|
|
||
| fun detect(image: InputImage, onFaceDetected: (FaceDetected) -> Unit, onMessage: (String) -> Unit) { | ||
| facefyController.detect(image, onFaceDetected, onMessage) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,12 +2,15 @@ package ai.cyberlabs.yoonit.facefy | |
|
|
||
| import ai.cyberlabs.yoonit.facefy.model.FaceDetected | ||
| import ai.cyberlabs.yoonit.facefy.model.FacefyOptions | ||
| import ai.cyberlabs.yoonit.facefy.model.Message | ||
| import android.graphics.PointF | ||
| import android.graphics.Rect | ||
| import android.graphics.RectF | ||
| import androidx.core.graphics.toRectF | ||
| 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 java.lang.Exception | ||
|
|
||
| internal class FacefyController { | ||
|
|
||
|
|
@@ -28,7 +31,7 @@ internal class FacefyController { | |
| fun detect( | ||
| inputImage: InputImage, | ||
| onFaceDetected: (FaceDetected) -> Unit, | ||
| onFaceUndetected: (Exception) -> Unit | ||
| onMessage: (String) -> Unit | ||
| ) { | ||
| this.detector | ||
| .process(inputImage) | ||
|
|
@@ -42,6 +45,7 @@ internal class FacefyController { | |
| closestFace?.let { face -> | ||
|
|
||
| val faceContours = mutableListOf<PointF>() | ||
| var roi = Rect() | ||
|
||
| var leftEyeOpenProbability: Float? = null | ||
| var rightEyeOpenProbability: Float? = null | ||
| var smilingProbability: Float? = null | ||
|
|
@@ -60,6 +64,27 @@ internal class FacefyController { | |
| } | ||
| } | ||
|
|
||
| if (FacefyOptions.faceROI.enable) { | ||
| val rect = Rect(1,2,4,5) | ||
| rect.top = 1 | ||
|
||
|
|
||
| roi = Rect( | ||
| (inputImage.width * FacefyOptions.faceROI.rectOffset.left).toInt(), | ||
| (inputImage.height * FacefyOptions.faceROI.rectOffset.top).toInt(), | ||
| (inputImage.width - (inputImage.width * FacefyOptions.faceROI.rectOffset.right)).toInt(), | ||
| (inputImage.height - (inputImage.height * FacefyOptions.faceROI.rectOffset.bottom)).toInt() | ||
| ) | ||
| } | ||
|
|
||
| val boundingBox: RectF = face.boundingBox.toRectF() | ||
|
|
||
| val message = this.getMessage(boundingBox, inputImage.width, inputImage.height) | ||
|
|
||
| if (message.isNotEmpty()) { | ||
| onMessage(message) | ||
| return@addOnSuccessListener | ||
| } | ||
|
Comment on lines
+78
to
+83
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (In first place, this is self criticism) We must change, don't you agree?
|
||
|
|
||
| onFaceDetected( | ||
| FaceDetected( | ||
| leftEyeOpenProbability, | ||
|
|
@@ -69,12 +94,57 @@ internal class FacefyController { | |
| face.headEulerAngleY, | ||
| face.headEulerAngleZ, | ||
| faceContours, | ||
| face.boundingBox | ||
| face.boundingBox, | ||
| roi | ||
| ) | ||
| ) | ||
| } | ||
| } | ||
| .addOnFailureListener { e -> onFaceUndetected(e) } | ||
| .addOnFailureListener { e -> | ||
| e.message?.let { message -> onMessage(message) } | ||
| } | ||
| .addOnCompleteListener { detector.close() } | ||
| } | ||
|
|
||
| private fun getMessage(boundingBox: RectF, imageWidth: Int, imageHeight: Int): String { | ||
| val boundingBoxWidthRelatedWithImage = boundingBox.width() / imageWidth | ||
|
|
||
| if (boundingBoxWidthRelatedWithImage < FacefyOptions.faceCaptureMinSize) { | ||
| return Message.INVALID_CAPTURE_FACE_MIN_SIZE | ||
| } | ||
|
|
||
| if (boundingBoxWidthRelatedWithImage > FacefyOptions.faceCaptureMaxSize) { | ||
| return Message.INVALID_CAPTURE_FACE_MAX_SIZE | ||
| } | ||
|
|
||
| val topOffset: Float = boundingBox.top / imageHeight | ||
| val rightOffset: Float = (imageWidth - boundingBox.right) / imageWidth | ||
| val bottomOffset: Float = (imageHeight - boundingBox.bottom) / imageHeight | ||
| val leftOffset: Float = boundingBox.left / imageWidth | ||
|
|
||
| if (FacefyOptions.faceROI.isOutOf( | ||
|
||
| topOffset, | ||
| rightOffset, | ||
| bottomOffset, | ||
| leftOffset) | ||
| ) { | ||
| return Message.INVALID_CAPTURE_FACE_OUT_OF_ROI | ||
| } | ||
|
|
||
| if (FacefyOptions.faceROI.hasChanges) { | ||
|
||
|
|
||
| // Face is inside the region of interest and faceROI is setted. | ||
| // Face is smaller than the defined "minimumSize". | ||
| val roiWidth: Float = | ||
| imageWidth - | ||
| ((FacefyOptions.faceROI.rectOffset.right + FacefyOptions.faceROI.rectOffset.left) * imageWidth) | ||
| val faceRelatedWithROI: Float = boundingBox.width() / roiWidth | ||
|
|
||
| if (FacefyOptions.faceROI.minimumSize > faceRelatedWithROI) { | ||
| return Message.INVALID_CAPTURE_FACE_ROI_MIN_SIZE | ||
| } | ||
| } | ||
|
|
||
| return "" | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,5 +11,6 @@ data class FaceDetected( | |
| var headEulerAngleY: Float, | ||
| var headEulerAngleZ: Float, | ||
| var contours: MutableList<PointF> = mutableListOf(), | ||
| var boundingBox: Rect | ||
| var boundingBox: Rect, | ||
| var roi: Rect | ||
|
||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| package ai.cyberlabs.yoonit.facefy.model | ||
|
|
||
| import android.graphics.Color | ||
|
||
| import android.graphics.RectF | ||
|
|
||
| /** | ||
| * Model to set face region of interest. | ||
| */ | ||
| class FaceROI { | ||
|
||
| // Enable or disable ROI. | ||
| var enable: Boolean = false | ||
|
|
||
| // Region of interest in percentage. | ||
| // Values valid [0, 1]. | ||
| var rectOffset: RectF = RectF() | ||
|
|
||
| // Minimum face size in percentage in relation of the ROI. | ||
| var minimumSize: Float = 0.0f | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Must be |
||
|
|
||
| // Return if any attributes has modifications. | ||
| val hasChanges: Boolean | ||
| get() { | ||
| return (this.rectOffset.top != 0.0f || | ||
| this.rectOffset.right != 0.0f || | ||
| this.rectOffset.bottom != 0.0f || | ||
| this.rectOffset.left != 0.0f) | ||
| } | ||
|
|
||
| /** | ||
| * Current offsets is out of the offset parameters. | ||
| * | ||
| * @param topOffset top offset. | ||
| * @param rightOffset right offset. | ||
| * @param bottomOffset bottom offset. | ||
| * @param leftOffset left offset. | ||
| * @return is out of the offset parameters. | ||
| */ | ||
| fun isOutOf( | ||
| topOffset: Float, | ||
| rightOffset: Float, | ||
| bottomOffset: Float, | ||
| leftOffset: Float | ||
| ): Boolean { | ||
| return (this.rectOffset.top > topOffset || | ||
| this.rectOffset.right > rightOffset || | ||
| this.rectOffset.bottom > bottomOffset || | ||
| this.rectOffset.left > leftOffset) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,9 +2,25 @@ package ai.cyberlabs.yoonit.facefy.model | |
|
|
||
| object FacefyOptions { | ||
|
|
||
| var faceROI: FaceROI = FaceROI() | ||
|
||
|
|
||
| var classification: Boolean = true | ||
|
|
||
| var contours: Boolean = true | ||
|
|
||
| var boundingBox: Boolean = true | ||
|
|
||
|
Comment on lines
+6
to
+12
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sincerely, do you think that we need this variables of control ( |
||
| /** | ||
| * * Limit the minimum face capture size. | ||
| * This variable is the face detection box percentage in relation with the UI graphic view. | ||
| * The value must be between 0 and 1. | ||
| */ | ||
| var faceCaptureMinSize: Float = 0.0f | ||
|
||
|
|
||
| /** | ||
| * Limit the maximum face capture size. | ||
| * This variable is the face detection box percentage in relation with the UI graphic view. | ||
| * The value must be between 0 and 1. | ||
| */ | ||
| var faceCaptureMaxSize: Float = 1.0f | ||
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package ai.cyberlabs.yoonit.facefy.model | ||
|
|
||
| object Message { | ||
| // Face width percentage in relation of the screen width is less than the CaptureOptions.faceCaptureMinSize | ||
| const val INVALID_CAPTURE_FACE_MIN_SIZE = "INVALID_CAPTURE_FACE_MIN_SIZE" | ||
|
|
||
| // Face width percentage in relation of the screen width is more than the CaptureOptions.faceCaptureMinSize | ||
| const val INVALID_CAPTURE_FACE_MAX_SIZE = "INVALID_CAPTURE_FACE_MAX_SIZE" | ||
|
|
||
| // Face bounding box is out of the setted region of interest. | ||
| const val INVALID_CAPTURE_FACE_OUT_OF_ROI = "INVALID_CAPTURE_FACE_OUT_OF_ROI" | ||
|
|
||
| // Face width percentage in relation of the screen width is less than the CaptureOptions.FaceROI.minimumSize. | ||
| const val INVALID_CAPTURE_FACE_ROI_MIN_SIZE = "INVALID_CAPTURE_FACE_ROI_MIN_SIZE" | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think to change the
faceROItoroi?