react-native-rectangle-doc-scanner 3.240.0 → 3.241.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -7,6 +7,7 @@ import android.graphics.Bitmap
|
|
|
7
7
|
import android.graphics.BitmapFactory
|
|
8
8
|
import android.graphics.Matrix
|
|
9
9
|
import android.graphics.SurfaceTexture
|
|
10
|
+
import android.graphics.Rect
|
|
10
11
|
import android.graphics.RectF
|
|
11
12
|
import android.graphics.ImageFormat
|
|
12
13
|
import android.hardware.camera2.CameraCaptureSession
|
|
@@ -68,6 +69,7 @@ class CameraController(
|
|
|
68
69
|
.enableMultipleObjects()
|
|
69
70
|
.build()
|
|
70
71
|
)
|
|
72
|
+
private var lastRefineTimestamp = 0L
|
|
71
73
|
|
|
72
74
|
var onFrameAnalyzed: ((Rectangle?, Int, Int) -> Unit)? = null
|
|
73
75
|
|
|
@@ -379,13 +381,11 @@ class CameraController(
|
|
|
379
381
|
val box = obj.boundingBox
|
|
380
382
|
box.width() * box.height()
|
|
381
383
|
}
|
|
382
|
-
val
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
Point(box.right.toDouble(), box.bottom.toDouble())
|
|
388
|
-
)
|
|
384
|
+
val mlBox = best?.boundingBox
|
|
385
|
+
val rectangle = when {
|
|
386
|
+
mlBox == null -> null
|
|
387
|
+
shouldRefineWithOpenCv() -> refineWithOpenCv(image, rotationDegrees, mlBox) ?: boxToRectangle(mlBox)
|
|
388
|
+
else -> boxToRectangle(mlBox)
|
|
389
389
|
}
|
|
390
390
|
|
|
391
391
|
val frameWidth = if (rotationDegrees == 90 || rotationDegrees == 270) image.height else image.width
|
|
@@ -485,10 +485,7 @@ class CameraController(
|
|
|
485
485
|
|
|
486
486
|
val matrix = Matrix()
|
|
487
487
|
bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY())
|
|
488
|
-
matrix.setRectToRect(
|
|
489
|
-
|
|
490
|
-
val scale = max(viewWidth / bufferWidth, viewHeight / bufferHeight)
|
|
491
|
-
matrix.postScale(scale, scale, centerX, centerY)
|
|
488
|
+
matrix.setRectToRect(bufferRect, viewRect, Matrix.ScaleToFit.FILL)
|
|
492
489
|
matrix.postRotate(rotation.toFloat(), centerX, centerY)
|
|
493
490
|
previewView.setTransform(matrix)
|
|
494
491
|
}
|
|
@@ -529,6 +526,100 @@ class CameraController(
|
|
|
529
526
|
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
|
|
530
527
|
}
|
|
531
528
|
|
|
529
|
+
private fun shouldRefineWithOpenCv(): Boolean {
|
|
530
|
+
val now = System.currentTimeMillis()
|
|
531
|
+
if (now - lastRefineTimestamp < 200) {
|
|
532
|
+
return false
|
|
533
|
+
}
|
|
534
|
+
lastRefineTimestamp = now
|
|
535
|
+
return true
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
private fun refineWithOpenCv(image: Image, rotationDegrees: Int, mlBox: Rect): Rectangle? {
|
|
539
|
+
return try {
|
|
540
|
+
val nv21 = imageToNv21(image)
|
|
541
|
+
val openCvRect = DocumentDetector.detectRectangleInYUV(nv21, image.width, image.height, rotationDegrees)
|
|
542
|
+
if (openCvRect == null) {
|
|
543
|
+
null
|
|
544
|
+
} else {
|
|
545
|
+
val openRectBounds = rectangleBounds(openCvRect)
|
|
546
|
+
if (computeIoU(openRectBounds, mlBox) >= 0.2f) openCvRect else null
|
|
547
|
+
}
|
|
548
|
+
} catch (e: Exception) {
|
|
549
|
+
Log.w(TAG, "[CAMERA2] OpenCV refine failed", e)
|
|
550
|
+
null
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
private fun boxToRectangle(box: Rect): Rectangle {
|
|
555
|
+
return Rectangle(
|
|
556
|
+
Point(box.left.toDouble(), box.top.toDouble()),
|
|
557
|
+
Point(box.right.toDouble(), box.top.toDouble()),
|
|
558
|
+
Point(box.left.toDouble(), box.bottom.toDouble()),
|
|
559
|
+
Point(box.right.toDouble(), box.bottom.toDouble())
|
|
560
|
+
)
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
private fun rectangleBounds(rectangle: Rectangle): Rect {
|
|
564
|
+
val left = listOf(rectangle.topLeft.x, rectangle.bottomLeft.x, rectangle.topRight.x, rectangle.bottomRight.x).minOrNull() ?: 0.0
|
|
565
|
+
val right = listOf(rectangle.topLeft.x, rectangle.bottomLeft.x, rectangle.topRight.x, rectangle.bottomRight.x).maxOrNull() ?: 0.0
|
|
566
|
+
val top = listOf(rectangle.topLeft.y, rectangle.bottomLeft.y, rectangle.topRight.y, rectangle.bottomRight.y).minOrNull() ?: 0.0
|
|
567
|
+
val bottom = listOf(rectangle.topLeft.y, rectangle.bottomLeft.y, rectangle.topRight.y, rectangle.bottomRight.y).maxOrNull() ?: 0.0
|
|
568
|
+
return Rect(left.toInt(), top.toInt(), right.toInt(), bottom.toInt())
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
private fun computeIoU(a: Rect, b: Rect): Float {
|
|
572
|
+
val left = max(a.left, b.left)
|
|
573
|
+
val top = max(a.top, b.top)
|
|
574
|
+
val right = minOf(a.right, b.right)
|
|
575
|
+
val bottom = minOf(a.bottom, b.bottom)
|
|
576
|
+
if (right <= left || bottom <= top) return 0f
|
|
577
|
+
val intersection = (right - left).toFloat() * (bottom - top).toFloat()
|
|
578
|
+
val union = (a.width() * a.height() + b.width() * b.height() - intersection).toFloat()
|
|
579
|
+
return if (union <= 0f) 0f else intersection / union
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
private fun imageToNv21(image: Image): ByteArray {
|
|
583
|
+
val width = image.width
|
|
584
|
+
val height = image.height
|
|
585
|
+
val ySize = width * height
|
|
586
|
+
val uvSize = width * height / 2
|
|
587
|
+
val nv21 = ByteArray(ySize + uvSize)
|
|
588
|
+
|
|
589
|
+
val yBuffer = image.planes[0].buffer
|
|
590
|
+
val uBuffer = image.planes[1].buffer
|
|
591
|
+
val vBuffer = image.planes[2].buffer
|
|
592
|
+
|
|
593
|
+
val yRowStride = image.planes[0].rowStride
|
|
594
|
+
val yPixelStride = image.planes[0].pixelStride
|
|
595
|
+
var outputOffset = 0
|
|
596
|
+
for (row in 0 until height) {
|
|
597
|
+
var inputOffset = row * yRowStride
|
|
598
|
+
for (col in 0 until width) {
|
|
599
|
+
nv21[outputOffset++] = yBuffer.get(inputOffset)
|
|
600
|
+
inputOffset += yPixelStride
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
val uvRowStride = image.planes[1].rowStride
|
|
605
|
+
val uvPixelStride = image.planes[1].pixelStride
|
|
606
|
+
val vRowStride = image.planes[2].rowStride
|
|
607
|
+
val vPixelStride = image.planes[2].pixelStride
|
|
608
|
+
val uvHeight = height / 2
|
|
609
|
+
val uvWidth = width / 2
|
|
610
|
+
for (row in 0 until uvHeight) {
|
|
611
|
+
var uInputOffset = row * uvRowStride
|
|
612
|
+
var vInputOffset = row * vRowStride
|
|
613
|
+
for (col in 0 until uvWidth) {
|
|
614
|
+
nv21[outputOffset++] = vBuffer.get(vInputOffset)
|
|
615
|
+
nv21[outputOffset++] = uBuffer.get(uInputOffset)
|
|
616
|
+
uInputOffset += uvPixelStride
|
|
617
|
+
vInputOffset += vPixelStride
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
return nv21
|
|
622
|
+
}
|
|
532
623
|
private fun hasCameraPermission(): Boolean {
|
|
533
624
|
return ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED
|
|
534
625
|
}
|
package/package.json
CHANGED