无法检测人脸,并且返回矩形大小始终为 1x0
Posted
技术标签:
【中文标题】无法检测人脸,并且返回矩形大小始终为 1x0【英文标题】:Can not detect faces, and return rectangles size always 1x0 【发布时间】:2019-09-23 11:33:16 【问题描述】:我有以下 android 代码:
class MainActivity : AppCompatActivity(), TtsSpeaker.Listener, PocketSphinx.Listener
override fun onCreate(savedInstanceState: Bundle?)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val ocvLoaded = OpenCVLoader.initDebug()
if (ocvLoaded)
loadModel(this) // to be sure model in sot alled before initiation openCV
else
Log.d("openCV", "loader: $OpenCVLoader.initDebug()")
并使用下面的人脸检测类:
package hasan.tts_mobile
import android.app.Activity
import android.graphics.Bitmap
import org.opencv.core.Mat
import org.opencv.core.MatOfRect
import org.opencv.core.Size
import org.opencv.imgproc.Imgproc
import org.opencv.objdetect.CascadeClassifier
import java.io.File
import org.opencv.core.CvType
import android.opengl.ETC1.getWidth
import android.opengl.ETC1.getHeight
object FaceDetection
private val faceModel = "haarcascade_frontalface_default.xml" //lateinit var faceModel: String // = "haarcascade_frontalface_default.xml"
private lateinit var faceCascade: CascadeClassifier
fun loadModel(activity: Activity)
println("started loading the model")
faceCascade = CascadeClassifier(File(activity.filesDir, "das").apply
writeBytes(activity.assets.open(faceModel).readBytes())
.path)
println("completed loading the model")
tts!!.say("I'm 100% ready!")
fun detectFaces(activity: Activity, image: Bitmap?): Long
// bitmap
val matImage = Mat(image!!.height, image.width, CvType.CV_8UC1)
val bmpImage = image.copy(Bitmap.Config.ARGB_8888, true)
Utils.bitmapToMat(bmpImage, matImage)
val rectangles = MatOfRect() //RectVector() //MatOfRect()
val grayScaled = matImage .prepare()
// loadModel(activity)
faceCascade.detectMultiScale(
grayScaled, rectangles, 1.2, 10, 0,
Size(40.0, 40.0),
null)
return rectangles.size() as Long
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
使用以下代码从相机成功拍摄照片后调用人脸检测功能:
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 it1 ->
FileProvider.getUriForFile(
it1,
BuildConfig.APPLICATION_ID,
output!!
)
intent.putExtra(MediaStore.EXTRA_OUTPUT, outPutFileUri)
startActivityForResult(intent, REQUEST_IMAGE_CAPTURE)
@SuppressLint("MissingSuperCall")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?)
if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == Activity.RESULT_OK)
val bitmap = outPutFileUri?.let getCapturedImage(it)
imageView.setImageBitmap(bitmap)
FaceDetection.detectFaces(this, bitmap) // Calling facedetection
private fun getCapturedImage(selectedPhotoUri: Uri): Bitmap
val bitmap = when
Build.VERSION.SDK_INT < 28 -> MediaStore.Images.Media.getBitmap(
this.contentResolver,
selectedPhotoUri
)
else ->
val source = ImageDecoder.createSource(this.contentResolver, selectedPhotoUri)
ImageDecoder.decodeBitmap(source)
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
我得到的完整错误是:
D/AndroidRuntime: Shutting down VM
E/AndroidRuntime: FATAL EXCEPTION: main
Process: hasan.tts_mobile, PID: 14110
java.lang.RuntimeException: Failure delivering result ResultInfowho=null, request=1, result=-1, data=Intent to activity hasan.tts_mobile/hasan.tts_mobile.MainActivity: java.lang.NullPointerException: Attempt to read from field 'double org.opencv.core.Size.width' on a null object reference
at android.app.ActivityThread.deliverResults(ActivityThread.java:4845)
at android.app.ActivityThread.handleSendResult(ActivityThread.java:4886)
at android.app.servertransaction.ActivityResultItem.execute(ActivityResultItem.java:51)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
Caused by: java.lang.NullPointerException: Attempt to read from field 'double org.opencv.core.Size.width' on a null object reference
at org.opencv.objdetect.CascadeClassifier.detectMultiScale(CascadeClassifier.java:156)
at hasan.tts_mobile.FaceDetection.detectFaces(FaceDetection.kt:38)
at hasan.tts_mobile.MainActivity.onActivityResult(MainActivity.kt:173)
at android.app.Activity.dispatchActivityResult(Activity.java:8110)
at android.app.ActivityThread.deliverResults(ActivityThread.java:4838)
at android.app.ActivityThread.handleSendResult(ActivityThread.java:4886)
at android.app.servertransaction.ActivityResultItem.execute(ActivityResultItem.java:51)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
I/Process: Sending signal. PID: 14110 SIG: 9
Process 14110 terminated.
我添加
println("pic size: $grayScaled.size()")
println("faceCascade: $faceCascade.originalWindowSize")
之前
faceCascade.detectMultiScale(
grayScaled, rectangles, 1.2, 10, 0,
Size(40.0, 40.0),
null)
得到以下结果:
I/System.out: pic size: 960x1280
faceCascade: 24x24
D/AndroidRuntime: Shutting down VM
E/AndroidRuntime: FATAL EXCEPTION: main
Process: hasan.tts_mobile, PID: 15177
更新
看起来问题是我将maxSize
定义为null
,它应该是Size()
或全尺寸为:Size(40.0, 40.0)
现在我将其更改为:
faceCascade.detectMultiScale(
grayScaled, rectangles, 1.1, 3, 0,
Size(30.0, 30.0), Size()
)
println("rectangles $rectangles.size()")
我没有挂起或崩溃,而是将rectangles.size()
返回为:
I/System.out: pic size: 3264x2448
faceCascade: 24x24
I/System.out: rectangles 1x0
这是否意味着它没有检测到任何人脸,如果是,如何解决?
【问题讨论】:
faceModel
不是String
。它是一个XmlResourceParser
,就像that is what getXml()
returns。
【参考方案1】:
我想通了,人脸检测过程的结果是在过程本身完成之前读取的,所以解决方案是使用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
【讨论】:
以上是关于无法检测人脸,并且返回矩形大小始终为 1x0的主要内容,如果未能解决你的问题,请参考以下文章
在实时人脸检测 Android 中的 Live CameraPreview 上围绕人脸绘制矩形