6.6.0 - 内置模块重写; 新增部分模块及方法; 修复高版本安卓系统兼容问题; 优化文件管理器及打包功能体验
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
package com.benjaminwan.ocrlibrary
|
||||
|
||||
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.benjaminwan.ocrlibrary.test", appContext.packageName)
|
||||
}
|
||||
}
|
||||
1
libs/rapidocr/src/main/AndroidManifest.xml
Normal file
1
libs/rapidocr/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest/>
|
||||
69
libs/rapidocr/src/main/cpp/CMakeLists.txt
Normal file
69
libs/rapidocr/src/main/cpp/CMakeLists.txt
Normal file
@@ -0,0 +1,69 @@
|
||||
cmake_minimum_required(VERSION 3.22.1)
|
||||
|
||||
project(RapidOcr)
|
||||
|
||||
# OnnxRuntime
|
||||
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/../onnxruntime-shared/OnnxRuntimeWrapper.cmake)
|
||||
find_package(OnnxRuntime REQUIRED)
|
||||
if (OnnxRuntime_FOUND)
|
||||
message(STATUS "OnnxRuntime_LIBS: ${OnnxRuntime_LIBS}")
|
||||
message(STATUS "OnnxRuntime_INCLUDE_DIRS: ${OnnxRuntime_INCLUDE_DIRS}")
|
||||
else ()
|
||||
message(FATAL_ERROR "onnxruntime Not Found!")
|
||||
endif (OnnxRuntime_FOUND)
|
||||
|
||||
## opencv 库
|
||||
|
||||
set(OpenCV_DIR "${CMAKE_SOURCE_DIR}/../../sdk/native/jni")
|
||||
find_package(OpenCV REQUIRED)
|
||||
if (OpenCV_FOUND)
|
||||
message(STATUS "OpenCV_LIBS: ${OpenCV_LIBS}")
|
||||
message(STATUS "OpenCV_INCLUDE_DIRS: ${OpenCV_INCLUDE_DIRS}")
|
||||
else ()
|
||||
message(FATAL_ERROR "opencv Not Found!")
|
||||
endif (OpenCV_FOUND)
|
||||
|
||||
# openmp
|
||||
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fopenmp")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fopenmp")
|
||||
|
||||
if (DEFINED ANDROID_NDK_MAJOR AND ${ANDROID_NDK_MAJOR} GREATER 20)
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -static-openmp")
|
||||
endif ()
|
||||
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fomit-frame-pointer -fstrict-aliasing -ffast-math")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fomit-frame-pointer -fstrict-aliasing -ffast-math")
|
||||
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden -fvisibility-inlines-hidden")
|
||||
|
||||
# disable rtti and exceptions
|
||||
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti -fno-exceptions")
|
||||
|
||||
include_directories(include)
|
||||
file(GLOB OCR_SRC src/*.cpp)
|
||||
set(OCR_COMPILE_CODE ${OCR_SRC})
|
||||
|
||||
add_library(RapidOcr SHARED ${OCR_COMPILE_CODE})
|
||||
|
||||
find_library( # Sets the name of the path variable.
|
||||
log-lib
|
||||
log)
|
||||
|
||||
find_library(
|
||||
android-lib
|
||||
android
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
RapidOcr
|
||||
${OnnxRuntime_LIBS}
|
||||
${OpenCV_LIBS}
|
||||
android
|
||||
z
|
||||
${log-lib}
|
||||
${android-lib}
|
||||
jnigraphics)
|
||||
40
libs/rapidocr/src/main/cpp/include/AngleNet.h
Normal file
40
libs/rapidocr/src/main/cpp/include/AngleNet.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#ifndef __OCR_ANGLENET_H__
|
||||
#define __OCR_ANGLENET_H__
|
||||
|
||||
#include "OcrStruct.h"
|
||||
#include "onnxruntime/core/session/onnxruntime_cxx_api.h"
|
||||
#include <opencv2/core.hpp>
|
||||
#include <android/asset_manager.h>
|
||||
#include <android/asset_manager_jni.h>
|
||||
|
||||
class AngleNet {
|
||||
public:
|
||||
AngleNet();
|
||||
|
||||
~AngleNet();
|
||||
|
||||
void setNumThread(int numOfThread);
|
||||
|
||||
void initModel(AAssetManager *mgr, const std::string &name);
|
||||
|
||||
std::vector<Angle> getAngles(std::vector<cv::Mat> &partImgs, bool doAngle, bool mostAngle);
|
||||
|
||||
private:
|
||||
Ort::Session *session;
|
||||
Ort::Env ortEnv = Ort::Env(ORT_LOGGING_LEVEL_ERROR, "AngleNet");
|
||||
Ort::SessionOptions sessionOptions = Ort::SessionOptions();
|
||||
int numThread = 0;
|
||||
|
||||
std::vector<Ort::AllocatedStringPtr> inputNamesPtr;
|
||||
std::vector<Ort::AllocatedStringPtr> outputNamesPtr;
|
||||
|
||||
const float meanValues[3] = {127.5, 127.5, 127.5};
|
||||
const float normValues[3] = {1.0 / 127.5, 1.0 / 127.5, 1.0 / 127.5};
|
||||
const int dstWidth = 192;
|
||||
const int dstHeight = 48;
|
||||
|
||||
Angle getAngle(cv::Mat &src);
|
||||
};
|
||||
|
||||
|
||||
#endif //__OCR_ANGLENET_H__
|
||||
13
libs/rapidocr/src/main/cpp/include/BitmapUtils.h
Normal file
13
libs/rapidocr/src/main/cpp/include/BitmapUtils.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#ifndef __OCR_LITE_BITMAP_UTILS_H__
|
||||
#define __OCR_LITE_BITMAP_UTILS_H__
|
||||
|
||||
#include <jni.h>
|
||||
#include "opencv2/core.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
|
||||
void bitmapToMat(JNIEnv *env, jobject bitmap, cv::Mat &dst);
|
||||
|
||||
void matToBitmap(JNIEnv *env, cv::Mat &src, jobject bitmap);
|
||||
|
||||
|
||||
#endif //__OCR_LITE_BITMAP_UTILS_H__
|
||||
45
libs/rapidocr/src/main/cpp/include/CrnnNet.h
Normal file
45
libs/rapidocr/src/main/cpp/include/CrnnNet.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#ifndef __OCR_CRNNNET_H__
|
||||
#define __OCR_CRNNNET_H__
|
||||
|
||||
#include "OcrStruct.h"
|
||||
#include "onnxruntime/core/session/onnxruntime_cxx_api.h"
|
||||
#include <opencv2/core.hpp>
|
||||
#include <opencv2/imgproc.hpp>
|
||||
#include <android/asset_manager.h>
|
||||
#include <android/asset_manager_jni.h>
|
||||
|
||||
class CrnnNet {
|
||||
public:
|
||||
|
||||
CrnnNet();
|
||||
|
||||
~CrnnNet();
|
||||
|
||||
void setNumThread(int numOfThread);
|
||||
|
||||
void initModel(AAssetManager *mgr, const std::string &name, const std::string &keysName);
|
||||
|
||||
std::vector<TextLine> getTextLines(std::vector<cv::Mat> &partImg);
|
||||
|
||||
private:
|
||||
Ort::Session *session;
|
||||
Ort::Env ortEnv = Ort::Env(ORT_LOGGING_LEVEL_ERROR, "CrnnNet");
|
||||
Ort::SessionOptions sessionOptions = Ort::SessionOptions();
|
||||
int numThread = 0;
|
||||
|
||||
std::vector<Ort::AllocatedStringPtr> inputNamesPtr;
|
||||
std::vector<Ort::AllocatedStringPtr> outputNamesPtr;
|
||||
|
||||
const float meanValues[3] = {127.5, 127.5, 127.5};
|
||||
const float normValues[3] = {1.0 / 127.5, 1.0 / 127.5, 1.0 / 127.5};
|
||||
const int dstHeight = 48;
|
||||
|
||||
std::vector<std::string> keys;
|
||||
|
||||
TextLine scoreToTextLine(const std::vector<float> &outputData, int h, int w);
|
||||
|
||||
TextLine getTextLine(cv::Mat &src);
|
||||
};
|
||||
|
||||
|
||||
#endif //__OCR_CRNNNET_H__
|
||||
38
libs/rapidocr/src/main/cpp/include/DbNet.h
Normal file
38
libs/rapidocr/src/main/cpp/include/DbNet.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#ifndef __OCR_DBNET_H__
|
||||
#define __OCR_DBNET_H__
|
||||
|
||||
#include "OcrStruct.h"
|
||||
#include "onnxruntime/core/session/onnxruntime_cxx_api.h"
|
||||
#include <opencv2/core.hpp>
|
||||
#include <opencv2/imgproc.hpp>
|
||||
#include <android/asset_manager.h>
|
||||
#include <android/asset_manager_jni.h>
|
||||
|
||||
class DbNet {
|
||||
public:
|
||||
DbNet();
|
||||
|
||||
~DbNet();
|
||||
|
||||
void setNumThread(int numOfThread);
|
||||
|
||||
void initModel(AAssetManager *mgr, const std::string &name);
|
||||
|
||||
std::vector<TextBox> getTextBoxes(cv::Mat &src, ScaleParam &s, float boxScoreThresh,
|
||||
float boxThresh, float unClipRatio);
|
||||
|
||||
private:
|
||||
Ort::Session *session;
|
||||
Ort::Env ortEnv = Ort::Env(ORT_LOGGING_LEVEL_ERROR, "DbNet");
|
||||
Ort::SessionOptions sessionOptions = Ort::SessionOptions();
|
||||
int numThread = 0;
|
||||
|
||||
std::vector<Ort::AllocatedStringPtr> inputNamesPtr;
|
||||
std::vector<Ort::AllocatedStringPtr> outputNamesPtr;
|
||||
|
||||
const float meanValues[3] = {0.485 * 255, 0.456 * 255, 0.406 * 255};
|
||||
const float normValues[3] = {1.0 / 0.229 / 255.0, 1.0 / 0.224 / 255.0, 1.0 / 0.225 / 255.0};
|
||||
};
|
||||
|
||||
|
||||
#endif //__OCR_DBNET_H__
|
||||
36
libs/rapidocr/src/main/cpp/include/OcrLite.h
Normal file
36
libs/rapidocr/src/main/cpp/include/OcrLite.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#ifndef __OCR_LITE_H__
|
||||
#define __OCR_LITE_H__
|
||||
|
||||
#include "opencv2/core.hpp"
|
||||
#include "onnxruntime/core/session/onnxruntime_cxx_api.h"
|
||||
#include "OcrStruct.h"
|
||||
#include "DbNet.h"
|
||||
#include "AngleNet.h"
|
||||
#include "CrnnNet.h"
|
||||
|
||||
class OcrLite {
|
||||
public:
|
||||
OcrLite();
|
||||
|
||||
~OcrLite();
|
||||
|
||||
void init(JNIEnv *jniEnv, jobject assetManager, int numOfThread, std::string detName,
|
||||
std::string clsName, std::string recName, std::string keysName);
|
||||
|
||||
//void initLogger(bool isDebug);
|
||||
|
||||
//void Logger(const char *format, ...);
|
||||
|
||||
OcrResult detect(cv::Mat &src, cv::Rect &originRect, ScaleParam &scale,
|
||||
float boxScoreThresh, float boxThresh,
|
||||
float unClipRatio, bool doAngle, bool mostAngle);
|
||||
|
||||
private:
|
||||
bool isLOG = true;
|
||||
DbNet dbNet;
|
||||
AngleNet angleNet;
|
||||
CrnnNet crnnNet;
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
36
libs/rapidocr/src/main/cpp/include/OcrResultUtils.h
Normal file
36
libs/rapidocr/src/main/cpp/include/OcrResultUtils.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#ifndef __OCR_RESULT_UTILS_H__
|
||||
#define __OCR_RESULT_UTILS_H__
|
||||
|
||||
#include <jni.h>
|
||||
#include "OcrStruct.h"
|
||||
|
||||
class OcrResultUtils {
|
||||
public:
|
||||
OcrResultUtils(JNIEnv *env, OcrResult &ocrResult, jobject boxImg);
|
||||
|
||||
~OcrResultUtils();
|
||||
|
||||
jobject getJObject();
|
||||
|
||||
private:
|
||||
JNIEnv *jniEnv;
|
||||
jobject jOcrResult;
|
||||
|
||||
jclass newJListClass();
|
||||
|
||||
jmethodID getListConstructor(jclass clazz);
|
||||
|
||||
jobject getTextBlock(TextBlock &textBlock);
|
||||
|
||||
jobject getTextBlocks(std::vector<TextBlock> &textBlocks);
|
||||
|
||||
jobject newJPoint(cv::Point &point);
|
||||
|
||||
jobject newJBoxPoint(std::vector<cv::Point> &boxPoint);
|
||||
|
||||
jfloatArray newJScoreArray(std::vector<float> &scores);
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif //__OCR_RESULT_UTILS_H__
|
||||
53
libs/rapidocr/src/main/cpp/include/OcrStruct.h
Normal file
53
libs/rapidocr/src/main/cpp/include/OcrStruct.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#ifndef __OCR_STRUCT_H__
|
||||
#define __OCR_STRUCT_H__
|
||||
|
||||
#include "opencv2/core.hpp"
|
||||
#include <vector>
|
||||
|
||||
struct ScaleParam {
|
||||
int srcWidth;
|
||||
int srcHeight;
|
||||
int dstWidth;
|
||||
int dstHeight;
|
||||
float ratioWidth;
|
||||
float ratioHeight;
|
||||
};
|
||||
|
||||
struct TextBox {
|
||||
std::vector<cv::Point> boxPoint;
|
||||
float score;
|
||||
};
|
||||
|
||||
struct Angle {
|
||||
int index;
|
||||
float score;
|
||||
double time;
|
||||
};
|
||||
|
||||
struct TextLine {
|
||||
std::string text;
|
||||
std::vector<float> charScores;
|
||||
double time;
|
||||
};
|
||||
|
||||
struct TextBlock {
|
||||
std::vector<cv::Point> boxPoint;
|
||||
float boxScore;
|
||||
int angleIndex;
|
||||
float angleScore;
|
||||
double angleTime;
|
||||
std::string text;
|
||||
std::vector<float> charScores;
|
||||
double crnnTime;
|
||||
double blockTime;
|
||||
};
|
||||
|
||||
struct OcrResult {
|
||||
double dbNetTime;
|
||||
std::vector<TextBlock> textBlocks;
|
||||
cv::Mat boxImg;
|
||||
double detectTime;
|
||||
std::string strRes;
|
||||
};
|
||||
|
||||
#endif //__OCR_STRUCT_H__
|
||||
99
libs/rapidocr/src/main/cpp/include/OcrUtils.h
Normal file
99
libs/rapidocr/src/main/cpp/include/OcrUtils.h
Normal file
@@ -0,0 +1,99 @@
|
||||
#ifndef __OCR_UTILS_H__
|
||||
#define __OCR_UTILS_H__
|
||||
|
||||
#include <opencv2/core.hpp>
|
||||
#include "OcrStruct.h"
|
||||
#include "onnxruntime/core/session/onnxruntime_cxx_api.h"
|
||||
#include <android/log.h>
|
||||
#include <android/asset_manager.h>
|
||||
#include <android/asset_manager_jni.h>
|
||||
|
||||
#define TAG "OcrLite"
|
||||
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE,TAG,__VA_ARGS__)
|
||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG,__VA_ARGS__)
|
||||
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)
|
||||
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG,__VA_ARGS__)
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG,__VA_ARGS__)
|
||||
|
||||
#define __ENABLE_CONSOLE__ false
|
||||
#define Logger(format, ...) {\
|
||||
if(__ENABLE_CONSOLE__) LOGI(format,##__VA_ARGS__); \
|
||||
}
|
||||
|
||||
template<typename T, typename... Ts>
|
||||
static std::unique_ptr<T> makeUnique(Ts &&... params) {
|
||||
return std::unique_ptr<T>(new T(std::forward<Ts>(params)...));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static double getMean(std::vector<T> &input) {
|
||||
auto sum = accumulate(input.begin(), input.end(), 0.0);
|
||||
return sum / input.size();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static double getStdev(std::vector<T> &input, double mean) {
|
||||
if (input.size() <= 1) return 0;
|
||||
double accum = 0.0;
|
||||
for_each(input.begin(), input.end(), [&](const double d) {
|
||||
accum += (d - mean) * (d - mean);
|
||||
});
|
||||
double stdev = sqrt(accum / (input.size() - 1));
|
||||
return stdev;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline T clamp(T x, T min, T max) {
|
||||
if (x > max)
|
||||
return max;
|
||||
if (x < min)
|
||||
return min;
|
||||
return x;
|
||||
}
|
||||
|
||||
double getCurrentTime();
|
||||
|
||||
ScaleParam getScaleParam(cv::Mat &src, const float scale);
|
||||
|
||||
ScaleParam getScaleParam(cv::Mat &src, const int targetSize);
|
||||
|
||||
cv::RotatedRect getPartRect(std::vector<cv::Point> &box, float scaleWidth, float scaleHeight);
|
||||
|
||||
int getThickness(cv::Mat &boxImg);
|
||||
|
||||
std::vector<cv::Point2f> getBox(const cv::RotatedRect &rect);
|
||||
|
||||
void drawTextBox(cv::Mat &boxImg, cv::RotatedRect &rect, int thickness);
|
||||
|
||||
void drawTextBox(cv::Mat &boxImg, const std::vector<cv::Point> &box, int thickness);
|
||||
|
||||
void drawTextBoxes(cv::Mat &boxImg, std::vector<TextBox> &textBoxes, int thickness);
|
||||
|
||||
cv::Mat matRotateClockWise180(cv::Mat src);
|
||||
|
||||
cv::Mat matRotateClockWise90(cv::Mat src);
|
||||
|
||||
cv::Mat getRotateCropImage(const cv::Mat &src, std::vector<cv::Point> box);
|
||||
|
||||
cv::Mat adjustTargetImg(cv::Mat &src, int dstWidth, int dstHeight);
|
||||
|
||||
std::vector<cv::Point2f> getMinBoxes(const cv::RotatedRect &boxRect, float &maxSideLen);
|
||||
|
||||
float boxScoreFast(const std::vector<cv::Point2f> &boxes, const cv::Mat &pred);
|
||||
|
||||
cv::RotatedRect unClip(std::vector<cv::Point2f> box, float unClipRatio);
|
||||
|
||||
std::vector<float>
|
||||
substractMeanNormalize(cv::Mat &src, const float *meanVals, const float *normVals);
|
||||
|
||||
std::vector<int> getAngleIndexes(std::vector<Angle> &angles);
|
||||
|
||||
std::vector<Ort::AllocatedStringPtr> getInputNames(Ort::Session *session);
|
||||
|
||||
std::vector<Ort::AllocatedStringPtr> getOutputNames(Ort::Session *session);
|
||||
|
||||
void *getModelDataFromAssets(AAssetManager *mgr, const char *modelName, int &size);
|
||||
|
||||
std::string jstringTostring(JNIEnv *env, jstring input);
|
||||
|
||||
#endif //__OCR_UTILS_H__
|
||||
406
libs/rapidocr/src/main/cpp/include/clipper.hpp
Normal file
406
libs/rapidocr/src/main/cpp/include/clipper.hpp
Normal file
@@ -0,0 +1,406 @@
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Author : Angus Johnson *
|
||||
* Version : 6.4.2 *
|
||||
* Date : 27 February 2017 *
|
||||
* Website : http://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2017 *
|
||||
* *
|
||||
* License: *
|
||||
* Use, modification & distribution is subject to Boost Software License Ver 1. *
|
||||
* http://www.boost.org/LICENSE_1_0.txt *
|
||||
* *
|
||||
* Attributions: *
|
||||
* The code in this library is an extension of Bala Vatti's clipping algorithm: *
|
||||
* "A generic solution to polygon clipping" *
|
||||
* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. *
|
||||
* http://portal.acm.org/citation.cfm?id=129906 *
|
||||
* *
|
||||
* Computer graphics and geometric modeling: implementation and algorithms *
|
||||
* By Max K. Agoston *
|
||||
* Springer; 1 edition (January 4, 2005) *
|
||||
* http://books.google.com/books?q=vatti+clipping+agoston *
|
||||
* *
|
||||
* See also: *
|
||||
* "Polygon Offsetting by Computing Winding Numbers" *
|
||||
* Paper no. DETC2005-85513 pp. 565-575 *
|
||||
* ASME 2005 International Design Engineering Technical Conferences *
|
||||
* and Computers and Information in Engineering Conference (IDETC/CIE2005) *
|
||||
* September 24-28, 2005 , Long Beach, California, USA *
|
||||
* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf *
|
||||
* *
|
||||
*******************************************************************************/
|
||||
|
||||
#ifndef clipper_hpp
|
||||
#define clipper_hpp
|
||||
|
||||
#define CLIPPER_VERSION "6.4.2"
|
||||
|
||||
//use_int32: When enabled 32bit ints are used instead of 64bit ints. This
|
||||
//improve performance but coordinate values are limited to the range +/- 46340
|
||||
//#define use_int32
|
||||
|
||||
//use_xyz: adds a Z member to IntPoint. Adds a minor cost to perfomance.
|
||||
//#define use_xyz
|
||||
|
||||
//use_lines: Enables line clipping. Adds a very minor cost to performance.
|
||||
#define use_lines
|
||||
|
||||
//use_deprecated: Enables temporary support for the obsolete functions
|
||||
//#define use_deprecated
|
||||
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <ostream>
|
||||
#include <functional>
|
||||
#include <queue>
|
||||
|
||||
namespace ClipperLib {
|
||||
|
||||
enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor };
|
||||
enum PolyType { ptSubject, ptClip };
|
||||
//By far the most widely used winding rules for polygon filling are
|
||||
//EvenOdd & NonZero (GDI, GDI+, XLib, OpenGL, Cairo, AGG, Quartz, SVG, Gr32)
|
||||
//Others rules include Positive, Negative and ABS_GTR_EQ_TWO (only in OpenGL)
|
||||
//see http://glprogramming.com/red/chapter11.html
|
||||
enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative };
|
||||
|
||||
#ifdef use_int32
|
||||
typedef int cInt;
|
||||
static cInt const loRange = 0x7FFF;
|
||||
static cInt const hiRange = 0x7FFF;
|
||||
#else
|
||||
typedef signed long long cInt;
|
||||
static cInt const loRange = 0x3FFFFFFF;
|
||||
static cInt const hiRange = 0x3FFFFFFFFFFFFFFFLL;
|
||||
typedef signed long long long64; //used by Int128 class
|
||||
typedef unsigned long long ulong64;
|
||||
|
||||
#endif
|
||||
|
||||
struct IntPoint {
|
||||
cInt X;
|
||||
cInt Y;
|
||||
#ifdef use_xyz
|
||||
cInt Z;
|
||||
IntPoint(cInt x = 0, cInt y = 0, cInt z = 0): X(x), Y(y), Z(z) {};
|
||||
#else
|
||||
IntPoint(cInt x = 0, cInt y = 0): X(x), Y(y) {};
|
||||
#endif
|
||||
|
||||
friend inline bool operator== (const IntPoint& a, const IntPoint& b)
|
||||
{
|
||||
return a.X == b.X && a.Y == b.Y;
|
||||
}
|
||||
friend inline bool operator!= (const IntPoint& a, const IntPoint& b)
|
||||
{
|
||||
return a.X != b.X || a.Y != b.Y;
|
||||
}
|
||||
};
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
typedef std::vector< IntPoint > Path;
|
||||
typedef std::vector< Path > Paths;
|
||||
|
||||
inline Path& operator <<(Path& poly, const IntPoint& p) {poly.push_back(p); return poly;}
|
||||
inline Paths& operator <<(Paths& polys, const Path& p) {polys.push_back(p); return polys;}
|
||||
|
||||
std::ostream& operator <<(std::ostream &s, const IntPoint &p);
|
||||
std::ostream& operator <<(std::ostream &s, const Path &p);
|
||||
std::ostream& operator <<(std::ostream &s, const Paths &p);
|
||||
|
||||
struct DoublePoint
|
||||
{
|
||||
double X;
|
||||
double Y;
|
||||
DoublePoint(double x = 0, double y = 0) : X(x), Y(y) {}
|
||||
DoublePoint(IntPoint ip) : X((double)ip.X), Y((double)ip.Y) {}
|
||||
};
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#ifdef use_xyz
|
||||
typedef void (*ZFillCallback)(IntPoint& e1bot, IntPoint& e1top, IntPoint& e2bot, IntPoint& e2top, IntPoint& pt);
|
||||
#endif
|
||||
|
||||
enum InitOptions {ioReverseSolution = 1, ioStrictlySimple = 2, ioPreserveCollinear = 4};
|
||||
enum JoinType {jtSquare, jtRound, jtMiter};
|
||||
enum EndType {etClosedPolygon, etClosedLine, etOpenButt, etOpenSquare, etOpenRound};
|
||||
|
||||
class PolyNode;
|
||||
typedef std::vector< PolyNode* > PolyNodes;
|
||||
|
||||
class PolyNode
|
||||
{
|
||||
public:
|
||||
PolyNode();
|
||||
virtual ~PolyNode(){};
|
||||
Path Contour;
|
||||
PolyNodes Childs;
|
||||
PolyNode* Parent;
|
||||
PolyNode* GetNext() const;
|
||||
bool IsHole() const;
|
||||
bool IsOpen() const;
|
||||
int ChildCount() const;
|
||||
private:
|
||||
//PolyNode& operator =(PolyNode& other);
|
||||
unsigned Index; //node index in Parent.Childs
|
||||
bool m_IsOpen;
|
||||
JoinType m_jointype;
|
||||
EndType m_endtype;
|
||||
PolyNode* GetNextSiblingUp() const;
|
||||
void AddChild(PolyNode& child);
|
||||
friend class Clipper; //to access Index
|
||||
friend class ClipperOffset;
|
||||
};
|
||||
|
||||
class PolyTree: public PolyNode
|
||||
{
|
||||
public:
|
||||
~PolyTree(){ Clear(); };
|
||||
PolyNode* GetFirst() const;
|
||||
void Clear();
|
||||
int Total() const;
|
||||
private:
|
||||
//PolyTree& operator =(PolyTree& other);
|
||||
PolyNodes AllNodes;
|
||||
friend class Clipper; //to access AllNodes
|
||||
};
|
||||
|
||||
bool Orientation(const Path &poly);
|
||||
double Area(const Path &poly);
|
||||
int PointInPolygon(const IntPoint &pt, const Path &path);
|
||||
|
||||
void SimplifyPolygon(const Path &in_poly, Paths &out_polys, PolyFillType fillType = pftEvenOdd);
|
||||
void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType = pftEvenOdd);
|
||||
void SimplifyPolygons(Paths &polys, PolyFillType fillType = pftEvenOdd);
|
||||
|
||||
void CleanPolygon(const Path& in_poly, Path& out_poly, double distance = 1.415);
|
||||
void CleanPolygon(Path& poly, double distance = 1.415);
|
||||
void CleanPolygons(const Paths& in_polys, Paths& out_polys, double distance = 1.415);
|
||||
void CleanPolygons(Paths& polys, double distance = 1.415);
|
||||
|
||||
void MinkowskiSum(const Path& pattern, const Path& path, Paths& solution, bool pathIsClosed);
|
||||
void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, bool pathIsClosed);
|
||||
void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution);
|
||||
|
||||
void PolyTreeToPaths(const PolyTree& polytree, Paths& paths);
|
||||
void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths);
|
||||
void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths);
|
||||
|
||||
void ReversePath(Path& p);
|
||||
void ReversePaths(Paths& p);
|
||||
|
||||
struct IntRect { cInt left; cInt top; cInt right; cInt bottom; };
|
||||
|
||||
//enums that are used internally ...
|
||||
enum EdgeSide { esLeft = 1, esRight = 2};
|
||||
|
||||
//forward declarations (for stuff used internally) ...
|
||||
struct TEdge;
|
||||
struct IntersectNode;
|
||||
struct LocalMinimum;
|
||||
struct OutPt;
|
||||
struct OutRec;
|
||||
struct Join;
|
||||
|
||||
typedef std::vector < OutRec* > PolyOutList;
|
||||
typedef std::vector < TEdge* > EdgeList;
|
||||
typedef std::vector < Join* > JoinList;
|
||||
typedef std::vector < IntersectNode* > IntersectList;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
//ClipperBase is the ancestor to the Clipper class. It should not be
|
||||
//instantiated directly. This class simply abstracts the conversion of sets of
|
||||
//polygon coordinates into edge objects that are stored in a LocalMinima list.
|
||||
class ClipperBase
|
||||
{
|
||||
public:
|
||||
ClipperBase();
|
||||
virtual ~ClipperBase();
|
||||
virtual bool AddPath(const Path &pg, PolyType PolyTyp, bool Closed);
|
||||
bool AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed);
|
||||
virtual void Clear();
|
||||
IntRect GetBounds();
|
||||
bool PreserveCollinear() {return m_PreserveCollinear;};
|
||||
void PreserveCollinear(bool value) {m_PreserveCollinear = value;};
|
||||
protected:
|
||||
void DisposeLocalMinimaList();
|
||||
TEdge* AddBoundsToLML(TEdge *e, bool IsClosed);
|
||||
virtual void Reset();
|
||||
TEdge* ProcessBound(TEdge* E, bool IsClockwise);
|
||||
void InsertScanbeam(const cInt Y);
|
||||
bool PopScanbeam(cInt &Y);
|
||||
bool LocalMinimaPending();
|
||||
bool PopLocalMinima(cInt Y, const LocalMinimum *&locMin);
|
||||
OutRec* CreateOutRec();
|
||||
void DisposeAllOutRecs();
|
||||
void DisposeOutRec(PolyOutList::size_type index);
|
||||
void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2);
|
||||
void DeleteFromAEL(TEdge *e);
|
||||
void UpdateEdgeIntoAEL(TEdge *&e);
|
||||
|
||||
typedef std::vector<LocalMinimum> MinimaList;
|
||||
MinimaList::iterator m_CurrentLM;
|
||||
MinimaList m_MinimaList;
|
||||
|
||||
bool m_UseFullRange;
|
||||
EdgeList m_edges;
|
||||
bool m_PreserveCollinear;
|
||||
bool m_HasOpenPaths;
|
||||
PolyOutList m_PolyOuts;
|
||||
TEdge *m_ActiveEdges;
|
||||
|
||||
typedef std::priority_queue<cInt> ScanbeamList;
|
||||
ScanbeamList m_Scanbeam;
|
||||
};
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class Clipper : public virtual ClipperBase
|
||||
{
|
||||
public:
|
||||
Clipper(int initOptions = 0);
|
||||
bool Execute(ClipType clipType,
|
||||
Paths &solution,
|
||||
PolyFillType fillType = pftEvenOdd);
|
||||
bool Execute(ClipType clipType,
|
||||
Paths &solution,
|
||||
PolyFillType subjFillType,
|
||||
PolyFillType clipFillType);
|
||||
bool Execute(ClipType clipType,
|
||||
PolyTree &polytree,
|
||||
PolyFillType fillType = pftEvenOdd);
|
||||
bool Execute(ClipType clipType,
|
||||
PolyTree &polytree,
|
||||
PolyFillType subjFillType,
|
||||
PolyFillType clipFillType);
|
||||
bool ReverseSolution() { return m_ReverseOutput; };
|
||||
void ReverseSolution(bool value) {m_ReverseOutput = value;};
|
||||
bool StrictlySimple() {return m_StrictSimple;};
|
||||
void StrictlySimple(bool value) {m_StrictSimple = value;};
|
||||
//set the callback function for z value filling on intersections (otherwise Z is 0)
|
||||
#ifdef use_xyz
|
||||
void ZFillFunction(ZFillCallback zFillFunc);
|
||||
#endif
|
||||
protected:
|
||||
virtual bool ExecuteInternal();
|
||||
private:
|
||||
JoinList m_Joins;
|
||||
JoinList m_GhostJoins;
|
||||
IntersectList m_IntersectList;
|
||||
ClipType m_ClipType;
|
||||
typedef std::list<cInt> MaximaList;
|
||||
MaximaList m_Maxima;
|
||||
TEdge *m_SortedEdges;
|
||||
bool m_ExecuteLocked;
|
||||
PolyFillType m_ClipFillType;
|
||||
PolyFillType m_SubjFillType;
|
||||
bool m_ReverseOutput;
|
||||
bool m_UsingPolyTree;
|
||||
bool m_StrictSimple;
|
||||
#ifdef use_xyz
|
||||
ZFillCallback m_ZFill; //custom callback
|
||||
#endif
|
||||
void SetWindingCount(TEdge& edge);
|
||||
bool IsEvenOddFillType(const TEdge& edge) const;
|
||||
bool IsEvenOddAltFillType(const TEdge& edge) const;
|
||||
void InsertLocalMinimaIntoAEL(const cInt botY);
|
||||
void InsertEdgeIntoAEL(TEdge *edge, TEdge* startEdge);
|
||||
void AddEdgeToSEL(TEdge *edge);
|
||||
bool PopEdgeFromSEL(TEdge *&edge);
|
||||
void CopyAELToSEL();
|
||||
void DeleteFromSEL(TEdge *e);
|
||||
void SwapPositionsInSEL(TEdge *edge1, TEdge *edge2);
|
||||
bool IsContributing(const TEdge& edge) const;
|
||||
bool IsTopHorz(const cInt XPos);
|
||||
void DoMaxima(TEdge *e);
|
||||
void ProcessHorizontals();
|
||||
void ProcessHorizontal(TEdge *horzEdge);
|
||||
void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt);
|
||||
OutPt* AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt);
|
||||
OutRec* GetOutRec(int idx);
|
||||
void AppendPolygon(TEdge *e1, TEdge *e2);
|
||||
void IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &pt);
|
||||
OutPt* AddOutPt(TEdge *e, const IntPoint &pt);
|
||||
OutPt* GetLastOutPt(TEdge *e);
|
||||
bool ProcessIntersections(const cInt topY);
|
||||
void BuildIntersectList(const cInt topY);
|
||||
void ProcessIntersectList();
|
||||
void ProcessEdgesAtTopOfScanbeam(const cInt topY);
|
||||
void BuildResult(Paths& polys);
|
||||
void BuildResult2(PolyTree& polytree);
|
||||
void SetHoleState(TEdge *e, OutRec *outrec);
|
||||
void DisposeIntersectNodes();
|
||||
bool FixupIntersectionOrder();
|
||||
void FixupOutPolygon(OutRec &outrec);
|
||||
void FixupOutPolyline(OutRec &outrec);
|
||||
bool IsHole(TEdge *e);
|
||||
bool FindOwnerFromSplitRecs(OutRec &outRec, OutRec *&currOrfl);
|
||||
void FixHoleLinkage(OutRec &outrec);
|
||||
void AddJoin(OutPt *op1, OutPt *op2, const IntPoint offPt);
|
||||
void ClearJoins();
|
||||
void ClearGhostJoins();
|
||||
void AddGhostJoin(OutPt *op, const IntPoint offPt);
|
||||
bool JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2);
|
||||
void JoinCommonEdges();
|
||||
void DoSimplePolygons();
|
||||
void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec);
|
||||
void FixupFirstLefts2(OutRec* InnerOutRec, OutRec* OuterOutRec);
|
||||
void FixupFirstLefts3(OutRec* OldOutRec, OutRec* NewOutRec);
|
||||
#ifdef use_xyz
|
||||
void SetZ(IntPoint& pt, TEdge& e1, TEdge& e2);
|
||||
#endif
|
||||
};
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class ClipperOffset
|
||||
{
|
||||
public:
|
||||
ClipperOffset(double miterLimit = 2.0, double roundPrecision = 0.25);
|
||||
~ClipperOffset();
|
||||
void AddPath(const Path& path, JoinType joinType, EndType endType);
|
||||
void AddPaths(const Paths& paths, JoinType joinType, EndType endType);
|
||||
void Execute(Paths& solution, double delta);
|
||||
void Execute(PolyTree& solution, double delta);
|
||||
void Clear();
|
||||
double MiterLimit;
|
||||
double ArcTolerance;
|
||||
private:
|
||||
Paths m_destPolys;
|
||||
Path m_srcPoly;
|
||||
Path m_destPoly;
|
||||
std::vector<DoublePoint> m_normals;
|
||||
double m_delta, m_sinA, m_sin, m_cos;
|
||||
double m_miterLim, m_StepsPerRad;
|
||||
IntPoint m_lowest;
|
||||
PolyNode m_polyNodes;
|
||||
|
||||
void FixOrientations();
|
||||
void DoOffset(double delta);
|
||||
void OffsetPoint(int j, int& k, JoinType jointype);
|
||||
void DoSquare(int j, int k);
|
||||
void DoMiter(int j, int k, double r);
|
||||
void DoRound(int j, int k);
|
||||
};
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class clipperException : public std::exception
|
||||
{
|
||||
public:
|
||||
clipperException(const char* description): m_descr(description) {}
|
||||
virtual ~clipperException() throw() {}
|
||||
virtual const char* what() const throw() {return m_descr.c_str();}
|
||||
private:
|
||||
std::string m_descr;
|
||||
};
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
} //ClipperLib namespace
|
||||
|
||||
#endif //clipper_hpp
|
||||
|
||||
|
||||
126
libs/rapidocr/src/main/cpp/src/AngleNet.cpp
Normal file
126
libs/rapidocr/src/main/cpp/src/AngleNet.cpp
Normal file
@@ -0,0 +1,126 @@
|
||||
#include "AngleNet.h"
|
||||
#include "OcrUtils.h"
|
||||
#include <numeric>
|
||||
|
||||
AngleNet::AngleNet() {}
|
||||
|
||||
AngleNet::~AngleNet() {
|
||||
delete session;
|
||||
inputNamesPtr.clear();
|
||||
outputNamesPtr.clear();
|
||||
}
|
||||
|
||||
void AngleNet::setNumThread(int numOfThread) {
|
||||
numThread = numOfThread;
|
||||
//===session options===
|
||||
// Sets the number of threads used to parallelize the execution within nodes
|
||||
// A value of 0 means ORT will pick a default
|
||||
sessionOptions.SetIntraOpNumThreads(numThread);
|
||||
//set OMP_NUM_THREADS=16
|
||||
|
||||
// Sets the number of threads used to parallelize the execution of the graph (across nodes)
|
||||
// If sequential execution is enabled this value is ignored
|
||||
// A value of 0 means ORT will pick a default
|
||||
sessionOptions.SetInterOpNumThreads(numThread);
|
||||
|
||||
// Sets graph optimization level
|
||||
// ORT_DISABLE_ALL -> To disable all optimizations
|
||||
// ORT_ENABLE_BASIC -> To enable basic optimizations (Such as redundant node removals)
|
||||
// ORT_ENABLE_EXTENDED -> To enable extended optimizations (Includes level 1 + more complex optimizations like node fusions)
|
||||
// ORT_ENABLE_ALL -> To Enable All possible opitmizations
|
||||
sessionOptions.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_EXTENDED);
|
||||
}
|
||||
|
||||
void AngleNet::initModel(AAssetManager *mgr, const std::string &name) {
|
||||
int dbModelDataLength = 0;
|
||||
void *dbModelData = getModelDataFromAssets(mgr, name.c_str(), dbModelDataLength);
|
||||
session = new Ort::Session(ortEnv, dbModelData, dbModelDataLength,
|
||||
sessionOptions);
|
||||
free(dbModelData);
|
||||
inputNamesPtr = getInputNames(session);
|
||||
outputNamesPtr = getOutputNames(session);
|
||||
}
|
||||
|
||||
Angle scoreToAngle(const std::vector<float> &outputData) {
|
||||
int maxIndex = 0;
|
||||
float maxScore = 0;
|
||||
for (int i = 0; i < outputData.size(); i++) {
|
||||
if (outputData[i] > maxScore) {
|
||||
maxScore = outputData[i];
|
||||
maxIndex = i;
|
||||
}
|
||||
}
|
||||
return {maxIndex, maxScore};
|
||||
}
|
||||
|
||||
Angle AngleNet::getAngle(cv::Mat &src) {
|
||||
|
||||
std::vector<float> inputTensorValues = substractMeanNormalize(src, meanValues, normValues);
|
||||
|
||||
std::array<int64_t, 4> inputShape{1, src.channels(), src.rows, src.cols};
|
||||
|
||||
auto memoryInfo = Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU);
|
||||
|
||||
Ort::Value inputTensor = Ort::Value::CreateTensor<float>(memoryInfo, inputTensorValues.data(),
|
||||
inputTensorValues.size(),
|
||||
inputShape.data(),
|
||||
inputShape.size());
|
||||
assert(inputTensor.IsTensor());
|
||||
std::vector<const char *> inputNames = {inputNamesPtr.data()->get()};
|
||||
std::vector<const char *> outputNames = {outputNamesPtr.data()->get()};
|
||||
auto outputTensor = session->Run(Ort::RunOptions{nullptr}, inputNames.data(), &inputTensor,
|
||||
inputNames.size(), outputNames.data(), outputNames.size());
|
||||
|
||||
assert(outputTensor.size() == 1 && outputTensor.front().IsTensor());
|
||||
|
||||
std::vector<int64_t> outputShape = outputTensor[0].GetTensorTypeAndShapeInfo().GetShape();
|
||||
|
||||
int64_t outputCount = std::accumulate(outputShape.begin(), outputShape.end(), 1,
|
||||
std::multiplies<int64_t>());
|
||||
|
||||
float *floatArray = outputTensor.front().GetTensorMutableData<float>();
|
||||
std::vector<float> outputData(floatArray, floatArray + outputCount);
|
||||
return scoreToAngle(outputData);
|
||||
}
|
||||
|
||||
std::vector<Angle> AngleNet::getAngles(std::vector<cv::Mat> &partImgs,
|
||||
bool doAngle, bool mostAngle) {
|
||||
int size = partImgs.size();
|
||||
std::vector<Angle> angles(size);
|
||||
if (doAngle) {
|
||||
for (int i = 0; i < size; ++i) {
|
||||
double startAngle = getCurrentTime();
|
||||
auto angleImg = adjustTargetImg(partImgs[i], dstWidth, dstHeight);
|
||||
Angle angle = getAngle(angleImg);
|
||||
double endAngle = getCurrentTime();
|
||||
angle.time = endAngle - startAngle;
|
||||
|
||||
angles[i] = angle;
|
||||
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < size; ++i) {
|
||||
angles[i] = Angle{-1, 0.f};
|
||||
}
|
||||
}
|
||||
//Most Possible AngleIndex
|
||||
if (doAngle && mostAngle) {
|
||||
auto angleIndexes = getAngleIndexes(angles);
|
||||
double sum = std::accumulate(angleIndexes.begin(), angleIndexes.end(), 0.0);
|
||||
double halfPercent = angles.size() / 2.0f;
|
||||
int mostAngleIndex;
|
||||
if (sum < halfPercent) {//all angle set to 0
|
||||
mostAngleIndex = 0;
|
||||
} else {//all angle set to 1
|
||||
mostAngleIndex = 1;
|
||||
}
|
||||
Logger("Set All Angle to mostAngleIndex(%d)", mostAngleIndex);
|
||||
for (int i = 0; i < angles.size(); ++i) {
|
||||
Angle angle = angles[i];
|
||||
angle.index = mostAngleIndex;
|
||||
angles.at(i) = angle;
|
||||
}
|
||||
}
|
||||
|
||||
return angles;
|
||||
}
|
||||
116
libs/rapidocr/src/main/cpp/src/BitmapUtils.cpp
Normal file
116
libs/rapidocr/src/main/cpp/src/BitmapUtils.cpp
Normal file
@@ -0,0 +1,116 @@
|
||||
#include "BitmapUtils.h"
|
||||
#include <android/bitmap.h>
|
||||
#include <OcrUtils.h>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
using namespace cv;
|
||||
|
||||
void bitmapToMat(JNIEnv *env, jobject bitmap, Mat &dst) {
|
||||
AndroidBitmapInfo info;
|
||||
void *pixels = 0;
|
||||
|
||||
try {
|
||||
LOGI("nBitmapToMat");
|
||||
CV_Assert(AndroidBitmap_getInfo(env, bitmap, &info) >= 0);
|
||||
CV_Assert(info.format == ANDROID_BITMAP_FORMAT_RGBA_8888 ||
|
||||
info.format == ANDROID_BITMAP_FORMAT_RGB_565);
|
||||
CV_Assert(AndroidBitmap_lockPixels(env, bitmap, &pixels) >= 0);
|
||||
CV_Assert(pixels);
|
||||
dst.create(info.height, info.width, CV_8UC4);
|
||||
if (info.format == ANDROID_BITMAP_FORMAT_RGBA_8888) {
|
||||
LOGI("nBitmapToMat: RGBA_8888 -> CV_8UC4");
|
||||
Mat tmp(info.height, info.width, CV_8UC4, pixels);
|
||||
//if (needUnPremultiplyAlpha) cvtColor(tmp, dst, COLOR_mRGBA2RGBA);
|
||||
//else
|
||||
tmp.copyTo(dst);
|
||||
} else {
|
||||
// info.format == ANDROID_BITMAP_FORMAT_RGB_565
|
||||
LOGI("nBitmapToMat: RGB_565 -> CV_8UC4");
|
||||
Mat tmp(info.height, info.width, CV_8UC2, pixels);
|
||||
cvtColor(tmp, dst, COLOR_BGR5652RGBA);
|
||||
}
|
||||
AndroidBitmap_unlockPixels(env, bitmap);
|
||||
return;
|
||||
} /*catch (const cv::Exception &e) {
|
||||
AndroidBitmap_unlockPixels(env, bitmap);
|
||||
LOGE("nBitmapToMat caught cv::Exception: %s", e.what());
|
||||
jclass je = env->FindClass("java/lang/Exception");
|
||||
if (!je) je = env->FindClass("java/lang/Exception");
|
||||
env->ThrowNew(je, e.what());
|
||||
return;
|
||||
}*/ catch (...) {
|
||||
AndroidBitmap_unlockPixels(env, bitmap);
|
||||
LOGE("nBitmapToMat caught unknown exception (...)");
|
||||
jclass je = env->FindClass("java/lang/Exception");
|
||||
env->ThrowNew(je, "Unknown exception in JNI code {nBitmapToMat}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void matToBitmap(JNIEnv *env, cv::Mat &src, jobject bitmap) {
|
||||
AndroidBitmapInfo info;
|
||||
void *pixels = 0;
|
||||
|
||||
try {
|
||||
LOGI("nMatToBitmap");
|
||||
CV_Assert(AndroidBitmap_getInfo(env, bitmap, &info) >= 0);
|
||||
CV_Assert(info.format == ANDROID_BITMAP_FORMAT_RGBA_8888 ||
|
||||
info.format == ANDROID_BITMAP_FORMAT_RGB_565);
|
||||
CV_Assert(src.dims == 2);
|
||||
|
||||
// LOGI("info.width: %s", std::to_string(info.width).c_str());
|
||||
// LOGI("info.height: %s", std::to_string(info.height).c_str());
|
||||
// LOGI("src.rows: %s", std::to_string(src.rows).c_str());
|
||||
// LOGI("src.cols: %s", std::to_string(src.cols).c_str());
|
||||
// CV_Assert(info.height == (uint32_t) src.rows);
|
||||
// CV_Assert(info.width == (uint32_t) src.cols);
|
||||
|
||||
CV_Assert(src.type() == CV_8UC1 || src.type() == CV_8UC3 || src.type() == CV_8UC4);
|
||||
CV_Assert(AndroidBitmap_lockPixels(env, bitmap, &pixels) >= 0);
|
||||
CV_Assert(pixels);
|
||||
if (info.format == ANDROID_BITMAP_FORMAT_RGBA_8888) {
|
||||
Mat tmp(info.height, info.width, CV_8UC4, pixels);
|
||||
if (src.type() == CV_8UC1) {
|
||||
LOGI("nMatToBitmap: CV_8UC1 -> RGBA_8888");
|
||||
cvtColor(src, tmp, COLOR_GRAY2RGBA);
|
||||
} else if (src.type() == CV_8UC3) {
|
||||
LOGI("nMatToBitmap: CV_8UC3 -> RGBA_8888");
|
||||
cvtColor(src, tmp, COLOR_RGB2RGBA);
|
||||
} else if (src.type() == CV_8UC4) {
|
||||
LOGI("nMatToBitmap: CV_8UC4 -> RGBA_8888");
|
||||
//if (needPremultiplyAlpha) cvtColor(src, tmp, COLOR_RGBA2mRGBA);
|
||||
//else
|
||||
src.copyTo(tmp);
|
||||
}
|
||||
} else {
|
||||
// info.format == ANDROID_BITMAP_FORMAT_RGB_565
|
||||
Mat tmp(info.height, info.width, CV_8UC2, pixels);
|
||||
if (src.type() == CV_8UC1) {
|
||||
LOGI("nMatToBitmap: CV_8UC1 -> RGB_565");
|
||||
cvtColor(src, tmp, COLOR_GRAY2BGR565);
|
||||
} else if (src.type() == CV_8UC3) {
|
||||
LOGI("nMatToBitmap: CV_8UC3 -> RGB_565");
|
||||
cvtColor(src, tmp, COLOR_RGB2BGR565);
|
||||
} else if (src.type() == CV_8UC4) {
|
||||
LOGI("nMatToBitmap: CV_8UC4 -> RGB_565");
|
||||
cvtColor(src, tmp, COLOR_RGBA2BGR565);
|
||||
}
|
||||
}
|
||||
AndroidBitmap_unlockPixels(env, bitmap);
|
||||
return;
|
||||
} /*catch (const cv::Exception &e) {
|
||||
AndroidBitmap_unlockPixels(env, bitmap);
|
||||
LOGE("nMatToBitmap caught cv::Exception: %s", e.what());
|
||||
jclass je = env->FindClass("java/lang/Exception");
|
||||
if (!je) je = env->FindClass("java/lang/Exception");
|
||||
env->ThrowNew(je, e.what());
|
||||
return;
|
||||
}*/ catch (...) {
|
||||
AndroidBitmap_unlockPixels(env, bitmap);
|
||||
LOGE("nMatToBitmap caught unknown exception (...)");
|
||||
jclass je = env->FindClass("java/lang/Exception");
|
||||
env->ThrowNew(je, "Unknown exception in JNI code {nMatToBitmap}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
168
libs/rapidocr/src/main/cpp/src/CrnnNet.cpp
Normal file
168
libs/rapidocr/src/main/cpp/src/CrnnNet.cpp
Normal file
@@ -0,0 +1,168 @@
|
||||
#include "CrnnNet.h"
|
||||
#include "OcrUtils.h"
|
||||
#include <numeric>
|
||||
|
||||
CrnnNet::CrnnNet() {}
|
||||
|
||||
CrnnNet::~CrnnNet() {
|
||||
delete session;
|
||||
inputNamesPtr.clear();
|
||||
outputNamesPtr.clear();
|
||||
}
|
||||
|
||||
void CrnnNet::setNumThread(int numOfThread) {
|
||||
numThread = numOfThread;
|
||||
//===session options===
|
||||
// Sets the number of threads used to parallelize the execution within nodes
|
||||
// A value of 0 means ORT will pick a default
|
||||
sessionOptions.SetIntraOpNumThreads(numThread);
|
||||
//set OMP_NUM_THREADS=16
|
||||
|
||||
// Sets the number of threads used to parallelize the execution of the graph (across nodes)
|
||||
// If sequential execution is enabled this value is ignored
|
||||
// A value of 0 means ORT will pick a default
|
||||
sessionOptions.SetInterOpNumThreads(numThread);
|
||||
|
||||
// Sets graph optimization level
|
||||
// ORT_DISABLE_ALL -> To disable all optimizations
|
||||
// ORT_ENABLE_BASIC -> To enable basic optimizations (Such as redundant node removals)
|
||||
// ORT_ENABLE_EXTENDED -> To enable extended optimizations (Includes level 1 + more complex optimizations like node fusions)
|
||||
// ORT_ENABLE_ALL -> To Enable All possible opitmizations
|
||||
sessionOptions.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_EXTENDED);
|
||||
}
|
||||
|
||||
char *readKeysFromAssets(AAssetManager *mgr, const std::string &keysName) {
|
||||
//LOGI("readKeysFromAssets start...");
|
||||
if (mgr == NULL) {
|
||||
LOGE(" %s", "AAssetManager==NULL");
|
||||
return NULL;
|
||||
}
|
||||
char *buffer;
|
||||
/*获取文件名并打开*/
|
||||
AAsset *asset = AAssetManager_open(mgr, keysName.c_str(), AASSET_MODE_UNKNOWN);
|
||||
if (asset == NULL) {
|
||||
LOGE(" %s", "asset==NULL");
|
||||
return NULL;
|
||||
}
|
||||
/*获取文件大小*/
|
||||
off_t bufferSize = AAsset_getLength(asset);
|
||||
//LOGI("file size : %d", bufferSize);
|
||||
buffer = (char *) malloc(bufferSize + 1);
|
||||
buffer[bufferSize] = 0;
|
||||
int numBytesRead = AAsset_read(asset, buffer, bufferSize);
|
||||
//LOGI("readKeysFromAssets: %d", numBytesRead);
|
||||
/*关闭文件*/
|
||||
AAsset_close(asset);
|
||||
//LOGI("readKeysFromAssets exit...");
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void CrnnNet::initModel(AAssetManager *mgr, const std::string &name, const std::string &keysName) {
|
||||
int dbModelDataLength = 0;
|
||||
void *dbModelData = getModelDataFromAssets(mgr, name.c_str(), dbModelDataLength);
|
||||
session = new Ort::Session(ortEnv, dbModelData, dbModelDataLength,
|
||||
sessionOptions);
|
||||
free(dbModelData);
|
||||
inputNamesPtr = getInputNames(session);
|
||||
outputNamesPtr = getOutputNames(session);
|
||||
|
||||
//load keys
|
||||
char *buffer = readKeysFromAssets(mgr, keysName);
|
||||
if (buffer != NULL) {
|
||||
std::istringstream inStr(buffer);
|
||||
std::string line;
|
||||
while (getline(inStr, line)) {
|
||||
keys.emplace_back(line);
|
||||
}
|
||||
free(buffer);
|
||||
} else {
|
||||
LOGE(" txt file not found");
|
||||
return;
|
||||
}
|
||||
keys.insert(keys.begin(),
|
||||
"#"); // blank char for ctc
|
||||
keys.emplace_back(" ");
|
||||
LOGI("keys size(%zu)", keys.size());
|
||||
}
|
||||
|
||||
template<class ForwardIterator>
|
||||
inline static size_t argmax(ForwardIterator first, ForwardIterator last) {
|
||||
return std::distance(first, std::max_element(first, last));
|
||||
}
|
||||
|
||||
TextLine CrnnNet::scoreToTextLine(const std::vector<float> &outputData, int h, int w) {
|
||||
auto keySize = keys.size();
|
||||
auto dataSize = outputData.size();
|
||||
std::string strRes;
|
||||
std::vector<float> scores;
|
||||
int lastIndex = 0;
|
||||
int maxIndex;
|
||||
float maxValue;
|
||||
|
||||
for (int i = 0; i < h; i++) {
|
||||
int start = i * w;
|
||||
int stop = (i + 1) * w;
|
||||
if (stop > dataSize - 1) {
|
||||
stop = (i + 1) * w - 1;
|
||||
}
|
||||
maxIndex = int(argmax(&outputData[start], &outputData[stop]));
|
||||
maxValue = float(*std::max_element(&outputData[start], &outputData[stop]));
|
||||
|
||||
if (maxIndex > 0 && maxIndex < keySize && (!(i > 0 && maxIndex == lastIndex))) {
|
||||
scores.emplace_back(maxValue);
|
||||
strRes.append(keys[maxIndex]);
|
||||
}
|
||||
lastIndex = maxIndex;
|
||||
}
|
||||
return {strRes, scores};
|
||||
}
|
||||
|
||||
TextLine CrnnNet::getTextLine(cv::Mat &src) {
|
||||
float scale = (float) dstHeight / (float) src.rows;
|
||||
int dstWidth = int((float) src.cols * scale);
|
||||
|
||||
cv::Mat srcResize;
|
||||
resize(src, srcResize, cv::Size(dstWidth, dstHeight));
|
||||
|
||||
std::vector<float> inputTensorValues = substractMeanNormalize(srcResize, meanValues,
|
||||
normValues);
|
||||
|
||||
std::array<int64_t, 4> inputShape{1, srcResize.channels(), srcResize.rows, srcResize.cols};
|
||||
|
||||
auto memoryInfo = Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU);
|
||||
|
||||
Ort::Value inputTensor = Ort::Value::CreateTensor<float>(memoryInfo, inputTensorValues.data(),
|
||||
inputTensorValues.size(),
|
||||
inputShape.data(),
|
||||
inputShape.size());
|
||||
assert(inputTensor.IsTensor());
|
||||
std::vector<const char *> inputNames = {inputNamesPtr.data()->get()};
|
||||
std::vector<const char *> outputNames = {outputNamesPtr.data()->get()};
|
||||
auto outputTensor = session->Run(Ort::RunOptions{nullptr}, inputNames.data(), &inputTensor,
|
||||
inputNames.size(), outputNames.data(), outputNames.size());
|
||||
|
||||
assert(outputTensor.size() == 1 && outputTensor.front().IsTensor());
|
||||
|
||||
std::vector<int64_t> outputShape = outputTensor[0].GetTensorTypeAndShapeInfo().GetShape();
|
||||
|
||||
int64_t outputCount = std::accumulate(outputShape.begin(), outputShape.end(), 1,
|
||||
std::multiplies<int64_t>());
|
||||
|
||||
float *floatArray = outputTensor.front().GetTensorMutableData<float>();
|
||||
std::vector<float> outputData(floatArray, floatArray + outputCount);
|
||||
return scoreToTextLine(outputData, outputShape[1], outputShape[2]);
|
||||
}
|
||||
|
||||
std::vector<TextLine> CrnnNet::getTextLines(std::vector<cv::Mat> &partImg) {
|
||||
int size = partImg.size();
|
||||
std::vector<TextLine> textLines(size);
|
||||
for (int i = 0; i < size; ++i) {
|
||||
//getTextLine
|
||||
double startCrnnTime = getCurrentTime();
|
||||
TextLine textLine = getTextLine(partImg[i]);
|
||||
double endCrnnTime = getCurrentTime();
|
||||
textLine.time = endCrnnTime - startCrnnTime;
|
||||
textLines[i] = textLine;
|
||||
}
|
||||
return textLines;
|
||||
}
|
||||
158
libs/rapidocr/src/main/cpp/src/DbNet.cpp
Normal file
158
libs/rapidocr/src/main/cpp/src/DbNet.cpp
Normal file
@@ -0,0 +1,158 @@
|
||||
#include "DbNet.h"
|
||||
#include "OcrUtils.h"
|
||||
#include <numeric>
|
||||
|
||||
DbNet::DbNet() {}
|
||||
|
||||
DbNet::~DbNet() {
|
||||
delete session;
|
||||
inputNamesPtr.clear();
|
||||
outputNamesPtr.clear();
|
||||
}
|
||||
|
||||
void DbNet::setNumThread(int numOfThread) {
|
||||
numThread = numOfThread;
|
||||
//===session options===
|
||||
// Sets the number of threads used to parallelize the execution within nodes
|
||||
// A value of 0 means ORT will pick a default
|
||||
sessionOptions.SetIntraOpNumThreads(numThread);
|
||||
//set OMP_NUM_THREADS=16
|
||||
|
||||
// Sets the number of threads used to parallelize the execution of the graph (across nodes)
|
||||
// If sequential execution is enabled this value is ignored
|
||||
// A value of 0 means ORT will pick a default
|
||||
sessionOptions.SetInterOpNumThreads(numThread);
|
||||
|
||||
// Sets graph optimization level
|
||||
// ORT_DISABLE_ALL -> To disable all optimizations
|
||||
// ORT_ENABLE_BASIC -> To enable basic optimizations (Such as redundant node removals)
|
||||
// ORT_ENABLE_EXTENDED -> To enable extended optimizations (Includes level 1 + more complex optimizations like node fusions)
|
||||
// ORT_ENABLE_ALL -> To Enable All possible opitmizations
|
||||
sessionOptions.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_EXTENDED);
|
||||
}
|
||||
|
||||
void DbNet::initModel(AAssetManager *mgr, const std::string &name) {
|
||||
int dbModelDataLength = 0;
|
||||
void *dbModelData = getModelDataFromAssets(mgr, name.c_str(), dbModelDataLength);
|
||||
session = new Ort::Session(ortEnv, dbModelData, dbModelDataLength, sessionOptions);
|
||||
free(dbModelData);
|
||||
inputNamesPtr = getInputNames(session);
|
||||
outputNamesPtr = getOutputNames(session);
|
||||
}
|
||||
|
||||
std::vector<TextBox> findRsBoxes(const cv::Mat &predMat, const cv::Mat &dilateMat, ScaleParam &s,
|
||||
const float boxScoreThresh, const float unClipRatio) {
|
||||
const int longSideThresh = 3;//minBox 长边门限
|
||||
const int maxCandidates = 1000;
|
||||
|
||||
std::vector<std::vector<cv::Point>> contours;
|
||||
std::vector<cv::Vec4i> hierarchy;
|
||||
|
||||
cv::findContours(dilateMat, contours, hierarchy, cv::RETR_LIST,
|
||||
cv::CHAIN_APPROX_SIMPLE);
|
||||
|
||||
int numContours = contours.size() >= maxCandidates ? maxCandidates : contours.size();
|
||||
|
||||
std::vector<TextBox> rsBoxes;
|
||||
|
||||
for (int i = 0; i < numContours; i++) {
|
||||
if (contours[i].size() <= 2) {
|
||||
continue;
|
||||
}
|
||||
cv::RotatedRect minAreaRect = cv::minAreaRect(contours[i]);
|
||||
|
||||
float longSide;
|
||||
std::vector<cv::Point2f> minBoxes = getMinBoxes(minAreaRect, longSide);
|
||||
|
||||
if (longSide < longSideThresh) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float boxScore = boxScoreFast(minBoxes, predMat);
|
||||
if (boxScore < boxScoreThresh)
|
||||
continue;
|
||||
|
||||
//-----unClip-----
|
||||
cv::RotatedRect clipRect = unClip(minBoxes, unClipRatio);
|
||||
if (clipRect.size.height < 1.001 && clipRect.size.width < 1.001) {
|
||||
continue;
|
||||
}
|
||||
//-----unClip-----
|
||||
|
||||
std::vector<cv::Point2f> clipMinBoxes = getMinBoxes(clipRect, longSide);
|
||||
if (longSide < longSideThresh + 2)
|
||||
continue;
|
||||
|
||||
std::vector<cv::Point> intClipMinBoxes;
|
||||
|
||||
for (int p = 0; p < clipMinBoxes.size(); p++) {
|
||||
float x = clipMinBoxes[p].x / s.ratioWidth;
|
||||
float y = clipMinBoxes[p].y / s.ratioHeight;
|
||||
int ptX = (std::min)((std::max)(int(x), 0), s.srcWidth - 1);
|
||||
int ptY = (std::min)((std::max)(int(y), 0), s.srcHeight - 1);
|
||||
cv::Point point{ptX, ptY};
|
||||
intClipMinBoxes.push_back(point);
|
||||
}
|
||||
rsBoxes.push_back(TextBox{intClipMinBoxes, boxScore});
|
||||
}
|
||||
reverse(rsBoxes.begin(), rsBoxes.end());
|
||||
return rsBoxes;
|
||||
}
|
||||
|
||||
std::vector<TextBox>
|
||||
DbNet::getTextBoxes(cv::Mat &src, ScaleParam &s, float boxScoreThresh, float boxThresh,
|
||||
float unClipRatio) {
|
||||
cv::Mat srcResize;
|
||||
resize(src, srcResize, cv::Size(s.dstWidth, s.dstHeight));
|
||||
std::vector<float> inputTensorValues = substractMeanNormalize(srcResize, meanValues,
|
||||
normValues);
|
||||
|
||||
std::array<int64_t, 4> inputShape{1, srcResize.channels(), srcResize.rows, srcResize.cols};
|
||||
|
||||
auto memoryInfo = Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU);
|
||||
|
||||
Ort::Value inputTensor = Ort::Value::CreateTensor<float>(memoryInfo, inputTensorValues.data(),
|
||||
inputTensorValues.size(),
|
||||
inputShape.data(),
|
||||
inputShape.size());
|
||||
assert(inputTensor.IsTensor());
|
||||
std::vector<const char *> inputNames = {inputNamesPtr.data()->get()};
|
||||
std::vector<const char *> outputNames = {outputNamesPtr.data()->get()};
|
||||
auto outputTensor = session->Run(Ort::RunOptions{nullptr}, inputNames.data(), &inputTensor,
|
||||
inputNames.size(), outputNames.data(), outputNames.size());
|
||||
assert(outputTensor.size() == 1 && outputTensor.front().IsTensor());
|
||||
std::vector<int64_t> outputShape = outputTensor[0].GetTensorTypeAndShapeInfo().GetShape();
|
||||
int64_t outputCount = std::accumulate(outputShape.begin(), outputShape.end(), 1,
|
||||
std::multiplies<int64_t>());
|
||||
float *floatArray = outputTensor.front().GetTensorMutableData<float>();
|
||||
std::vector<float> outputData(floatArray, floatArray + outputCount);
|
||||
|
||||
//-----Data preparation-----
|
||||
int outHeight = outputShape[2];
|
||||
int outWidth = outputShape[3];
|
||||
int area = outHeight * outWidth;
|
||||
|
||||
std::vector<float> predData(area, 0.0);
|
||||
std::vector<unsigned char> cbufData(area, ' ');
|
||||
|
||||
for (int i = 0; i < area; i++) {
|
||||
predData[i] = float(outputData[i]);
|
||||
cbufData[i] = (unsigned char) ((outputData[i]) * 255);
|
||||
}
|
||||
|
||||
cv::Mat predMat(outHeight, outWidth, CV_32F, (float *) predData.data());
|
||||
cv::Mat cBufMat(outHeight, outWidth, CV_8UC1, (unsigned char *) cbufData.data());
|
||||
|
||||
//-----boxThresh-----
|
||||
const double maxValue = 255;
|
||||
const double threshold = boxThresh * 255;
|
||||
cv::Mat thresholdMat;
|
||||
cv::threshold(cBufMat, thresholdMat, threshold, maxValue, cv::THRESH_BINARY);
|
||||
|
||||
//-----dilate-----
|
||||
cv::Mat dilateMat;
|
||||
cv::Mat dilateElement = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(2, 2));
|
||||
cv::dilate(thresholdMat, dilateMat, dilateElement);
|
||||
|
||||
return findRsBoxes(predMat, dilateMat, s, boxScoreThresh, unClipRatio);
|
||||
}
|
||||
159
libs/rapidocr/src/main/cpp/src/OcrLite.cpp
Normal file
159
libs/rapidocr/src/main/cpp/src/OcrLite.cpp
Normal file
@@ -0,0 +1,159 @@
|
||||
#include <android/asset_manager.h>
|
||||
#include <android/asset_manager_jni.h>
|
||||
#include "OcrLite.h"
|
||||
#include "OcrUtils.h"
|
||||
|
||||
OcrLite::OcrLite() {}
|
||||
|
||||
OcrLite::~OcrLite() {}
|
||||
|
||||
void OcrLite::init(JNIEnv *jniEnv, jobject assetManager, int numThread, std::string detName,
|
||||
std::string clsName, std::string recName, std::string keysName) {
|
||||
AAssetManager *mgr = AAssetManager_fromJava(jniEnv, assetManager);
|
||||
if (mgr == NULL) {
|
||||
LOGE(" %s", "AAssetManager==NULL");
|
||||
}
|
||||
|
||||
Logger("--- Init DbNet ---\n");
|
||||
dbNet.setNumThread(numThread);
|
||||
dbNet.initModel(mgr, detName);
|
||||
|
||||
Logger("--- Init AngleNet ---\n");
|
||||
angleNet.setNumThread(numThread);
|
||||
angleNet.initModel(mgr, clsName);
|
||||
|
||||
Logger("--- Init CrnnNet ---\n");
|
||||
crnnNet.setNumThread(numThread);
|
||||
crnnNet.initModel(mgr, recName, keysName);
|
||||
|
||||
LOGI("初始化完成!");
|
||||
}
|
||||
|
||||
/*void OcrLite::initLogger(bool isDebug) {
|
||||
isLOG = isDebug;
|
||||
}
|
||||
|
||||
void OcrLite::Logger(const char *format, ...) {
|
||||
if (!isLOG) return;
|
||||
char *buffer = (char *) malloc(8192);
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vsprintf(buffer, format, args);
|
||||
va_end(args);
|
||||
if (isLOG) LOGI("%s", buffer);
|
||||
free(buffer);
|
||||
}*/
|
||||
|
||||
std::vector<cv::Mat> getPartImages(cv::Mat &src, std::vector<TextBox> &textBoxes) {
|
||||
std::vector<cv::Mat> partImages;
|
||||
for (int i = 0; i < textBoxes.size(); ++i) {
|
||||
cv::Mat partImg = getRotateCropImage(src, textBoxes[i].boxPoint);
|
||||
partImages.emplace_back(partImg);
|
||||
}
|
||||
return partImages;
|
||||
}
|
||||
|
||||
OcrResult OcrLite::detect(cv::Mat &src, cv::Rect &originRect, ScaleParam &scale,
|
||||
float boxScoreThresh, float boxThresh,
|
||||
float unClipRatio, bool doAngle, bool mostAngle) {
|
||||
|
||||
cv::Mat textBoxPaddingImg = src.clone();
|
||||
int thickness = getThickness(src);
|
||||
|
||||
Logger("=====Start detect=====");
|
||||
Logger("ScaleParam(sw:%d,sh:%d,dw:%d,dh:%d,%f,%f)", scale.srcWidth, scale.srcHeight,
|
||||
scale.dstWidth, scale.dstHeight,
|
||||
scale.ratioWidth, scale.ratioHeight);
|
||||
|
||||
Logger("---------- step: dbNet getTextBoxes ----------");
|
||||
double startTime = getCurrentTime();
|
||||
std::vector<TextBox> textBoxes = dbNet.getTextBoxes(src, scale, boxScoreThresh, boxThresh, unClipRatio);
|
||||
Logger("TextBoxesSize(%zu)", textBoxes.size());
|
||||
double endDbNetTime = getCurrentTime();
|
||||
double dbNetTime = endDbNetTime - startTime;
|
||||
Logger("dbNetTime(%fms)", dbNetTime);
|
||||
|
||||
for (int i = 0; i < textBoxes.size(); ++i) {
|
||||
Logger("TextBox[%d][score(%f),[x: %d, y: %d], [x: %d, y: %d], [x: %d, y: %d], [x: %d, y: %d]]",
|
||||
i,
|
||||
textBoxes[i].score,
|
||||
textBoxes[i].boxPoint[0].x, textBoxes[i].boxPoint[0].y,
|
||||
textBoxes[i].boxPoint[1].x, textBoxes[i].boxPoint[1].y,
|
||||
textBoxes[i].boxPoint[2].x, textBoxes[i].boxPoint[2].y,
|
||||
textBoxes[i].boxPoint[3].x, textBoxes[i].boxPoint[3].y);
|
||||
}
|
||||
|
||||
Logger("---------- step: drawTextBoxes ----------");
|
||||
drawTextBoxes(textBoxPaddingImg, textBoxes, thickness);
|
||||
|
||||
//---------- getPartImages ----------
|
||||
std::vector<cv::Mat> partImages = getPartImages(src, textBoxes);
|
||||
|
||||
Logger("---------- step: angleNet getAngles ----------");
|
||||
std::vector<Angle> angles;
|
||||
angles = angleNet.getAngles(partImages, doAngle, mostAngle);
|
||||
|
||||
//Log Angles
|
||||
for (int i = 0; i < angles.size(); ++i) {
|
||||
Logger("angle[%d][index(%d), score(%f), time(%fms)]", i, angles[i].index, angles[i].score, angles[i].time);
|
||||
}
|
||||
|
||||
//Rotate partImgs
|
||||
for (int i = 0; i < partImages.size(); ++i) {
|
||||
if (angles[i].index == 1) {
|
||||
partImages.at(i) = matRotateClockWise180(partImages[i]);
|
||||
}
|
||||
}
|
||||
|
||||
Logger("---------- step: crnnNet getTextLine ----------");
|
||||
std::vector<TextLine> textLines = crnnNet.getTextLines(partImages);
|
||||
//Log TextLines
|
||||
for (int i = 0; i < textLines.size(); ++i) {
|
||||
Logger("textLine[%d](%s)", i, textLines[i].text.c_str());
|
||||
std::ostringstream txtScores;
|
||||
for (int s = 0; s < textLines[i].charScores.size(); ++s) {
|
||||
if (s == 0) {
|
||||
txtScores << textLines[i].charScores[s];
|
||||
} else {
|
||||
txtScores << " ," << textLines[i].charScores[s];
|
||||
}
|
||||
}
|
||||
Logger("textScores[%d]{%s}", i, std::string(txtScores.str()).c_str());
|
||||
Logger("crnnTime[%d](%fms)", i, textLines[i].time);
|
||||
}
|
||||
|
||||
std::vector<TextBlock> textBlocks;
|
||||
for (int i = 0; i < textLines.size(); ++i) {
|
||||
std::vector<cv::Point> boxPoint = std::vector<cv::Point>(4);
|
||||
int padding = originRect.x;//padding conversion
|
||||
boxPoint[0] = cv::Point(textBoxes[i].boxPoint[0].x - padding, textBoxes[i].boxPoint[0].y - padding);
|
||||
boxPoint[1] = cv::Point(textBoxes[i].boxPoint[1].x - padding, textBoxes[i].boxPoint[1].y - padding);
|
||||
boxPoint[2] = cv::Point(textBoxes[i].boxPoint[2].x - padding, textBoxes[i].boxPoint[2].y - padding);
|
||||
boxPoint[3] = cv::Point(textBoxes[i].boxPoint[3].x - padding, textBoxes[i].boxPoint[3].y - padding);
|
||||
TextBlock textBlock{boxPoint, textBoxes[i].score, angles[i].index, angles[i].score,
|
||||
angles[i].time, textLines[i].text, textLines[i].charScores, textLines[i].time,
|
||||
angles[i].time + textLines[i].time};
|
||||
textBlocks.emplace_back(textBlock);
|
||||
}
|
||||
|
||||
double endTime = getCurrentTime();
|
||||
double fullTime = endTime - startTime;
|
||||
Logger("=====End detect=====");
|
||||
Logger("FullDetectTime(%fms)", fullTime);
|
||||
|
||||
//cropped to original size
|
||||
cv::Mat textBoxImg;
|
||||
if (originRect.x > 0 && originRect.y > 0) {
|
||||
textBoxPaddingImg(originRect).copyTo(textBoxImg);
|
||||
} else {
|
||||
textBoxImg = textBoxPaddingImg;
|
||||
}
|
||||
|
||||
std::string strRes;
|
||||
for (int i = 0; i < textBlocks.size(); ++i) {
|
||||
strRes.append(textBlocks[i].text);
|
||||
strRes.append("\n");
|
||||
}
|
||||
|
||||
return OcrResult{dbNetTime, textBlocks, textBoxImg, fullTime, strRes};
|
||||
}
|
||||
111
libs/rapidocr/src/main/cpp/src/OcrResultUtils.cpp
Normal file
111
libs/rapidocr/src/main/cpp/src/OcrResultUtils.cpp
Normal file
@@ -0,0 +1,111 @@
|
||||
#include <OcrUtils.h>
|
||||
#include "OcrResultUtils.h"
|
||||
|
||||
OcrResultUtils::OcrResultUtils(JNIEnv *env, OcrResult &ocrResult, jobject boxImg) {
|
||||
jniEnv = env;
|
||||
|
||||
jclass jOcrResultClass = env->FindClass("com/benjaminwan/ocrlibrary/OcrResult");
|
||||
|
||||
if (jOcrResultClass == NULL) {
|
||||
LOGE("OcrResult class is null");
|
||||
}
|
||||
|
||||
jmethodID jOcrResultConstructor = env->GetMethodID(jOcrResultClass, "<init>",
|
||||
"(DLjava/util/ArrayList;Landroid/graphics/Bitmap;DLjava/lang/String;)V");
|
||||
|
||||
jobject textBlocks = getTextBlocks(ocrResult.textBlocks);
|
||||
jdouble dbNetTime = (jdouble) ocrResult.dbNetTime;
|
||||
jdouble detectTime = (jdouble) ocrResult.detectTime;
|
||||
jstring jStrRest = jniEnv->NewStringUTF(ocrResult.strRes.c_str());
|
||||
|
||||
jOcrResult = env->NewObject(jOcrResultClass, jOcrResultConstructor, dbNetTime,
|
||||
textBlocks, boxImg, detectTime, jStrRest);
|
||||
}
|
||||
|
||||
OcrResultUtils::~OcrResultUtils() {
|
||||
jniEnv = NULL;
|
||||
}
|
||||
|
||||
jobject OcrResultUtils::getJObject() {
|
||||
return jOcrResult;
|
||||
}
|
||||
|
||||
jclass OcrResultUtils::newJListClass() {
|
||||
jclass clazz = jniEnv->FindClass("java/util/ArrayList");
|
||||
if (clazz == NULL) {
|
||||
LOGE("ArrayList class is null");
|
||||
return NULL;
|
||||
}
|
||||
return clazz;
|
||||
}
|
||||
|
||||
jmethodID OcrResultUtils::getListConstructor(jclass clazz) {
|
||||
jmethodID constructor = jniEnv->GetMethodID(clazz, "<init>", "()V");
|
||||
return constructor;
|
||||
}
|
||||
|
||||
jobject OcrResultUtils::newJPoint(cv::Point &point) {
|
||||
jclass clazz = jniEnv->FindClass("com/benjaminwan/ocrlibrary/Point");
|
||||
if (clazz == NULL) {
|
||||
LOGE("Point class is null");
|
||||
return NULL;
|
||||
}
|
||||
jmethodID constructor = jniEnv->GetMethodID(clazz, "<init>", "(II)V");
|
||||
jobject obj = jniEnv->NewObject(clazz, constructor, point.x, point.y);
|
||||
return obj;
|
||||
}
|
||||
|
||||
jobject OcrResultUtils::newJBoxPoint(std::vector<cv::Point> &boxPoint) {
|
||||
jclass jListClass = newJListClass();
|
||||
jmethodID jListConstructor = getListConstructor(jListClass);
|
||||
jobject jList = jniEnv->NewObject(jListClass, jListConstructor);
|
||||
jmethodID jListAdd = jniEnv->GetMethodID(jListClass, "add", "(Ljava/lang/Object;)Z");
|
||||
|
||||
for (auto point : boxPoint) {
|
||||
jobject jPoint = newJPoint(point);
|
||||
jniEnv->CallBooleanMethod(jList, jListAdd, jPoint);
|
||||
}
|
||||
return jList;
|
||||
}
|
||||
|
||||
jobject OcrResultUtils::getTextBlock(TextBlock &textBlock) {
|
||||
jobject jBoxPint = newJBoxPoint(textBlock.boxPoint);
|
||||
jfloat jBoxScore = (jfloat) textBlock.boxScore;
|
||||
jfloat jAngleScore = (jfloat) textBlock.angleScore;
|
||||
jdouble jAngleTime = (jdouble) textBlock.angleTime;
|
||||
jstring jText = jniEnv->NewStringUTF(textBlock.text.c_str());
|
||||
jobject jCharScores = newJScoreArray(textBlock.charScores);
|
||||
jdouble jCrnnTime = (jdouble) textBlock.crnnTime;
|
||||
jdouble jBlockTime = (jdouble) textBlock.blockTime;
|
||||
jclass clazz = jniEnv->FindClass("com/benjaminwan/ocrlibrary/TextBlock");
|
||||
if (clazz == NULL) {
|
||||
LOGE("TextBlock class is null");
|
||||
return NULL;
|
||||
}
|
||||
jmethodID constructor = jniEnv->GetMethodID(clazz, "<init>",
|
||||
"(Ljava/util/ArrayList;FIFDLjava/lang/String;[FDD)V");
|
||||
jobject obj = jniEnv->NewObject(clazz, constructor, jBoxPint, jBoxScore, textBlock.angleIndex,
|
||||
jAngleScore, jAngleTime, jText, jCharScores, jCrnnTime,
|
||||
jBlockTime);
|
||||
return obj;
|
||||
}
|
||||
|
||||
jobject OcrResultUtils::getTextBlocks(std::vector<TextBlock> &textBlocks) {
|
||||
jclass jListClass = newJListClass();
|
||||
jmethodID jListConstructor = getListConstructor(jListClass);
|
||||
jobject jList = jniEnv->NewObject(jListClass, jListConstructor);
|
||||
jmethodID jListAdd = jniEnv->GetMethodID(jListClass, "add", "(Ljava/lang/Object;)Z");
|
||||
|
||||
for (int i = 0; i < textBlocks.size(); ++i) {
|
||||
auto textBlock = textBlocks[i];
|
||||
jobject jTextBlock = getTextBlock(textBlock);
|
||||
jniEnv->CallBooleanMethod(jList, jListAdd, jTextBlock);
|
||||
}
|
||||
return jList;
|
||||
}
|
||||
|
||||
jfloatArray OcrResultUtils::newJScoreArray(std::vector<float> &scores) {
|
||||
jfloatArray jScores = jniEnv->NewFloatArray(scores.size());
|
||||
jniEnv->SetFloatArrayRegion(jScores, 0, scores.size(), (jfloat *) scores.data());
|
||||
return jScores;
|
||||
}
|
||||
414
libs/rapidocr/src/main/cpp/src/OcrUtils.cpp
Normal file
414
libs/rapidocr/src/main/cpp/src/OcrUtils.cpp
Normal file
@@ -0,0 +1,414 @@
|
||||
#include <opencv2/imgproc.hpp>
|
||||
#include "OcrUtils.h"
|
||||
#include "clipper.hpp"
|
||||
|
||||
double getCurrentTime() {
|
||||
return (static_cast<double>(cv::getTickCount())) / cv::getTickFrequency() * 1000;//单位毫秒
|
||||
}
|
||||
|
||||
ScaleParam getScaleParam(cv::Mat &src, const float scale) {
|
||||
int srcWidth = src.cols;
|
||||
int srcHeight = src.rows;
|
||||
int dstWidth = int((float) srcWidth * scale);
|
||||
int dstHeight = int((float) srcHeight * scale);
|
||||
if (dstWidth % 32 != 0) {
|
||||
dstWidth = (dstWidth / 32 - 1) * 32;
|
||||
dstWidth = (std::max)(dstWidth, 32);
|
||||
}
|
||||
if (dstHeight % 32 != 0) {
|
||||
dstHeight = (dstHeight / 32 - 1) * 32;
|
||||
dstHeight = (std::max)(dstHeight, 32);
|
||||
}
|
||||
float scaleWidth = (float) dstWidth / (float) srcWidth;
|
||||
float scaleHeight = (float) dstHeight / (float) srcHeight;
|
||||
return {srcWidth, srcHeight, dstWidth, dstHeight, scaleWidth, scaleHeight};
|
||||
}
|
||||
|
||||
ScaleParam getScaleParam(cv::Mat &src, const int targetSize) {
|
||||
int srcWidth, srcHeight, dstWidth, dstHeight;
|
||||
srcWidth = dstWidth = src.cols;
|
||||
srcHeight = dstHeight = src.rows;
|
||||
|
||||
float scale = 1.f;
|
||||
if (dstWidth > dstHeight) {
|
||||
scale = (float) targetSize / (float) dstWidth;
|
||||
dstWidth = targetSize;
|
||||
dstHeight = int((float) dstHeight * scale);
|
||||
} else {
|
||||
scale = (float) targetSize / (float) dstHeight;
|
||||
dstHeight = targetSize;
|
||||
dstWidth = int((float) dstWidth * scale);
|
||||
}
|
||||
if (dstWidth % 32 != 0) {
|
||||
dstWidth = (dstWidth / 32 - 1) * 32;
|
||||
dstWidth = (std::max)(dstWidth, 32);
|
||||
}
|
||||
if (dstHeight % 32 != 0) {
|
||||
dstHeight = (dstHeight / 32 - 1) * 32;
|
||||
dstHeight = (std::max)(dstHeight, 32);
|
||||
}
|
||||
float scaleWidth = (float) dstWidth / (float) srcWidth;
|
||||
float scaleHeight = (float) dstHeight / (float) srcHeight;
|
||||
return {srcWidth, srcHeight, dstWidth, dstHeight, scaleWidth, scaleHeight};
|
||||
}
|
||||
|
||||
cv::RotatedRect getPartRect(std::vector<cv::Point> &box, float scaleWidth, float scaleHeight) {
|
||||
cv::RotatedRect rect = cv::minAreaRect(box);
|
||||
int minSize = rect.size.width > rect.size.height ? rect.size.height : rect.size.width;
|
||||
if (rect.size.width > rect.size.height) {
|
||||
rect.size.width = rect.size.width + (float) minSize * scaleWidth;
|
||||
rect.size.height = rect.size.height + (float) minSize * scaleHeight;
|
||||
} else {
|
||||
rect.size.width = rect.size.width + (float) minSize * scaleHeight;
|
||||
rect.size.height = rect.size.height + (float) minSize * scaleWidth;
|
||||
}
|
||||
return rect;
|
||||
}
|
||||
|
||||
int getThickness(cv::Mat &boxImg) {
|
||||
int minSize = boxImg.cols > boxImg.rows ? boxImg.rows : boxImg.cols;
|
||||
int thickness = minSize / 1000 + 2;
|
||||
return thickness;
|
||||
}
|
||||
|
||||
std::vector<cv::Point2f> getBox(const cv::RotatedRect &rect) {
|
||||
cv::Point2f vertices[4];
|
||||
rect.points(vertices);
|
||||
//std::vector<cv::Point2f> ret(4);
|
||||
std::vector<cv::Point2f> ret2(vertices, vertices + sizeof(vertices) / sizeof(vertices[0]));
|
||||
//memcpy(vertices, &ret[0], ret.size() * sizeof(ret[0]));
|
||||
return ret2;
|
||||
}
|
||||
|
||||
void drawTextBox(cv::Mat &boxImg, cv::RotatedRect &rect, int thickness) {
|
||||
cv::Point2f vertices[4];
|
||||
rect.points(vertices);
|
||||
for (int i = 0; i < 4; i++)
|
||||
cv::line(boxImg, vertices[i], vertices[(i + 1) % 4], cv::Scalar(0, 0, 255), thickness);
|
||||
//cv::polylines(srcmat, textpoint, true, cv::Scalar(0, 255, 0), 2);
|
||||
}
|
||||
|
||||
void drawTextBox(cv::Mat &boxImg, const std::vector<cv::Point> &box, int thickness) {
|
||||
auto color = cv::Scalar(0, 0, 255);// B(0) G(0) R(255)
|
||||
cv::line(boxImg, box[0], box[1], color, thickness);
|
||||
cv::line(boxImg, box[1], box[2], color, thickness);
|
||||
cv::line(boxImg, box[2], box[3], color, thickness);
|
||||
cv::line(boxImg, box[3], box[0], color, thickness);
|
||||
}
|
||||
|
||||
void drawTextBoxes(cv::Mat &boxImg, std::vector<TextBox> &textBoxes, int thickness) {
|
||||
for (int i = 0; i < textBoxes.size(); ++i) {
|
||||
drawTextBox(boxImg, textBoxes[i].boxPoint, thickness);
|
||||
}
|
||||
}
|
||||
|
||||
cv::Mat matRotateClockWise180(cv::Mat src) {
|
||||
flip(src, src, 0);
|
||||
flip(src, src, 1);
|
||||
return src;
|
||||
}
|
||||
|
||||
cv::Mat matRotateClockWise90(cv::Mat src) {
|
||||
transpose(src, src);
|
||||
flip(src, src, 1);
|
||||
return src;
|
||||
}
|
||||
|
||||
cv::Mat getRotateCropImage(const cv::Mat &src, std::vector<cv::Point> box) {
|
||||
cv::Mat image;
|
||||
src.copyTo(image);
|
||||
std::vector<cv::Point> points = box;
|
||||
|
||||
int collectX[4] = {box[0].x, box[1].x, box[2].x, box[3].x};
|
||||
int collectY[4] = {box[0].y, box[1].y, box[2].y, box[3].y};
|
||||
int left = int(*std::min_element(collectX, collectX + 4));
|
||||
int right = int(*std::max_element(collectX, collectX + 4));
|
||||
int top = int(*std::min_element(collectY, collectY + 4));
|
||||
int bottom = int(*std::max_element(collectY, collectY + 4));
|
||||
|
||||
cv::Mat imgCrop;
|
||||
image(cv::Rect(left, top, right - left, bottom - top)).copyTo(imgCrop);
|
||||
|
||||
for (int i = 0; i < points.size(); i++) {
|
||||
points[i].x -= left;
|
||||
points[i].y -= top;
|
||||
}
|
||||
|
||||
int imgCropWidth = int(sqrt(pow(points[0].x - points[1].x, 2) +
|
||||
pow(points[0].y - points[1].y, 2)));
|
||||
int imgCropHeight = int(sqrt(pow(points[0].x - points[3].x, 2) +
|
||||
pow(points[0].y - points[3].y, 2)));
|
||||
|
||||
cv::Point2f ptsDst[4];
|
||||
ptsDst[0] = cv::Point2f(0., 0.);
|
||||
ptsDst[1] = cv::Point2f(imgCropWidth, 0.);
|
||||
ptsDst[2] = cv::Point2f(imgCropWidth, imgCropHeight);
|
||||
ptsDst[3] = cv::Point2f(0.f, imgCropHeight);
|
||||
|
||||
cv::Point2f ptsSrc[4];
|
||||
ptsSrc[0] = cv::Point2f(points[0].x, points[0].y);
|
||||
ptsSrc[1] = cv::Point2f(points[1].x, points[1].y);
|
||||
ptsSrc[2] = cv::Point2f(points[2].x, points[2].y);
|
||||
ptsSrc[3] = cv::Point2f(points[3].x, points[3].y);
|
||||
|
||||
cv::Mat M = cv::getPerspectiveTransform(ptsSrc, ptsDst);
|
||||
|
||||
cv::Mat partImg;
|
||||
cv::warpPerspective(imgCrop, partImg, M,
|
||||
cv::Size(imgCropWidth, imgCropHeight),
|
||||
cv::BORDER_REPLICATE);
|
||||
|
||||
if (float(partImg.rows) >= float(partImg.cols) * 1.5) {
|
||||
cv::Mat srcCopy = cv::Mat(partImg.rows, partImg.cols, partImg.depth());
|
||||
cv::transpose(partImg, srcCopy);
|
||||
cv::flip(srcCopy, srcCopy, 0);
|
||||
return srcCopy;
|
||||
} else {
|
||||
return partImg;
|
||||
}
|
||||
}
|
||||
|
||||
cv::Mat adjustTargetImg(cv::Mat &src, int dstWidth, int dstHeight) {
|
||||
cv::Mat srcResize;
|
||||
float scale = (float) dstHeight / (float) src.rows;
|
||||
int angleWidth = int((float) src.cols * scale);
|
||||
cv::resize(src, srcResize, cv::Size(angleWidth, dstHeight));
|
||||
cv::Mat srcFit = cv::Mat(dstHeight, dstWidth, CV_8UC3, cv::Scalar(255, 255, 255));
|
||||
if (angleWidth < dstWidth) {
|
||||
cv::Rect rect(0, 0, srcResize.cols, srcResize.rows);
|
||||
srcResize.copyTo(srcFit(rect));
|
||||
} else {
|
||||
cv::Rect rect(0, 0, dstWidth, dstHeight);
|
||||
srcResize(rect).copyTo(srcFit);
|
||||
}
|
||||
return srcFit;
|
||||
}
|
||||
|
||||
bool cvPointCompare(cv::Point a, cv::Point b) {
|
||||
return a.x < b.x;
|
||||
}
|
||||
|
||||
std::vector<cv::Point2f> getMinBoxes(const cv::RotatedRect &boxRect, float &maxSideLen) {
|
||||
maxSideLen = std::max(boxRect.size.width, boxRect.size.height);
|
||||
std::vector<cv::Point2f> boxPoint = getBox(boxRect);
|
||||
std::sort(boxPoint.begin(), boxPoint.end(), cvPointCompare);
|
||||
int index1, index2, index3, index4;
|
||||
if (boxPoint[1].y > boxPoint[0].y) {
|
||||
index1 = 0;
|
||||
index4 = 1;
|
||||
} else {
|
||||
index1 = 1;
|
||||
index4 = 0;
|
||||
}
|
||||
if (boxPoint[3].y > boxPoint[2].y) {
|
||||
index2 = 2;
|
||||
index3 = 3;
|
||||
} else {
|
||||
index2 = 3;
|
||||
index3 = 2;
|
||||
}
|
||||
std::vector<cv::Point2f> minBox(4);
|
||||
minBox[0] = boxPoint[index1];
|
||||
minBox[1] = boxPoint[index2];
|
||||
minBox[2] = boxPoint[index3];
|
||||
minBox[3] = boxPoint[index4];
|
||||
return minBox;
|
||||
}
|
||||
|
||||
float boxScoreFast(const std::vector<cv::Point2f> &boxes, const cv::Mat &pred) {
|
||||
int width = pred.cols;
|
||||
int height = pred.rows;
|
||||
|
||||
float arrayX[4] = {boxes[0].x, boxes[1].x, boxes[2].x, boxes[3].x};
|
||||
float arrayY[4] = {boxes[0].y, boxes[1].y, boxes[2].y, boxes[3].y};
|
||||
|
||||
int minX = clamp(int(std::floor(*(std::min_element(arrayX, arrayX + 4)))), 0, width - 1);
|
||||
int maxX = clamp(int(std::ceil(*(std::max_element(arrayX, arrayX + 4)))), 0, width - 1);
|
||||
int minY = clamp(int(std::floor(*(std::min_element(arrayY, arrayY + 4)))), 0, height - 1);
|
||||
int maxY = clamp(int(std::ceil(*(std::max_element(arrayY, arrayY + 4)))), 0, height - 1);
|
||||
|
||||
cv::Mat mask = cv::Mat::zeros(maxY - minY + 1, maxX - minX + 1, CV_8UC1);
|
||||
|
||||
cv::Point box[4];
|
||||
box[0] = cv::Point(int(boxes[0].x) - minX, int(boxes[0].y) - minY);
|
||||
box[1] = cv::Point(int(boxes[1].x) - minX, int(boxes[1].y) - minY);
|
||||
box[2] = cv::Point(int(boxes[2].x) - minX, int(boxes[2].y) - minY);
|
||||
box[3] = cv::Point(int(boxes[3].x) - minX, int(boxes[3].y) - minY);
|
||||
const cv::Point *pts[1] = {box};
|
||||
int npts[] = {4};
|
||||
cv::fillPoly(mask, pts, npts, 1, cv::Scalar(1));
|
||||
|
||||
cv::Mat croppedImg;
|
||||
pred(cv::Rect(minX, minY, maxX - minX + 1, maxY - minY + 1))
|
||||
.copyTo(croppedImg);
|
||||
|
||||
auto score = cv::mean(croppedImg, mask)[0];
|
||||
return score;
|
||||
}
|
||||
|
||||
float getContourArea(const std::vector<cv::Point2f> &box, float unClipRatio) {
|
||||
int size = box.size();
|
||||
float area = 0.0f;
|
||||
float dist = 0.0f;
|
||||
for (int i = 0; i < size; i++) {
|
||||
area += box[i].x * box[(i + 1) % size].y -
|
||||
box[i].y * box[(i + 1) % size].x;
|
||||
dist += sqrtf((box[i].x - box[(i + 1) % size].x) *
|
||||
(box[i].x - box[(i + 1) % size].x) +
|
||||
(box[i].y - box[(i + 1) % size].y) *
|
||||
(box[i].y - box[(i + 1) % size].y));
|
||||
}
|
||||
area = fabs(float(area / 2.0));
|
||||
|
||||
return area * unClipRatio / dist;
|
||||
}
|
||||
|
||||
cv::RotatedRect unClip(std::vector<cv::Point2f> box, float unClipRatio) {
|
||||
float distance = getContourArea(box, unClipRatio);
|
||||
|
||||
ClipperLib::ClipperOffset offset;
|
||||
ClipperLib::Path p;
|
||||
p << ClipperLib::IntPoint(int(box[0].x), int(box[0].y))
|
||||
<< ClipperLib::IntPoint(int(box[1].x), int(box[1].y))
|
||||
<< ClipperLib::IntPoint(int(box[2].x), int(box[2].y))
|
||||
<< ClipperLib::IntPoint(int(box[3].x), int(box[3].y));
|
||||
offset.AddPath(p, ClipperLib::jtRound, ClipperLib::etClosedPolygon);
|
||||
|
||||
ClipperLib::Paths soln;
|
||||
offset.Execute(soln, distance);
|
||||
std::vector<cv::Point2f> points;
|
||||
|
||||
for (int j = 0; j < soln.size(); j++) {
|
||||
for (int i = 0; i < soln[soln.size() - 1].size(); i++) {
|
||||
points.emplace_back(soln[j][i].X, soln[j][i].Y);
|
||||
}
|
||||
}
|
||||
cv::RotatedRect res;
|
||||
if (points.empty()) {
|
||||
res = cv::RotatedRect(cv::Point2f(0, 0), cv::Size2f(1, 1), 0);
|
||||
} else {
|
||||
res = cv::minAreaRect(points);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
std::vector<float> substractMeanNormalize(cv::Mat &src, const float *meanVals, const float *normVals) {
|
||||
auto inputTensorSize = src.cols * src.rows * src.channels();
|
||||
std::vector<float> inputTensorValues(inputTensorSize);
|
||||
size_t numChannels = src.channels();
|
||||
size_t imageSize = src.cols * src.rows;
|
||||
|
||||
for (size_t pid = 0; pid < imageSize; pid++) {
|
||||
for (size_t ch = 0; ch < numChannels; ++ch) {
|
||||
float data = (float) (src.data[pid * numChannels + ch] * normVals[ch] - meanVals[ch] * normVals[ch]);
|
||||
inputTensorValues[ch * imageSize + pid] = data;
|
||||
}
|
||||
}
|
||||
return inputTensorValues;
|
||||
}
|
||||
|
||||
std::vector<int> getAngleIndexes(std::vector<Angle> &angles) {
|
||||
std::vector<int> angleIndexes;
|
||||
angleIndexes.reserve(angles.size());
|
||||
for (int i = 0; i < angles.size(); ++i) {
|
||||
angleIndexes.push_back(angles[i].index);
|
||||
}
|
||||
return angleIndexes;
|
||||
}
|
||||
|
||||
std::vector<Ort::AllocatedStringPtr> getInputNames(Ort::Session *session) {
|
||||
Ort::AllocatorWithDefaultOptions allocator;
|
||||
const size_t numInputNodes = session->GetInputCount();
|
||||
|
||||
std::vector<Ort::AllocatedStringPtr> inputNamesPtr;
|
||||
inputNamesPtr.reserve(numInputNodes);
|
||||
std::vector<int64_t> input_node_dims;
|
||||
|
||||
// iterate over all input nodes
|
||||
for (size_t i = 0; i < numInputNodes; i++) {
|
||||
auto inputName = session->GetInputNameAllocated(i, allocator);
|
||||
inputNamesPtr.push_back(std::move(inputName));
|
||||
/*printf("inputName[%zu] = %s\n", i, inputName.get());
|
||||
|
||||
// print input node types
|
||||
auto typeInfo = session->GetInputTypeInfo(i);
|
||||
auto tensorInfo = typeInfo.GetTensorTypeAndShapeInfo();
|
||||
|
||||
ONNXTensorElementDataType type = tensorInfo.GetElementType();
|
||||
printf("inputType[%zu] = %u\n", i, type);
|
||||
|
||||
// print input shapes/dims
|
||||
input_node_dims = tensorInfo.GetShape();
|
||||
printf("Input num_dims = %zu\n", input_node_dims.size());
|
||||
for (size_t j = 0; j < input_node_dims.size(); j++) {
|
||||
printf("Input dim[%zu] = %llu\n",j, input_node_dims[j]);
|
||||
}*/
|
||||
}
|
||||
return inputNamesPtr;
|
||||
}
|
||||
|
||||
std::vector<Ort::AllocatedStringPtr> getOutputNames(Ort::Session *session) {
|
||||
Ort::AllocatorWithDefaultOptions allocator;
|
||||
const size_t numOutputNodes = session->GetOutputCount();
|
||||
|
||||
std::vector<Ort::AllocatedStringPtr> outputNamesPtr;
|
||||
outputNamesPtr.reserve(numOutputNodes);
|
||||
std::vector<int64_t> output_node_dims;
|
||||
|
||||
for (size_t i = 0; i < numOutputNodes; i++) {
|
||||
auto outputName = session->GetOutputNameAllocated(i, allocator);
|
||||
outputNamesPtr.push_back(std::move(outputName));
|
||||
/*printf("outputName[%zu] = %s\n", i, outputName.get());
|
||||
|
||||
// print input node types
|
||||
auto type_info = session->GetOutputTypeInfo(i);
|
||||
auto tensor_info = type_info.GetTensorTypeAndShapeInfo();
|
||||
|
||||
ONNXTensorElementDataType type = tensor_info.GetElementType();
|
||||
printf("outputType[%zu] = %u\n", i, type);
|
||||
|
||||
// print input shapes/dims
|
||||
output_node_dims = tensor_info.GetShape();
|
||||
printf("output num_dims = %zu\n", output_node_dims.size());
|
||||
for (size_t j = 0; j < output_node_dims.size(); j++) {
|
||||
printf("output dim[%zu] = %llu\n",j, output_node_dims[j]);
|
||||
}*/
|
||||
}
|
||||
return outputNamesPtr;
|
||||
}
|
||||
|
||||
void *getModelDataFromAssets(AAssetManager *mgr, const char *modelName, int &size) {
|
||||
if (mgr == NULL) {
|
||||
LOGE(" %s", "AAssetManager==NULL");
|
||||
return NULL;
|
||||
}
|
||||
AAsset *asset = AAssetManager_open(mgr, modelName, AASSET_MODE_UNKNOWN);
|
||||
if (asset == NULL) {
|
||||
LOGE(" %s", "asset==NULL");
|
||||
return NULL;
|
||||
}
|
||||
off_t bufferSize = AAsset_getLength(asset);
|
||||
void *modelData = malloc(bufferSize + 1);
|
||||
size = AAsset_read(asset, modelData, bufferSize);
|
||||
AAsset_close(asset);
|
||||
LOGI("model=%s, numBytesRead=%d", modelName, size);
|
||||
return modelData;
|
||||
}
|
||||
|
||||
std::string jstringTostring(JNIEnv *env, jstring input) {
|
||||
char *str = NULL;
|
||||
jclass clsstring = env->FindClass("java/lang/String");
|
||||
jstring strencode = env->NewStringUTF("utf-8");
|
||||
jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
|
||||
jbyteArray barr = (jbyteArray) env->CallObjectMethod(input, mid, strencode);
|
||||
jsize alen = env->GetArrayLength(barr);
|
||||
jbyte *ba = env->GetByteArrayElements(barr, JNI_FALSE);
|
||||
if (alen > 0) {
|
||||
str = (char *) malloc(alen + 1);
|
||||
memcpy(str, ba, alen);
|
||||
str[alen] = 0;
|
||||
}
|
||||
env->ReleaseByteArrayElements(barr, ba, 0);
|
||||
std::string ret = str;
|
||||
return ret;
|
||||
}
|
||||
4629
libs/rapidocr/src/main/cpp/src/clipper.cpp
Normal file
4629
libs/rapidocr/src/main/cpp/src/clipper.cpp
Normal file
File diff suppressed because it is too large
Load Diff
111
libs/rapidocr/src/main/cpp/src/main.cpp
Normal file
111
libs/rapidocr/src/main/cpp/src/main.cpp
Normal file
@@ -0,0 +1,111 @@
|
||||
#include "OcrResultUtils.h"
|
||||
#include "BitmapUtils.h"
|
||||
#include "OcrLite.h"
|
||||
#include "OcrUtils.h"
|
||||
|
||||
static OcrLite *ocrLite;
|
||||
|
||||
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
|
||||
ocrLite = new OcrLite();
|
||||
return JNI_VERSION_1_4;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNI_OnUnload(JavaVM *vm, void *reserved) {
|
||||
LOGI("Goodbye OcrLite!");
|
||||
delete ocrLite;
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_benjaminwan_ocrlibrary_OcrEngine_init(JNIEnv *env, jobject thiz, jobject assetManager,
|
||||
jint numThread, jstring detName, jstring clsName,
|
||||
jstring recName, jstring keysName) {
|
||||
std::string modelDetName = jstringTostring(env, detName);
|
||||
std::string modelClsName = jstringTostring(env, clsName);
|
||||
std::string modelRecName = jstringTostring(env, recName);
|
||||
std::string modelKeysName = jstringTostring(env, keysName);
|
||||
ocrLite->init(env, assetManager, numThread, modelDetName, modelClsName, modelRecName, modelKeysName);
|
||||
//ocrLite->initLogger(false);
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
cv::Mat makePadding(cv::Mat &src, const int padding) {
|
||||
if (padding <= 0) return src;
|
||||
cv::Scalar paddingScalar = {255, 255, 255};
|
||||
cv::Mat paddingSrc;
|
||||
cv::copyMakeBorder(src, paddingSrc, padding, padding, padding, padding, cv::BORDER_ISOLATED,
|
||||
paddingScalar);
|
||||
return paddingSrc;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jobject JNICALL
|
||||
Java_com_benjaminwan_ocrlibrary_OcrEngine_detect(JNIEnv *env, jobject thiz, jobject input, jobject output,
|
||||
jint padding, jint maxSideLen, jfloat boxScoreThresh, jfloat boxThresh,
|
||||
jfloat unClipRatio, jboolean doAngle, jboolean mostAngle) {
|
||||
Logger("padding(%d),maxSideLen(%d),boxScoreThresh(%f),boxThresh(%f),unClipRatio(%f),doAngle(%d),mostAngle(%d)",
|
||||
padding, maxSideLen, boxScoreThresh, boxThresh, unClipRatio, doAngle, mostAngle);
|
||||
cv::Mat imgRGBA, imgBGR, imgOut;
|
||||
bitmapToMat(env, input, imgRGBA);
|
||||
cv::cvtColor(imgRGBA, imgBGR, cv::COLOR_RGBA2BGR);
|
||||
int originMaxSide = (std::max)(imgBGR.cols, imgBGR.rows);
|
||||
int resize;
|
||||
if (maxSideLen <= 0 || maxSideLen > originMaxSide) {
|
||||
resize = originMaxSide;
|
||||
} else {
|
||||
resize = maxSideLen;
|
||||
}
|
||||
resize += 2*padding;
|
||||
cv::Rect paddingRect(padding, padding, imgBGR.cols, imgBGR.rows);
|
||||
cv::Mat paddingSrc = makePadding(imgBGR, padding);
|
||||
//按比例缩小图像,减少文字分割时间
|
||||
ScaleParam s = getScaleParam(paddingSrc, resize);//例:按长或宽缩放 src.cols=不缩放,src.cols/2=长度缩小一半
|
||||
OcrResult ocrResult = ocrLite->detect(paddingSrc, paddingRect, s, boxScoreThresh, boxThresh,
|
||||
unClipRatio, doAngle, mostAngle);
|
||||
|
||||
cv::cvtColor(ocrResult.boxImg, imgOut, cv::COLOR_BGR2RGBA);
|
||||
matToBitmap(env, imgOut, output);
|
||||
|
||||
return OcrResultUtils(env, ocrResult, output).getJObject();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jdouble JNICALL
|
||||
Java_com_benjaminwan_ocrlibrary_OcrEngine_benchmark(JNIEnv *env, jobject thiz, jobject input,
|
||||
jint loop) {
|
||||
int padding = 50;
|
||||
int paddingRect= 0;
|
||||
float boxScoreThresh = 0.6;
|
||||
float boxThresh = 0.3;
|
||||
float unClipRatio = 2.0;
|
||||
bool doAngle = true;
|
||||
bool mostAngle = true;
|
||||
LOGI("padding(%d),paddingRect(%d),boxScoreThresh(%f),boxThresh(%f),unClipRatio(%f),doAngle(%d),mostAngle(%d)",
|
||||
padding, paddingRect, boxScoreThresh, boxThresh, unClipRatio, doAngle, mostAngle);
|
||||
cv::Mat imgRGBA, imgBGR, imgOut;
|
||||
bitmapToMat(env, input, imgRGBA);
|
||||
cv::cvtColor(imgRGBA, imgBGR, cv::COLOR_RGBA2BGR);
|
||||
cv::Rect originRect(padding, padding, imgBGR.cols, imgBGR.rows);
|
||||
cv::Mat src = makePadding(imgBGR, padding);
|
||||
//按比例缩小图像,减少文字分割时间
|
||||
ScaleParam s = getScaleParam(src, src.cols);//例:按长或宽缩放 src.cols=不缩放,src.cols/2=长度缩小一半
|
||||
|
||||
LOGI("=====warmup=====");
|
||||
OcrResult result = ocrLite->detect(src, originRect, s, boxScoreThresh, boxThresh,
|
||||
unClipRatio, doAngle, mostAngle);
|
||||
LOGI("dbNetTime(%f) detectTime(%f)\n", result.dbNetTime, result.detectTime);
|
||||
double dbTime = 0.0f;
|
||||
double detectTime = 0.0f;
|
||||
int loopCount = loop;
|
||||
for (int i = 0; i < loopCount; ++i) {
|
||||
LOGI("=====loop:%d=====", i + 1);
|
||||
OcrResult ocrResult = ocrLite->detect(src, originRect, s, boxScoreThresh, boxThresh,
|
||||
unClipRatio, doAngle, mostAngle);
|
||||
LOGI("dbNetTime(%f) detectTime(%f)\n", ocrResult.dbNetTime, ocrResult.detectTime);
|
||||
dbTime += ocrResult.dbNetTime;
|
||||
detectTime += ocrResult.detectTime;
|
||||
}
|
||||
LOGI("=====result=====\n");
|
||||
double averageTime = detectTime / loopCount;
|
||||
LOGI("average dbNetTime=%fms, average detectTime=%fms\n", dbTime / loopCount,
|
||||
averageTime);
|
||||
return (jdouble) averageTime;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.benjaminwan.ocrlibrary
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.AssetManager
|
||||
import android.graphics.Bitmap
|
||||
|
||||
class OcrEngine(context: Context) {
|
||||
companion object {
|
||||
const val numThread: Int = 4
|
||||
}
|
||||
|
||||
init {
|
||||
System.loadLibrary("RapidOcr")
|
||||
val ret = init(
|
||||
context.assets, numThread,
|
||||
"models/ch_PP-OCRv3_det_infer.onnx",
|
||||
"models/ch_ppocr_mobile_v2.0_cls_infer.onnx",
|
||||
"models/ch_PP-OCRv3_rec_infer.onnx",
|
||||
"models/ppocr_keys_v1.txt",
|
||||
)
|
||||
if (!ret) throw IllegalArgumentException()
|
||||
}
|
||||
|
||||
var padding: Int = 50
|
||||
var boxScoreThresh: Float = 0.5f
|
||||
var boxThresh: Float = 0.3f
|
||||
var unClipRatio: Float = 1.6f
|
||||
var doAngle: Boolean = true
|
||||
var mostAngle: Boolean = true
|
||||
|
||||
fun detect(input: Bitmap, output: Bitmap, maxSideLen: Int) =
|
||||
detect(
|
||||
input, output, padding, maxSideLen,
|
||||
boxScoreThresh, boxThresh,
|
||||
unClipRatio, doAngle, mostAngle
|
||||
)
|
||||
|
||||
external fun init(
|
||||
assetManager: AssetManager,
|
||||
numThread: Int, detName: String,
|
||||
clsName: String, recName: String, keysName: String
|
||||
): Boolean
|
||||
|
||||
external fun detect(
|
||||
input: Bitmap, output: Bitmap, padding: Int, maxSideLen: Int,
|
||||
boxScoreThresh: Float, boxThresh: Float,
|
||||
unClipRatio: Float, doAngle: Boolean, mostAngle: Boolean
|
||||
): OcrResult
|
||||
|
||||
external fun benchmark(input: Bitmap, loop: Int): Double
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.benjaminwan.ocrlibrary
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
open class OcrOutput
|
||||
|
||||
object OcrStop : OcrOutput()
|
||||
object OcrFailed : OcrOutput()
|
||||
|
||||
@Parcelize
|
||||
data class OcrResult(
|
||||
val dbNetTime: Double,
|
||||
val textBlocks: ArrayList<TextBlock>,
|
||||
var boxImg: Bitmap,
|
||||
var detectTime: Double,
|
||||
var strRes: String
|
||||
) : Parcelable, OcrOutput()
|
||||
|
||||
@Parcelize
|
||||
data class Point(var x: Int, var y: Int) : Parcelable
|
||||
|
||||
@Parcelize
|
||||
data class TextBlock(
|
||||
val boxPoint: ArrayList<Point>, var boxScore: Float,
|
||||
val angleIndex: Int, val angleScore: Float, val angleTime: Double,
|
||||
val text: String, val charScores: FloatArray, val crnnTime: Double,
|
||||
val blockTime: Double
|
||||
) : Parcelable {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as TextBlock
|
||||
|
||||
if (boxPoint != other.boxPoint) return false
|
||||
if (boxScore != other.boxScore) return false
|
||||
if (angleIndex != other.angleIndex) return false
|
||||
if (angleScore != other.angleScore) return false
|
||||
if (angleTime != other.angleTime) return false
|
||||
if (text != other.text) return false
|
||||
if (!charScores.contentEquals(other.charScores)) return false
|
||||
if (crnnTime != other.crnnTime) return false
|
||||
if (blockTime != other.blockTime) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = boxPoint.hashCode()
|
||||
result = 31 * result + boxScore.hashCode()
|
||||
result = 31 * result + angleIndex
|
||||
result = 31 * result + angleScore.hashCode()
|
||||
result = 31 * result + angleTime.hashCode()
|
||||
result = 31 * result + text.hashCode()
|
||||
result = 31 * result + charScores.contentHashCode()
|
||||
result = 31 * result + crnnTime.hashCode()
|
||||
result = 31 * result + blockTime.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.benjaminwan.ocrlibrary
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user