-
-
Save jav974/072425f14927e6ca2c7a4439d8ac5457 to your computer and use it in GitHub Desktop.
#!/bin/bash | |
# You should tweak this section to adapt the paths to your need | |
export ANDROID_HOME=/home/jeremy/Android/Sdk | |
export NDK_ROOT=/home/jeremy/Android/Sdk/ndk-bundle | |
ANDROID_PLATFORM="android-21" | |
# In my case, FindJNI.cmake does not find java, so i had to manually specify these | |
# You could try without it and remove the cmake variable specification at the bottom of this file | |
JAVA_HOME=/usr/lib/jvm/oracle-java8-jdk-amd64 | |
JAVA_AWT_LIBRARY=$JAVA_HOME/jre/lib/amd64 | |
JAVA_JVM_LIBRARY=$JAVA_HOME/jre/lib/amd64 | |
JAVA_INCLUDE_PATH=$JAVA_HOME/include | |
JAVA_INCLUDE_PATH2=$JAVA_HOME/include/linux | |
JAVA_AWT_INCLUDE_PATH=$JAVA_HOME/include | |
SCRIPT=`realpath $0` | |
SCRIPTPATH=`dirname $SCRIPT` | |
#################################################################### | |
# Prepare Tesseract and Leptonica, using rmtheis/tess-two repository | |
#################################################################### | |
git clone --recursive https://github.com/rmtheis/tess-two.git tess2 | |
cd tess2 | |
echo "sdk.dir=$ANDROID_HOME | |
ndk.dir=$NDK_ROOT" > local.properties | |
./gradlew assemble | |
cd .. | |
#################################################################### | |
# Download and extract OpenCV4Android | |
#################################################################### | |
wget -O opencv-3.2.0-android-sdk.zip -- https://sourceforge.net/projects/opencvlibrary/files/opencv-android/3.2.0/opencv-3.2.0-android-sdk.zip/download | |
unzip opencv-3.2.0-android-sdk.zip | |
rm opencv-3.2.0-android-sdk.zip | |
#################################################################### | |
# Download and configure openalpr from jav974/openalpr forked repo | |
#################################################################### | |
git clone https://github.com/jav974/openalpr.git openalpr | |
mkdir openalpr/android-build | |
TESSERACT_SRC_DIR=$SCRIPTPATH/tess2/tess-two/jni/com_googlecode_tesseract_android/src | |
rm -rf openalpr/src/openalpr/ocr/tesseract | |
mkdir openalpr/src/openalpr/ocr/tesseract | |
shopt -s globstar | |
cd $TESSERACT_SRC_DIR | |
cp **/*.h $SCRIPTPATH/openalpr/src/openalpr/ocr/tesseract | |
cd $SCRIPTPATH | |
declare -a ANDROID_ABIS=("armeabi" | |
"armeabi-v7a" | |
"armeabi-v7a with NEON" | |
"arm64-v8a" | |
"mips" | |
"mips64" | |
"x86" | |
"x86_64" | |
) | |
cd openalpr/android-build | |
for i in "${ANDROID_ABIS[@]}" | |
do | |
if [ "$i" == "armeabi-v7a with NEON" ]; then abi="armeabi-v7a"; else abi="$i"; fi | |
TESSERACT_LIB_DIR=$SCRIPTPATH/tess2/tess-two/libs/$abi | |
if [[ "$i" == armeabi* ]]; | |
then | |
arch="arm" | |
lib="lib" | |
elif [[ "$i" == arm64-v8a ]]; | |
then | |
arch="arm64" | |
lib="lib" | |
elif [[ "$i" == mips ]] || [[ "$i" == x86 ]]; | |
then | |
arch="$i" | |
lib="lib" | |
elif [[ "$i" == mips64 ]] || [[ "$i" == x86_64 ]]; | |
then | |
arch="$i" | |
lib="lib64" | |
fi | |
echo " | |
###################################### | |
Generating project for arch $i | |
###################################### | |
" | |
rm -rf "$i" && mkdir "$i" | |
cd "$i" | |
cmake \ | |
-DANDROID_TOOLCHAIN=clang \ | |
-DCMAKE_TOOLCHAIN_FILE=$NDK_ROOT/build/cmake/android.toolchain.cmake \ | |
-DANDROID_NDK=$NDK_ROOT \ | |
-DCMAKE_BUILD_TYPE=Release \ | |
-DANDROID_PLATFORM=$ANDROID_PLATFORM \ | |
-DANDROID_ABI="$i" \ | |
-DANDROID_STL=gnustl_static \ | |
-DANDROID_CPP_FEATURES="rtti exceptions" \ | |
-DTesseract_INCLUDE_BASEAPI_DIR=$TESSERACT_SRC_DIR/api \ | |
-DTesseract_INCLUDE_CCSTRUCT_DIR=$TESSERACT_SRC_DIR/ccstruct \ | |
-DTesseract_INCLUDE_CCMAIN_DIR=$TESSERACT_SRC_DIR/ccmain \ | |
-DTesseract_INCLUDE_CCUTIL_DIR=$TESSERACT_SRC_DIR/ccutil \ | |
-DTesseract_LIB=$TESSERACT_LIB_DIR/libtess.so \ | |
-DLeptonica_LIB=$TESSERACT_LIB_DIR/liblept.so \ | |
-DOpenCV_DIR=$SCRIPTPATH/OpenCV-android-sdk/sdk/native/jni \ | |
-DJAVA_AWT_LIBRARY=$JAVA_AWT_LIBRARY \ | |
-DJAVA_JVM_LIBRARY=$JAVA_JVM_LIBRARY \ | |
-DJAVA_INCLUDE_PATH=$JAVA_INCLUDE_PATH \ | |
-DJAVA_INCLUDE_PATH2=$JAVA_INCLUDE_PATH2 \ | |
-DJAVA_AWT_INCLUDE_PATH=$JAVA_AWT_INCLUDE_PATH \ | |
-DPngt_LIB=$TESSERACT_LIB_DIR/libpngt.so \ | |
-DJpgt_LIB=$TESSERACT_LIB_DIR/libjpgt.so \ | |
-DJnigraphics_LIB=$NDK_ROOT/platforms/$ANDROID_PLATFORM/arch-$arch/usr/$lib/libjnigraphics.so \ | |
-DANDROID_ARM_MODE=arm \ | |
../../src/ | |
cmake --build . -- -j 8 | |
cd .. | |
done | |
echo " | |
All done !!!" |
@cefaci thank you for information.
on the other phone (also android 6.0) throws the following exception and the library is not initialized, what should I do?
`E/--(!) Config file does not exist!: /data/user/0/com.example.var8/runtime_data/eu.conf
E/C++->AlprImpl->config->loaded: FAILED: 0`
Really, again? If you don't start posting/trying EVERYTHING in your questions I won't help you anymore...
- What is the absolute path of your
runtime_data directory
you init innew Alpr()
? - What is the absolute path of your
config_file
you init innew Alpr()
? - What is the absolute path of your
coutry_code
you init innew Alpr()
? - What is the absolute path where you copy your assets e.g.
runtime_data directory
- Did you check out the copied assets
runtime_data
path after you copied on your phone e.g. w/ Total Commander?
If you would have just checked this message on your own E/C++->AlprImpl->config->loaded: FAILED: 0
then maybe you would have found an if
in the file alpr_impl.cpp
saying this:
namespace alpr
{
AlprImpl::AlprImpl(const std::string country, const std::string configFile, const std::string runtimeDir)
{
...
// Config file or runtime dir not found. Don't process any further.
if (config->loaded == false)
{
return;
}
...
The comment is not from me and is already saying everything, where I just added the logging (in my files e.g. the .so
you have):
namespace alpr
{
AlprImpl::AlprImpl(const std::string country, const std::string configFile, const std::string runtimeDir)
{
...
// Config file or runtime dir not found. Don't process any further.
if (config->loaded == false)
{
__android_log_print(ANDROID_LOG_ERROR, "C++->AlprImpl->config->loaded: FAILED ", "%d", config->loaded);
return;
}
...
Config.cpp
namespace alpr
{
Config::Config(const std::string country, const std::string config_file, const std::string runtime_dir)
{
...
if (fileExists(config_file_path.c_str()) == false && fileExists(CONFIG_FILE_TEMPLATE_LOCATION) == false)
{
std::cerr << "--(!) Config file '" << config_file_path << "' does not exist!" << endl;
std::cerr << "--(!) You can specify the configuration file location via the command line " << endl;
std::cerr << "--(!) or by setting the environment variable '" << ENV_VARIABLE_CONFIG_FILE << "'" << endl;
__android_log_print(ANDROID_LOG_ERROR, "--(!) Config file does not exist! ", "%s", config_file_path.c_str());
return;
}
else if (DirectoryExists(config_file_path.c_str()))
{
std::cerr << "--(!) Config file '" << config_file_path << "' was specified as a directory, rather than a file!" << endl;
std::cerr << "--(!) Please specify the full path to the 'openalpr.conf file'" << endl;
std::cerr << "--(!) e.g., /etc/openalpr/openalpr.conf" << endl;
__android_log_print(ANDROID_LOG_ERROR, "--(!) Config file was specified as a directory, rather than a file! ", "%s", config_file_path.c_str());
return;
}
....
Sorry. I did not change the code, I just reinstalled it on my phone and installed it on another, this error appeared.
Here is my code:
@cefaci
I changed the function itself a bitpublic Alpr(Context context, String androidDataDir, String country, String configFile, String runtimeDir) { Utils.copyAssetFolder(context.getAssets(), "runtime_data", androidDataDir + File.separatorChar + "runtime_data"); initialize(country, configFile, runtimeDir); }
and call it as follows:
String runtimeDir = ANDROID_DATA_DIR + File.separatorChar + "runtime_data"; ANDROID_DATA_DIR = this.getApplicationInfo().dataDir; Alpr alpr = new Alpr(this, ANDROID_DATA_DIR, "us", openAlprConfFile, runtimeDir);
with your files I get the following result:
{"version":2,"data_type":"alpr_results","epoch_time":1578932220551,"img_width":720,"img_height":1280,"processing_time_ms":471.592621,"regions_of_interest":[{"x":0,"y":0,"width":720,"height":1280}],"results":[]}
and in console:E/C++->AlprImpl->OpenALPR Initialization Time:: 94.849615 I/C++->AlprImpl->recognizeFullDetails(): Analyzing:: us
why use OpenCV 4.1.1?
Really, again? :/
I understood what you have done.... Your code seems to not work on your other phone as the message already said
E/--(!) Config file does not exist!: /data/user/0/com.example.var8/runtime_data/eu.conf
...
So, how we can find out why? Change YOUR code, print and then POST here what I've asked you:
- What is the absolute path of your
runtime_data directory
you init in new Alpr()?- What is the absolute path of your
config_file
you init in new Alpr()?- What is
country_code
you init in new Alpr()?- What is the absolute path where you copy your assets e.g.
runtime_data directory
?
Did you check out the copied assets
runtime_data path
after you copied on your phone e.g. w/ Total Commander?
After you printed all these 4 paths check out all of them on your phone and POST them here too... your OWN investigation w/ everything I have asked you!
Thanks...
@cefaci
Yes, it helped, thank you, the error was that the configuration files were in the config folder, but you just needed in runtime_dir. The library is initializing, but the application crashes (with eu.conf).
Still, again, post ALL your details...
@cefaci
Okay.
- I use your .so files.
- And code in previous post.
- my runtime_data (runtimeDir):
/data/user/0/com.example.var8/runtime_data
- my abs path config (openAlprConfFile):
/data/user/0/com.example.var8/runtime_data/eu.conf
- country code: eu
- new Alpr():
Alpr alpr = new Alpr(MainActivity.this, this.getApplicationInfo().dataDir, "eu", openAlprConfFile, runtimeDir);
and I get:
Disconnected from the target VM, address: 'localhost:8603', transport: 'socket'
- But the
openAlprConfFile
is not copied fromruntime_data/config/eu.conf
? See mine namedopenalpr.conf
- Please post your directory structure of
runtime_data
, is it like mine? You shouldn't change there anything unless you know what you do... Disconnected from the target VM, address: 'localhost:8603', transport: 'socket'
is that all from logcat? If no config is found the alpr_impl is not initialized and every call to the API of new Alpr() will fail and kill your app. I posted it in my first post...
My openaplr.conf
aka openAlprConfFile
(can be anywhere as well outside of runtime_data
):
ocr_img_size_percent = 1.33333333
state_id_img_size_percent = 2.0
; Calibrating your camera improves detection accuracy in cases where vehicle plates are captured at a steep angle
; Use the openalpr-utils-calibrate utility to calibrate your fixed camera to adjust for an angle
; Once done, update the prewarp config with the values obtained from the tool
;prewarp = planar,1280.000000,720.000000,0.000550,0.000750,0.130000,1.000000,1.000000,0.000000,0.000000
prewarp =
; detection will ignore plates that are too large. This is a good efficiency technique to use if the
; plates are going to be a fixed distance away from the camera (e.g., you will never see plates that fill
; up the entire image
max_plate_width_percent = 100
max_plate_height_percent = 100
; detection_iteration_increase is the percentage that the LBP frame increases each iteration.
; It must be greater than 1.0. A value of 1.01 means increase by 1%, 1.10 increases it by 10% each time.
; So a 1% increase would be ~10x slower than 10% to process, but it has a higher chance of landing
; directly on the plate and getting a strong detection
detection_iteration_increase = 1.20
; The minimum detection strength determines how sure the detection algorithm must be before signaling that
; a plate region exists. Technically this corresponds to LBP nearest neighbors (e.g., how many detections
; are clustered around the same area). For example, 2 = very lenient, 9 = very strict.
detection_strictness = 1
; The detection doesn't necessarily need an extremely high resolution image in order to detect plates
; Using a smaller input image should still find the plates and will do it faster
; Tweaking the max_detection_input values will resize the input image if it is larger than these sizes
; max_detection_input_width/height are specified in pixels
max_detection_input_width = 1280
max_detection_input_height = 960
; detector is the technique used to find license plate regions in an image. Value can be set to
; lbpcpu - default LBP-based detector uses the system CPU
; lbpgpu - LBP-based detector that uses Nvidia GPU to increase recognition speed.
; lbpopencl - LBP-based detector that uses OpenCL GPU to increase recognition speed. Requires OpenCV 3.0
; morphcpu - Experimental detector that detects white rectangles in an image. Does not require training.
detector = lbpcpu
; If set to true, all results must match a postprocess text pattern if a pattern is available.
; If not, the result is disqualified.
must_match_pattern = 1
; Bypasses plate detection. If this is set to 1, the library assumes that each region provided is a likely plate area.
skip_detection = 0
; Specifies the full path to an image file that constrains the detection area. Only the plate regions allowed through the mask
; will be analyzed. The mask image must match the resolution of your image to be analyzed. The mask is black and white.
; Black areas will be ignored, white areas will be searched. An empty value means no mask (scan the entire image)
detection_mask_image =
; OpenALPR can scan the same image multiple times with different randomization. Setting this to a value larger than
; 1 may increase accuracy, but will increase processing time linearly (e.g., analysis_count = 3 is 3x slower)
analysis_count = 2
; OpenALPR detects high-contrast plate crops and uses an alternative edge detection technique. Setting this to 0.0
; would classify ALL images as high-contrast, setting it to 1.0 would classify no images as high-contrast.
contrast_detection_threshold = 0.5
max_plate_angle_degrees = 90
ocr_min_font_point = 6
; Minimum OCR confidence percent to consider.
postprocess_min_confidence = 80
; Any OCR character lower than this will also add an equally likely
; chance that the character is incorrect and will be skipped. Value is a confidence percent
postprocess_confidence_skip_level = 80
- In android studio my eu.conf was in the directory
runtime_data / config
, moving it to the directory/runtime_data
alpr.isLoaded()
returned true. - I already wrote that the structure is exactly the same as yours
- No, it is console in debug process on my device:
`
01/16 19:07:58: Launching 'app' on Sony F3111.
$ adb shell am start -n "com.example.var8/com.example.var8.MainActivity" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -D
Waiting for application to come online: com.example.var8.test | com.example.var8
Waiting for application to come online: com.example.var8.test | com.example.var8
Waiting for application to come online: com.example.var8.test | com.example.var8
Connecting to com.example.var8
Connected to the target VM, address: 'localhost:8601', transport: 'socket'
Capturing and displaying logcat messages from application. This behavior can be disabled in the "Logcat output" section of the "Debugger" settings page.
I/art: Late-enabling -Xcheck:jni
W/ActivityThread: Application com.example.var8 is waiting for the debugger on port 8100...
I/System.out: Sending WAIT chunk
I/art: Debugger is active
I/System.out: Debugger has connected
waiting for debugger to settle...
I/System.out: waiting for debugger to settle...
I/System.out: waiting for debugger to settle...
I/System.out: waiting for debugger to settle...
I/System.out: waiting for debugger to settle...
I/System.out: waiting for debugger to settle...
I/System.out: waiting for debugger to settle...
I/System.out: debugger has settled (1428)
W/art: Before Android 4.1, method android.graphics.PorterDuffColorFilter androidx.vectordrawable.graphics.drawable.VectorDrawableCompat.updateTintFilter(android.graphics.PorterDuffColorFilter, android.content.res.ColorStateList, android.graphics.PorterDuff$Mode) would have incorrectly overridden the package-private method in android.graphics.drawable.Drawable
I/art: Rejecting re-init on previously-failed class java.lang.Class<androidx.core.view.ViewCompat$2>
I/art: Rejecting re-init on previously-failed class java.lang.Class<androidx.core.view.ViewCompat$2>
I/System: FinalizerDaemon: finalize objects = 1
I/[MALI][Gralloc]: [+]r_hnd(0x7f79939320), client(35), share_fd(33)
E/GED: Failed to get GED Log Buf, err(0)
I/OpenGLRenderer: Initialized EGL, version 1.4
I/OpenGLRenderer: Get enable program binary service property (1)
Initializing program atlas...
I/OpenGLRenderer: Program binary detail: Binary length is 147292, program map length is 128.
I/OpenGLRenderer: Succeeded to mmap program binaries. File descriptor is 40, and path is /dev/ashmem.
No need to use file discriptor anymore, close fd(40).
W/libEGL: [ANDROID_RECORDABLE] format: 1
I/PerfService: PerfServiceNative api init
I/[MALI][Gralloc]: [+]r_hnd(0x7f89060d20), client(35), share_fd(42)
I/Timeline: Timeline: Activity_idle id: android.os.BinderProxy@770160e time:6933542
I/[MALI][Gralloc]: [+]r_hnd(0x7f89060dc0), client(35), share_fd(44)
I/Timeline: Timeline: Activity_idle id: android.os.BinderProxy@770160e time:6937954
I/[MALI][Gralloc]: [+]r_hnd(0x7f890606e0), client(35), share_fd(46)
I/[MALI][Gralloc]: [+]r_hnd(0x7f89060fa0), client(35), share_fd(48)
I/[MALI][Gralloc]: [-]r_hnd(0x7f89060dc0), client(35), share_fd(44)
I/[MALI][Gralloc]: [-]r_hnd(0x7f890606e0), client(35), share_fd(46)
I/[MALI][Gralloc]: [-]r_hnd(0x7f89060fa0), client(35), share_fd(48)
I/[MALI][Gralloc]: [-]r_hnd(0x7f89060d20), client(35), share_fd(42)
E/C++->AlprImpl->OpenALPR Initialization Time:: 107.559384
I/Choreographer: Skipped 310 frames! The application may be doing too much work on its main thread.
W/libEGL: [ANDROID_RECORDABLE] format: 1
W/libEGL: [ANDROID_RECORDABLE] format: 1
I/[MALI][Gralloc]: [+]r_hnd(0x7f89060d20), client(35), share_fd(44)
I/[MALI][Gralloc]: [+]r_hnd(0x7f89060b40), client(35), share_fd(46)
I/[MALI][Gralloc]: [+]r_hnd(0x7f89060c80), client(35), share_fd(48)
I/Timeline: Timeline: Activity_idle id: android.os.BinderProxy@770160e time:6950058
I/[MALI][Gralloc]: [+]r_hnd(0x7f890610e0), client(35), share_fd(42)
I/[MALI][Gralloc]: [+]r_hnd(0x7f89061180), client(35), share_fd(52)
I/C++->AlprImpl->recognizeFullDetails(): Analyzing:: eu
Disconnected from the target VM, address: 'localhost:8601', transport: 'socket'
`
Did you read what I post?
In android studio my eu.conf was in the directory runtime_data / config, moving it to the directory /runtime_data alpr.isLoaded() returned true.
- DON'T copy/move/touch ANYTHING in runtime_data, leave it as you have git cloned (unless you know what you do or extend it w/ other countries e.g. RU) and COPY it completly unchanged in your assets and then on your phone.
- COPY my openaplr.conf aka openAlprConfFile, change it as needed, move it anywhere and load it in the new Alpr()
- As I wrote 10 times country_code looks for a file in runtime_data/config/[coutry_code].xml and has nothing to do with the openaplr.conf aka openAlprConfFile! Your copied eu.xml is not a valid openAlprConfFile
I/C++->AlprImpl->recognizeFullDetails(): Analyzing:: eu
Disconnected from the target VM, address: 'localhost:8601', transport: 'socket'
openaplr.conf is not the runtime_data/config/[coutry_code].xml file. Just check the C++ code there are the default config values...
I started to migrate this gist into a proper repo with a Travis for building. So that we can easy reproduce building every time.
Repository https://github.com/RobertSasak/openalpr-android
Travis: https://travis-ci.com/RobertSasak/openalpr-android
Currently I have two issues:
- Error when building Tess-two. I believe this is issue with the latest AndroidX changes. I am quite sure that I need to downgrade something, but I am not sure what and to what version.
error: package android.support.annotation does not exist import android.support.annotation.Size;
- In cmake. I think this can also be resolved by downgrading NDK. What version I should use?
CMake Error at /usr/local/android-sdk/ndk-bundle/build/cmake/android.toolchain.cmake:250 (message):
gnustl_static is no longer supported. Please switch to either c++_shared
or c++_static. See
https://developer.android.com/ndk/guides/cpp-support.html for more
information.
We are using OpenAlpr in react-native projects https://github.com/RobertSasak/react-native-openalpr . This work very well. However Google Play requires 64bits libraries in order to publish app. So we are in desperate need to OpenAlpr 64bit libraries.
I am having trouble generating the library files using this script but I am using:
Windows 10
Ubuntu 18.04
Which compiler do I need to use in order to generate the .so files Windows Visual Studio, Ninja, Unix Makefiles, or another compiler?
- I mailed you the .so files
- Use OpenCV 4.1.1 for android then you can link it in your project static
- To check your code doesn't make sense, as everything is happening in the library and the return looks good, as it executes and takes time. I still think it can be your images or your configs are not right.
- My build.gradle:
... android { compileSdkVersion 28 buildToolsVersion "28.0.3" defaultConfig { ... minSdkVersion 24 targetSdkVersion 28 versionCode 1 versionName "1.0.02" ndk { abiFilters "arm64-v8a","armeabi-v7a" } } buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' } } ...
- I mailed you the .so files
- Use OpenCV 4.1.1 for android then you can link it in your project static
- To check your code doesn't make sense, as everything is happening in the library and the return looks good, as it executes and takes time. I still think it can be your images or your configs are not right.
- My build.gradle:
... android { compileSdkVersion 28 buildToolsVersion "28.0.3" defaultConfig { ... minSdkVersion 24 targetSdkVersion 28 versionCode 1 versionName "1.0.02" ndk { abiFilters "arm64-v8a","armeabi-v7a" } } buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' } } ...
@cefaci Can you send me .so file for 64 bit? My mail is : [email protected]
@AbhishekHirapara can you mail me the .so files please if you managed to get them ?, my mail is [email protected]
Config:
openalpr.conf
is for general detection settings.new Aplr()
object will look for a file inruntime_data/config/[COUNTRY_CODE].conf
where there is a propertyocr_language = l[COUNTRY_CODE]
e.g.leu
.ocr_language = l[COUNTRY_CODE]
value will look for a file inruntime_data/ocr-tessdata/l[COUNTRY_CODE].trainedata
you should have trained with openalpr/train-ocr (?)config/postprocess/[COUNTRY_CODE].patterns
file, in the directory is areadme.txt
how to make RU patterns.config/region/[COUNTRY_CODE].xml
for the plates.You should create background threads for openalpr processing I do this for my image buffering as well. I have a constant flow of 29FPS where I use ~7-8 FPS for asnyc openalpr processing as it takes ~50-300ms per image (if nothing is found it takes longer).
I could show you videos of my results:
openalpr local (android phone) vs. commercial openalpr (cloud) vs. LPRnet local (android phone).
LPRnet performs in my opinion best, but is not multi-line or 2-line ready. Commercial openalpr (cloud) works multiline.