capacitor-plugin-camera-forked 3.1.130 → 3.1.131

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.
@@ -76,8 +76,6 @@ dependencies {
76
76
  implementation 'org.tensorflow:tensorflow-lite-support:0.4.4'
77
77
  implementation 'org.tensorflow:tensorflow-lite-gpu:2.14.0'
78
78
 
79
- // Google ML Kit dependencies for text recognition and object detection
80
- implementation "com.google.mlkit:text-recognition:16.0.2"
81
- implementation "com.google.mlkit:object-detection:17.0.2"
82
- implementation "com.google.mlkit:vision-common:17.3.0"
79
+ // Google ML Kit dependencies for text recognition and object detection - REMOVED
80
+
83
81
  }
@@ -115,7 +115,8 @@ public class CameraPreviewPlugin extends Plugin {
115
115
 
116
116
  // Store the desired JPEG quality, set during initialization
117
117
  private int desiredJpegQuality = 95; // Default to high quality
118
- private BlurDetectionHelper blurDetectionHelper; // TFLite blur detection
118
+ // private BlurDetectionHelper blurDetectionHelper; // TFLite blur detection - REMOVED
119
+
119
120
 
120
121
  @PluginMethod
121
122
  public void initialize(PluginCall call) {
@@ -142,9 +143,10 @@ public class CameraPreviewPlugin extends Plugin {
142
143
  exec = Executors.newSingleThreadExecutor();
143
144
  cameraProviderFuture = ProcessCameraProvider.getInstance(getContext());
144
145
 
145
- // Initialize TFLite blur detection helper
146
- blurDetectionHelper = new BlurDetectionHelper();
147
- boolean tfliteInitialized = blurDetectionHelper.initialize(getContext());
146
+ // Initialize TFLite blur detection helper - REMOVED
147
+ // blurDetectionHelper = new BlurDetectionHelper();
148
+ // boolean tfliteInitialized = blurDetectionHelper.initialize(getContext());
149
+
148
150
 
149
151
  cameraProviderFuture.addListener(() -> {
150
152
  try {
@@ -222,7 +224,11 @@ public class CameraPreviewPlugin extends Plugin {
222
224
  // Only detect blur if checkBlur option is true
223
225
  boolean shouldCheckBlur = takeSnapshotCall.getBoolean("checkBlur", false);
224
226
  if (shouldCheckBlur) {
225
- // Get blur detection result with bounding boxes in one call
227
+ Log.d("Camera", "Blur detection disabled/removed");
228
+ result.put("isBlur", false);
229
+ result.put("confidence", 0.0);
230
+ // Get blur detection result with bounding boxes in one call
231
+ /* REMOVED BlurDetectionHelper usage
226
232
  if (blurDetectionHelper != null && blurDetectionHelper.isInitialized()) {
227
233
  java.util.Map<String, Object> blurResult = blurDetectionHelper.detectBlurWithConfidence(bitmap);
228
234
 
@@ -286,7 +292,9 @@ public class CameraPreviewPlugin extends Plugin {
286
292
  result.put("boundingBoxes", new java.util.ArrayList<>());
287
293
  result.put("detectionMethod", "laplacian_fallback");
288
294
  }
295
+ */
289
296
  } else {
297
+
290
298
  Log.d("Camera", "Blur detection disabled for performance");
291
299
  }
292
300
 
@@ -1451,83 +1459,83 @@ public class CameraPreviewPlugin extends Plugin {
1451
1459
  }
1452
1460
 
1453
1461
  // Use the new 3-step pipeline confidence detection method
1454
- if (blurDetectionHelper != null && blurDetectionHelper.isInitialized()) {
1455
- java.util.Map<String, Object> result = blurDetectionHelper.detectBlurWithConfidence(bitmap);
1456
-
1457
- JSObject jsResult = new JSObject();
1458
- jsResult.put("isBlur", result.get("isBlur"));
1459
- jsResult.put("method", result.get("method"));
1460
-
1461
- // Handle different confidence values based on detection method
1462
- String method = (String) result.get("method");
1463
- Double blurConfidence = (Double) result.get("blurConfidence");
1464
- Double sharpConfidence = (Double) result.get("sharpConfidence");
1465
- Double textConfidence = (Double) result.get("textConfidence");
1466
-
1467
- if ("text_detection".equals(method) && textConfidence != null) {
1468
- // For text detection, use text confidence as sharp confidence
1469
- jsResult.put("sharpConfidence", textConfidence);
1470
- jsResult.put("blurConfidence", 1.0 - textConfidence);
1471
- } else if ("tflite".equals(method) && sharpConfidence != null) {
1472
- // For TFLite model, use the provided confidence values
1473
- jsResult.put("sharpConfidence", sharpConfidence);
1474
- jsResult.put("blurConfidence", blurConfidence != null ? blurConfidence : (1.0 - sharpConfidence));
1475
- } else if ("laplacian".equals(method) && sharpConfidence != null) {
1476
- // For Laplacian fallback, use the provided confidence values
1477
- jsResult.put("sharpConfidence", sharpConfidence);
1478
- jsResult.put("blurConfidence", blurConfidence != null ? blurConfidence : (1.0 - sharpConfidence));
1479
- } else if (sharpConfidence != null) {
1480
- // Generic fallback for any method with sharp confidence
1481
- jsResult.put("sharpConfidence", sharpConfidence);
1482
- jsResult.put("blurConfidence", blurConfidence != null ? blurConfidence : (1.0 - sharpConfidence));
1483
- } else if (blurConfidence != null) {
1484
- // Fallback if only blur confidence is available
1485
- jsResult.put("blurConfidence", blurConfidence);
1486
- jsResult.put("sharpConfidence", 1.0 - blurConfidence);
1487
- } else {
1488
- // Final fallback values
1489
- Boolean isBlur = (Boolean) result.get("isBlur");
1490
- if (isBlur != null) {
1491
- jsResult.put("blurConfidence", isBlur ? 1.0 : 0.0);
1492
- jsResult.put("sharpConfidence", isBlur ? 0.0 : 1.0);
1493
- } else {
1494
- jsResult.put("blurConfidence", 0.0);
1495
- jsResult.put("sharpConfidence", 0.0);
1496
- }
1497
- }
1498
-
1499
- // Add additional information if available
1500
- if (result.containsKey("roiResults")) {
1501
- @SuppressWarnings("unchecked")
1502
- List<Map<String, Object>> roiResults = (List<Map<String, Object>>) result.get("roiResults");
1503
- List<List<Double>> boundingBoxes = new ArrayList<>();
1462
+ // if (blurDetectionHelper != null && blurDetectionHelper.isInitialized()) {
1463
+ // java.util.Map<String, Object> result = blurDetectionHelper.detectBlurWithConfidence(bitmap);
1464
+
1465
+ // JSObject jsResult = new JSObject();
1466
+ // jsResult.put("isBlur", result.get("isBlur"));
1467
+ // jsResult.put("method", result.get("method"));
1468
+
1469
+ // // Handle different confidence values based on detection method
1470
+ // String method = (String) result.get("method");
1471
+ // Double blurConfidence = (Double) result.get("blurConfidence");
1472
+ // Double sharpConfidence = (Double) result.get("sharpConfidence");
1473
+ // Double textConfidence = (Double) result.get("textConfidence");
1474
+
1475
+ // if ("text_detection".equals(method) && textConfidence != null) {
1476
+ // // For text detection, use text confidence as sharp confidence
1477
+ // jsResult.put("sharpConfidence", textConfidence);
1478
+ // jsResult.put("blurConfidence", 1.0 - textConfidence);
1479
+ // } else if ("tflite".equals(method) && sharpConfidence != null) {
1480
+ // // For TFLite model, use the provided confidence values
1481
+ // jsResult.put("sharpConfidence", sharpConfidence);
1482
+ // jsResult.put("blurConfidence", blurConfidence != null ? blurConfidence : (1.0 - sharpConfidence));
1483
+ // } else if ("laplacian".equals(method) && sharpConfidence != null) {
1484
+ // // For Laplacian fallback, use the provided confidence values
1485
+ // jsResult.put("sharpConfidence", sharpConfidence);
1486
+ // jsResult.put("blurConfidence", blurConfidence != null ? blurConfidence : (1.0 - sharpConfidence));
1487
+ // } else if (sharpConfidence != null) {
1488
+ // // Generic fallback for any method with sharp confidence
1489
+ // jsResult.put("sharpConfidence", sharpConfidence);
1490
+ // jsResult.put("blurConfidence", blurConfidence != null ? blurConfidence : (1.0 - sharpConfidence));
1491
+ // } else if (blurConfidence != null) {
1492
+ // // Fallback if only blur confidence is available
1493
+ // jsResult.put("blurConfidence", blurConfidence);
1494
+ // jsResult.put("sharpConfidence", 1.0 - blurConfidence);
1495
+ // } else {
1496
+ // // Final fallback values
1497
+ // Boolean isBlur = (Boolean) result.get("isBlur");
1498
+ // if (isBlur != null) {
1499
+ // jsResult.put("blurConfidence", isBlur ? 1.0 : 0.0);
1500
+ // jsResult.put("sharpConfidence", isBlur ? 0.0 : 1.0);
1501
+ // } else {
1502
+ // jsResult.put("blurConfidence", 0.0);
1503
+ // jsResult.put("sharpConfidence", 0.0);
1504
+ // }
1505
+ // }
1506
+
1507
+ // // Add additional information if available
1508
+ // if (result.containsKey("roiResults")) {
1509
+ // @SuppressWarnings("unchecked")
1510
+ // List<Map<String, Object>> roiResults = (List<Map<String, Object>>) result.get("roiResults");
1511
+ // List<List<Double>> boundingBoxes = new ArrayList<>();
1504
1512
 
1505
- for (Map<String, Object> roi : roiResults) {
1506
- Object boundingBoxObj = roi.get("boundingBox");
1507
- if (boundingBoxObj instanceof android.graphics.Rect) {
1508
- android.graphics.Rect rect = (android.graphics.Rect) boundingBoxObj;
1509
- List<Double> box = new ArrayList<>();
1510
- box.add((double) rect.left);
1511
- box.add((double) rect.top);
1512
- box.add((double) rect.right);
1513
- box.add((double) rect.bottom);
1514
- boundingBoxes.add(box);
1515
- }
1516
- }
1517
- jsResult.put("boundingBoxes", boundingBoxes);
1518
- }
1519
- if (result.containsKey("objectCount")) {
1520
- jsResult.put("objectCount", result.get("objectCount"));
1521
- }
1522
- if (result.containsKey("wordCount")) {
1523
- jsResult.put("wordCount", result.get("wordCount"));
1524
- }
1525
- if (result.containsKey("readableWords")) {
1526
- jsResult.put("readableWords", result.get("readableWords"));
1527
- }
1528
-
1529
- call.resolve(jsResult);
1530
- } else {
1513
+ // for (Map<String, Object> roi : roiResults) {
1514
+ // Object boundingBoxObj = roi.get("boundingBox");
1515
+ // if (boundingBoxObj instanceof android.graphics.Rect) {
1516
+ // android.graphics.Rect rect = (android.graphics.Rect) boundingBoxObj;
1517
+ // List<Double> box = new ArrayList<>();
1518
+ // box.add((double) rect.left);
1519
+ // box.add((double) rect.top);
1520
+ // box.add((double) rect.right);
1521
+ // box.add((double) rect.bottom);
1522
+ // boundingBoxes.add(box);
1523
+ // }
1524
+ // }
1525
+ // jsResult.put("boundingBoxes", boundingBoxes);
1526
+ // }
1527
+ // if (result.containsKey("objectCount")) {
1528
+ // jsResult.put("objectCount", result.get("objectCount"));
1529
+ // }
1530
+ // if (result.containsKey("wordCount")) {
1531
+ // jsResult.put("wordCount", result.get("wordCount"));
1532
+ // }
1533
+ // if (result.containsKey("readableWords")) {
1534
+ // jsResult.put("readableWords", result.get("readableWords"));
1535
+ // }
1536
+
1537
+ // call.resolve(jsResult);
1538
+ // } else {
1531
1539
  // Fallback to Laplacian algorithm with confidence scores
1532
1540
  double laplacianScore = calculateLaplacianBlurScore(bitmap);
1533
1541
  boolean isBlur = laplacianScore < 150;
@@ -1542,7 +1550,7 @@ public class CameraPreviewPlugin extends Plugin {
1542
1550
  result.put("method", "laplacian_fallback");
1543
1551
 
1544
1552
  call.resolve(result);
1545
- }
1553
+ // }
1546
1554
 
1547
1555
  } catch (Exception e) {
1548
1556
  call.reject("Failed to process image: " + e.getMessage());
@@ -1569,13 +1577,15 @@ public class CameraPreviewPlugin extends Plugin {
1569
1577
  if (bitmap == null) return false;
1570
1578
 
1571
1579
  // Use TFLite model if available, otherwise fallback to Laplacian
1572
- if (blurDetectionHelper != null && blurDetectionHelper.isInitialized()) {
1573
- return blurDetectionHelper.isBlurry(bitmap);
1574
- } else {
1580
+ // Use TFLite model if available, otherwise fallback to Laplacian
1581
+ // if (blurDetectionHelper != null && blurDetectionHelper.isInitialized()) {
1582
+ // return blurDetectionHelper.isBlurry(bitmap);
1583
+ //} else {
1575
1584
  // Fallback to original Laplacian algorithm
1576
1585
  double laplacianScore = calculateLaplacianBlurScore(bitmap);
1577
1586
  return laplacianScore < 50;
1578
- }
1587
+ //}
1588
+
1579
1589
  }
1580
1590
 
1581
1591
  /**
@@ -1585,6 +1595,8 @@ public class CameraPreviewPlugin extends Plugin {
1585
1595
  if (bitmap == null) return 0.0;
1586
1596
 
1587
1597
  // Use the new 3-step pipeline blur detection
1598
+ // Use the new 3-step pipeline blur detection - DISABLED
1599
+ /*
1588
1600
  if (blurDetectionHelper != null && blurDetectionHelper.isInitialized()) {
1589
1601
  java.util.Map<String, Object> result = blurDetectionHelper.detectBlurWithConfidence(bitmap);
1590
1602
  String method = (String) result.get("method");
@@ -1637,11 +1649,13 @@ public class CameraPreviewPlugin extends Plugin {
1637
1649
 
1638
1650
  return 0.0;
1639
1651
  } else {
1652
+ */
1640
1653
  // Fallback to Laplacian algorithm with confidence calculation
1641
1654
  double laplacianScore = calculateLaplacianBlurScore(bitmap);
1642
1655
  // Normalize to 0-1 range (higher score = sharper image)
1643
1656
  return Math.max(0.0, Math.min(1.0, laplacianScore / 300.0));
1644
- }
1657
+ //}
1658
+
1645
1659
  }
1646
1660
 
1647
1661
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "capacitor-plugin-camera-forked",
3
- "version": "3.1.130",
3
+ "version": "3.1.131",
4
4
  "description": "A capacitor camera plugin - A custom Capacitor camera plugin with additional features.",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",
@@ -1,1029 +0,0 @@
1
- package com.tonyxlh.capacitor.camera;
2
-
3
- import android.content.Context;
4
- import android.graphics.Bitmap;
5
- import android.graphics.Rect;
6
- import android.util.Log;
7
-
8
- import com.google.mlkit.vision.common.InputImage;
9
- import com.google.mlkit.vision.objects.DetectedObject;
10
- import com.google.mlkit.vision.objects.ObjectDetection;
11
- import com.google.mlkit.vision.objects.ObjectDetector;
12
- import com.google.mlkit.vision.objects.defaults.ObjectDetectorOptions;
13
- import com.google.mlkit.vision.text.Text;
14
- import com.google.mlkit.vision.text.TextRecognition;
15
- import com.google.mlkit.vision.text.TextRecognizer;
16
- import com.google.mlkit.vision.text.latin.TextRecognizerOptions;
17
-
18
- import org.tensorflow.lite.Interpreter;
19
- import org.tensorflow.lite.support.common.FileUtil;
20
- import org.tensorflow.lite.support.image.ImageProcessor;
21
- import org.tensorflow.lite.support.image.TensorImage;
22
- import org.tensorflow.lite.support.image.ops.ResizeOp;
23
- import org.tensorflow.lite.support.image.ops.ResizeWithCropOrPadOp;
24
-
25
- import org.tensorflow.lite.support.tensorbuffer.TensorBuffer;
26
- import android.graphics.ColorMatrix;
27
- import android.graphics.ColorMatrixColorFilter;
28
- import android.graphics.Paint;
29
- import android.graphics.Canvas;
30
- import org.tensorflow.lite.DataType;
31
-
32
- import java.io.IOException;
33
- import java.nio.ByteBuffer;
34
- import java.nio.FloatBuffer;
35
- import java.nio.MappedByteBuffer;
36
- import java.nio.ByteOrder;
37
- import java.util.ArrayList;
38
- import java.util.Collections;
39
- import java.util.Comparator;
40
- import java.util.HashMap;
41
- import java.util.HashSet;
42
- import java.util.List;
43
- import java.util.Map;
44
- import java.util.Set;
45
- import java.util.concurrent.CountDownLatch;
46
- import java.util.concurrent.ExecutorService;
47
- import java.util.concurrent.Executors;
48
- import java.util.concurrent.TimeUnit;
49
- import java.util.concurrent.atomic.AtomicBoolean;
50
- import java.util.concurrent.atomic.AtomicReference;
51
-
52
- /**
53
- * Comprehensive Blur Detection Helper
54
- * Implements the 3-step pipeline: Object Detection -> Text Detection -> Full-Image Blur Detection
55
- */
56
- public class BlurDetectionHelper {
57
- private static final String TAG = "BlurDetectionHelper";
58
- private static final String MODEL_FILENAME = "blur_detection_model.tflite";
59
- private static int INPUT_SIZE = 600; // Will be updated based on actual model input size
60
- private static final int NUM_CLASSES = 2; // blur, sharp
61
-
62
- // Timeout settings
63
- private static final int TIMEOUT_MS = 5000; // 5 second timeout
64
-
65
- // Text recognition settings
66
- private static final double MIN_WORD_CONFIDENCE = 0.8; // 80% confidence threshold
67
- private static final double AT_LEAST_N_PERCENT_OF_WORDS_ARE_READABLE = 0.6; // 60% of words are readable
68
- private static final double AT_LEAST_N_PERCENT_OF_AVERAGE_CONFIDENCE = 0.85; // 85% of average confidence
69
-
70
- // Method based confidence threshold
71
- private static final double MIN_SHARP_CONFIDENCE_FOR_OBJECT_DETECTION = 0.45; // 45% confidence threshold
72
- private static final double MIN_SHARP_CONFIDENCE_FOR_TEXT_DETECTION = 0.09; // 9% confidence threshold
73
- private static final double MIN_SHARP_CONFIDENCE_FOR_FULL_IMAGE = 0.65; // 65% confidence threshold
74
-
75
- // TFLite components
76
- private Interpreter tflite;
77
- private ImageProcessor imageProcessor;
78
- private TensorImage inputImageBuffer;
79
- private TensorBuffer outputProbabilityBuffer;
80
-
81
- // ML Kit components
82
- private ObjectDetector objectDetector;
83
- private TextRecognizer textRecognizer;
84
-
85
- // Configuration flags
86
- private boolean isInitialized = false;
87
- private boolean useObjectDetection = true;
88
- private boolean useTextRecognition = true;
89
- private boolean useDictionaryCheck = false;
90
-
91
- // Dictionary for text validation
92
- private Set<String> commonWords;
93
-
94
- // Executor for parallel processing
95
- private ExecutorService executorService;
96
-
97
-
98
- public BlurDetectionHelper() {
99
- // Initialize image processor for MobileNetV2 preprocessing with aspect ratio preservation
100
- imageProcessor = new ImageProcessor.Builder()
101
- .add(new ResizeWithCropOrPadOp(INPUT_SIZE, INPUT_SIZE)) // This preserves aspect ratio by cropping/padding
102
- .build();
103
-
104
- // Initialize common words dictionary
105
- initializeCommonWords();
106
-
107
- // Initialize executor service for parallel processing
108
- executorService = Executors.newFixedThreadPool(3);
109
- }
110
-
111
- /**
112
- * Initialize all components (TFLite, Object Detection, Text Recognition)
113
- * @param context Android context to access assets
114
- * @return true if initialization successful
115
- */
116
- public boolean initialize(Context context) {
117
- boolean tfliteInitialized = false;
118
- boolean objectDetectorInitialized = false;
119
- boolean textRecognizerInitialized = false;
120
-
121
- // Initialize TFLite model
122
- try {
123
- // Load model from assets
124
- MappedByteBuffer tfliteModel = FileUtil.loadMappedFile(context, MODEL_FILENAME);
125
-
126
- // Configure interpreter options for better performance
127
- Interpreter.Options options = new Interpreter.Options();
128
- options.setNumThreads(4); // Use multiple threads for better performance
129
-
130
- // Try to use GPU acceleration if available
131
- try {
132
- options.setUseXNNPACK(true);
133
- } catch (Exception e) {
134
- // XNNPACK not available, using CPU
135
- }
136
-
137
- tflite = new Interpreter(tfliteModel, options);
138
-
139
- // Initialize input and output buffers
140
- inputImageBuffer = new TensorImage(tflite.getInputTensor(0).dataType());
141
- outputProbabilityBuffer = TensorBuffer.createFixedSize(
142
- tflite.getOutputTensor(0).shape(),
143
- tflite.getOutputTensor(0).dataType()
144
- );
145
-
146
- // Update INPUT_SIZE based on actual model input shape
147
- int[] inputShape = tflite.getInputTensor(0).shape();
148
- if (inputShape.length >= 3) {
149
- int modelInputSize = inputShape[1]; // height dimension
150
- if (modelInputSize != INPUT_SIZE) {
151
- INPUT_SIZE = modelInputSize;
152
-
153
- // Recreate image processor with correct size and aspect ratio preservation
154
- imageProcessor = new ImageProcessor.Builder()
155
- .add(new ResizeWithCropOrPadOp(INPUT_SIZE, INPUT_SIZE)) // Preserves aspect ratio
156
- .build();
157
- }
158
- }
159
-
160
- tfliteInitialized = true;
161
-
162
- } catch (Exception e) {
163
- Log.e(TAG, "Failed to initialize TFLite model: " + e.getMessage());
164
- }
165
-
166
- // Initialize Object Detector
167
- try {
168
- ObjectDetectorOptions detectorOptions = new ObjectDetectorOptions.Builder()
169
- .setDetectorMode(ObjectDetectorOptions.SINGLE_IMAGE_MODE)
170
- .enableClassification() // Enable classification to get confidence scores
171
- .build();
172
-
173
- objectDetector = ObjectDetection.getClient(detectorOptions);
174
- objectDetectorInitialized = true;
175
-
176
- } catch (Exception e) {
177
- Log.e(TAG, "Failed to initialize Object Detector: " + e.getMessage());
178
- }
179
-
180
- // Initialize Text Recognizer
181
- try {
182
- textRecognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS);
183
- textRecognizerInitialized = true;
184
-
185
- } catch (Exception e) {
186
- Log.e(TAG, "Failed to initialize Text Recognizer: " + e.getMessage());
187
- }
188
-
189
- // Set initialization status
190
- isInitialized = tfliteInitialized || objectDetectorInitialized || textRecognizerInitialized;
191
-
192
- Log.d(TAG, "Initialization status - TFLite: " + tfliteInitialized +
193
- ", Object Detection: " + objectDetectorInitialized +
194
- ", Text Recognition: " + textRecognizerInitialized);
195
-
196
- return isInitialized;
197
- }
198
-
199
- /**
200
- * Main blur detection method implementing the 3-step pipeline
201
- * @param bitmap Input image bitmap
202
- * @return Blur confidence score (0.0 = sharp, 1.0 = very blurry)
203
- */
204
- public double detectBlur(Bitmap bitmap) {
205
- Map<String, Object> result = detectBlurWithConfidence(bitmap);
206
- Boolean isBlur = (Boolean) result.get("isBlur");
207
- return (isBlur != null && isBlur) ? 1.0 : 0.0;
208
- }
209
-
210
- /**
211
- * Detect blur with detailed confidence scores using the 3-step pipeline
212
- * @param bitmap Input image bitmap
213
- * @return Map with comprehensive blur detection results
214
- */
215
- public Map<String, Object> detectBlurWithConfidence(Bitmap bitmap) {
216
- Map<String, Object> result = new HashMap<>();
217
-
218
- if (!isInitialized) {
219
- result.put("isBlur", false);
220
- result.put("method", "none");
221
- result.put("error", "Blur detector not initialized");
222
- return result;
223
- }
224
-
225
- try {
226
- // Step 1: Object Detection (Preferred Path)
227
- if (useObjectDetection && objectDetector != null) {
228
- Log.d(TAG, "Step 1: Trying Object Detection");
229
- List<DetectedObject> objects = detectObjects(bitmap);
230
-
231
- if (!objects.isEmpty()) {
232
- Log.d(TAG, "Objects detected: " + objects.size());
233
-
234
- // Process ROIs from detected objects
235
- List<Map<String, Object>> roiResults = new ArrayList<>();
236
- boolean anyBlurry = false;
237
-
238
- for (DetectedObject object : objects) {
239
- Rect boundingBox = object.getBoundingBox();
240
- if (boundingBox != null) {
241
- // Crop ROI from original image
242
- Bitmap roi = cropBitmap(bitmap, boundingBox);
243
- if (roi != null) {
244
- // Run TFLite blur detection on ROI
245
- Map<String, Object> roiResult = detectBlurWithTFLiteConfidence(roi, MIN_SHARP_CONFIDENCE_FOR_OBJECT_DETECTION);
246
-
247
- Log.d(TAG, "Object Detection ROI Result isBlur: " + roiResult.get("isBlur"));
248
- roiResult.put("boundingBox", boundingBox);
249
- roiResults.add(roiResult);
250
-
251
- Boolean isBlur = (Boolean) roiResult.get("isBlur");
252
- if (isBlur != null && isBlur) {
253
- anyBlurry = true;
254
- }
255
- }
256
- }
257
- }
258
-
259
- result.put("method", "object_detection");
260
- result.put("isBlur", anyBlurry);
261
- result.put("roiResults", roiResults);
262
- result.put("objectCount", objects.size());
263
- return result;
264
- }
265
- }
266
-
267
- // Step 2: Text Detection (Fallback if Object Not Found)
268
- if (useTextRecognition && textRecognizer != null) {
269
- Log.d(TAG, "Step 2: Trying Text Detection");
270
- TextRecognitionResult textResult = detectTextWithBoundingBoxes(bitmap);
271
-
272
- if (textResult.totalWords > 0) {
273
- Log.d(TAG, "Text detected: " + textResult.totalWords + " words");
274
-
275
- // Combine all detected text areas into a single bounding box
276
- List<Rect> topTextAreas = combineAllTextAreas(textResult.boundingBoxes);
277
-
278
- if (!topTextAreas.isEmpty()) {
279
- // Process ROIs from text areas
280
- List<Map<String, Object>> roiResults = processROIsInParallel(bitmap, topTextAreas);
281
-
282
- // Check if any ROI is blurry
283
- boolean anyBlurry = false;
284
- for (Map<String, Object> roiResult : roiResults) {
285
- Boolean isBlur = (Boolean) roiResult.get("isBlur");
286
- if (isBlur != null && isBlur) {
287
- anyBlurry = true;
288
- break;
289
- }
290
- }
291
-
292
- result.put("method", "text_detection");
293
- result.put("isBlur", anyBlurry);
294
- result.put("roiResults", roiResults);
295
- result.put("textConfidence", textResult.averageConfidence);
296
- result.put("wordCount", textResult.totalWords);
297
- result.put("readableWords", textResult.readableWords);
298
- return result;
299
- }
300
- }
301
- }
302
-
303
- // Step 3: Full-Image Blur Detection (Final Fallback)
304
- Log.d(TAG, "Step 3: Using Full-Image Blur Detection");
305
- return detectBlurWithTFLiteConfidence(bitmap, MIN_SHARP_CONFIDENCE_FOR_FULL_IMAGE);
306
-
307
- } catch (Exception e) {
308
- Log.e(TAG, "Error in blur detection pipeline: " + e.getMessage());
309
- result.put("isBlur", false);
310
- result.put("method", "error");
311
- result.put("error", e.getMessage());
312
- return result;
313
- }
314
- }
315
-
316
- /**
317
- * Detect blur in image using TFLite model only
318
- * @param bitmap Input image bitmap
319
- * @return Blur confidence score (0.0 = sharp, 1.0 = very blurry)
320
- */
321
- public double detectBlurWithTFLite(Bitmap bitmap) {
322
- if (!isInitialized || tflite == null) {
323
- double laplacianScore = calculateLaplacianBlurScore(bitmap);
324
- return laplacianScore;
325
- }
326
-
327
-
328
- try {
329
- // Use the original bitmap directly (no image enhancement)
330
- Bitmap processedBitmap = bitmap;
331
-
332
- // Preprocess image for model (resize and potential enhancement)
333
- inputImageBuffer.load(processedBitmap);
334
- inputImageBuffer = imageProcessor.process(inputImageBuffer);
335
-
336
- // Ensure black padding for better accuracy (matches iOS implementation)
337
- ensureBlackPadding(inputImageBuffer);
338
-
339
- // Get tensor buffer
340
- ByteBuffer tensorBuffer = inputImageBuffer.getBuffer();
341
-
342
- // Check if we need normalization based on data types
343
- ByteBuffer inferenceBuffer;
344
- if (inputImageBuffer.getDataType() == DataType.UINT8 && tflite.getInputTensor(0).dataType() == DataType.FLOAT32) {
345
- inferenceBuffer = normalizeImageBuffer(tensorBuffer);
346
- } else if (inputImageBuffer.getDataType() == DataType.FLOAT32) {
347
- // Check if values are in [0,1] range or [0,255] range
348
- inferenceBuffer = checkAndNormalizeFloat32Buffer(tensorBuffer);
349
- } else {
350
- inferenceBuffer = tensorBuffer;
351
- }
352
-
353
- // Run inference
354
- tflite.run(inferenceBuffer, outputProbabilityBuffer.getBuffer().rewind());
355
-
356
- // Get output probabilities
357
- float[] probabilities = outputProbabilityBuffer.getFloatArray();
358
-
359
- // probabilities[0] = blur probability, probabilities[1] = sharp probability
360
- double blurConfidence = probabilities.length > 0 ? probabilities[0] : 0.0;
361
- double sharpConfidence = probabilities.length > 1 ? probabilities[1] : 0.0;
362
-
363
- // Determine if image is blurry using TFLite confidence
364
- boolean isBlur = sharpConfidence < MIN_SHARP_CONFIDENCE_FOR_FULL_IMAGE;
365
-
366
-
367
- // Return 1.0 for blur, 0.0 for sharp (to maintain double return type)
368
- return isBlur ? 1.0 : 0.0;
369
-
370
- } catch (Exception e) {
371
- // Fallback to Laplacian algorithm
372
- double laplacianScore = calculateLaplacianBlurScore(bitmap);
373
- boolean isBlur = laplacianScore < 150;
374
- return isBlur ? 1.0 : 0.0;
375
- }
376
- }
377
-
378
- /**
379
- * Check if float32 buffer needs normalization and normalize if needed
380
- * @param float32Buffer Input buffer with float32 pixel values
381
- * @return Normalized float32 buffer (values in [0,1] range)
382
- */
383
- private ByteBuffer checkAndNormalizeFloat32Buffer(ByteBuffer float32Buffer) {
384
- float32Buffer.rewind();
385
- FloatBuffer floatBuffer = float32Buffer.asFloatBuffer();
386
-
387
- // Sample a few values to check if they're in [0,1] or [0,255] range
388
- float maxSample = 0.0f;
389
- int sampleCount = Math.min(100, floatBuffer.remaining());
390
-
391
- for (int i = 0; i < sampleCount; i++) {
392
- float value = Math.abs(floatBuffer.get());
393
- maxSample = Math.max(maxSample, value);
394
- }
395
-
396
- // If max value is > 1.5, assume it's in [0,255] range and needs normalization
397
- if (maxSample > 1.5f) {
398
- return normalizeFloat32Buffer(float32Buffer);
399
- } else {
400
- float32Buffer.rewind();
401
- return float32Buffer;
402
- }
403
- }
404
-
405
- /**
406
- * Normalize float32 buffer from [0,255] to [0,1]
407
- * @param float32Buffer Input buffer with float32 pixel values in [0,255] range
408
- * @return Normalized float32 buffer
409
- */
410
- private ByteBuffer normalizeFloat32Buffer(ByteBuffer float32Buffer) {
411
- int pixelCount = INPUT_SIZE * INPUT_SIZE * 3;
412
- ByteBuffer normalizedBuffer = ByteBuffer.allocateDirect(pixelCount * 4);
413
- normalizedBuffer.order(ByteOrder.nativeOrder());
414
- FloatBuffer normalizedFloats = normalizedBuffer.asFloatBuffer();
415
-
416
- float32Buffer.rewind();
417
- FloatBuffer sourceFloats = float32Buffer.asFloatBuffer();
418
-
419
- while (sourceFloats.hasRemaining() && normalizedFloats.hasRemaining()) {
420
- float pixelValue = sourceFloats.get();
421
- float normalizedValue = pixelValue / 255.0f;
422
- normalizedFloats.put(normalizedValue);
423
- }
424
-
425
- normalizedBuffer.rewind();
426
- return normalizedBuffer;
427
- }
428
-
429
- /**
430
- * Ensure black padding in the processed image buffer for better accuracy
431
- * @param tensorImage Processed tensor image
432
- */
433
- private void ensureBlackPadding(TensorImage tensorImage) {
434
- ByteBuffer buffer = tensorImage.getBuffer();
435
- DataType dataType = tensorImage.getDataType();
436
-
437
- if (dataType == DataType.FLOAT32) {
438
- // For float32, ensure padding areas are 0.0 (black)
439
- FloatBuffer floatBuffer = buffer.asFloatBuffer();
440
- int totalPixels = INPUT_SIZE * INPUT_SIZE * 3;
441
-
442
- // Check if we need to fill with zeros (black)
443
- for (int i = 0; i < totalPixels; i++) {
444
- if (floatBuffer.get(i) < 0.001f) { // Near zero values
445
- floatBuffer.put(i, 0.0f); // Ensure exact zero (black)
446
- }
447
- }
448
- } else if (dataType == DataType.UINT8) {
449
- // For uint8, ensure padding areas are 0 (black)
450
- buffer.rewind();
451
- while (buffer.hasRemaining()) {
452
- byte value = buffer.get();
453
- if (value == 0) {
454
- buffer.put(buffer.position() - 1, (byte) 0); // Ensure exact zero
455
- }
456
- }
457
- }
458
- }
459
-
460
- /**
461
- * Normalize image buffer from uint8 [0,255] to float32 [0,1]
462
- * @param uint8Buffer Input buffer with uint8 pixel values
463
- * @return Normalized float32 buffer
464
- */
465
- private ByteBuffer normalizeImageBuffer(ByteBuffer uint8Buffer) {
466
- // Create float32 buffer with proper size and byte order
467
- int pixelCount = INPUT_SIZE * INPUT_SIZE * 3;
468
- ByteBuffer float32Buffer = ByteBuffer.allocateDirect(pixelCount * 4); // 4 bytes per float
469
- float32Buffer.order(ByteOrder.nativeOrder());
470
- FloatBuffer floatBuffer = float32Buffer.asFloatBuffer();
471
-
472
- // Reset uint8 buffer position
473
- uint8Buffer.rewind();
474
-
475
- // Convert each uint8 pixel to normalized float32
476
- while (uint8Buffer.hasRemaining() && floatBuffer.hasRemaining()) {
477
- int pixelValue = uint8Buffer.get() & 0xFF; // Convert to unsigned int
478
- float normalizedValue = pixelValue / 255.0f; // Normalize to [0,1]
479
- floatBuffer.put(normalizedValue);
480
- }
481
-
482
- float32Buffer.rewind();
483
- return float32Buffer;
484
- }
485
-
486
- /**
487
- * Fallback Laplacian blur detection (from original implementation)
488
- */
489
- private double calculateLaplacianBlurScore(Bitmap bitmap) {
490
- if (bitmap == null) return 0.0;
491
-
492
- int width = bitmap.getWidth();
493
- int height = bitmap.getHeight();
494
-
495
- // Convert to grayscale for better blur detection
496
- int[] pixels = new int[width * height];
497
- bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
498
-
499
- double[] grayscale = new double[width * height];
500
- for (int i = 0; i < pixels.length; i++) {
501
- int pixel = pixels[i];
502
- int r = (pixel >> 16) & 0xFF;
503
- int g = (pixel >> 8) & 0xFF;
504
- int b = pixel & 0xFF;
505
- grayscale[i] = 0.299 * r + 0.587 * g + 0.114 * b;
506
- }
507
-
508
- // Apply Laplacian kernel for edge detection
509
- double variance = 0.0;
510
- int count = 0;
511
-
512
- // Sample every 4th pixel for performance
513
- int step = 4;
514
- for (int y = step; y < height - step; y += step) {
515
- for (int x = step; x < width - step; x += step) {
516
- int idx = y * width + x;
517
-
518
- // 3x3 Laplacian kernel
519
- double laplacian =
520
- -grayscale[idx - width - 1] - grayscale[idx - width] - grayscale[idx - width + 1] +
521
- -grayscale[idx - 1] + 8 * grayscale[idx] - grayscale[idx + 1] +
522
- -grayscale[idx + width - 1] - grayscale[idx + width] - grayscale[idx + width + 1];
523
-
524
- variance += laplacian * laplacian;
525
- count++;
526
- }
527
- }
528
-
529
- return count > 0 ? variance / count : 0.0;
530
- }
531
-
532
-
533
- /**
534
- * Detect blur with detailed confidence scores using TFLite only
535
- * @param bitmap Input image bitmap
536
- * @return Map with isBlur, blurConfidence, and sharpConfidence
537
- */
538
- public java.util.Map<String, Object> detectBlurWithTFLiteConfidence(Bitmap bitmap, double minSharpConfidence) {
539
- java.util.Map<String, Object> result = new java.util.HashMap<>();
540
-
541
- if (!isInitialized || tflite == null) {
542
- double laplacianScore = calculateLaplacianBlurScore(bitmap);
543
- boolean isBlur = laplacianScore < 150;
544
- double normalizedScore = Math.max(0.0, Math.min(1.0, laplacianScore / 300.0));
545
- double sharpConfidence = normalizedScore;
546
- double blurConfidence = 1.0 - normalizedScore;
547
-
548
- result.put("method", "laplacian");
549
- result.put("isBlur", isBlur);
550
- result.put("blurConfidence", blurConfidence);
551
- result.put("sharpConfidence", sharpConfidence);
552
- result.put("laplacianScore", laplacianScore);
553
- result.put("hasText", false);
554
- result.put("boundingBoxes", new java.util.ArrayList<>());
555
- return result;
556
- }
557
-
558
- try {
559
- // Use the original bitmap directly (no image enhancement)
560
- Bitmap processedBitmap = bitmap;
561
-
562
- // Preprocess image for model (resize and potential enhancement)
563
- inputImageBuffer.load(processedBitmap);
564
- inputImageBuffer = imageProcessor.process(inputImageBuffer);
565
-
566
- // Ensure black padding for better accuracy (matches iOS implementation)
567
- ensureBlackPadding(inputImageBuffer);
568
-
569
- // Get tensor buffer
570
- ByteBuffer tensorBuffer = inputImageBuffer.getBuffer();
571
-
572
- // Check if we need normalization based on data types
573
- ByteBuffer inferenceBuffer;
574
- if (inputImageBuffer.getDataType() == DataType.UINT8 && tflite.getInputTensor(0).dataType() == DataType.FLOAT32) {
575
- inferenceBuffer = normalizeImageBuffer(tensorBuffer);
576
- } else if (inputImageBuffer.getDataType() == DataType.FLOAT32) {
577
- // Check if values are in [0,1] range or [0,255] range
578
- inferenceBuffer = checkAndNormalizeFloat32Buffer(tensorBuffer);
579
- } else {
580
- inferenceBuffer = tensorBuffer;
581
- }
582
-
583
- // Run inference
584
- tflite.run(inferenceBuffer, outputProbabilityBuffer.getBuffer().rewind());
585
-
586
- // Get output probabilities
587
- float[] probabilities = outputProbabilityBuffer.getFloatArray();
588
-
589
- // probabilities[0] = blur probability, probabilities[1] = sharp probability
590
- double blurConfidence = probabilities.length > 0 ? probabilities[0] : 0.0;
591
- double sharpConfidence = probabilities.length > 1 ? probabilities[1] : 0.0;
592
-
593
- Log.d(TAG, "TFLite Blur confidence: " + blurConfidence + " Sharp confidence: " + sharpConfidence);
594
-
595
- // Determine if image is blurry using TFLite confidence
596
- boolean isBlur = sharpConfidence < minSharpConfidence;
597
-
598
- Log.d(TAG, "TFLite Blur detection isBlur: " + isBlur);
599
-
600
- result.put("isBlur", isBlur);
601
- result.put("method", "tflite");
602
- result.put("blurConfidence", blurConfidence);
603
- result.put("sharpConfidence", sharpConfidence);
604
- result.put("boundingBoxes", new java.util.ArrayList<>());
605
- return result;
606
-
607
- } catch (Exception e) {
608
- // Fallback to Laplacian algorithm
609
- double laplacianScore = calculateLaplacianBlurScore(bitmap);
610
- boolean isBlur = laplacianScore < 150;
611
- double normalizedScore = Math.max(0.0, Math.min(1.0, laplacianScore / 300.0));
612
- double sharpConfidence = normalizedScore;
613
- double blurConfidence = 1.0 - normalizedScore;
614
-
615
- result.put("isBlur", isBlur);
616
- result.put("blurConfidence", blurConfidence);
617
- result.put("sharpConfidence", sharpConfidence);
618
- result.put("boundingBoxes", new java.util.ArrayList<>());
619
- return result;
620
- }
621
- }
622
-
623
- /**
624
- * Check if image is blurry
625
- * @param bitmap Input image
626
- * @return true if image is blurry, false if sharp
627
- */
628
- public boolean isBlurry(Bitmap bitmap) {
629
- double result = detectBlur(bitmap);
630
- return result == 1.0;
631
- }
632
-
633
- /**
634
- * Get blur percentage (0-100%) - Deprecated, use isBlurry() instead
635
- * @param bitmap Input image
636
- * @return Blur percentage where 0% = sharp, 100% = very blurry
637
- */
638
- @Deprecated
639
- public double getBlurPercentage(Bitmap bitmap) {
640
- // Convert boolean result to percentage for backward compatibility
641
- return isBlurry(bitmap) ? 100.0 : 0.0;
642
- }
643
-
644
-
645
- /**
646
- * Detect objects in the image using ML Kit Object Detection
647
- * @param bitmap Input image
648
- * @return List of detected objects
649
- */
650
- private List<DetectedObject> detectObjects(Bitmap bitmap) {
651
- if (objectDetector == null) {
652
- return new ArrayList<>();
653
- }
654
-
655
- InputImage image = InputImage.fromBitmap(bitmap, 0);
656
-
657
- CountDownLatch latch = new CountDownLatch(1);
658
- AtomicReference<List<DetectedObject>> resultRef = new AtomicReference<>(new ArrayList<>());
659
-
660
- objectDetector.process(image)
661
- .addOnSuccessListener(detectedObjects -> {
662
- resultRef.set(detectedObjects);
663
- latch.countDown();
664
- })
665
- .addOnFailureListener(e -> {
666
- Log.e(TAG, "Object detection failed: " + e.getMessage());
667
- latch.countDown();
668
- });
669
-
670
- try {
671
- latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
672
- } catch (InterruptedException e) {
673
- Thread.currentThread().interrupt();
674
- }
675
-
676
- return resultRef.get();
677
- }
678
-
679
- /**
680
- * Detect text in image with bounding boxes
681
- * @param bitmap Input image
682
- * @return TextRecognitionResult with bounding boxes
683
- */
684
- private TextRecognitionResult detectTextWithBoundingBoxes(Bitmap bitmap) {
685
- if (textRecognizer == null) {
686
- return new TextRecognitionResult(false, 0.0, 0, 0, new ArrayList<>());
687
- }
688
-
689
- InputImage image = InputImage.fromBitmap(bitmap, 0);
690
-
691
- CountDownLatch latch = new CountDownLatch(1);
692
- AtomicReference<TextRecognitionResult> resultRef = new AtomicReference<>();
693
- AtomicBoolean hasError = new AtomicBoolean(false);
694
-
695
- textRecognizer.process(image)
696
- .addOnSuccessListener(visionText -> {
697
- try {
698
- TextRecognitionResult result = analyzeTextConfidence(visionText);
699
- resultRef.set(result);
700
- } catch (Exception e) {
701
- hasError.set(true);
702
- } finally {
703
- latch.countDown();
704
- }
705
- })
706
- .addOnFailureListener(e -> {
707
- hasError.set(true);
708
- latch.countDown();
709
- });
710
-
711
- try {
712
- boolean completed = latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
713
- if (!completed || hasError.get()) {
714
- return new TextRecognitionResult(false, 0.0, 0, 0, new ArrayList<>());
715
- }
716
-
717
- TextRecognitionResult result = resultRef.get();
718
- return result != null ? result : new TextRecognitionResult(false, 0.0, 0, 0, new ArrayList<>());
719
-
720
- } catch (InterruptedException e) {
721
- Thread.currentThread().interrupt();
722
- return new TextRecognitionResult(false, 0.0, 0, 0, new ArrayList<>());
723
- }
724
- }
725
-
726
- /**
727
- * Analyze text confidence and extract bounding boxes
728
- * @param visionText Recognized text from ML Kit
729
- * @return TextRecognitionResult with analysis
730
- */
731
- private TextRecognitionResult analyzeTextConfidence(Text visionText) {
732
- int totalWords = 0;
733
- int readableWords = 0;
734
- double totalConfidence = 0.0;
735
- List<Rect> boundingBoxes = new ArrayList<>();
736
-
737
- for (Text.TextBlock block : visionText.getTextBlocks()) {
738
- Rect blockBoundingBox = block.getBoundingBox();
739
- if (blockBoundingBox != null) {
740
- boundingBoxes.add(blockBoundingBox);
741
- }
742
-
743
- for (Text.Line line : block.getLines()) {
744
- for (Text.Element element : line.getElements()) {
745
- String text = element.getText().trim();
746
- if (!text.isEmpty()) {
747
- totalWords++;
748
-
749
- // Estimate word confidence
750
- double confidence = estimateWordConfidence(element, text);
751
- totalConfidence += confidence;
752
-
753
- if (confidence >= MIN_WORD_CONFIDENCE) {
754
- if (!useDictionaryCheck || isInDictionary(text)) {
755
- readableWords++;
756
- }
757
- }
758
- }
759
- }
760
- }
761
- }
762
-
763
- double averageConfidence = totalWords > 0 ? totalConfidence / totalWords : 0.0;
764
-
765
- // Image is readable if we have text and sufficient readable words or high confidence
766
- boolean isReadable = totalWords > 0 &&
767
- (readableWords >= Math.max(1, totalWords * AT_LEAST_N_PERCENT_OF_WORDS_ARE_READABLE) ||
768
- averageConfidence >= AT_LEAST_N_PERCENT_OF_AVERAGE_CONFIDENCE);
769
-
770
- return new TextRecognitionResult(isReadable, averageConfidence, totalWords, readableWords, boundingBoxes);
771
- }
772
-
773
- /**
774
- * Combine all detected text areas into a single bounding box
775
- * @param boundingBoxes List of bounding boxes
776
- * @return List containing a single combined bounding box
777
- */
778
- private List<Rect> combineAllTextAreas(List<Rect> boundingBoxes) {
779
- if (boundingBoxes.isEmpty()) {
780
- return new ArrayList<>();
781
- }
782
-
783
- // Find the minimum and maximum coordinates across all boxes
784
- int minLeft = Integer.MAX_VALUE;
785
- int minTop = Integer.MAX_VALUE;
786
- int maxRight = Integer.MIN_VALUE;
787
- int maxBottom = Integer.MIN_VALUE;
788
-
789
- for (Rect rect : boundingBoxes) {
790
- if (rect == null) continue;
791
- minLeft = Math.min(minLeft, rect.left);
792
- minTop = Math.min(minTop, rect.top);
793
- maxRight = Math.max(maxRight, rect.right);
794
- maxBottom = Math.max(maxBottom, rect.bottom);
795
- }
796
-
797
- if (minLeft == Integer.MAX_VALUE) {
798
- return new ArrayList<>();
799
- }
800
-
801
- Rect combinedRect = new Rect(minLeft, minTop, maxRight, maxBottom);
802
-
803
- Log.d(TAG, "Combined " + boundingBoxes.size() + " text areas into single bounding box: " +
804
- "(" + minLeft + ", " + minTop + ", " + maxRight + ", " + maxBottom + ")");
805
-
806
- // Minimum width and height
807
- int minWidth = 100;
808
- int minHeight = 40;
809
- if (combinedRect.width() < minWidth || combinedRect.height() < minHeight) {
810
- return new ArrayList<>();
811
- }
812
-
813
- List<Rect> result = new ArrayList<>();
814
- result.add(combinedRect);
815
- return result;
816
- }
817
-
818
- /**
819
- * Process multiple ROIs in parallel for blur detection
820
- * @param bitmap Original bitmap
821
- * @param rois List of regions of interest
822
- * @return List of blur detection results for each ROI
823
- */
824
- private List<Map<String, Object>> processROIsInParallel(Bitmap bitmap, List<Rect> rois) {
825
- List<Map<String, Object>> results = Collections.synchronizedList(new ArrayList<>());
826
- CountDownLatch latch = new CountDownLatch(rois.size());
827
-
828
- for (Rect roi : rois) {
829
- executorService.execute(() -> {
830
- try {
831
- Bitmap cropped = cropBitmap(bitmap, roi);
832
- if (cropped != null) {
833
- Map<String, Object> result = detectBlurWithTFLiteConfidence(cropped, MIN_SHARP_CONFIDENCE_FOR_TEXT_DETECTION);
834
- result.put("boundingBox", roi);
835
- results.add(result);
836
- }
837
- } catch (Exception e) {
838
- Log.e(TAG, "Error processing ROI: " + e.getMessage());
839
- } finally {
840
- latch.countDown();
841
- }
842
- });
843
- }
844
-
845
- try {
846
- latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
847
- } catch (InterruptedException e) {
848
- Thread.currentThread().interrupt();
849
- }
850
-
851
- return results;
852
- }
853
-
854
- /**
855
- * Crop a bitmap to the specified bounding box
856
- * @param source Source bitmap
857
- * @param rect Bounding box to crop
858
- * @return Cropped bitmap or null if invalid
859
- */
860
- private Bitmap cropBitmap(Bitmap source, Rect rect) {
861
- try {
862
- // Ensure bounds are within image
863
- int left = Math.max(0, rect.left);
864
- int top = Math.max(0, rect.top);
865
- int right = Math.min(source.getWidth(), rect.right);
866
- int bottom = Math.min(source.getHeight(), rect.bottom);
867
-
868
- int width = right - left;
869
- int height = bottom - top;
870
-
871
- if (width > 0 && height > 0) {
872
- return Bitmap.createBitmap(source, left, top, width, height);
873
- }
874
- } catch (Exception e) {
875
- Log.e(TAG, "Error cropping bitmap: " + e.getMessage());
876
- }
877
- return null;
878
- }
879
-
880
- /**
881
- * Estimate word confidence based on element properties
882
- * @param element Text element from ML Kit
883
- * @param text The actual text content
884
- * @return Estimated confidence score
885
- */
886
- private double estimateWordConfidence(Text.Element element, String text) {
887
- double confidence = 0.55; // Base confidence
888
-
889
- // Text length check
890
- if (text.length() >= 3 && text.length() <= 15) {
891
- confidence += 0.2;
892
- }
893
-
894
- // Common patterns check
895
- if (text.matches("[a-zA-Z0-9\\s\\-\\.]+")) {
896
- confidence += 0.15;
897
- }
898
-
899
- // Mixed case check
900
- if (text.matches(".*[a-z].*") && text.matches(".*[A-Z].*")) {
901
- confidence += 0.1;
902
- }
903
-
904
- // Numbers check
905
- if (text.matches(".*\\d.*")) {
906
- confidence += 0.1;
907
- }
908
-
909
- // Special characters penalty
910
- if (text.matches(".*[^a-zA-Z0-9\\s\\-\\.].*")) {
911
- confidence -= 0.1;
912
- }
913
-
914
- return Math.max(0.0, Math.min(1.0, confidence));
915
- }
916
-
917
- /**
918
- * Check if word is in common dictionary
919
- * @param word Word to check
920
- * @return true if word is in dictionary
921
- */
922
- private boolean isInDictionary(String word) {
923
- if (commonWords == null || word == null) {
924
- return false;
925
- }
926
-
927
- String cleanWord = word.toLowerCase().replaceAll("[^a-zA-Z]", "");
928
- return commonWords.contains(cleanWord);
929
- }
930
-
931
- /**
932
- * Initialize common English words for dictionary check
933
- */
934
- private void initializeCommonWords() {
935
- commonWords = new HashSet<>();
936
-
937
- // Add common English words
938
- String[] words = {
939
- "the", "be", "to", "of", "and", "a", "in", "that", "have", "i", "it", "for", "not", "on", "with",
940
- "he", "as", "you", "do", "at", "this", "but", "his", "by", "from", "they", "we", "say", "her",
941
- "she", "or", "an", "will", "my", "one", "all", "would", "there", "their", "what", "so", "up",
942
- "out", "if", "about", "who", "get", "which", "go", "me", "when", "make", "can", "like", "time",
943
- "no", "just", "him", "know", "take", "people", "into", "year", "your", "good", "some", "could",
944
- "them", "see", "other", "than", "then", "now", "look", "only", "come", "its", "over", "think",
945
- "also", "back", "after", "use", "two", "how", "our", "work", "first", "well", "way", "even",
946
- "new", "want", "because", "any", "these", "give", "day", "most", "us", "is", "was", "are",
947
- "were", "been", "has", "had", "having", "does", "did", "doing", "can", "could", "should",
948
- "would", "may", "might", "must", "shall", "will", "am", "being", "became", "become", "becomes"
949
- };
950
-
951
- for (String word : words) {
952
- commonWords.add(word);
953
- }
954
- }
955
-
956
- /**
957
- * Clean up resources
958
- */
959
- public void close() {
960
- if (tflite != null) {
961
- tflite.close();
962
- tflite = null;
963
- }
964
- if (objectDetector != null) {
965
- objectDetector.close();
966
- objectDetector = null;
967
- }
968
- if (textRecognizer != null) {
969
- textRecognizer.close();
970
- textRecognizer = null;
971
- }
972
- if (executorService != null) {
973
- executorService.shutdown();
974
- executorService = null;
975
- }
976
- isInitialized = false;
977
- }
978
-
979
- /**
980
- * Check if blur detector is properly initialized
981
- */
982
- public boolean isInitialized() {
983
- return isInitialized;
984
- }
985
-
986
- /**
987
- * Enable or disable object detection
988
- * @param enable true to enable object detection
989
- */
990
- public void setObjectDetectionEnabled(boolean enable) {
991
- this.useObjectDetection = enable;
992
- }
993
-
994
- /**
995
- * Enable or disable text recognition
996
- * @param enable true to enable text recognition
997
- */
998
- public void setTextRecognitionEnabled(boolean enable) {
999
- this.useTextRecognition = enable;
1000
- }
1001
-
1002
- /**
1003
- * Enable or disable dictionary check in text recognition
1004
- * @param enable true to enable dictionary check
1005
- */
1006
- public void setDictionaryCheckEnabled(boolean enable) {
1007
- this.useDictionaryCheck = enable;
1008
- }
1009
-
1010
- /**
1011
- * Result class for text recognition analysis
1012
- */
1013
- private static class TextRecognitionResult {
1014
- final boolean isReadable;
1015
- final double averageConfidence;
1016
- final int totalWords;
1017
- final int readableWords;
1018
- final List<Rect> boundingBoxes;
1019
-
1020
- TextRecognitionResult(boolean isReadable, double averageConfidence, int totalWords,
1021
- int readableWords, List<Rect> boundingBoxes) {
1022
- this.isReadable = isReadable;
1023
- this.averageConfidence = averageConfidence;
1024
- this.totalWords = totalWords;
1025
- this.readableWords = readableWords;
1026
- this.boundingBoxes = boundingBoxes;
1027
- }
1028
- }
1029
- }