Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Object detection implementation for single image - iOS and android #21

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions object-detection/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# OSX
#
.DS_Store

# node.js
#
node_modules/
npm-debug.log
yarn-error.log

# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
project.xcworkspace

# Android/IntelliJ
#
build/
.idea
.gradle
local.properties
*.iml

# BUCK
buck-out/
\.buckd/
*.keystore
21 changes: 21 additions & 0 deletions object-detection/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2021 Ahmed Mahmoud

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
31 changes: 31 additions & 0 deletions object-detection/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# @react-native-ml-kit/object-detection

React Native On-Device Custom Image Labeling w/ Google ML Kit

## Getting started

`npm install @react-native-ml-kit/object-detection --save`

### Linking

#### React Native > 0.59

[CLI autolink feature](https://github.com/react-native-community/cli/blob/master/docs/autolinking.md) links the module while building the app.

#### React Native <= 0.59

`react-native link @react-native-ml-kit/object-detection`

### Installing Pods

On iOS, use CocoaPods to add the native RNMLKitObjectDetection to your project:

`npx pod-install`

## Usage

```javascript
import ObjectDetection from '@react-native-ml-kit/object-detection';

const labels = await ObjectDetection.label(imageURL);
```
26 changes: 26 additions & 0 deletions object-detection/RNMLKitObjectDetection.podspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# RNMLKitObjectDetection.podspec

require "json"

package = JSON.parse(File.read(File.join(__dir__, "package.json")))

Pod::Spec.new do |s|
s.name = "RNMLKitObjectDetection"
s.version = package["version"]
s.summary = package["description"]

s.homepage = "https://github.com/a7med-mahmoud/react-native-ml-kit"
# brief license entry:
s.license = "MIT"
# optional - use expanded license entry instead:
# s.license = { :type => "MIT", :file => "LICENSE" }
s.authors = { "Ahmed" => "[email protected]" }
s.platforms = { :ios => "9.0" }
s.source = { :git => "https://github.com/a7med-mahmoud/react-native-ml-kit.git", :tag => "#{s.version}" }

s.source_files = "ios/**/*.{h,c,cc,cpp,m,mm,swift}"
s.requires_arc = true

s.dependency "React"
s.dependency "GoogleMLKit/ImageLabeling", "2.6.0"
end
4 changes: 4 additions & 0 deletions object-detection/android/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*.iml
gradle/
gradlew
gradlew.bat
10 changes: 10 additions & 0 deletions object-detection/android/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
*.iml
.DS_Store
.gradle/
.idea/
.npmignore
build/
gradle/
gradlew
gradlew.bat
local.properties
14 changes: 14 additions & 0 deletions object-detection/android/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
README
======

If you want to publish the lib as a maven dependency, follow these steps before publishing a new version to npm:

1. Be sure to have the Android [SDK](https://developer.android.com/studio/index.html) and [NDK](https://developer.android.com/ndk/guides/index.html) installed
2. Be sure to have a `local.properties` file in this folder that points to the Android SDK and NDK
```
ndk.dir=/Users/{username}/Library/Android/sdk/ndk-bundle
sdk.dir=/Users/{username}/Library/Android/sdk
```
3. Delete the `maven` folder
4. Run `./gradlew installArchives`
5. Verify that latest set of generated files is in the maven folder with the correct version number
64 changes: 64 additions & 0 deletions object-detection/android/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// These defaults should reflect the SDK versions used by
// the minimum React Native version supported.
def DEFAULT_COMPILE_SDK_VERSION = 28
def DEFAULT_BUILD_TOOLS_VERSION = '28.0.3'
def DEFAULT_MIN_SDK_VERSION = 16
def DEFAULT_TARGET_SDK_VERSION = 28

def safeExtGet(prop, fallback) {
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
}

apply plugin: 'com.android.library'

buildscript {
// The Android Gradle plugin is only required when opening the android folder stand-alone.
// This avoids unnecessary downloads and potential conflicts when the library is included as a
// module dependency in an application project.
// ref: https://docs.gradle.org/current/userguide/tutorial_using_tasks.html#sec:build_script_external_dependencies
if (project == rootProject) {
repositories {
google()
}
dependencies {
// This should reflect the Gradle plugin version used by
// the minimum React Native version supported.
classpath 'com.android.tools.build:gradle:3.4.1'
}
}
}

android {
compileSdkVersion safeExtGet('compileSdkVersion', DEFAULT_COMPILE_SDK_VERSION)
buildToolsVersion safeExtGet('buildToolsVersion', DEFAULT_BUILD_TOOLS_VERSION)
defaultConfig {
minSdkVersion safeExtGet('minSdkVersion', DEFAULT_MIN_SDK_VERSION)
targetSdkVersion safeExtGet('targetSdkVersion', DEFAULT_TARGET_SDK_VERSION)
versionCode 1
versionName "1.0"
}
lintOptions {
abortOnError false
}
}

repositories {
mavenCentral()
// ref: https://www.baeldung.com/maven-local-repository
mavenLocal()
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url "$rootDir/../node_modules/react-native/android"
}
maven {
// Android JSC is installed from npm
url "$rootDir/../node_modules/jsc-android/dist"
}
google()
}

dependencies {
//noinspection GradleDynamicVersion
implementation 'com.facebook.react:react-native:+' // From node_modules
implementation 'com.google.mlkit:object-detection:17.0.0'
}
6 changes: 6 additions & 0 deletions object-detection/android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<!-- AndroidManifest.xml -->

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.rnmlkit.objectdetection">

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// ObjectDetectionModule.java

package com.rnmlkit.objectdetection;

import android.net.Uri;
import android.content.res.AssetManager;
import android.graphics.BitmapFactory;
import android.graphics.Bitmap;
import android.graphics.Rect;

import androidx.annotation.NonNull;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.ReadableMap;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.mlkit.common.model.LocalModel;
import com.google.mlkit.vision.common.InputImage;
import com.google.mlkit.vision.objects.defaults.ObjectDetectorOptions;
import com.google.mlkit.vision.objects.ObjectDetector;
import com.google.mlkit.vision.objects.ObjectDetection;
import com.google.mlkit.vision.objects.DetectedObject;
import com.google.mlkit.vision.objects.DetectedObject.Label;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.net.URL;

public class ObjectDetectionModule extends ReactContextBaseJavaModule {

private final ReactApplicationContext reactContext;

public ObjectDetectionModule(ReactApplicationContext reactContext) {
super(reactContext);
this.reactContext = reactContext;
}

@Override
public String getName() {
return "ObjectDetection";
}

public static InputImage getInputImage(ReactApplicationContext reactContext, String url)
throws IOException {

if (url.contains("http://") || url.contains("https://")) {
URL urlInput = new URL(url);
Bitmap image = BitmapFactory.decodeStream(urlInput.openConnection().getInputStream());
InputImage inputImage = InputImage.fromBitmap(image, 0);
return inputImage;
}
else {
Uri uri = Uri.parse(url);
InputImage inputImage = InputImage.fromFilePath(reactContext, uri);
return inputImage;
}
}

@ReactMethod
public void detectSingleImage(final ReadableMap optionsMap, final Promise promise) {

String url = optionsMap.getString("url");

boolean shouldEnableMultipleObjects = optionsMap.getBoolean("shouldEnableMultipleObjects");
boolean shouldEnableClassification = optionsMap.getBoolean("shouldEnableClassification");

ObjectDetectorOptions.Builder options =
new ObjectDetectorOptions.Builder();

options.setDetectorMode(ObjectDetectorOptions.SINGLE_IMAGE_MODE);

if (shouldEnableClassification) {
options.enableClassification();
}

if (shouldEnableMultipleObjects) {
options.enableMultipleObjects();
}

ObjectDetector objectDetector = ObjectDetection.getClient(options.build());

try {
InputImage image = getInputImage(this.reactContext, url);

objectDetector.process(image)
.addOnSuccessListener(
new OnSuccessListener<List<DetectedObject>>() {
@Override
public void onSuccess(List<DetectedObject> detectedObjects) {

WritableArray result = Arguments.createArray();

for (DetectedObject detectedObject : detectedObjects) {
Rect boundingBox = detectedObject.getBoundingBox();

for (Label label : detectedObject.getLabels()) {
WritableMap map = Arguments.createMap();
map.putInt("frameX", boundingBox.left);
map.putInt("frameY", boundingBox.top);
map.putInt("frameWidth", boundingBox.right - boundingBox.left);
map.putInt("frameHeight", boundingBox.bottom - boundingBox.top);
map.putString("text", label.getText());
map.putDouble("confidence", label.getConfidence());
map.putInt("index", label.getIndex());
result.pushMap(map);
}
}
promise.resolve(result);
}
})
.addOnFailureListener(
new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
e.printStackTrace();
promise.reject("Image labeling failed", e);
}
});

} catch (IOException e) {
e.printStackTrace();
promise.reject("Image labeling failed", e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// ObjectDetectionObjectDetectionPackage.java

package com.rnmlkit.objectdetection;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

public class ObjectDetectionPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Arrays.<NativeModule>asList(new ObjectDetectionModule(reactContext));
}

@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
6 changes: 6 additions & 0 deletions object-detection/example/.buckconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

[android]
target = Google Inc.:Google APIs:23

[maven_repositories]
central = https://repo1.maven.org/maven2
3 changes: 3 additions & 0 deletions object-detection/example/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Windows files
[*.bat]
end_of_line = crlf
Loading