feat: 增加ocr文字识别功能

This commit is contained in:
xiaoyan 2023-03-23 15:48:24 +08:00
parent 506299b3a3
commit 12b870ea79
53 changed files with 9603 additions and 166 deletions

View File

@ -81,5 +81,10 @@
<option name="name" value="maven2" />
<option name="url" value="https://repo.spring.io/libs-release/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven7" />
<option name="name" value="maven7" />
<option name="url" value="https://maven.aliyun.com/nexus/content/groups/public/" />
</remote-repository>
</component>
</project>

View File

@ -86,6 +86,7 @@ dependencies {
implementation 'androidx.navigation:navigation-fragment:2.1.0'
implementation 'androidx.navigation:navigation-ui:2.1.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation project(path: ':ocr')
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

View File

@ -7,6 +7,7 @@ import com.kongzue.dialog.interfaces.OnDialogButtonClickListener;
import com.kongzue.dialog.util.BaseDialog;
import com.kongzue.dialog.util.DialogSettings;
import com.kongzue.dialog.v3.MessageDialog;
import com.navinfo.ocr.OCRManager;
import com.navinfo.outdoor.api.Constant;
import com.navinfo.outdoor.api.UserApplication;
import com.navinfo.outdoor.base.BaseActivity;
@ -115,7 +116,8 @@ public class HomeActivity extends BaseActivity {
// 注册位置更新的lifeCycle
getLifecycle().addObserver(LocationLifeCycle.getInstance());
// 初始化图像识别组件
OCRManager.Companion.getInstance().init(HomeActivity.this);
} else {
finish();
}

View File

@ -29,15 +29,20 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.elvishew.xlog.XLog;
import com.github.lazylibrary.util.FileUtils;
import com.kongzue.dialog.interfaces.OnDialogButtonClickListener;
import com.kongzue.dialog.util.BaseDialog;
import com.kongzue.dialog.util.DialogSettings;
import com.kongzue.dialog.v3.MessageDialog;
import com.navinfo.ocr.OCRManager;
import com.navinfo.ocr.model.OcrViewResultModel;
import com.navinfo.outdoor.R;
import com.navinfo.outdoor.api.Constant;
import com.navinfo.outdoor.api.UserApplication;
import com.navinfo.outdoor.base.BaseActivity;
import com.navinfo.outdoor.bean.LocationRecorder;
import com.navinfo.outdoor.util.BitmapUtil;
import com.navinfo.outdoor.util.GPSUtils;
import com.navinfo.outdoor.util.Geohash;
import com.navinfo.outdoor.util.GeometryTools;
@ -51,6 +56,7 @@ import com.navinfo.outdoor.util.ToastUtils;
import com.otaliastudios.cameraview.CameraListener;
import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.CameraOptions;
import com.otaliastudios.cameraview.CameraUtils;
import com.otaliastudios.cameraview.CameraView;
import com.otaliastudios.cameraview.FileCallback;
import com.otaliastudios.cameraview.PictureResult;
@ -82,13 +88,16 @@ import org.locationtech.jts.geom.MultiLineString;
import com.wanghong.webpnative.WebPNative;
import org.greenrobot.eventbus.EventBus;
import org.reactivestreams.Subscription;
import java.io.File;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Timer;
import java.util.TimerTask;
@ -96,12 +105,21 @@ import java.util.concurrent.TimeUnit;
import static com.tencent.tencentmap.mapsdk.maps.model.MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE;
import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
import io.reactivex.FlowableEmitter;
import io.reactivex.FlowableOnSubscribe;
import io.reactivex.FlowableSubscriber;
import io.reactivex.Notification;
import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.BiFunction;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;
/**
@ -118,12 +136,14 @@ public class PicturesActivity extends BaseActivity implements View.OnClickListen
private String finalVideoPath, geoWkt, detail; // 摄像后最终保存的文件名
private ViewGroup layerChange; // 切换地图和相机的父控件
private CheckBox capturePicture; //拍照
private File paperFile, logFile;
private File paperFile, logFile, checkFile/*辅助检查文件*/;
private ImageView ivZoomAdd, ivZoomDel, ivLocation, ivPicRoadImage, ivPicVideoImage, imageView;
private View layerMapController;
private MyLocation oldCurrentLocation = null;
private Timer timer;
private TimerTask timerTask;
// private Timer timer;
// private TimerTask timerTask;
private Disposable disposable; // RxJava循环拍照的控制对象
private FlowableEmitter<PictureResult> pictureEmitter; // RxJava控制照片生成
private SystemTTS systemTTS;
private StringBuilder picturesBuilder;
private LatLng startLatLine, endLatLine;
@ -145,10 +165,7 @@ public class PicturesActivity extends BaseActivity implements View.OnClickListen
private Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
if (msg.what == 0x101) {
System.out.println("收到拍照按钮请求");
camera.takePictureSnapshot();
} else if (msg.what == 0x102) {
if (msg.what == 0x102) {
if (imageView != null) {
imageView.setEnabled(true);
}
@ -157,7 +174,7 @@ public class PicturesActivity extends BaseActivity implements View.OnClickListen
capturePicture.setText("开始采集");
}
capturePicture.setChecked(false);
stopTimer();
stopTakePhoto();
} else if (msg.what == 0x104) {
tvConvert.setText("转换成功:"+(++msg.arg1));
}
@ -207,12 +224,8 @@ public class PicturesActivity extends BaseActivity implements View.OnClickListen
if (finalVideoPath != null) {
File file = new File(finalVideoPath);
paperFile = new File(Objects.requireNonNull(file.getParentFile()).getAbsoluteFile() + "/" + "paper.txt");
checkFile = new File(Objects.requireNonNull(file.getParentFile()).getAbsoluteFile() + "/" + "check.json");
videoIndex = Integer.parseInt(file.getName().replace(".webp", ""));
if (videoIndex == 0) {
videoIndex = -1;
} else {
videoIndex = videoIndex - 1;
}
convertIndex = videoIndex;
}
}
@ -250,31 +263,25 @@ public class PicturesActivity extends BaseActivity implements View.OnClickListen
radioGroupPicture.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
stopTakePhoto();
capturePicture.setChecked(false);
switch (checkedId) {
case R.id.radio_btn_hand://手动
radioPicture = 1;
isOration = false;
capturePicture.setText("拍摄");
capturePicture.setChecked(false);
stopTimer();
break;
case R.id.radio_btn_auto://自动1秒:
radioPicture = 2;
capturePicture.setText("开始采集");
capturePicture.setChecked(false);
stopTimer();
break;
case R.id.radio_btn_auto_sec://自动2
radioPicture = 3;
capturePicture.setText("开始采集");
capturePicture.setChecked(false);
stopTimer();
break;
case R.id.radio_btn_half_sec://自动0.5
radioPicture = 4;
capturePicture.setText("开始采集");
capturePicture.setChecked(false);
stopTimer();
break;
}
}
@ -314,13 +321,14 @@ public class PicturesActivity extends BaseActivity implements View.OnClickListen
} else {
picturesBuilder.append(TimestampUtil.time()).append(",").append("capturePicture 点击了拍摄 ,");
}
startTimer();
startTakePhoto(radioPicture);
startTakePhoto(radioPicture);
} else {
if (radioPicture != 1) {
capturePicture.setText("开始采集");
picturesBuilder.append(TimestampUtil.time()).append(",").append("capturePicture 点击了暂停采集 ,");
}
stopTimer();
stopTakePhoto();
}
}
});
@ -382,6 +390,96 @@ public class PicturesActivity extends BaseActivity implements View.OnClickListen
}
});
Flowable<PictureResult> pictureResultFlowable = Flowable.create(new FlowableOnSubscribe<PictureResult>() {
@Override
public void subscribe(FlowableEmitter<PictureResult> emitter) throws Exception {
pictureEmitter = emitter;
}
}, BackpressureStrategy.BUFFER).subscribeOn(Schedulers.io());
Flowable<Integer> integerFlowable = Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) {
for (int i = videoIndex; ; i++) { // 标记当前是第几个图片记录Index
emitter.onNext(i);
}
}
}, BackpressureStrategy.BUFFER).subscribeOn(Schedulers.io());
Flowable.zip(pictureResultFlowable, integerFlowable, new BiFunction<PictureResult, Integer, Map>(){
@Override
public Map<String, Object> apply(PictureResult pictureResult, Integer fileIndex) throws Exception { // 将图片转为File文件
// 图片和paper.txt处理
File currentFile = new File(paperFile.getParentFile().getAbsolutePath() + "/" + fileIndex + ".webp");
CameraUtils.writeToFile(pictureResult.getData(), currentFile); // 生成照片文件
// 记录当前点位信息
LocationRecorder recorder = new LocationRecorder();
recorder.setTime(System.currentTimeMillis());
// 记录主定位方式
recorder.setTencentLocationX(LocationLifeCycle.getInstance().getMainLocation().getLongitude());
recorder.setTencentLocationY(LocationLifeCycle.getInstance().getMainLocation().getLatitude());
// 记录辅助定位方式
MyLocation gpsLocation = LocationLifeCycle.getInstance().getReferenceLocation();
if (gpsLocation!=null) {
recorder.setGpsLocationX(gpsLocation.getLongitude());
recorder.setGpsLocationY(gpsLocation.getLatitude());
}
recorder.setBearing(LocationLifeCycle.getInstance().getMainLocation().getBearing());
recorder.setRssi(GPSUtils.getInstance(PicturesActivity.this).getSateliteCount());
recorder.setSatelliteCount(GPSUtils.getInstance(PicturesActivity.this).getSateliteCount());
FileUtils.writeFile(paperFile.getAbsolutePath(), recorder.toString(formatter, fileIndex), true);
Map<String, Object> map = new HashMap<>();
map.put("index", fileIndex);
map.put("picture", pictureResult);
return map;
}
})
.subscribeOn(Schedulers.io()) // 指定上游为IO线程
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(new Consumer<Map>() {
@Override
public void accept(Map map) throws Exception {
// 绘制图标
initMarker(booleanExtra);
// 设置主界面拍照个数显示
tvTitle.setText(Integer.parseInt(map.get("index").toString())+"");
}
})
.observeOn(Schedulers.computation())// 开始进行图片辅助判定
.subscribe(new FlowableSubscriber<Map>() {
@Override
public void onSubscribe(Subscription s) {
s.request(Integer.MAX_VALUE);
}
@Override
public void onNext(Map map) {
int index = Integer.parseInt(map.get("index").toString());
PictureResult pictureResult = (PictureResult) map.get("picture");
// 记录当前完成多少个图片的处理
// 开始ocr文字识别
List<OcrViewResultModel> ocrViewResultModels = OCRManager.Companion.getInstance().ocr(BitmapUtil.getBitmapFromBytes(pictureResult.getData()));
if (ocrViewResultModels!=null&&!ocrViewResultModels.isEmpty()) {
// 该照片存在文字信息获取占比最大的文字为后续POI判定做准备
}
// 获取当前手机姿态判定该照片手机姿态是否合格
float yDegree = sensorOritationLifecycle.getYDegree();
// 周边搜索判定周边POI是否存在
}
@Override
public void onError(Throwable t) {
}
@Override
public void onComplete() {
}
});
camera.addCameraListener(new CameraListener() {
@Override
public void onPictureTaken(@NonNull PictureResult result) {
@ -389,44 +487,29 @@ public class PicturesActivity extends BaseActivity implements View.OnClickListen
super.onPictureTaken(result);
System.out.println("收到拍照按钮jieguo:"+result.getSize().toString());
isBack = true;
// 如果当前手机是竖向则不
if (isOration) {
if (Objects.requireNonNull(camera.getPictureSize()).getWidth() < camera.getPictureSize().getHeight()) {
isOration = true;
ToastUtils.Message(PicturesActivity.this, "不允许竖向拍摄...");
picturesBuilder.append("camera 用户竖屏拍照 ,");
stopTimer();
// 如果当前手机是竖向则不允许拍照
if (Objects.requireNonNull(camera.getPictureSize()).getWidth() < camera.getPictureSize().getHeight()) {
isOration = true;
ToastUtils.Message(PicturesActivity.this, "不允许竖向拍摄...");
picturesBuilder.append("camera 用户竖屏拍照 ,");
stopTakePhoto();
capturePicture.setChecked(false);
if (radioPicture != 1) {
capturePicture.setText("开始采集");
capturePicture.setChecked(false);
if (radioPicture != 1) {
capturePicture.setText("开始采集");
capturePicture.setChecked(false);
}
return;
} else {
isOration = false;
}
return;
} else {
isOration = false;
}
File file = new File(finalVideoPath);
synchronized (finalVideoPath) {
// 生成点位marker
initMarker(booleanExtra);
result.toFile(file, new FileCallback() {
@Override
public void onFileReady(@Nullable File file) {
int currentIndex = videoIndex = Integer.parseInt(file.getName().replace(".webp", ""));
// 下一张照片的路径
finalVideoPath = Objects.requireNonNull(file.getParentFile()).getAbsolutePath() + "/" + (videoIndex + 1) + ".webp";
tvTitle.setText("拍摄成功:" + (videoIndex + 1));
UserApplication.fixedThreadPool.execute(new Jpg2WebpRunnable(/*result, */file, 0, booleanExtra, currentIndex));
}
});
if (pictureEmitter!=null) {
pictureEmitter.onNext(result);
}
} else {
isBack = false;
if (isOration) {
stopTimer();
stopTakePhoto();
}
if (radioPicture != 1) {
capturePicture.setText("开始采集");
@ -441,18 +524,6 @@ public class PicturesActivity extends BaseActivity implements View.OnClickListen
layoutParamsMap.height = dm.widthPixels / 3;
layoutParamsMap.width = dm.heightPixels / 3;
tvMapView.setLayoutParams(layoutParamsMap);
timerTask = new TimerTask() {
@Override
public void run() {
if (radioPicture == 1) {
camera.takePictureSnapshot();
} else {
Message message = new Message();
message.what = 0x101;
handler.sendMessage(message);
}
}
};
Button btnSetting = findViewById(R.id.btn_setting);
btnSetting.setOnClickListener(new View.OnClickListener() {
@ -484,7 +555,6 @@ public class PicturesActivity extends BaseActivity implements View.OnClickListen
@Override
public void run() {
if (file.exists() && file != null) {
// if (initWeb(file, count, isBoolean, index)) {
initMarkerPaper(index);
if (PicturesActivity.this != null&&handler != null) {
if (radioPicture == 1) {
@ -498,18 +568,10 @@ public class PicturesActivity extends BaseActivity implements View.OnClickListen
handler.sendMessage(message);
}
}
// runOnUiThread(new Runnable() {
// @SuppressLint("SetTextI18n")
// @Override
// public void run() {
//
// }
// });
// }
} else {
isBack = false;
if (isOration) {
stopTimer();
stopTakePhoto();
}
if (radioPicture != 1) {
capturePicture.setText("开始采集");
@ -525,32 +587,6 @@ public class PicturesActivity extends BaseActivity implements View.OnClickListen
}
}
private boolean initWeb(File file, int count, boolean isBoolean, int index) {
try {
count++;
WebPNative webPNative = new WebPNative();
Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
webPNative.encodeRGBA(bitmap, file.getPath(), 90);
if (!bitmap.isRecycled()) {
bitmap.recycle();
}
initMarkerPaper(index);
return true;
} catch (RuntimeException e) {
e.printStackTrace();
//如果是写入txt记录失败上传失败记录
UMCrashManager.reportCrash(this, e);
} catch (Exception e) {
e.printStackTrace();
UMCrashManager.reportCrash(this, e);
if (count < 3) {
//当尝试次数小于3次则加入转换队列尝试重新转换
UserApplication.fixedThreadPool.execute(new Jpg2WebpRunnable(/*result, */file, count, isBoolean, index));
}
}
return false;
}
private void initLine() {
if (geoWkt != null) {
List<LineString> lineStringList = new ArrayList<>();
@ -871,7 +907,7 @@ public class PicturesActivity extends BaseActivity implements View.OnClickListen
picturesBuilder.append(TimestampUtil.time()).append(",").append("onPause ,");
tvMapView.onPause();
camera.close();
stopTimer();
stopTakePhoto();
}
@Override
@ -887,7 +923,7 @@ public class PicturesActivity extends BaseActivity implements View.OnClickListen
camera.destroy();
tvMapView.onDestroy();
systemTTS.stopSpeak();
stopTimer();
stopTakePhoto();
if (polyline != null) {
polyline.remove();
}
@ -911,21 +947,6 @@ public class PicturesActivity extends BaseActivity implements View.OnClickListen
}
}
// @Subscribe(threadMode = ThreadMode.MAIN)
// public void onEventMessageMainThread(Message msg) {
// if (msg.what == Constant.EVENT_WHAT_LOCATION_CHANGE) { // 用户位置更新
// if (tencentMap != null && !isMapSlide) {
// TencentLocation tencentLocation = (TencentLocation) msg.obj;
// CameraUpdate cameraSigma = CameraUpdateFactory.newCameraPosition(new CameraPosition(
// new LatLng(tencentLocation.getLatitude(), tencentLocation.getLongitude()), //中心点坐标地图目标经纬度
// 17, //目标缩放级别
// 0, //目标倾斜角
// tencentLocation.getBearing())); //目标旋转角 0~360° (正北方为0)
// tencentMap.animateCamera(cameraSigma);
// }
// }
// }
@RequiresApi(api = Build.VERSION_CODES.N)
@SuppressLint("SetTextI18n")
public void initMarkerPaper(int index) {
@ -1096,7 +1117,7 @@ public class PicturesActivity extends BaseActivity implements View.OnClickListen
Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> emitter) throws Exception {
stopTimer();
stopTakePhoto();
handler.removeMessages(0x101); // 如果handler中存在缓存的拍摄请求也清空
picturesBuilder.append(TimestampUtil.time()).append(",").append("onClick 点击了结束采集 ,");
emitter.onComplete();
@ -1150,48 +1171,26 @@ public class PicturesActivity extends BaseActivity implements View.OnClickListen
removables.add(marker);
}
private void startTimer() {
if (timer == null) {
timer = new Timer();
}
if (timerTask == null) {
timerTask = new TimerTask() {
@Override
public void run() {
if (handler!=null) {
if (radioPicture == 1) {
camera.takePictureSnapshot();
} else {
Message message = new Message();
message.what = 0x101;
handler.sendMessage(message);
}
}
}
};
}
private void startTakePhoto(int radioPicture) {
if (radioPicture == 1) {
timer.schedule(timerTask, 0);
} else if (radioPicture == 2) {
timer.schedule(timerTask, 0, 1000);
} else if (radioPicture == 3) {
timer.schedule(timerTask, 0, 2000);
} else if (radioPicture == 4) {
timer.schedule(timerTask, 0, 500);
camera.takePictureSnapshot();
} else {
disposable = Observable.interval(1000L, TimeUnit.MILLISECONDS, Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<Long>() {
@Override
public void accept(Long aLong) throws Exception {
camera.takePictureSnapshot();
}
});
}
}
private void stopTimer() {
if (timer != null) {
timer.cancel();
timer = null;
}
if (timerTask != null) {
timerTask.cancel();
timerTask = null;
private void stopTakePhoto() {
if (disposable!=null&&!disposable.isDisposed()) {
disposable.dispose();
}
}
// 设置当前界面亮度
private void setWindowBrightness(int brightness) {
// Window window = getWindow();

View File

@ -79,7 +79,7 @@ public class StaySubmitFragment extends BaseFragment implements View.OnClickList
private CheckBox cbSelect;
private File logFile;
private StringBuilder staySubmitBuilder;
private Logger logger = XLogUtils.Companion.getInstance().getUploadLogWriter();
private Logger logger;
public static StaySubmitFragment newInstance(Bundle bundle) {
StaySubmitFragment fragment = new StaySubmitFragment();
@ -93,6 +93,7 @@ public class StaySubmitFragment extends BaseFragment implements View.OnClickList
if (!EventBus.getDefault().isRegistered(this)) {//加上判断
EventBus.getDefault().register(this);
}
logger=XLogUtils.Companion.getInstance().getUploadLogWriter();
}
@Override

File diff suppressed because it is too large Load Diff

View File

@ -13,10 +13,9 @@ buildscript {
maven{ url 'https://maven.aliyun.com/repository/gradle-plugin'}
maven{ url 'https://maven.aliyun.com/repository/public'}
maven{ url 'https://maven.aliyun.com/repository/jcenter'}
maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/'}
maven{ url 'https://maven.aliyun.com/nexus/content/groups/public/'}
maven { url "https://jitpack.io" }
maven { url "https://repo.spring.io/libs-release/" }
maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/'}
// bug
maven { url 'https://repo1.maven.org/maven2/' }
jcenter()
@ -27,7 +26,7 @@ buildscript {
}
}
dependencies {
classpath 'com.android.tools.build:gradle:4.2.2'
classpath 'com.android.tools.build:gradle:7.0.0'
//kotlin支持
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
@ -48,10 +47,9 @@ allprojects {
maven{ url 'https://maven.aliyun.com/repository/gradle-plugin'}
maven{ url 'https://maven.aliyun.com/repository/public'}
maven{ url 'https://maven.aliyun.com/repository/jcenter'}
maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/'}
maven{ url 'https://maven.aliyun.com/nexus/content/groups/public/'}
maven { url "https://jitpack.io" }
maven { url "https://repo.spring.io/libs-release/" }
maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/'}
// bug
maven { url 'https://repo1.maven.org/maven2/' }
jcenter()

View File

@ -1,6 +1,6 @@
#Tue May 25 11:18:32 CST 2021
#Mon Mar 20 10:38:13 CST 2023
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip
zipStoreBase=GRADLE_USER_HOME

1
ocr/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

61
ocr/build.gradle Normal file
View File

@ -0,0 +1,61 @@
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
}
android {
namespace 'com.navinfo.ocr'
compileSdk 31
defaultConfig {
minSdk 23
targetSdk 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
ndk {
abiFilters "armeabi", "armeabi-v7a"
}
}
buildTypes {
debug {
packagingOptions {
doNotStrip '**/*.so' // so资源文件编译提示
}
}
release {
packagingOptions {
doNotStrip '**/*.so' // so资源文件编译提示
}
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
lintOptions {
abortOnError false
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'com.google.android.material:material:1.8.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
api files('libs/easyedge-sdk.jar')
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
ocr/libs/easyedge-sdk.jar Normal file

Binary file not shown.

21
ocr/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -0,0 +1,24 @@
package com.navinfo.ocr
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.navinfo.ocr", appContext.packageName)
}
}

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- <application-->
<!-- android:allowBackup="true"-->
<!-- />-->
</manifest>

View File

@ -0,0 +1,26 @@
{
"version": 1,
"framework": "fluid",
"model_info": {
"n_type": 0,
"best_threshold": 0.1,
"model_kind": 100
},
"pre_process": {
"mean": [123.675, 116.28, 103.53],
"scale": [
0.01712475383166,
0.01750700280112,
0.01742919389978
],
"color_format": "BGR",
"channel_order": "CHW",
"resize": [960, 960],
"rescale_mode": "keep_ratio",
"max_size":960,
"ocr_rec_resize": [320,32]
},
"extra": {
"fluid": {"optType": "nb"}
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,2 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1659598895605" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3308" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff2?t=1630033759944") format("woff2"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff?t=1630033759944") format("woff"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.ttf?t=1630033759944") format("truetype"); }
</style></defs><path d="M885.333333 938.666667H138.666667a53.393333 53.393333 0 0 1-53.333334-53.333334V138.666667a53.393333 53.393333 0 0 1 53.333334-53.333334h746.666666a53.393333 53.393333 0 0 1 53.333334 53.333334v746.666666a53.393333 53.393333 0 0 1-53.333334 53.333334zM138.666667 128a10.666667 10.666667 0 0 0-10.666667 10.666667v746.666666a10.666667 10.666667 0 0 0 10.666667 10.666667h746.666666a10.666667 10.666667 0 0 0 10.666667-10.666667V138.666667a10.666667 10.666667 0 0 0-10.666667-10.666667z" fill="#A1DBF5" p-id="3309"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,209 @@
package com.navinfo.ocr
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.os.Build
import android.text.TextUtils
import android.util.Log
import android.widget.Toast
import com.baidu.ai.edge.core.base.CallException
import com.baidu.ai.edge.core.base.Consts
import com.baidu.ai.edge.core.infer.InferConfig
import com.baidu.ai.edge.core.infer.InferManager
import com.baidu.ai.edge.core.ocr.OcrInterface
import com.baidu.ai.edge.core.ocr.OcrResultModel
import com.baidu.ai.edge.core.util.FileUtil
import com.navinfo.ocr.model.OcrViewResultModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import org.json.JSONException
import org.json.JSONObject
import java.util.*
class OCRManager {
private var mContext: Context? = null
private var modelName = ""
private val socList = ArrayList<String>()
private var modelType = 0
private var soc: String? = null
private var mOcrManager: OcrInterface? = null
private val CONFIDENCE = 0.3f
fun ocr(bitmap: Bitmap): List<OcrViewResultModel> {
Log.e("jingo", "OCRManager 线程名称3 ${Thread.currentThread().name}")
val list = ArrayList<OcrViewResultModel>()
if (mOcrManager != null) {
val modelList = mOcrManager!!.ocr(bitmap, CONFIDENCE)
for (i in modelList.indices) {
val mOcrResultModel: OcrResultModel = modelList[i]
val mOcrViewResultModel = OcrViewResultModel()
mOcrViewResultModel.colorId = mOcrResultModel.labelIndex
mOcrViewResultModel.index = i + 1
mOcrViewResultModel.confidence = mOcrResultModel.confidence
mOcrViewResultModel.name = mOcrResultModel.label
mOcrViewResultModel.bounds = mOcrResultModel.points
mOcrViewResultModel.isTextOverlay = true
list.add(mOcrViewResultModel)
}
}
return list
}
/**
* 原有的
*/
private fun initConfigFromDemoConfig(): Boolean {
val confJson =
FileUtil.readAssetsFileUTF8StringIfExists(mContext!!.assets, "demo/config.json")
if (TextUtils.isEmpty(confJson)) {
return false
}
try {
val confObj = JSONObject(confJson)
modelName = confObj.optString("modelName", "")
val str = confObj.optString("soc", Consts.SOC_ARM)
val socs = str.split(",".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
socList.addAll(listOf(*socs))
modelType = confObj.getInt("modelType")
} catch (e: JSONException) {
e.printStackTrace()
}
return true
}
/**
* 开放模型
*/
private fun initConfigFromDemoConf(): Boolean {
val confJson =
FileUtil.readAssetsFileUTF8StringIfExists(mContext!!.assets, "demo/conf.json")
if (TextUtils.isEmpty(confJson)) {
return false
}
try {
val confObj = JSONObject(confJson)
modelName = confObj.optString("modelName", "")
socList.add(Consts.SOC_ARM)
val inferCfgJson = FileUtil.readAssetsFileUTF8StringIfExists(
mContext!!.assets,
Consts.ASSETS_DIR_ARM + "/infer_cfg.json"
)
if (TextUtils.isEmpty(inferCfgJson)) {
return false
}
val inferCfgObj = JSONObject(inferCfgJson)
modelType = inferCfgObj.getJSONObject("model_info").getInt("model_kind")
} catch (e: JSONException) {
e.printStackTrace()
}
return true
}
private fun checkChip(): Boolean {
if (socList.contains(Consts.SOC_DSP) && Build.HARDWARE.equals("qcom", ignoreCase = true)) {
soc = Consts.SOC_DSP
return true
}
if (socList.contains(Consts.SOC_ADRENO_GPU) && Build.HARDWARE.equals(
"qcom",
ignoreCase = true
)
) {
soc = Consts.SOC_ADRENO_GPU
return true
}
if (socList.contains(Consts.SOC_NPU) && Build.HARDWARE.contains("kirin980")) {
soc = "npu200"
return true
}
if (socList.contains(Consts.SOC_NPU_VINCI)
&& (Build.HARDWARE.contains("kirin810") || Build.HARDWARE.contains("kirin820")
|| Build.HARDWARE.contains("kirin990") || Build.HARDWARE.contains("kirin985"))
) {
soc = Consts.SOC_NPU_VINCI
return true
}
if (socList.contains(Consts.SOC_ARM_GPU)) {
try {
if (InferManager.isSupportOpencl()) {
soc = Consts.SOC_ARM_GPU
return true
}
} catch (e: CallException) {
Toast.makeText(
mContext, e.errorCode.toString() + ", " + e.message,
Toast.LENGTH_SHORT
).show()
}
}
if (socList.contains(Consts.SOC_ARM)) {
soc = Consts.SOC_ARM
return true
}
return false
}
fun init(context: Context) {
mContext = context
if (initConfigFromDemoConfig()) {
return
}
if (initConfigFromDemoConf()) {
return
}
/* 从infer/读配置 */
var confJson = FileUtil.readAssetsFileUTF8StringIfExists(
mContext!!.applicationContext.assets,
Consts.ASSETS_DIR_ARM + "/conf.json"
)
if (!TextUtils.isEmpty(confJson)) {
try {
val confObj = JSONObject(confJson)
modelName = confObj.optString("modelName", "")
val str = confObj.optString("soc", Consts.SOC_ARM)
val socs = str.split(",".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
socList.addAll(listOf(*socs))
modelType = confObj.getInt("modelType")
} catch (e: JSONException) {
e.printStackTrace()
}
} else {
confJson = FileUtil.readAssetsFileUTF8StringIfExists(
mContext!!.applicationContext.assets,
Consts.ASSETS_DIR_ARM + "/infer_cfg.json"
)
try {
val confObj = JSONObject(confJson)
socList.add(Consts.SOC_ARM)
modelType = confObj.getJSONObject("model_info").getInt("model_kind")
} catch (e: JSONException) {
e.printStackTrace()
}
}
MainScope().launch(Dispatchers.IO) {
try {
Log.e("jingo", "OCRManager 线程名称 ${Thread.currentThread().name}")
/* 1. 准备配置类初始化Manager类。可以在onCreate或onResume中触发请在非UI线程里调用 */
val config = InferConfig(context.assets, "infer")
mOcrManager = InferManager(mContext, config, SERIAL_NUM)
// cancel()
} catch (e: Exception) {
Log.e("jingo", "OCRManager 线程名称 ${e.message}")
}
}
}
companion object {
val instance: OCRManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
OCRManager()
}
// 请替换为您的序列号
private const val SERIAL_NUM = "XXXX-XXXX-XXXX-XXXX"
}
}

View File

@ -0,0 +1,209 @@
package com.navinfo.ocr.model;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.ArrayList;
import java.util.List;
public class BasePolygonResultModel extends BaseResultModel implements Parcelable {
private int colorId;
private boolean isRect;
private boolean isTextOverlay;
private boolean isHasGroupColor = false;
private boolean isDrawPoints = false;
/**
* 姿态一个点会有多个pair
*/
private boolean multiplePairs = false;
protected BasePolygonResultModel(Parcel in) {
super(in);
colorId = in.readInt();
isRect = in.readByte() != 0;
isTextOverlay = in.readByte() != 0;
isHasGroupColor = in.readByte() != 0;
isDrawPoints = in.readByte() != 0;
multiplePairs = in.readByte() != 0;
in.readByteArray(mask);
semanticMask = in.readByte() != 0;
rect = in.readParcelable(Rect.class.getClassLoader());
bounds = in.createTypedArrayList(Point.CREATOR);
}
public static final Creator<BasePolygonResultModel> CREATOR = new Creator<BasePolygonResultModel>() {
@Override
public BasePolygonResultModel createFromParcel(Parcel in) {
return new BasePolygonResultModel(in);
}
@Override
public BasePolygonResultModel[] newArray(int size) {
return new BasePolygonResultModel[size];
}
};
public boolean isTextOverlay() {
return isTextOverlay;
}
public void setTextOverlay(boolean textOverlay) {
isTextOverlay = textOverlay;
}
public int getColorId() {
return colorId;
}
public void setColorId(int colorId) {
this.colorId = colorId;
}
private byte[] mask = {};
/**
* 是否是语义分割mask
*/
private boolean semanticMask;
private Rect rect;
BasePolygonResultModel() {
super();
}
BasePolygonResultModel(int index, String name, float confidence, Rect bounds) {
super(index, name, confidence);
parseFromRect(bounds);
}
BasePolygonResultModel(int index, String name, float confidence, List<Point> bounds) {
super(index, name, confidence);
this.bounds = bounds;
}
public Rect getRect() {
return rect;
}
public Rect getRect(float ratio, Point origin) {
return new Rect((int) (origin.x + rect.left * ratio),
(int) (origin.y + rect.top * ratio),
(int) (origin.x + rect.right * ratio),
(int) (origin.y + rect.bottom * ratio));
}
private void parseFromRect(Rect rect) {
Point ptTL = new Point(rect.left, rect.top);
Point ptTR = new Point(rect.right, rect.top);
Point ptRB = new Point(rect.right, rect.bottom);
Point ptLB = new Point(rect.left, rect.bottom);
this.bounds = new ArrayList<>();
this.bounds.add(ptTL);
this.bounds.add(ptTR);
this.bounds.add(ptRB);
this.bounds.add(ptLB);
this.rect = rect;
isRect = true;
}
public boolean isRect() {
return isRect;
}
public void setRect(boolean rect) {
isRect = rect;
}
public byte[] getMask() {
return mask;
}
public void setMask(byte[] mask) {
this.mask = mask;
}
public void setSemanticMask(boolean semanticMask) {
this.semanticMask = semanticMask;
}
public boolean isSemanticMask() {
return semanticMask;
}
private List<Point> bounds;
public List<Point> getBounds() {
return bounds;
}
public List<Point> getBounds(float ratio, Point origin) {
List<Point> pointList = new ArrayList<>();
for (Point pt : bounds) {
int nx = (int) (origin.x + pt.x * ratio);
int ny = (int) (origin.y + pt.y * ratio);
pointList.add(new Point(nx, ny));
}
return pointList;
}
public void setBounds(List<Point> bounds) {
this.bounds = bounds;
}
public void setBounds(Rect bounds) {
parseFromRect(bounds);
}
public boolean isHasMask() {
return (mask != null);
}
public boolean isHasGroupColor() {
return isHasGroupColor;
}
public void setHasGroupColor(boolean hasGroupColor) {
isHasGroupColor = hasGroupColor;
}
public boolean isDrawPoints() {
return isDrawPoints;
}
public void setDrawPoints(boolean drawPoints) {
isDrawPoints = drawPoints;
}
public void setMultiplePairs(boolean multiplePairs) {
this.multiplePairs = multiplePairs;
}
public boolean isMultiplePairs() {
return multiplePairs;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest,flags);
dest.writeInt(colorId);
dest.writeByte((byte) (isRect ? 1 : 0));
dest.writeByte((byte) (isTextOverlay ? 1 : 0));
dest.writeByte((byte) (isHasGroupColor ? 1 : 0));
dest.writeByte((byte) (isDrawPoints ? 1 : 0));
dest.writeByte((byte) (multiplePairs ? 1 : 0));
dest.writeByteArray(mask);
dest.writeByte((byte) (semanticMask ? 1 : 0));
dest.writeParcelable(rect, flags);
dest.writeTypedList(bounds);
}
}

View File

@ -0,0 +1,95 @@
package com.navinfo.ocr.model;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Created by ruanshimin on 2018/5/16.
*/
public class BaseRectBoundResultModel extends BaseResultModel implements Parcelable {
private int colorId;
protected BaseRectBoundResultModel(Parcel in) {
super(in);
colorId = in.readInt();
in.readByteArray(mask);
bounds = in.readParcelable(Rect.class.getClassLoader());
}
public static final Creator<BaseRectBoundResultModel> CREATOR = new Creator<BaseRectBoundResultModel>() {
@Override
public BaseRectBoundResultModel createFromParcel(Parcel in) {
return new BaseRectBoundResultModel(in);
}
@Override
public BaseRectBoundResultModel[] newArray(int size) {
return new BaseRectBoundResultModel[size];
}
};
public int getColorId() {
return colorId;
}
public void setColorId(int colorId) {
this.colorId = colorId;
}
private byte[] mask = {};
BaseRectBoundResultModel() {
super();
}
BaseRectBoundResultModel(int index, String name, float confidence, Rect bounds) {
super(index, name, confidence);
this.bounds = bounds;
}
public byte[] getMask() {
return mask;
}
public void setMask(byte[] mask) {
this.mask = mask;
}
private Rect bounds;
public Rect getBounds() {
return bounds;
}
public Rect getBounds(float ratio, Point origin) {
return new Rect((int) (origin.x + bounds.left * ratio),
(int) (origin.y + bounds.top * ratio),
(int) (origin.x + bounds.right * ratio),
(int) (origin.y + bounds.bottom * ratio));
}
public void setBounds(Rect bounds) {
this.bounds = bounds;
}
public boolean isHasMask() {
return (mask != null);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(colorId);
dest.writeByteArray(mask);
dest.writeParcelable(bounds, flags);
}
}

View File

@ -0,0 +1,80 @@
package com.navinfo.ocr.model;
import android.os.Parcel;
import android.os.Parcelable;
import java.io.Serializable;
/**
* Created by ruanshimin on 2018/5/16.
*/
public class BaseResultModel implements Parcelable {
private int index = 0;
private String name = "";
private float confidence = 0;
protected BaseResultModel() {
}
protected BaseResultModel(int index, String name, float confidence) {
this.index = index;
this.name = name;
this.confidence = confidence;
}
protected BaseResultModel(Parcel in) {
index = in.readInt();
name = in.readString();
confidence = in.readFloat();
}
public static final Creator<BaseResultModel> CREATOR = new Creator<BaseResultModel>() {
@Override
public BaseResultModel createFromParcel(Parcel in) {
return new BaseResultModel(in);
}
@Override
public BaseResultModel[] newArray(int size) {
return new BaseResultModel[size];
}
};
public float getConfidence() {
return confidence;
}
public void setConfidence(float confidence) {
this.confidence = confidence;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(index);
dest.writeString(name);
dest.writeFloat(confidence);
}
}

View File

@ -0,0 +1,22 @@
package com.navinfo.ocr.model;
import java.io.Serializable;
/**
* Created by ruanshimin on 2018/5/13.
*/
public class ClassifyResultModel extends BaseResultModel {
public ClassifyResultModel() {
super();
}
public ClassifyResultModel(int index, String name, float confidence) {
super(index, name, confidence);
}
}

View File

@ -0,0 +1,20 @@
package com.navinfo.ocr.model;
import android.graphics.Rect;
import java.io.Serializable;
/**
* Created by ruanshimin on 2018/5/13.
*/
public class DetectResultModel extends BasePolygonResultModel {
public DetectResultModel() {
super();
}
public DetectResultModel(int index, String name, float confidence, Rect bounds) {
super(index, name, confidence, bounds);
}
}

View File

@ -0,0 +1,17 @@
package com.navinfo.ocr.model;
import android.graphics.Point;
import android.os.Parcelable;
import java.io.Serializable;
import java.util.List;
public class OcrViewResultModel extends BasePolygonResultModel{
public OcrViewResultModel() {
super();
}
public OcrViewResultModel(int index, String name, float confidence, List<Point> bounds) {
super(index, name, confidence, bounds);
}
}

View File

@ -0,0 +1,14 @@
package com.navinfo.ocr.model;
import java.io.Serializable;
public class PoseViewResultModel extends BasePolygonResultModel {
public PoseViewResultModel() {
super();
setRect(false);
setTextOverlay(false);
setDrawPoints(true);
setMultiplePairs(true);
}
}

View File

@ -0,0 +1,29 @@
package com.navinfo.ocr.model;
import android.graphics.Point;
import android.graphics.Rect;
import java.io.Serializable;
import java.util.List;
/**
* Created by ruanshimin on 2018/5/13.
*/
public class SegmentResultModel extends BasePolygonResultModel {
public SegmentResultModel() {
super();
}
public SegmentResultModel(int index, String name, float confidence, List<Point> bounds, byte[] mask) {
super(index, name, confidence, bounds);
this.setMask(mask);
}
public SegmentResultModel(int index, String name, float confidence, List<Point> bounds) {
super(index, name, confidence, bounds);
}
}

View File

@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View File

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.NavinfoCollect" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name">ocr</string>
</resources>

View File

@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.NavinfoCollect" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@ -0,0 +1,17 @@
package com.navinfo.ocr
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}

View File

@ -1 +1 @@
include ':app', ':xrecyclerview'
include ':app', ':xrecyclerview', ":ocr"