我将我的 CameraX 与 Firebase MLKit 条形码阅读器结合使用来检测条形码。应用 毫无问题地识别条形码。但我正在尝试添加边界框,它在 CameraX 预览中实时显示条形码区域。从条形码检测器函数中检索边界框信息。但是它没有正确的位置和大小,如下所示。
这是我的 Activity 布局。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/camera_capture_button"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginBottom="50dp"
android:scaleType="fitCenter"
android:text="Take Photo"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:elevation="2dp" />
<SurfaceView
android:id="@+id/overlayView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<androidx.camera.view.PreviewView
android:id="@+id/previewView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
SurfaceView
就是用来绘制这个矩形的。
条形码检测发生在实现ImageAnalysis.Analyzer
的BarcodeAnalyzer
类中。在覆盖的 analyze
函数中,我检索条形码数据,如下所示。
@SuppressLint("UnsafeExperimentalUsageError")
override fun analyze(imageProxy: ImageProxy) {
val mediaImage = imageProxy.image
val rotationDegrees = degreesToFirebaseRotation(imageProxy.imageInfo.rotationDegrees)
if (mediaImage != null) {
val analyzedImageHeight = mediaImage.height
val analyzedImageWidth = mediaImage.width
val image = FirebaseVisionImage
.fromMediaImage(mediaImage,rotationDegrees)
detector.detectInImage(image)
.addOnSuccessListener { barcodes ->
for (barcode in barcodes) {
val bounds = barcode.boundingBox
val corners = barcode.cornerPoints
val rawValue = barcode.rawValue
if(::barcodeDetectListener.isInitialized && rawValue != null && bounds != null){
barcodeDetectListener.onBarcodeDetect(
rawValue,
bounds,
analyzedImageWidth,
analyzedImageHeight
)
}
}
imageProxy.close()
}
.addOnFailureListener {
Log.e(tag,"Barcode Reading Exception: ${it.localizedMessage}")
imageProxy.close()
}
.addOnCanceledListener {
Log.e(tag,"Barcode Reading Canceled")
imageProxy.close()
}
}
}
barcodeDetectListener
是对我创建的用于将此数据传回我的 Activity 的接口(interface)的引用。
interface BarcodeDetectListener {
fun onBarcodeDetect(code: String, codeBound: Rect, imageWidth: Int, imageHeight: Int)
}
在我的主要 Activity 中,我将这些数据发送到实现 SurfaceHolder.Callback
的 OverlaySurfaceHolder
。此类负责在叠加的 SurfaceView
上绘制边界框。
override fun onBarcodeDetect(code: String, codeBound: Rect, analyzedImageWidth: Int,
analyzedImageHeight: Int) {
Log.i(TAG,"barcode : $code")
overlaySurfaceHolder.repositionBound(codeBound,previewView.width,previewView.height,
analyzedImageWidth,analyzedImageHeight)
overlayView.invalidate()
}
正如您在此处看到的,我正在发送叠加的 SurfaceView
宽度和高度,以便在 OverlaySurfaceHolder
类中进行计算。
OverlaySurfaceHolder.kt
class OverlaySurfaceHolder: SurfaceHolder.Callback {
var previewViewWidth: Int = 0
var previewViewHeight: Int = 0
var analyzedImageWidth: Int = 0
var analyzedImageHeight: Int = 0
private lateinit var drawingThread: DrawingThread
private lateinit var barcodeBound :Rect
private val tag = OverlaySurfaceHolder::class.java.simpleName
override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
}
override fun surfaceDestroyed(holder: SurfaceHolder?) {
var retry = true
drawingThread.running = false
while (retry){
try {
drawingThread.join()
retry = false
} catch (e: InterruptedException) {
}
}
}
override fun surfaceCreated(holder: SurfaceHolder?) {
drawingThread = DrawingThread(holder)
drawingThread.running = true
drawingThread.start()
}
fun repositionBound(codeBound: Rect, previewViewWidth: Int, previewViewHeight: Int,
analyzedImageWidth: Int, analyzedImageHeight: Int){
this.barcodeBound = codeBound
this.previewViewWidth = previewViewWidth
this.previewViewHeight = previewViewHeight
this.analyzedImageWidth = analyzedImageWidth
this.analyzedImageHeight = analyzedImageHeight
}
inner class DrawingThread(private val holder: SurfaceHolder?): Thread() {
var running = false
private fun adjustXCoordinates(valueX: Int): Float{
return if(previewViewWidth != 0){
(valueX / analyzedImageWidth.toFloat()) * previewViewWidth.toFloat()
}else{
valueX.toFloat()
}
}
private fun adjustYCoordinates(valueY: Int): Float{
return if(previewViewHeight != 0){
(valueY / analyzedImageHeight.toFloat()) * previewViewHeight.toFloat()
}else{
valueY.toFloat()
}
}
override fun run() {
while(running){
if(::barcodeBound.isInitialized){
val canvas = holder!!.lockCanvas()
if (canvas != null) {
synchronized(holder) {
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
val myPaint = Paint()
myPaint.color = Color.rgb(20, 100, 50)
myPaint.strokeWidth = 6f
myPaint.style = Paint.Style.STROKE
val refinedRect = RectF()
refinedRect.left = adjustXCoordinates(barcodeBound.left)
refinedRect.right = adjustXCoordinates(barcodeBound.right)
refinedRect.top = adjustYCoordinates(barcodeBound.top)
refinedRect.bottom = adjustYCoordinates(barcodeBound.bottom)
canvas.drawRect(refinedRect,myPaint)
}
holder.unlockCanvasAndPost(canvas)
}else{
Log.e(tag, "Cannot draw onto the canvas as it's null")
}
try {
sleep(30)
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
}
}
}
}
谁能指出我做错了什么?
最佳答案
我没有很清楚的线索,但这里有一些你可以尝试的东西:
调整X坐标时,如果previewWidth为0,则直接返回valueX.toFloat()。您能否添加一些日志记录以查看它实际上属于这种情况?另外添加一些日志来打印分析和预览维度也可能会有帮助。
另一件值得注意的事情是,您发送到检测器的图像可能与预览 View 区域具有不同的纵横比。例如,如果您的相机拍摄一张 4:3 的照片,它会将它发送到检测器。但是,如果您的查看区域是 1:1,它会裁剪部分照片以显示在那里。在这种情况下,您在调整坐标时也需要考虑到这一点。根据我的测试,图像将适合基于 CENTER_CROP 的 View 区域。如果您想非常小心,可能值得检查相机开发站点中是否记录了这一点。
希望对您有所帮助,或多或少。
关于android - 无法在叠加 Surfaceview 上的正确位置获得条形码边界框,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61932528/