6.6.0 - 内置模块重写; 新增部分模块及方法; 修复高版本安卓系统兼容问题; 优化文件管理器及打包功能体验

This commit is contained in:
SuperMonster003
2024-12-02 15:07:37 +08:00
parent 00171e9235
commit 19fc1bcd76
1256 changed files with 89235 additions and 24130 deletions

View File

@@ -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)
}
}

View File

@@ -0,0 +1 @@
<manifest/>

View 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)

View 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__

View 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__

View 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__

View 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__

View 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

View 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__

View 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__

View 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__

View 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

View 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;
}

View 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;
}
}

View 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;
}

View 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);
}

View 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};
}

View 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;
}

View 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;
}

File diff suppressed because it is too large Load Diff

View 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;
}

View File

@@ -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
}

View File

@@ -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
}
}

View File

@@ -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)
}
}