Я понял, что результат процесса распознавания лиц читается до того, как сам процесс завершится, поэтому решение состоит в том, чтобы использовать вызов async
, что в Kotlin должно быть выполнено с помощью coroutine
.
В Kotlin есть coroutine-core
и coroutine-android
, поэтому я должен быть осторожен, чтобы выбрать правильный, который является:
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.1'
В сопрограмме Kotlin есть:
suspend fun mySuspendMethodThatWaitsForChildCoroutinesToFinish() {
coroutineScope {
launch { mySyspendMethod() }
}
}
// or
suspend fun mySuspendMethodThatWaitsForChildCoroutinesToFinish() = coroutineScope {
launch { mySyspendMethod() }
}
Но при этом будет продолжаться запрос на распространение приостановки по всей иерархии, и он застрянет при достижении onActivityResult
, который срабатывает после съемки фотографии, поэтому решение состоит в том, чтобы использовать MainScope() coroutine
, что позволяет использовать coroutine
изnon-suspended
функция, где вы можете:
1- Объявить coroutine scope
как val scope = MainScope()
2- Выполнить coroutine
как scope.launch { }
Сполное изображение, как показано ниже:
import kotlinx.coroutines.*
class MainActivity : AppCompatActivity() {
private val scope = MainScope()
override fun onCreate(savedInstanceState: Bundle?) { }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
detectFace(this, image, textView)
}
}
private fun detectFace(context: Context, image: Bitmap, facesValue: TextView) {
val frame = AndroidFrameConverter().convert(image)
val mat = OpenCVFrameConverter.ToMat().convert(frame)
scope.launch {
val numberOfFaces = FaceDetection.detectFaces(mat).toString()
(context as Activity).runOnUiThread {
facesValue.text = numberOfFaces
}
}
}
Так что ни мой MainActivity
не является:
class MainActivity : AppCompatActivity() {
private val scope = MainScope()
val REQUEST_IMAGE_CAPTURE = 1
private var output: File? = null
var outPutFileUri: Uri? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (!isPermissionGranted(permission.RECORD_AUDIO)) {
requestAudioPermission(this)
} else {
Toast.makeText(
this@MainActivity, "Audio permission is granted",
Toast.LENGTH_SHORT
).show()
}
val ocvLoaded = OpenCVLoader.initDebug()
if (ocvLoaded) {
loadModel(this)
Toast.makeText(
this@MainActivity, "OpenCV loaded",
Toast.LENGTH_SHORT
).show()
} else {
Toast.makeText(
this@MainActivity, "Unable to load OpenCV",
Toast.LENGTH_SHORT
).show()
Log.d("openCV", "loader: ${OpenCVLoader.initDebug()}")
}
btnCamera.setOnClickListener {
if(isPermissionGranted(permission.CAMERA)) startCamera()
else requestCameraPermission(this)
}
gryImage.setOnClickListener {
val bitmap = outPutFileUri?.let { getCapturedImage(it) }
val matImage = Mat(bitmap!!.height, bitmap.width, CvType.CV_8UC1)
val bmpImage = bitmap.copy(Bitmap.Config.ARGB_8888, true)
Utils.bitmapToMat(bmpImage, matImage)
val bmp = Bitmap.createBitmap(matImage.cols(), matImage.rows(), Bitmap.Config.ARGB_8888)
Imgproc.cvtColor(matImage, matImage, Imgproc.COLOR_RGB2GRAY)
Utils.matToBitmap(matImage, bmp)
imageView.setImageBitmap(bmp)
}
}
private fun startCamera() {
val fileName = System.currentTimeMillis().toString() + ".jpeg"
output = File(
this.getExternalFilesDir(Environment.DIRECTORY_PICTURES),
fileName
)
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
outPutFileUri = this.let { it ->
FileProvider.getUriForFile(
it,
BuildConfig.APPLICATION_ID,
output!!
)
}
intent.putExtra(MediaStore.EXTRA_OUTPUT, outPutFileUri)
startActivityForResult(intent, REQUEST_IMAGE_CAPTURE)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
val activity = this
if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == Activity.RESULT_OK) {
val bitmap = outPutFileUri?.let { getCapturedImage(it) }
imageView.setImageBitmap(bitmap)
outPutFileUri?.let {
scope.launch {
val detectedFaces = FaceDetection.detectFaces(bitmap)
println("Detected Faces = $detectedFaces")
Toast.makeText(
this@MainActivity, "Detected Faces = $detectedFaces",
Toast.LENGTH_SHORT
).show()
}
}
}
}
private fun getCapturedImage(selectedPhotoUri: Uri): Bitmap {
return when {
Build.VERSION.SDK_INT < 28 -> MediaStore.Images.Media.getBitmap(
contentResolver,
selectedPhotoUri
)
else -> {
val source = ImageDecoder.createSource(contentResolver, selectedPhotoUri)
ImageDecoder.decodeBitmap(source)
}
}
// If the image is rotated, fix it
/* return when (ExifInterface(contentResolver.run { openInputStream(selectedPhotoUri) }).getAttributeInt(
ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED)) {
ExifInterface.ORIENTATION_ROTATE_90 ->
Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, Matrix().apply {
postRotate(90F) }, true)
ExifInterface.ORIENTATION_ROTATE_180 ->
Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, Matrix().apply {
postRotate(180F) }, true)
ExifInterface.ORIENTATION_ROTATE_270 ->
Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, Matrix().apply {
postRotate(270F) }, true)
else -> bitmap
} */
}
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<String>,grantResults: IntArray) =
onPermissionsRequestResult(this@MainActivity,
requestCode, permissions, grantResults)
}
И моя FaceDetection
функциональность:
object FaceDetection {
private const val faceModel = "haarcascades/haarcascade_frontalface_default.xml" // main/assets/haarcascades
private lateinit var faceCascade: CascadeClassifier
fun loadModel(activity: Activity) {
faceCascade = CascadeClassifier(File(activity.filesDir, "das").apply {
writeBytes(activity.assets.open(faceModel).readBytes())
}.path))
}
fun detectFaces(image: Bitmap?): Int {
val matImage = Mat(image!!.height, image.width, CvType.CV_8UC1) // CV_8UC1 is gray scale image
val bmpImage = image.copy(Bitmap.Config.ARGB_8888, true)
Utils.bitmapToMat(bmpImage, matImage)
val faceDetections = MatOfRect()
val grayScaled = matImage.prepare()
faceCascade.detectMultiScale(matImage, faceDetections, 1.1, 7, 0,
Size(250.0, 40.0), Size())
// process faces found
for (rect in faceDetections.toArray()) {
println("face found")
// Imgproc.rectangle(
// image,
// Point(rect.x, rect.y),
// Point(rect.x + rect.width, rect.y + rect.height),
// Scalar(0.0, 255.0, 0.0)
// )
}
return faceDetections.toArray().size
}
private fun Mat.toGrayScale(): Mat =
if (channels() >= 3) Mat().apply {
Imgproc.cvtColor(
this@toGrayScale,
this,
Imgproc.COLOR_BGR2GRAY
)
}
else this
private fun Mat.prepare(): Mat {
val mat = toGrayScale()
Imgproc.equalizeHist(mat, mat)
return mat
}
}