diff --git a/android/build.gradle b/android/build.gradle index b2e2f59..22f5fdb 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -15,9 +15,7 @@ buildscript { dependencies { - - classpath 'com.android.tools.build:gradle:7.1.2' - + classpath 'com.android.tools.build:gradle:8.6.0' } } @@ -30,13 +28,7 @@ rootProject.allprojects { google() mavenCentral() - - flatDir{ - - dirs project(":flutter_vision").file("libs") - - } - + maven { url 'https://jitpack.io' @@ -60,12 +52,10 @@ apply plugin: 'com.android.library' android { - compileSdkVersion 31 - - namespace 'com.vladih.computer_vision.flutter_vision' - + + compileSdkVersion 36 compileOptions { @@ -79,7 +69,7 @@ android { defaultConfig { minSdkVersion 21 - + targetSdk 36 } aaptOptions { @@ -101,26 +91,11 @@ android { dependencies{ - - //implementation (files('libs/tesseract4android-release.aar')) - - api(name:"tesseract4android-release", ext: "aar") - implementation 'com.github.vladiH:opencv-android:v1.0.0' - - implementation 'org.tensorflow:tensorflow-lite:2.10.0' - - implementation 'org.tensorflow:tensorflow-lite-api:2.10.0' - - implementation 'org.tensorflow:tensorflow-lite-gpu:2.10.0' - - implementation 'org.tensorflow:tensorflow-lite-gpu-api:2.10.0' - - implementation 'org.tensorflow:tensorflow-lite-gpu-delegate-plugin:0.4.3' - - implementation 'org.tensorflow:tensorflow-lite-support:0.4.3' - - implementation 'org.tensorflow:tensorflow-lite-metadata:0.4.3' - - implementation 'org.tensorflow:tensorflow-lite-select-tf-ops:2.11.0' + implementation 'com.google.ai.edge.litert:litert:1.4.0' + implementation 'com.google.ai.edge.litert:litert-api:1.4.0' + implementation 'com.google.ai.edge.litert:litert-gpu:1.4.0' + implementation 'com.google.ai.edge.litert:litert-gpu-api:1.4.0' + implementation 'com.google.ai.edge.litert:litert-support:1.4.0' + implementation 'com.google.ai.edge.litert:litert-metadata:1.4.0' } diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 1a903dd..ac3b479 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Wed Feb 22 11:31:00 CET 2023 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip distributionPath=wrapper/dists -zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip diff --git a/android/gradle/wrapper/gradle-wrapper.properties.backup-20250623-230951 b/android/gradle/wrapper/gradle-wrapper.properties.backup-20250623-230951 new file mode 100644 index 0000000..5639002 --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties.backup-20250623-230951 @@ -0,0 +1,6 @@ +#Sat Jan 25 18:51:17 CET 2025 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/android/libs/tesseract4android-release.aar b/android/libs/tesseract4android-release.aar deleted file mode 100644 index 42fe53b..0000000 Binary files a/android/libs/tesseract4android-release.aar and /dev/null differ diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index 6854aa5..51d182c 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -1,5 +1,4 @@ - + diff --git a/android/src/main/java/com/vladih/computer_vision/flutter_vision/FlutterVisionPlugin.java b/android/src/main/java/com/vladih/computer_vision/flutter_vision/FlutterVisionPlugin.java index 5a71fa0..6ea37f5 100644 --- a/android/src/main/java/com/vladih/computer_vision/flutter_vision/FlutterVisionPlugin.java +++ b/android/src/main/java/com/vladih/computer_vision/flutter_vision/FlutterVisionPlugin.java @@ -3,11 +3,9 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.graphics.Matrix; import androidx.annotation.NonNull; -import com.vladih.computer_vision.flutter_vision.models.Tesseract; import com.vladih.computer_vision.flutter_vision.models.Yolo; import com.vladih.computer_vision.flutter_vision.models.Yolov8; import com.vladih.computer_vision.flutter_vision.models.Yolov5; @@ -15,8 +13,6 @@ import com.vladih.computer_vision.flutter_vision.utils.utils; import org.opencv.android.OpenCVLoader; -import org.opencv.android.Utils; -import org.opencv.core.Mat; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -41,7 +37,6 @@ public class FlutterVisionPlugin implements FlutterPlugin, MethodCallHandler { private Context context; private FlutterAssets assets; private Yolo yolo_model; - private Tesseract tesseract_model; private ExecutorService executor; @@ -61,7 +56,6 @@ public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { this.methodChannel.setMethodCallHandler(null); this.methodChannel = null; this.assets = null; - close_tesseract(); close_yolo(); this.executor.shutdownNow(); } catch (Exception e) { @@ -84,17 +78,7 @@ private void setupChannel(Context context, FlutterAssets assets, BinaryMessenger @Override public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { // Handle method calls from Flutter - if (call.method.equals("loadOcrModel")) { - try { - load_ocr_model((Map) call.arguments); - } catch (Exception e) { - result.error("100", "Error on load ocr components", e); - } - } else if (call.method.equals("ocrOnFrame")) { - ocr_on_frame((Map) call.arguments, result); - } else if (call.method.equals("closeOcrModel")) { - close_ocr_model(result); - } else if (call.method.equals("loadYoloModel")) { + if (call.method.equals("loadYoloModel")) { try { load_yolo_model((Map) call.arguments); result.success("ok"); @@ -107,77 +91,26 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { yolo_on_image((Map) call.arguments, result); } else if (call.method.equals("closeYoloModel")) { close_yolo_model(result); - } else if (call.method.equals("loadTesseractModel")) { - try { - load_tesseract_model((Map) call.arguments); - result.success("ok"); - } catch (Exception e) { - result.error("100", "Error on load Tesseract model", e); - } - } else if (call.method.equals("tesseractOnImage")) { - tesseract_on_image((Map) call.arguments, result); - } else if (call.method.equals("closeTesseractModel")) { - close_tesseract_model(result); - } else { + }else { result.notImplemented(); } } - private void load_ocr_model(Map args) throws Exception { - load_yolo_model(args); - load_tesseract_model(args); - } - - private void ocr_on_frame(Map args, Result result) { - try { - List image = (ArrayList) args.get("bytesList"); - int image_height = (int) args.get("image_height"); - int image_width = (int) args.get("image_width"); - float iou_threshold = (float) (double) (args.get("iou_threshold")); - float conf_threshold = (float) (double) (args.get("conf_threshold")); - float class_threshold = (float) (double) (args.get("class_threshold")); - List class_is_text = (List) args.get("class_is_text"); - Bitmap bitmap = utils.feedInputToBitmap(context.getApplicationContext(), image, image_height, image_width, 90); - int[] shape = yolo_model.getInputTensor().shape(); - ByteBuffer byteBuffer = utils.feedInputTensor(bitmap, shape[1], shape[2], image_width, image_height, 0, 255); - - List> yolo_results = yolo_model.detect_task(byteBuffer, image_height, image_width, iou_threshold, conf_threshold, class_threshold); - for (Map yolo_result : yolo_results) { - float[] box = (float[]) yolo_result.get("box"); - if (class_is_text.contains((int) box[5])) { - Bitmap crop = utils.crop_bitmap(bitmap, - box[0], box[1], box[2], box[3]); - //utils.getScreenshotBmp(crop, "crop"); - Bitmap tmp = crop.copy(crop.getConfig(), crop.isMutable()); - yolo_result.put("text", tesseract_model.predict_text(tmp)); - } else { - yolo_result.put("text", ""); - } - } - result.success(yolo_results); - } catch (Exception e) { - result.error("100", "Ocr error", e); - } - } - - private void close_ocr_model(Result result) { - try { - close_tesseract(); - close_yolo(); - result.success("OCR model closed succesfully"); - } catch (Exception e) { - result.error("100", "Fail closed ocr model", e); - } - } - private void load_yolo_model(Map args) throws Exception { - final String model = this.assets.getAssetFilePathByName(args.get("model_path").toString()); - final Object is_asset_obj = args.get("is_asset"); + String model = ""; + final Object is_asset_obj = args.get("is_asset"); final boolean is_asset = is_asset_obj == null ? false : (boolean) is_asset_obj; + String label_path = ""; + if(is_asset){ + model = this.assets.getAssetFilePathByName(args.get("model_path").toString()); + label_path = this.assets.getAssetFilePathByName(args.get("label_path").toString()); + }else{ + model = args.get("model_path").toString(); + label_path = args.get("label_path").toString(); + } final int num_threads = (int) args.get("num_threads"); final boolean quantization = (boolean) args.get("quantization"); final boolean use_gpu = (boolean) args.get("use_gpu"); - final String label_path = this.assets.getAssetFilePathByName(args.get("label_path").toString()); final int rotation = (int) args.get("rotation"); final String version = args.get("model_version").toString(); switch (version) { @@ -316,68 +249,6 @@ private void close_yolo_model(Result result) { } } - private void load_tesseract_model(Map args) throws Exception { - final String tess_data = args.get("tess_data").toString(); - final Map arg = (Map) args.get("arg"); - final String language = args.get("language").toString(); - tesseract_model = new Tesseract(tess_data, arg, language); - tesseract_model.initialize_model(); - } - - class PredictionTask implements Runnable { - private Tesseract tesseract; - private Bitmap bitmap; - private Result result; - - public PredictionTask(Tesseract tesseract, Map args, Result result) { - byte[] image = (byte[]) args.get("bytesList"); - this.tesseract = tesseract; - this.bitmap = BitmapFactory.decodeByteArray(image, 0, image.length); - this.result = result; - } - - @Override - public void run() { - try { - Mat mat = utils.rgbBitmapToMatGray(bitmap); - double angle = utils.computeSkewAngle(mat.clone()); - mat = utils.deskew(mat, angle); - mat = utils.filterTextFromImage(mat); - bitmap = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.ARGB_8888); - Utils.matToBitmap(mat, bitmap); -// utils.getScreenshotBmp(bitmap,"TESSEREACT"); - result.success(tesseract.predict_text(bitmap)); - } catch (Exception e) { - result.error("100", "Prediction text Error", e); - } - } - } - - private void tesseract_on_image(Map args, Result result) { - try { - PredictionTask predictionTask = new PredictionTask(tesseract_model, args, result); - executor.submit(predictionTask); - } catch (Exception e) { - result.error("100", "Prediction Error", e); - } - } - - private void close_tesseract_model(Result result) { - try { - close_tesseract(); - result.success("Tesseract model closed succesfully"); - } catch (Exception e) { - result.error("100", "close_tesseract_model error", e); - } - } - - private void close_tesseract(){ - if (tesseract_model != null) { - tesseract_model.close(); - tesseract_model = null; - } - } - private void close_yolo(){ if (yolo_model != null) { yolo_model.close(); diff --git a/android/src/main/java/com/vladih/computer_vision/flutter_vision/models/Tesseract.java b/android/src/main/java/com/vladih/computer_vision/flutter_vision/models/Tesseract.java deleted file mode 100644 index 9a7bc46..0000000 --- a/android/src/main/java/com/vladih/computer_vision/flutter_vision/models/Tesseract.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.vladih.computer_vision.flutter_vision.models; - -import android.graphics.Bitmap; - -import com.googlecode.tesseract.android.TessBaseAPI; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Vector; - -public class Tesseract { - private TessBaseAPI interpreter; - private final int default_page_seg_mode = TessBaseAPI.PageSegMode.PSM_SINGLE_BLOCK; - private final String tess_data; - private final Map arg; - private final String language; - - public Tesseract(String tess_data, Map arg, String language) { - this.tess_data = tess_data; - this.arg = arg; - this.language = language; - } - public void close(){ - if (interpreter!=null){ - interpreter.clear(); - interpreter.recycle(); - } - } - public void initialize_model() throws Exception { - try { - if(interpreter==null){ - this.interpreter = new TessBaseAPI(); - if (!this.interpreter.init(this.tess_data, this.language)) { - // Error initializing Tesseract (wrong data path or language) - this.interpreter.recycle(); - throw new Exception("Cannot initialize Tesseract model"); - } - if(!this.arg.isEmpty()){ - for(Map.Entry entry:this.arg.entrySet()){ - interpreter.setVariable(entry.getKey(),entry.getValue()); - } - } - interpreter.setPageSegMode(this.default_page_seg_mode); - } - } - catch (Exception e){ - throw e; - } - } - - public Map predict_text(Bitmap bitmap) throws Exception { - try{ - this.interpreter.setImage(bitmap); - String result = this.interpreter.getUTF8Text(); - return out(result, interpreter.meanConfidence(), interpreter.wordConfidences()); - }catch (Exception e){ - throw new Exception(e.getMessage()); - }finally { - if(!bitmap.isRecycled()){ - bitmap.recycle(); - } - } - } - - protected Map out(String text, int mean, int[] word_conf){ - try { - Map result = new HashMap(); - result.put("text",text); - result.put("mean_conf", mean); - result.put("word_conf",word_conf); - return result; - }catch (Exception e){ - throw e; - } - } -} diff --git a/android/src/main/java/com/vladih/computer_vision/flutter_vision/models/Yolo.java b/android/src/main/java/com/vladih/computer_vision/flutter_vision/models/Yolo.java index 97d6254..6069fdf 100644 --- a/android/src/main/java/com/vladih/computer_vision/flutter_vision/models/Yolo.java +++ b/android/src/main/java/com/vladih/computer_vision/flutter_vision/models/Yolo.java @@ -2,16 +2,11 @@ import static java.lang.Math.min; -import android.annotation.SuppressLint; import android.content.Context; import android.content.res.AssetFileDescriptor; import android.content.res.AssetManager; import android.util.Log; -import com.vladih.computer_vision.flutter_vision.utils.FeedInputTensorHelper; - -import org.opencv.core.CvType; -import org.opencv.core.Mat; import org.tensorflow.lite.Interpreter; import org.tensorflow.lite.Tensor; import org.tensorflow.lite.gpu.CompatibilityList; @@ -19,7 +14,6 @@ import org.tensorflow.lite.gpu.GpuDelegateFactory; import java.io.BufferedReader; -import java.io.File; import java.io.FileInputStream; import java.io.InputStreamReader; import java.lang.reflect.Array; @@ -34,9 +28,6 @@ import java.util.Map; import java.util.Vector; -import io.flutter.embedding.engine.FlutterEngine; -import io.flutter.embedding.engine.plugins.FlutterPlugin; - public class Yolo { protected float[][][] output; protected Interpreter interpreter; @@ -68,15 +59,15 @@ public Yolo(Context context, this.rotation = rotation; } - // public Vector getLabels(){return this.labels;} + public Tensor getInputTensor() { + if (interpreter == null) return null; return this.interpreter.getInputTensor(0); } - @SuppressLint("SuspiciousIndentation") public void initialize_model() throws Exception { AssetManager asset_manager = null; - MappedByteBuffer buffer = null; + MappedByteBuffer buffer; FileChannel file_channel = null; FileInputStream input_stream = null; @@ -85,15 +76,15 @@ public void initialize_model() throws Exception { asset_manager = context.getAssets(); AssetFileDescriptor file_descriptor = asset_manager.openFd(this.model_path); input_stream = new FileInputStream(file_descriptor.getFileDescriptor()); - file_channel = input_stream.getChannel(); buffer = file_channel.map( - FileChannel.MapMode.READ_ONLY, file_descriptor.getStartOffset(), + FileChannel.MapMode.READ_ONLY, + file_descriptor.getStartOffset(), file_descriptor.getLength() ); file_descriptor.close(); } else { - input_stream = new FileInputStream(new File(this.model_path)); + input_stream = new FileInputStream(this.model_path); file_channel = input_stream.getChannel(); buffer = file_channel.map(FileChannel.MapMode.READ_ONLY, 0, file_channel.size()); } @@ -119,39 +110,50 @@ public void initialize_model() throws Exception { } this.interpreter.allocateTensors(); this.labels = load_labels(asset_manager, label_path); - int[] shape = interpreter.getOutputTensor(0).shape();//3dimension - this.output = (float [][][]) Array.newInstance(float.class, shape); + int[] shape = interpreter.getOutputTensor(0).shape(); + this.output = (float[][][]) Array.newInstance(float.class, shape); } catch (Exception e) { - throw e; + throw new Exception("Model initialization failed: " + e.getMessage()); } finally { - if (buffer != null) - buffer.clear(); - if (file_channel != null && file_channel.isOpen()) { - file_channel.close(); - input_stream.close(); + try { + if (file_channel != null && file_channel.isOpen()) { + file_channel.close(); + } + if (input_stream != null) { + input_stream.close(); + } + } catch (Exception e) { + Log.e("Yolo", "Resource cleanup error", e); } } } protected Vector load_labels(AssetManager asset_manager, String label_path) throws Exception { + if (label_path == null || label_path.isEmpty()) { + throw new Exception("Invalid label path"); + } + BufferedReader br = null; try { if (asset_manager != null) { br = new BufferedReader(new InputStreamReader(asset_manager.open(label_path))); } else { - br = new BufferedReader(new InputStreamReader(new FileInputStream(new File(label_path)))); + br = new BufferedReader(new InputStreamReader(new FileInputStream(label_path))); } - String line; + Vector labels = new Vector<>(); + String line; while ((line = br.readLine()) != null) { labels.add(line); } return labels; } catch (Exception e) { - throw new Exception(e.getMessage()); + throw new Exception("Label loading failed: " + e.getMessage()); } finally { - if (br != null) { - br.close(); + try { + if (br != null) br.close(); + } catch (Exception e) { + Log.e("Yolo", "Label reader close error", e); } } } @@ -161,6 +163,10 @@ public List> detect_task(ByteBuffer byteBuffer, int source_width, float iou_threshold, float conf_threshold, float class_threshold) throws Exception { + if (interpreter == null) { + throw new Exception("Interpreter not initialized"); + } + try { int[] input_shape = this.interpreter.getInputTensor(0).shape(); this.interpreter.run(byteBuffer, this.output); @@ -169,7 +175,7 @@ public List> detect_task(ByteBuffer byteBuffer, boxes = restore_size(boxes, input_shape[1], input_shape[2], source_width, source_height); return out(boxes, this.labels); } catch (Exception e) { - throw e; + throw new Exception("Detection failed: " + e.getMessage()); } finally { byteBuffer.clear(); } @@ -185,8 +191,8 @@ protected List filter_box(float[][][] model_outputs, float iou_threshol int dimension = model_outputs[0][0].length; int rows = model_outputs[0].length; float x1, y1, x2, y2, conf; - int max_index = 0; - float max = 0f; + int max_index; + float max; for (int i = 0; i < rows; i++) { //convert xywh to xyxy x1 = (model_outputs[0][i][0] - model_outputs[0][i][2] / 2f) * input_width; @@ -256,11 +262,13 @@ protected static List nms(List boxes, float iou_threshold) { } return filteredBoxes; } catch (Exception e) { - Log.e("nms", e.getMessage()); + Log.e("nms", e.getMessage() != null ? e.getMessage() : "Unknown error"); throw e; } } + + protected List restore_size(List nms, int input_width, int input_height, @@ -311,10 +319,12 @@ protected List> out(List yolo_result, Vector rects = findRects(mat); - //join posibles box text that belong to same horizontal line - rects = mergeRects(rects); - //remove boxes which belong to another - rects = non_max_suppression(rects); - - Mat new_image = new Mat(mat.height(), mat.width(), CvType.CV_8UC1, new Scalar(255)); - for (Rect box : rects) { - try{ - //Todo: Still there are error to fix when image have black border ie. images, stains - //this errors are produced by findRects function, also text doesnt working when - // images has wave text - Mat crop = mat.submat(box); - Core.normalize(crop, crop, 0, 255, Core.NORM_MINMAX); - Imgproc.threshold(crop, crop, 0, 255, Imgproc.THRESH_BINARY + Imgproc.THRESH_OTSU); - Scalar meanScalar = Core.mean(crop); - double meanValue = meanScalar.val[0]; - if (meanValue<100){ - continue; - } - Mat roi = new_image.submat(new Rect(box.x, box.y, box.width, box.height)); - Core.bitwise_and(crop,roi,roi); - }catch (Exception e){ - System.err.println("Warning, vission text error filter"); - } -// crop.copyTo(roi); - } - return new_image; - }catch (Exception e){ - throw e; - } - } public static Mat rgbBitmapToMatGray(Bitmap bitmap){ Mat mat = new Mat(bitmap.getHeight(), bitmap.getWidth(), CvType.CV_8UC3); Utils.bitmapToMat(bitmap,mat); @@ -128,169 +33,6 @@ public static Mat rgbBitmapToMatGray(Bitmap bitmap){ return mat; } - public static List non_max_suppression(List boxes) { - // Sort the list of bounding boxes in ascending order based on their y-coordinates - Collections.sort(boxes, new Comparator() { - @Override - public int compare(Rect r1, Rect r2) { - return Integer.compare(r1.y, r2.y); - } - }); - - // Initialize the list of selected boxes - List selected_boxes = new ArrayList<>(); - - // Perform NMS - while (boxes.size() > 0) { - Rect current = boxes.get(0); - selected_boxes.add(current); - boxes.remove(0); - - List next_boxes = new ArrayList<>(); - for (Rect box : boxes) { - if (!contain(current, box) && (box.width/ box.height)>1) { - next_boxes.add(box); - } - } - boxes = next_boxes; - } - - return selected_boxes; - } - - public static boolean contain(Rect box1, Rect box2) { - // Calculate the coordinates of the intersection rectangle - int x1 = Math.max(box1.x, box2.x); - int y1 = Math.max(box1.y, box2.y); - int x2 = Math.min(box1.x+box1.width, box2.x+box2.width); - int y2 = Math.min(box1.y+box1.height, box2.y+box2.height); - int w = Math.max(0, x2 - x1); - int h = Math.max(0, y2 - y1); - - // Calculate the area of intersection rectangle - int intersection = w * h; - - // Calculate the area of both bounding boxes -// int area_box1 = box1.width * box1.height; - int area_box2 = box2.width * box2.height; - - return area_box2 == intersection; - } - public static List mergeRects(List rects){ - Collections.sort(rects, new Comparator() { - @Override - public int compare(Rect r1, Rect r2) { - return r1.y - r2.y; - } - }); - List mergedBoxes = new ArrayList<>(); - for (int i = 0; i < rects.size(); i++) { - Rect rect = rects.get(i); - if (mergedBoxes.isEmpty()) { - mergedBoxes.add(rect); - } else { - Rect prevRect = mergedBoxes.get(mergedBoxes.size()-1); - if (Math.abs(rect.y - prevRect.y) > prevRect.height * 0.5) { - mergedBoxes.add(rect); - } else { - if(rect.x - (prevRect.x + prevRect.width)> 0){ - mergedBoxes.add(rect); - } - else{ - int newX = Math.min(rect.x, prevRect.x); - int newY = Math.min(rect.y, prevRect.y); - int newW = Math.max(rect.x + rect.width, prevRect.x + prevRect.width) - newX; - int newH = Math.max(rect.y + rect.height, prevRect.y + prevRect.height) - newY; - mergedBoxes.set(mergedBoxes.size()-1, new Rect(newX, newY, newW, newH)); - } - } - } - } - return mergedBoxes; - } - public static List findRects(Mat image){ - Mat thresh = new Mat(); - //find countours in gray image - Imgproc.Canny(image, thresh, 50, 150, 3, false); - List contours = new ArrayList<>(); - Mat hierarchy = new Mat(); - Imgproc.findContours(thresh, contours, hierarchy, Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_SIMPLE); - List filteredContours = new ArrayList<>(); - double height = 0; - for (int i = 0; i < contours.size(); i++) { - Rect rect = Imgproc.boundingRect(contours.get(i)); - double area = rect.width * rect.height; - double aspectRatio = (double)rect.width / rect.height; - //remove rect that doesn't satisface this rules - if (area > 80 && aspectRatio > 0.4 && aspectRatio < 5) { - height += rect.height; - filteredContours.add(new Rect((int)Math.max(0, rect.x-rect.height/2), rect.y, (int)Math.min(image.width(),rect.width+rect.height), rect.height)); - } - } - //remove rects with large height than mean height - height = height / Math.sqrt(filteredContours.size()); - List rects = new ArrayList<>(); - for (int i = 0; i < filteredContours.size(); i++) { - Rect rect = filteredContours.get(i); - if (rect.height > height) continue; - rects.add(rect); - } - return rects; - } - public static Mat deskew(Mat image, double skewAngle) { - try { - Mat rotationMatrix = Imgproc.getRotationMatrix2D(new Point(image.width() / 2, image.height() / 2), skewAngle, 1); - Scalar borderValue = new Scalar(255); // white border value - // Crop the output image to remove border artifacts - Rect cropRect = new Rect(0, 0, image.width(), image.height()); -// System.out.println(image.size()); -// System.out.println(skewAngle); - Imgproc.warpAffine(image, image, rotationMatrix, image.size(), Imgproc.INTER_CUBIC + Imgproc.WARP_FILL_OUTLIERS, Core.BORDER_CONSTANT, borderValue); - image = image.submat(cropRect); - return image; - }catch (Exception e){ - throw e; - } - } - - //input:binary matrix - public static double computeSkewAngle(Mat image) { - try { - // Apply Canny edge detection to find the edges in the image - Imgproc.Canny(image, image, 50, 150, 3); - // Apply the Hough transform to find the lines in the image - Mat lines = new Mat(); - Imgproc.HoughLinesP(image, lines, 1, Math.PI / 180, 100, 100, 10); - - // Compute the average angle of the lines - double angle = 0.0; - int numLines = lines.cols(); - if (numLines > 0) { - // Find the longest line - double longestLineLength = -1; - Point[] longestLine = null; - for (int i = 0; i < lines.cols(); i++) { - double[] line = lines.get(0, i); - Point pt1 = new Point(line[0], line[1]); - Point pt2 = new Point(line[2], line[3]); - double length = Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2)); - if (length > longestLineLength) { - longestLineLength = length; - longestLine = new Point[] { pt1, pt2 }; - } - } - // Calculate angle between longest line and horizontal axis - double dx = longestLine[1].x - longestLine[0].x; - double dy = longestLine[1].y - longestLine[0].y; - angle = Math.atan2(dy, dx) * 180 / Math.PI; - } - // Convert the angle from radians to degrees and return it - return angle; - } catch (Exception e) { - throw e; - } - } - public static ByteBuffer feedInputTensor( Bitmap bitmap, diff --git a/example/pubspec.lock b/example/pubspec.lock index ea61623..de9c0f6 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -77,10 +77,10 @@ packages: dependency: transitive description: name: collection - sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf url: "https://pub.dev" source: hosted - version: "1.17.2" + version: "1.19.0" cross_file: dependency: transitive description: @@ -161,7 +161,7 @@ packages: path: ".." relative: true source: path - version: "1.1.2" + version: "2.0.0" flutter_web_plugins: dependency: transitive description: flutter @@ -231,6 +231,30 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.4" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" + url: "https://pub.dev" + source: hosted + version: "10.0.7" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" + url: "https://pub.dev" + source: hosted + version: "3.0.8" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" lints: dependency: transitive description: @@ -243,82 +267,82 @@ packages: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.15.0" path: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.0" path_provider: dependency: transitive description: name: path_provider - sha256: dcea5feb97d8abf90cab9e9030b497fb7c3cbf26b7a1fe9e3ef7dcb0a1ddec95 + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" url: "https://pub.dev" source: hosted - version: "2.0.12" + version: "2.1.5" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: a776c088d671b27f6e3aa8881d64b87b3e80201c64e8869b811325de7a76c15e + sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" url: "https://pub.dev" source: hosted - version: "2.0.22" + version: "2.2.15" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "62a68e7e1c6c459f9289859e2fae58290c981ce21d1697faf54910fe1faa4c74" + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.4.1" path_provider_linux: dependency: transitive description: name: path_provider_linux - sha256: "2e32f1640f07caef0d3cb993680f181c79e54a3827b997d5ee221490d131fbd9" + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 url: "https://pub.dev" source: hosted - version: "2.1.8" + version: "2.2.1" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface - sha256: f0abc8ebd7253741f05488b4813d936b4d07c6bae3e86148a09e342ee4b08e76 + sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" url: "https://pub.dev" source: hosted - version: "2.0.5" + version: "2.1.1" path_provider_windows: dependency: transitive description: name: path_provider_windows - sha256: bcabbe399d4042b8ee687e17548d5d3f527255253b4a639f5f8d2094a9c2b45c + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.3.0" platform: dependency: transitive description: @@ -355,7 +379,7 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: @@ -368,18 +392,18 @@ packages: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.12.0" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" stream_transform: dependency: transitive description: @@ -392,10 +416,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" term_glyph: dependency: transitive description: @@ -408,10 +432,10 @@ packages: dependency: transitive description: name: test_api - sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.7.3" typed_data: dependency: transitive description: @@ -428,22 +452,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" - web: - dependency: transitive - description: - name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 - url: "https://pub.dev" - source: hosted - version: "0.1.4-beta" - win32: + vm_service: dependency: transitive description: - name: win32 - sha256: c9ebe7ee4ab0c2194e65d3a07d8c54c5d00bb001b76081c4a04cdb8448b59e46 + name: vm_service + sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "14.3.0" xdg_directories: dependency: transitive description: @@ -453,5 +469,5 @@ packages: source: hosted version: "1.0.0" sdks: - dart: ">=3.1.0-185.0.dev <4.0.0" - flutter: ">=3.0.0" + dart: ">=3.5.0 <4.0.0" + flutter: ">=3.24.0" diff --git a/lib/flutter_vision.dart b/lib/flutter_vision.dart index 993d8ca..9081d1f 100644 --- a/lib/flutter_vision.dart +++ b/lib/flutter_vision.dart @@ -94,6 +94,7 @@ abstract class FlutterVision { required String modelVersion, bool? quantization, int? numThreads, + bool? is_asset, bool? useGpu}); ///yoloOnFrame accept a byte List as input and diff --git a/lib/src/plugin/android.dart b/lib/src/plugin/android.dart index c13f298..eedd97f 100644 --- a/lib/src/plugin/android.dart +++ b/lib/src/plugin/android.dart @@ -87,11 +87,12 @@ class AndroidFlutterVision extends BaseFlutterVision implements FlutterVision { required String modelVersion, bool? quantization, int? numThreads, + bool? is_asset, bool? useGpu}) async { try { await channel.invokeMethod('loadYoloModel', { 'model_path': modelPath, - 'is_asset': true, + 'is_asset': is_asset ?? false, 'quantization': quantization ?? false, 'num_threads': numThreads ?? 1, 'use_gpu': useGpu ?? false, diff --git a/lib/src/plugin/base.dart b/lib/src/plugin/base.dart index 0245f4d..20808b5 100644 --- a/lib/src/plugin/base.dart +++ b/lib/src/plugin/base.dart @@ -70,6 +70,7 @@ abstract class BaseFlutterVision { bool? quantization, int? numThreads, bool? useGpu, + bool? is_asset, }); Future>> yoloOnFrame({ diff --git a/pubspec.yaml b/pubspec.yaml index e1cbb82..68e27d5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: flutter_vision -description: Plugin for managing Yolov5, Yolov8 and Tesseract v5 accessing with TensorFlow Lite 2.x. Support object detection, segmentation and OCR on Android. iOS, Working in progress. +description: Plugin for managing Yolov5, Yolov8 and Yolov11 accessing with LiteRT (TensorFlow Lite) Support object detection, segmentation and OCR on Android. iOS, Working in progress. -version: 1.1.4 +version: 2.0.0 homepage: https://github.com/vladiH/flutter_vision @@ -12,13 +12,11 @@ environment: dependencies: flutter: sdk: flutter - path: ^1.8.1 - path_provider: ^2.0.11 + path: ^1.9.1 + path_provider: ^2.1.5 dev_dependencies: - flutter_lints: ^2.0.1 - flutter_test: - sdk: flutter + flutter_lints: ^6.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter. diff --git a/test/flutter_vision_test.dart b/test/flutter_vision_test.dart deleted file mode 100644 index 872da52..0000000 --- a/test/flutter_vision_test.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; -// import 'package:flutter_vision/flutter_vision.dart'; - -void main() { - const MethodChannel channel = MethodChannel('flutter_vision'); - - TestWidgetsFlutterBinding.ensureInitialized(); - - handler(MethodCall methodCall) async { - if (methodCall.method == 'getAll') { - return { - 'appName': 'myapp', - 'packageName': 'com.mycompany.myapp', - 'version': '0.0.1', - 'buildNumber': '1' - }; - } - return null; - } - - TestWidgetsFlutterBinding.ensureInitialized(); - - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler(channel, handler); -}