Browse Source

init

master
divenswu 2 years ago
parent
commit
ea53ad1962
  1. 25
      open-anpr-core/pom.xml
  2. 110
      open-anpr-core/src/main/java/com/visual/open/anpr/core/base/BaseOnnxInfer.java
  3. 8
      open-anpr-core/src/main/java/com/visual/open/anpr/core/base/OpenCVLoader.java
  4. 54
      open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/Clipper.java
  5. 691
      open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/ClipperBase.java
  6. 481
      open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/ClipperOffset.java
  7. 2518
      open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/DefaultClipper.java
  8. 339
      open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/Edge.java
  9. 26
      open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/LongRect.java
  10. 414
      open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/Path.java
  11. 125
      open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/Paths.java
  12. 220
      open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/Point.java
  13. 116
      open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/PolyNode.java
  14. 37
      open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/PolyTree.java
  15. 692
      open-anpr-core/src/main/java/com/visual/open/anpr/core/domain/ImageMat.java
  16. 1
      open-anpr-core/src/main/java/com/visual/open/anpr/core/package-info.java
  17. 122
      open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/AlignUtil.java
  18. 37
      open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/ArrayUtil.java
  19. 59
      open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/CropUtil.java
  20. 128
      open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/JsonUtil.java
  21. 111
      open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/MaskUtil.java
  22. 97
      open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/MatUtil.java
  23. 289
      open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/MathUtil.java
  24. 39
      open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/PointUtil.java
  25. 98
      open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/ReleaseUtil.java
  26. 39
      open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/SigmoidUtil.java
  27. 96
      open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/Similarity.java
  28. 40
      open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/SoftMaxUtil.java
  29. 9
      open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/ThreadUtil.java
  30. BIN
      open-anpr-core/src/main/resources/models/det.onnx

25
open-anpr-core/pom.xml

@ -11,5 +11,30 @@
<artifactId>open-anpr-core</artifactId> <artifactId>open-anpr-core</artifactId>
<dependencies>
<dependency>
<groupId>org.openpnp</groupId>
<artifactId>opencv</artifactId>
</dependency>
<dependency>
<groupId>com.microsoft.onnxruntime</groupId>
<artifactId>onnxruntime</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-math3</artifactId>
</dependency>
</dependencies>
</project> </project>

110
open-anpr-core/src/main/java/com/visual/open/anpr/core/base/BaseOnnxInfer.java

@ -0,0 +1,110 @@
package com.visual.open.anpr.core.base;
import ai.onnxruntime.OrtEnvironment;
import ai.onnxruntime.OrtLoggingLevel;
import ai.onnxruntime.OrtSession;
public abstract class BaseOnnxInfer extends OpenCVLoader{
private OrtEnvironment env;
private String[] inputNames;
private OrtSession[] sessions;
/**
* 构造函数
* @param modelPath
* @param threads
*/
public BaseOnnxInfer(String modelPath, int threads){
try {
this.env = OrtEnvironment.getEnvironment();
OrtSession.SessionOptions opts = new OrtSession.SessionOptions();
opts.setInterOpNumThreads(threads);
opts.setSessionLogLevel(OrtLoggingLevel.ORT_LOGGING_LEVEL_ERROR);
opts.setOptimizationLevel(OrtSession.SessionOptions.OptLevel.BASIC_OPT);
this.sessions = new OrtSession[]{env.createSession(modelPath, opts)};
this.inputNames = new String[]{this.sessions[0].getInputNames().iterator().next()};
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 构造函数
* @param modelPaths
* @param threads
*/
public BaseOnnxInfer(String[] modelPaths, int threads){
try {
OrtEnvironment env = OrtEnvironment.getEnvironment();
OrtSession.SessionOptions opts = new OrtSession.SessionOptions();
opts.setInterOpNumThreads(threads);
opts.setSessionLogLevel(OrtLoggingLevel.ORT_LOGGING_LEVEL_ERROR);
opts.setOptimizationLevel(OrtSession.SessionOptions.OptLevel.BASIC_OPT);
this.inputNames = new String[modelPaths.length];
this.sessions = new OrtSession[modelPaths.length];
for(int i=0; i< modelPaths.length; i++){
OrtSession session = env.createSession(modelPaths[i], opts);
this.sessions[i] = session;
this.inputNames[i] = session.getInputNames().iterator().next();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 获取环境信息
* @return
*/
public OrtEnvironment getEnv() {
return env;
}
/**
* 获取输入端的名称
* @return
*/
public String getInputName() {
return inputNames[0];
}
/**
* 获取session
* @return
*/
public OrtSession getSession() {
return sessions[0];
}
/**
* 获取输入端的名称
* @return
*/
public String[] getInputNames() {
return inputNames;
}
/**
* 获取session
* @return
*/
public OrtSession[] getSessions() {
return sessions;
}
/**
* 关闭服务
*/
public void close(){
try {
if(sessions != null){
for(OrtSession session : sessions){
session.close();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

8
open-anpr-core/src/main/java/com/visual/open/anpr/core/base/OpenCVLoader.java

@ -0,0 +1,8 @@
package com.visual.open.anpr.core.base;
public abstract class OpenCVLoader {
//静态加载动态链接库
static{ nu.pattern.OpenCV.loadShared(); }
}

54
open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/Clipper.java

@ -0,0 +1,54 @@
package com.visual.open.anpr.core.clipper;
import com.visual.open.anpr.core.clipper.Point.LongPoint;
public interface Clipper {
enum ClipType {
INTERSECTION, UNION, DIFFERENCE, XOR
}
enum Direction {
RIGHT_TO_LEFT, LEFT_TO_RIGHT
}
enum EndType {
CLOSED_POLYGON, CLOSED_LINE, OPEN_BUTT, OPEN_SQUARE, OPEN_ROUND
}
enum JoinType {
SQUARE, ROUND, MITER
}
enum PolyFillType {
EVEN_ODD, NON_ZERO, POSITIVE, NEGATIVE
}
enum PolyType {
SUBJECT, CLIP
}
interface ZFillCallback {
void zFill(LongPoint bot1, LongPoint top1, LongPoint bot2, LongPoint top2, LongPoint pt);
}
//InitOptions that can be passed to the constructor ...
int REVERSE_SOLUTION = 1;
int STRICTLY_SIMPLE = 2;
int PRESERVE_COLINEAR = 4;
boolean addPath(Path pg, PolyType polyType, boolean Closed);
boolean addPaths(Paths ppg, PolyType polyType, boolean closed);
void clear();
boolean execute(ClipType clipType, Paths solution);
boolean execute(ClipType clipType, Paths solution, PolyFillType subjFillType, PolyFillType clipFillType);
boolean execute(ClipType clipType, PolyTree polytree);
boolean execute(ClipType clipType, PolyTree polytree, PolyFillType subjFillType, PolyFillType clipFillType);
}

691
open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/ClipperBase.java

@ -0,0 +1,691 @@
package com.visual.open.anpr.core.clipper;
import com.visual.open.anpr.core.clipper.Path.OutRec;
import com.visual.open.anpr.core.clipper.Point.LongPoint;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
public abstract class ClipperBase implements Clipper {
protected class LocalMinima {
long y;
Edge leftBound;
Edge rightBound;
LocalMinima next;
}
protected class Scanbeam {
long y;
Scanbeam next;
}
protected class Maxima {
long x;
Maxima next;
Maxima prev;
}
private static void initEdge(Edge e, Edge eNext, Edge ePrev, LongPoint pt ) {
e.next = eNext;
e.prev = ePrev;
e.setCurrent( new LongPoint( pt ) );
e.outIdx = Edge.UNASSIGNED;
}
private static void initEdge2(Edge e, PolyType polyType ) {
if (e.getCurrent().getY() >= e.next.getCurrent().getY()) {
e.setBot( new LongPoint( e.getCurrent() ) );
e.setTop( new LongPoint( e.next.getCurrent() ) );
}
else {
e.setTop( new LongPoint( e.getCurrent() ) );
e.setBot( new LongPoint( e.next.getCurrent() ) );
}
e.updateDeltaX();
e.polyTyp = polyType;
}
private static void rangeTest( LongPoint Pt ) {
if (Pt.getX() > LOW_RANGE || Pt.getY() > LOW_RANGE || -Pt.getX() > LOW_RANGE || -Pt.getY() > LOW_RANGE) {
if (Pt.getX() > HI_RANGE || Pt.getY() > HI_RANGE || -Pt.getX() > HI_RANGE || -Pt.getY() > HI_RANGE) {
throw new IllegalStateException( "Coordinate outside allowed range" );
}
}
}
private static Edge removeEdge(Edge e ) {
//removes e from double_linked_list (but without removing from memory)
e.prev.next = e.next;
e.next.prev = e.prev;
final Edge result = e.next;
e.prev = null; //flag as removed (see ClipperBase.Clear)
return result;
}
private final static long LOW_RANGE = 0x3FFFFFFF;
private final static long HI_RANGE = 0x3FFFFFFFFFFFFFFFL;
protected LocalMinima minimaList;
protected LocalMinima currentLM;
protected Scanbeam scanbeam;
protected final List<OutRec> polyOuts = new ArrayList<>();
protected Edge activeEdges;
protected boolean hasOpenPaths;
protected final boolean preserveCollinear;
private final static Logger LOGGER = Logger.getLogger( Clipper.class.getName() );
protected ClipperBase( boolean preserveCollinear ) //constructor (nb: no external instantiation)
{
this.preserveCollinear = preserveCollinear;
minimaList = null;
currentLM = null;
hasOpenPaths = false;
}
@Override
public boolean addPath(Path pg, PolyType polyType, boolean Closed ) {
if (!Closed && polyType == PolyType.CLIP) {
throw new IllegalStateException( "AddPath: Open paths must be subject." );
}
int highI = pg.size() - 1;
if (Closed) {
while (highI > 0 && pg.get( highI ).equals( pg.get( 0 ) )) {
--highI;
}
}
while (highI > 0 && pg.get( highI ).equals( pg.get( highI - 1 ) )) {
--highI;
}
if (Closed && highI < 2 || !Closed && highI < 1) {
return false;
}
//create a new edge array ...
final List<Edge> edges = new ArrayList<>( highI + 1 );
for (int i = 0; i <= highI; i++) {
edges.add( new Edge() );
}
boolean IsFlat = true;
//1. Basic (first) edge initialization ...
edges.get( 1 ).setCurrent( new LongPoint( pg.get( 1 ) ) );
rangeTest( pg.get( 0 ) );
rangeTest( pg.get( highI ) );
initEdge( edges.get( 0 ), edges.get( 1 ), edges.get( highI ), pg.get( 0 ) );
initEdge( edges.get( highI ), edges.get( 0 ), edges.get( highI - 1 ), pg.get( highI ) );
for (int i = highI - 1; i >= 1; --i) {
rangeTest( pg.get( i ) );
initEdge( edges.get( i ), edges.get( i + 1 ), edges.get( i - 1 ), pg.get( i ) );
}
Edge eStart = edges.get( 0 );
//2. Remove duplicate vertices, and (when closed) collinear edges ...
Edge e = eStart, eLoopStop = eStart;
for (;;) {
//nb: allows matching start and end points when not Closed ...
if (e.getCurrent().equals( e.next.getCurrent() ) && (Closed || !e.next.equals( eStart ))) {
if (e == e.next) {
break;
}
if (e == eStart) {
eStart = e.next;
}
e = removeEdge( e );
eLoopStop = e;
continue;
}
if (e.prev == e.next) {
break; //only two vertices
}
else if (Closed && Point.slopesEqual( e.prev.getCurrent(), e.getCurrent(), e.next.getCurrent() )
&& (!isPreserveCollinear() || !Point.isPt2BetweenPt1AndPt3( e.prev.getCurrent(), e.getCurrent(), e.next.getCurrent() ))) {
//Collinear edges are allowed for open paths but in closed paths
//the default is to merge adjacent collinear edges into a single edge.
//However, if the PreserveCollinear property is enabled, only overlapping
//collinear edges (ie spikes) will be removed from closed paths.
if (e == eStart) {
eStart = e.next;
}
e = removeEdge( e );
e = e.prev;
eLoopStop = e;
continue;
}
e = e.next;
if (e == eLoopStop || !Closed && e.next == eStart) {
break;
}
}
if (!Closed && e == e.next || Closed && e.prev == e.next) {
return false;
}
if (!Closed) {
hasOpenPaths = true;
eStart.prev.outIdx = Edge.SKIP;
}
//3. Do second stage of edge initialization ...
e = eStart;
do {
initEdge2( e, polyType );
e = e.next;
if (IsFlat && e.getCurrent().getY() != eStart.getCurrent().getY()) {
IsFlat = false;
}
}
while (e != eStart);
//4. Finally, add edge bounds to LocalMinima list ...
//Totally flat paths must be handled differently when adding them
//to LocalMinima list to avoid endless loops etc ...
if (IsFlat) {
if (Closed) {
return false;
}
e.prev.outIdx = Edge.SKIP;
final LocalMinima locMin = new LocalMinima();
locMin.next = null;
locMin.y = e.getBot().getY();
locMin.leftBound = null;
locMin.rightBound = e;
locMin.rightBound.side = Edge.Side.RIGHT;
locMin.rightBound.windDelta = 0;
for ( ; ; ) {
if (e.getBot().getX() != e.prev.getTop().getX()) {
e.reverseHorizontal();
}
if (e.next.outIdx == Edge.SKIP) break;
e.nextInLML = e.next;
e = e.next;
}
insertLocalMinima( locMin );
return true;
}
boolean leftBoundIsForward;
Edge EMin = null;
//workaround to avoid an endless loop in the while loop below when
//open paths have matching start and end points ...
if (e.prev.getBot().equals( e.prev.getTop() )) {
e = e.next;
}
for (;;) {
e = e.findNextLocMin();
if (e == EMin) {
break;
}
else if (EMin == null) {
EMin = e;
}
//E and E.Prev now share a local minima (left aligned if horizontal).
//Compare their slopes to find which starts which bound ...
final LocalMinima locMin = new LocalMinima();
locMin.next = null;
locMin.y = e.getBot().getY();
if (e.deltaX < e.prev.deltaX) {
locMin.leftBound = e.prev;
locMin.rightBound = e;
leftBoundIsForward = false; //Q.nextInLML = Q.prev
}
else {
locMin.leftBound = e;
locMin.rightBound = e.prev;
leftBoundIsForward = true; //Q.nextInLML = Q.next
}
locMin.leftBound.side = Edge.Side.LEFT;
locMin.rightBound.side = Edge.Side.RIGHT;
if (!Closed) {
locMin.leftBound.windDelta = 0;
}
else if (locMin.leftBound.next == locMin.rightBound) {
locMin.leftBound.windDelta = -1;
}
else {
locMin.leftBound.windDelta = 1;
}
locMin.rightBound.windDelta = -locMin.leftBound.windDelta;
e = processBound( locMin.leftBound, leftBoundIsForward );
if (e.outIdx == Edge.SKIP) {
e = processBound( e, leftBoundIsForward );
}
Edge E2 = processBound( locMin.rightBound, !leftBoundIsForward );
if (E2.outIdx == Edge.SKIP) {
E2 = processBound( E2, !leftBoundIsForward );
}
if (locMin.leftBound.outIdx == Edge.SKIP) {
locMin.leftBound = null;
}
else if (locMin.rightBound.outIdx == Edge.SKIP) {
locMin.rightBound = null;
}
insertLocalMinima( locMin );
if (!leftBoundIsForward) {
e = E2;
}
}
return true;
}
@Override
public boolean addPaths(Paths paths, PolyType polyType, boolean closed ) {
boolean result = false;
for (Path path : paths) {
if (addPath(path, polyType, closed)) {
result = true;
}
}
return result;
}
@Override
public void clear() {
disposeLocalMinimaList();
hasOpenPaths = false;
}
private void disposeLocalMinimaList() {
while (minimaList != null) {
final LocalMinima tmpLm = minimaList.next;
minimaList = null;
minimaList = tmpLm;
}
currentLM = null;
}
private void insertLocalMinima( LocalMinima newLm ) {
if (minimaList == null) {
minimaList = newLm;
}
else if (newLm.y >= minimaList.y) {
newLm.next = minimaList;
minimaList = newLm;
}
else {
LocalMinima tmpLm = minimaList;
while (tmpLm.next != null && newLm.y < tmpLm.next.y) {
tmpLm = tmpLm.next;
}
newLm.next = tmpLm.next;
tmpLm.next = newLm;
}
}
private boolean isPreserveCollinear() {
return preserveCollinear;
}
protected boolean popLocalMinima( long y, LocalMinima[] current ) {
LOGGER.entering( ClipperBase.class.getName(), "popLocalMinima" );
current[0] = currentLM;
if (currentLM != null && currentLM.y == y) {
currentLM = currentLM.next;
return true;
}
return false;
}
private Edge processBound(Edge e, boolean LeftBoundIsForward ) {
Edge EStart, result = e;
Edge Horz;
if (result.outIdx == Edge.SKIP) {
//check if there are edges beyond the skip edge in the bound and if so
//create another LocMin and calling ProcessBound once more ...
e = result;
if (LeftBoundIsForward) {
while (e.getTop().getY() == e.next.getBot().getY()) {
e = e.next;
}
while (e != result && e.deltaX == Edge.HORIZONTAL) {
e = e.prev;
}
}
else {
while (e.getTop().getY() == e.prev.getBot().getY()) {
e = e.prev;
}
while (e != result && e.deltaX == Edge.HORIZONTAL) {
e = e.next;
}
}
if (e == result) {
if (LeftBoundIsForward) {
result = e.next;
}
else {
result = e.prev;
}
}
else {
//there are more edges in the bound beyond result starting with E
if (LeftBoundIsForward) {
e = result.next;
}
else {
e = result.prev;
}
final LocalMinima locMin = new LocalMinima();
locMin.next = null;
locMin.y = e.getBot().getY();
locMin.leftBound = null;
locMin.rightBound = e;
e.windDelta = 0;
result = processBound( e, LeftBoundIsForward );
insertLocalMinima( locMin );
}
return result;
}
if (e.deltaX == Edge.HORIZONTAL) {
//We need to be careful with open paths because this may not be a
//true local minima (ie E may be following a skip edge).
//Also, consecutive horz. edges may start heading left before going right.
if (LeftBoundIsForward) {
EStart = e.prev;
}
else {
EStart = e.next;
}
if (EStart.deltaX == Edge.HORIZONTAL) //ie an adjoining horizontal skip edge
{
if (EStart.getBot().getX() != e.getBot().getX() && EStart.getTop().getX() != e.getBot().getX()) {
e.reverseHorizontal();
}
}
else if (EStart.getBot().getX() != e.getBot().getX()) {
e.reverseHorizontal();
}
}
EStart = e;
if (LeftBoundIsForward) {
while (result.getTop().getY() == result.next.getBot().getY() && result.next.outIdx != Edge.SKIP) {
result = result.next;
}
if (result.deltaX == Edge.HORIZONTAL && result.next.outIdx != Edge.SKIP) {
//nb: at the top of a bound, horizontals are added to the bound
//only when the preceding edge attaches to the horizontal's left vertex
//unless a Skip edge is encountered when that becomes the top divide
Horz = result;
while (Horz.prev.deltaX == Edge.HORIZONTAL) {
Horz = Horz.prev;
}
if (Horz.prev.getTop().getX() > result.next.getTop().getX()) {
result = Horz.prev;
}
}
while (e != result) {
e.nextInLML = e.next;
if (e.deltaX == Edge.HORIZONTAL && e != EStart && e.getBot().getX() != e.prev.getTop().getX()) {
e.reverseHorizontal();
}
e = e.next;
}
if (e.deltaX == Edge.HORIZONTAL && e != EStart && e.getBot().getX() != e.prev.getTop().getX()) {
e.reverseHorizontal();
}
result = result.next; //move to the edge just beyond current bound
}
else {
while (result.getTop().getY() == result.prev.getBot().getY() && result.prev.outIdx != Edge.SKIP) {
result = result.prev;
}
if (result.deltaX == Edge.HORIZONTAL && result.prev.outIdx != Edge.SKIP) {
Horz = result;
while (Horz.next.deltaX == Edge.HORIZONTAL) {
Horz = Horz.next;
}
if (Horz.next.getTop().getX() == result.prev.getTop().getX() ||
Horz.next.getTop().getX() > result.prev.getTop().getX()) {
result = Horz.next;
}
}
while (e != result) {
e.nextInLML = e.prev;
if (e.deltaX == Edge.HORIZONTAL && e != EStart && e.getBot().getX() != e.next.getTop().getX()) {
e.reverseHorizontal();
}
e = e.prev;
}
if (e.deltaX == Edge.HORIZONTAL && e != EStart && e.getBot().getX() != e.next.getTop().getX()) {
e.reverseHorizontal();
}
result = result.prev; //move to the edge just beyond current bound
}
return result;
}
protected void reset() {
currentLM = minimaList;
if (currentLM == null) {
return; //ie nothing to process
}
//reset all edges ...
scanbeam = null;
LocalMinima lm = minimaList;
while (lm != null) {
insertScanbeam(lm.y);
Edge e = lm.leftBound;
if (e != null) {
e.setCurrent( new LongPoint( e.getBot() ) );
e.outIdx = Edge.UNASSIGNED;
}
e = lm.rightBound;
if (e != null) {
e.setCurrent( new LongPoint( e.getBot() ) );
e.outIdx = Edge.UNASSIGNED;
}
lm = lm.next;
}
activeEdges = null;
}
protected void insertScanbeam( long y ) {
LOGGER.entering( ClipperBase.class.getName(), "insertScanbeam" );
//single-linked list: sorted descending, ignoring dups.
if (scanbeam == null) {
scanbeam = new Scanbeam();
scanbeam.next = null;
scanbeam.y = y;
}
else if (y > scanbeam.y) {
final Scanbeam newSb = new Scanbeam();
newSb.y = y;
newSb.next = scanbeam;
scanbeam = newSb;
}
else {
Scanbeam sb2 = scanbeam;
while (sb2.next != null && (y <= sb2.next.y)) {
sb2 = sb2.next;
}
if (y == sb2.y) {
return; //ie ignores duplicates
}
final Scanbeam newSb = new Scanbeam();
newSb.y = y;
newSb.next = sb2.next;
sb2.next = newSb;
}
}
protected boolean popScanbeam( long[] y ) {
if (scanbeam == null) {
y[0] = 0;
return false;
}
y[0] = scanbeam.y;
scanbeam = scanbeam.next;
return true;
}
protected final boolean localMinimaPending() {
return currentLM != null;
}
protected OutRec createOutRec() {
OutRec result = new OutRec();
result.Idx = Edge.UNASSIGNED;
result.isHole = false;
result.isOpen = false;
result.firstLeft = null;
result.setPoints( null );
result.bottomPt = null;
result.polyNode = null;
polyOuts.add( result );
result.Idx = polyOuts.size() - 1;
return result;
}
protected void disposeOutRec( int index ) {
OutRec outRec = polyOuts.get( index );
outRec.setPoints( null );
outRec = null;
polyOuts.set( index, null );
}
protected void updateEdgeIntoAEL( Edge e ) {
if (e.nextInLML == null) {
throw new IllegalStateException("UpdateEdgeIntoAEL: invalid call");
}
final Edge aelPrev = e.prevInAEL;
final Edge aelNext = e.nextInAEL;
e.nextInLML.outIdx = e.outIdx;
if (aelPrev != null) {
aelPrev.nextInAEL = e.nextInLML;
}
else {
activeEdges = e.nextInLML;
}
if (aelNext != null) {
aelNext.prevInAEL = e.nextInLML;
}
e.nextInLML.side = e.side;
e.nextInLML.windDelta = e.windDelta;
e.nextInLML.windCnt = e.windCnt;
e.nextInLML.windCnt2 = e.windCnt2;
e = e.nextInLML;
e.setCurrent(e.getBot());
e.prevInAEL = aelPrev;
e.nextInAEL = aelNext;
if (e.isHorizontal()) {
insertScanbeam(e.getTop().getY());
}
}
protected void swapPositionsInAEL(Edge edge1, Edge edge2 ) {
LOGGER.entering( ClipperBase.class.getName(), "swapPositionsInAEL" );
//check that one or other edge hasn't already been removed from AEL ...
if (edge1.nextInAEL == edge1.prevInAEL || edge2.nextInAEL == edge2.prevInAEL) {
return;
}
if (edge1.nextInAEL == edge2) {
final Edge next = edge2.nextInAEL;
if (next != null) {
next.prevInAEL = edge1;
}
final Edge prev = edge1.prevInAEL;
if (prev != null) {
prev.nextInAEL = edge2;
}
edge2.prevInAEL = prev;
edge2.nextInAEL = edge1;
edge1.prevInAEL = edge2;
edge1.nextInAEL = next;
}
else if (edge2.nextInAEL == edge1) {
final Edge next = edge1.nextInAEL;
if (next != null) {
next.prevInAEL = edge2;
}
final Edge prev = edge2.prevInAEL;
if (prev != null) {
prev.nextInAEL = edge1;
}
edge1.prevInAEL = prev;
edge1.nextInAEL = edge2;
edge2.prevInAEL = edge1;
edge2.nextInAEL = next;
}
else {
final Edge next = edge1.nextInAEL;
final Edge prev = edge1.prevInAEL;
edge1.nextInAEL = edge2.nextInAEL;
if (edge1.nextInAEL != null) {
edge1.nextInAEL.prevInAEL = edge1;
}
edge1.prevInAEL = edge2.prevInAEL;
if (edge1.prevInAEL != null) {
edge1.prevInAEL.nextInAEL = edge1;
}
edge2.nextInAEL = next;
if (edge2.nextInAEL != null) {
edge2.nextInAEL.prevInAEL = edge2;
}
edge2.prevInAEL = prev;
if (edge2.prevInAEL != null) {
edge2.prevInAEL.nextInAEL = edge2;
}
}
if (edge1.prevInAEL == null) {
activeEdges = edge1;
}
else if (edge2.prevInAEL == null) {
activeEdges = edge2;
}
LOGGER.exiting( ClipperBase.class.getName(), "swapPositionsInAEL" );
}
protected void deleteFromAEL( Edge e ) {
LOGGER.entering( ClipperBase.class.getName(), "deleteFromAEL" );
Edge aelPrev = e.prevInAEL;
Edge aelNext = e.nextInAEL;
if (aelPrev == null && aelNext == null && (e != activeEdges)) {
return; //already deleted
}
if (aelPrev != null) {
aelPrev.nextInAEL = aelNext;
}
else {
activeEdges = aelNext;
}
if (aelNext != null) {
aelNext.prevInAEL = aelPrev;
}
e.nextInAEL = null;
e.prevInAEL = null;
LOGGER.exiting( ClipperBase.class.getName(), "deleteFromAEL" );
}
}

481
open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/ClipperOffset.java

@ -0,0 +1,481 @@
package com.visual.open.anpr.core.clipper;
import com.visual.open.anpr.core.clipper.Clipper.*;
import com.visual.open.anpr.core.clipper.Point.DoublePoint;
import com.visual.open.anpr.core.clipper.Point.LongPoint;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class ClipperOffset {
private static boolean nearZero( double val ) {
return val > -TOLERANCE && val < TOLERANCE;
}
private Paths destPolys;
private Path srcPoly;
private Path destPoly;
private final List<DoublePoint> normals;
private double delta, inA, sin, cos;
private double miterLim, stepsPerRad;
private LongPoint lowest;
private final PolyNode polyNodes;
private final double arcTolerance;
private final double miterLimit;
private final static double TWO_PI = Math.PI * 2;
private final static double DEFAULT_ARC_TOLERANCE = 0.25;
private final static double TOLERANCE = 1.0E-20;
public ClipperOffset() {
this( 2, DEFAULT_ARC_TOLERANCE );
}
public ClipperOffset( double miterLimit, double arcTolerance ) {
this.miterLimit = miterLimit;
this.arcTolerance = arcTolerance;
lowest = new LongPoint();
lowest.setX( -1L );
polyNodes = new PolyNode();
normals = new ArrayList<>();
}
public void addPath(Path path, JoinType joinType, EndType endType ) {
int highI = path.size() - 1;
if (highI < 0) {
return;
}
final PolyNode newNode = new PolyNode();
newNode.setJoinType( joinType );
newNode.setEndType( endType );
//strip duplicate points from path and also get index to the lowest point ...
if (endType == EndType.CLOSED_LINE || endType == EndType.CLOSED_POLYGON) {
while (highI > 0 && path.get( 0 ) == path.get( highI )) {
highI--;
}
}
newNode.getPolygon().add( path.get( 0 ) );
int j = 0, k = 0;
for (int i = 1; i <= highI; i++) {
if (newNode.getPolygon().get( j ) != path.get( i )) {
j++;
newNode.getPolygon().add( path.get( i ) );
if (path.get( i ).getY() > newNode.getPolygon().get( k ).getY() || path.get( i ).getY() == newNode.getPolygon().get( k ).getY()
&& path.get( i ).getX() < newNode.getPolygon().get( k ).getX()) {
k = j;
}
}
}
if (endType == EndType.CLOSED_POLYGON && j < 2) {
return;
}
polyNodes.addChild( newNode );
//if this path's lowest pt is lower than all the others then update m_lowest
if (endType != EndType.CLOSED_POLYGON) {
return;
}
if (lowest.getX() < 0) {
lowest = new LongPoint( polyNodes.getChildCount() - 1, k );
}
else {
final LongPoint ip = polyNodes.getChilds().get( (int) lowest.getX() ).getPolygon().get( (int) lowest.getY() );
if (newNode.getPolygon().get( k ).getY() > ip.getY() || newNode.getPolygon().get( k ).getY() == ip.getY()
&& newNode.getPolygon().get( k ).getX() < ip.getX()) {
lowest = new LongPoint( polyNodes.getChildCount() - 1, k );
}
}
}
public void addPaths(Paths paths, JoinType joinType, EndType endType ) {
for (final Path p : paths) {
addPath( p, joinType, endType );
}
}
public void clear() {
polyNodes.getChilds().clear();
lowest.setX( -1L );
}
private void doMiter( int j, int k, double r ) {
final double q = delta / r;
destPoly.add( new LongPoint( Math.round( srcPoly.get( j ).getX() + (normals.get( k ).getX() + normals.get( j ).getX()) * q ), Math
.round( srcPoly.get( j ).getY() + (normals.get( k ).getY() + normals.get( j ).getY()) * q ) ) );
}
private void doOffset( double delta ) {
destPolys = new Paths();
this.delta = delta;
//if Zero offset, just copy any CLOSED polygons to m_p and return ...
if (nearZero( delta )) {
for (int i = 0; i < polyNodes.getChildCount(); i++) {
final PolyNode node = polyNodes.getChilds().get( i );
if (node.getEndType() == EndType.CLOSED_POLYGON) {
destPolys.add( node.getPolygon() );
}
}
return;
}
//see offset_triginometry3.svg in the documentation folder ...
if (miterLimit > 2) {
miterLim = 2 / (miterLimit * miterLimit);
}
else {
miterLim = 0.5;
}
double y;
if (arcTolerance <= 0.0) {
y = DEFAULT_ARC_TOLERANCE;
}
else if (arcTolerance > Math.abs( delta ) * DEFAULT_ARC_TOLERANCE) {
y = Math.abs( delta ) * DEFAULT_ARC_TOLERANCE;
}
else {
y = arcTolerance;
}
//see offset_triginometry2.svg in the documentation folder ...
final double steps = Math.PI / Math.acos( 1 - y / Math.abs( delta ) );
sin = Math.sin( TWO_PI / steps );
cos = Math.cos( TWO_PI / steps );
stepsPerRad = steps / TWO_PI;
if (delta < 0.0) {
sin = -sin;
}
for (int i = 0; i < polyNodes.getChildCount(); i++) {
final PolyNode node = polyNodes.getChilds().get( i );
srcPoly = node.getPolygon();
final int len = srcPoly.size();
if (len == 0 || delta <= 0 && (len < 3 || node.getEndType() != EndType.CLOSED_POLYGON)) {
continue;
}
destPoly = new Path();
if (len == 1) {
if (node.getJoinType() == JoinType.ROUND) {
double X = 1.0, Y = 0.0;
for (int j = 1; j <= steps; j++) {
destPoly.add( new LongPoint( Math.round( srcPoly.get( 0 ).getX() + X * delta ), Math.round( srcPoly.get( 0 ).getY() + Y
* delta ) ) );
final double X2 = X;
X = X * cos - sin * Y;
Y = X2 * sin + Y * cos;
}
}
else {
double X = -1.0, Y = -1.0;
for (int j = 0; j < 4; ++j) {
destPoly.add( new LongPoint( Math.round( srcPoly.get( 0 ).getX() + X * delta ), Math.round( srcPoly.get( 0 ).getY() + Y
* delta ) ) );
if (X < 0) {
X = 1;
}
else if (Y < 0) {
Y = 1;
}
else {
X = -1;
}
}
}
destPolys.add( destPoly );
continue;
}
//build m_normals ...
normals.clear();
for (int j = 0; j < len - 1; j++) {
normals.add( Point.getUnitNormal( srcPoly.get( j ), srcPoly.get( j + 1 ) ) );
}
if (node.getEndType() == EndType.CLOSED_LINE || node.getEndType() == EndType.CLOSED_POLYGON) {
normals.add( Point.getUnitNormal( srcPoly.get( len - 1 ), srcPoly.get( 0 ) ) );
}
else {
normals.add( new DoublePoint( normals.get( len - 2 ) ) );
}
if (node.getEndType() == EndType.CLOSED_POLYGON) {
final int[] k = new int[] { len - 1 };
for (int j = 0; j < len; j++) {
offsetPoint( j, k, node.getJoinType() );
}
destPolys.add( destPoly );
}
else if (node.getEndType() == EndType.CLOSED_LINE) {
final int[] k = new int[] { len - 1 };
for (int j = 0; j < len; j++) {
offsetPoint( j, k, node.getJoinType() );
}
destPolys.add( destPoly );
destPoly = new Path();
//re-build m_normals ...
final DoublePoint n = normals.get( len - 1 );
for (int j = len - 1; j > 0; j--) {
normals.set( j, new DoublePoint( -normals.get( j - 1 ).getX(), -normals.get( j - 1 ).getY() ) );
}
normals.set( 0, new DoublePoint( -n.getX(), -n.getY(), 0 ) );
k[0] = 0;
for (int j = len - 1; j >= 0; j--) {
offsetPoint( j, k, node.getJoinType() );
}
destPolys.add( destPoly );
}
else {
final int[] k = new int[1];
for (int j = 1; j < len - 1; ++j) {
offsetPoint( j, k, node.getJoinType() );
}
LongPoint pt1;
if (node.getEndType() == EndType.OPEN_BUTT) {
final int j = len - 1;
pt1 = new LongPoint( Math.round( srcPoly.get( j ).getX() + normals.get( j ).getX() * delta ), Math.round( srcPoly.get( j )
.getY() + normals.get( j ).getY() * delta ), 0 );
destPoly.add( pt1 );
pt1 = new LongPoint( Math.round( srcPoly.get( j ).getX() - normals.get( j ).getX() * delta ), Math.round( srcPoly.get( j )
.getY() - normals.get( j ).getY() * delta ), 0 );
destPoly.add( pt1 );
}
else {
final int j = len - 1;
k[0] = len - 2;
inA = 0;
normals.set( j, new DoublePoint( -normals.get( j ).getX(), -normals.get( j ).getY() ) );
if (node.getEndType() == EndType.OPEN_SQUARE) {
doSquare( j, k[0] );
}
else {
doRound( j, k[0] );
}
}
//re-build m_normals ...
for (int j = len - 1; j > 0; j--) {
normals.set( j, new DoublePoint( -normals.get( j - 1 ).getX(), -normals.get( j - 1 ).getY() ) );
}
normals.set( 0, new DoublePoint( -normals.get( 1 ).getX(), -normals.get( 1 ).getY() ) );
k[0] = len - 1;
for (int j = k[0] - 1; j > 0; --j) {
offsetPoint( j, k, node.getJoinType() );
}
if (node.getEndType() == EndType.OPEN_BUTT) {
pt1 = new LongPoint( Math.round( srcPoly.get( 0 ).getX() - normals.get( 0 ).getX() * delta ), Math.round( srcPoly.get( 0 )
.getY() - normals.get( 0 ).getY() * delta ) );
destPoly.add( pt1 );
pt1 = new LongPoint( Math.round( srcPoly.get( 0 ).getX() + normals.get( 0 ).getX() * delta ), Math.round( srcPoly.get( 0 )
.getY() + normals.get( 0 ).getY() * delta ) );
destPoly.add( pt1 );
}
else {
k[0] = 1;
inA = 0;
if (node.getEndType() == EndType.OPEN_SQUARE) {
doSquare( 0, 1 );
}
else {
doRound( 0, 1 );
}
}
destPolys.add( destPoly );
}
}
}
private void doRound( int j, int k ) {
final double a = Math.atan2( inA, normals.get( k ).getX() * normals.get( j ).getX() + normals.get( k ).getY() * normals.get( j ).getY() );
final int steps = Math.max( (int) Math.round( stepsPerRad * Math.abs( a ) ), 1 );
double X = normals.get( k ).getX(), Y = normals.get( k ).getY(), X2;
for (int i = 0; i < steps; ++i) {
destPoly.add( new LongPoint( Math.round( srcPoly.get( j ).getX() + X * delta ), Math.round( srcPoly.get( j ).getY() + Y * delta ) ) );
X2 = X;
X = X * cos - sin * Y;
Y = X2 * sin + Y * cos;
}
destPoly.add( new LongPoint( Math.round( srcPoly.get( j ).getX() + normals.get( j ).getX() * delta ), Math.round( srcPoly.get( j ).getY()
+ normals.get( j ).getY() * delta ) ) );
}
private void doSquare( int j, int k ) {
final double nkx = normals.get( k ).getX();
final double nky = normals.get( k ).getY();
final double njx = normals.get( j ).getX();
final double njy = normals.get( j ).getY();
final double sjx = srcPoly.get( j ).getX();
final double sjy = srcPoly.get( j ).getY();
final double dx = Math.tan( Math.atan2( inA, nkx * njx + nky * njy ) / 4 );
destPoly.add( new LongPoint( Math.round( sjx + delta * (nkx - nky * dx) ), Math.round( sjy + delta * (nky + nkx * dx) ), 0 ) );
destPoly.add( new LongPoint( Math.round( sjx + delta * (njx + njy * dx) ), Math.round( sjy + delta * (njy - njx * dx) ), 0 ) );
}
//------------------------------------------------------------------------------
public void execute(Paths solution, double delta ) {
solution.clear();
fixOrientations();
doOffset( delta );
//now clean up 'corners' ...
final DefaultClipper clpr = new DefaultClipper( Clipper.REVERSE_SOLUTION );
clpr.addPaths( destPolys, PolyType.SUBJECT, true );
if (delta > 0) {
clpr.execute( ClipType.UNION, solution, PolyFillType.POSITIVE, PolyFillType.POSITIVE );
}
else {
final LongRect r = destPolys.getBounds();
final Path outer = new Path( 4 );
outer.add( new LongPoint( r.left - 10, r.bottom + 10, 0 ) );
outer.add( new LongPoint( r.right + 10, r.bottom + 10, 0 ) );
outer.add( new LongPoint( r.right + 10, r.top - 10, 0 ) );
outer.add( new LongPoint( r.left - 10, r.top - 10, 0 ) );
clpr.addPath( outer, PolyType.SUBJECT, true );
clpr.execute( ClipType.UNION, solution, PolyFillType.NEGATIVE, PolyFillType.NEGATIVE );
if (solution.size() > 0) {
solution.remove( 0 );
}
}
}
//------------------------------------------------------------------------------
public void execute(PolyTree solution, double delta ) {
solution.Clear();
fixOrientations();
doOffset( delta );
//now clean up 'corners' ...
final DefaultClipper clpr = new DefaultClipper( Clipper.REVERSE_SOLUTION );
clpr.addPaths( destPolys, PolyType.SUBJECT, true );
if (delta > 0) {
clpr.execute( ClipType.UNION, solution, PolyFillType.POSITIVE, PolyFillType.POSITIVE );
}
else {
final LongRect r = destPolys.getBounds();
final Path outer = new Path( 4 );
outer.add( new LongPoint( r.left - 10, r.bottom + 10, 0 ) );
outer.add( new LongPoint( r.right + 10, r.bottom + 10, 0 ) );
outer.add( new LongPoint( r.right + 10, r.top - 10, 0 ) );
outer.add( new LongPoint( r.left - 10, r.top - 10, 0 ) );
clpr.addPath( outer, PolyType.SUBJECT, true );
clpr.execute( ClipType.UNION, solution, PolyFillType.NEGATIVE, PolyFillType.NEGATIVE );
//remove the outer PolyNode rectangle ...
if (solution.getChildCount() == 1 && solution.getChilds().get( 0 ).getChildCount() > 0) {
final PolyNode outerNode = solution.getChilds().get( 0 );
solution.getChilds().set( 0, outerNode.getChilds().get( 0 ) );
solution.getChilds().get( 0 ).setParent( solution );
for (int i = 1; i < outerNode.getChildCount(); i++) {
solution.addChild( outerNode.getChilds().get( i ) );
}
}
else {
solution.Clear();
}
}
}
//------------------------------------------------------------------------------
private void fixOrientations() {
//fixup orientations of all closed paths if the orientation of the
//closed path with the lowermost vertex is wrong ...
if (lowest.getX() >= 0 && !polyNodes.childs.get( (int) lowest.getX() ).getPolygon().orientation()) {
for (int i = 0; i < polyNodes.getChildCount(); i++) {
final PolyNode node = polyNodes.childs.get( i );
if (node.getEndType() == EndType.CLOSED_POLYGON || node.getEndType() == EndType.CLOSED_LINE && node.getPolygon().orientation()) {
Collections.reverse( node.getPolygon() );
}
}
}
else {
for (int i = 0; i < polyNodes.getChildCount(); i++) {
final PolyNode node = polyNodes.childs.get( i );
if (node.getEndType() == EndType.CLOSED_LINE && !node.getPolygon().orientation()) {
Collections.reverse( node.getPolygon() );
}
}
}
}
private void offsetPoint( int j, int[] kV, JoinType jointype ) {
//cross product ...
final int k = kV[0];
final double nkx = normals.get( k ).getX();
final double nky = normals.get( k ).getY();
final double njy = normals.get( j ).getY();
final double njx = normals.get( j ).getX();
final long sjx = srcPoly.get( j ).getX();
final long sjy = srcPoly.get( j ).getY();
inA = nkx * njy - njx * nky;
if (Math.abs( inA * delta ) < 1.0) {
//dot product ...
final double cosA = nkx * njx + njy * nky;
if (cosA > 0) // angle ==> 0 degrees
{
destPoly.add( new LongPoint( Math.round( sjx + nkx * delta ), Math.round( sjy + nky * delta ), 0 ) );
return;
}
//else angle ==> 180 degrees
}
else if (inA > 1.0) {
inA = 1.0;
}
else if (inA < -1.0) {
inA = -1.0;
}
if (inA * delta < 0) {
destPoly.add( new LongPoint( Math.round( sjx + nkx * delta ), Math.round( sjy + nky * delta ) ) );
destPoly.add( srcPoly.get( j ) );
destPoly.add( new LongPoint( Math.round( sjx + njx * delta ), Math.round( sjy + njy * delta ) ) );
}
else {
switch (jointype) {
case MITER: {
final double r = 1 + njx * nkx + njy * nky;
if (r >= miterLim) {
doMiter( j, k, r );
}
else {
doSquare( j, k );
}
break;
}
case SQUARE:
doSquare( j, k );
break;
case ROUND:
doRound( j, k );
break;
}
}
kV[0] = j;
}
//------------------------------------------------------------------------------
}

2518
open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/DefaultClipper.java

File diff suppressed because it is too large

339
open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/Edge.java

@ -0,0 +1,339 @@
package com.visual.open.anpr.core.clipper;
import com.visual.open.anpr.core.clipper.Clipper.ClipType;
import com.visual.open.anpr.core.clipper.Clipper.Direction;
import com.visual.open.anpr.core.clipper.Clipper.PolyFillType;
import com.visual.open.anpr.core.clipper.Clipper.PolyType;
import com.visual.open.anpr.core.clipper.Point.LongPoint;
import java.util.logging.Logger;
class Edge {
enum Side {
LEFT, RIGHT
}
static boolean doesE2InsertBeforeE1(Edge e1, Edge e2 ) {
if (e2.current.getX() == e1.current.getX()) {
if (e2.top.getY() > e1.top.getY()) {
return e2.top.getX() < topX( e1, e2.top.getY() );
}
else {
return e1.top.getX() > topX( e2, e1.top.getY() );
}
}
else {
return e2.current.getX() < e1.current.getX();
}
}
static boolean slopesEqual(Edge e1, Edge e2 ) {
return e1.getDelta().getY() * e2.getDelta().getX() == e1.getDelta().getX() * e2.getDelta().getY();
}
static void swapPolyIndexes(Edge edge1, Edge edge2 ) {
final int outIdx = edge1.outIdx;
edge1.outIdx = edge2.outIdx;
edge2.outIdx = outIdx;
}
static void swapSides(Edge edge1, Edge edge2 ) {
final Side side = edge1.side;
edge1.side = edge2.side;
edge2.side = side;
}
static long topX(Edge edge, long currentY ) {
if (currentY == edge.getTop().getY()) {
return edge.getTop().getX();
}
return edge.getBot().getX() + Math.round( edge.deltaX * (currentY - edge.getBot().getY()) );
}
private final LongPoint bot;
private final LongPoint current; //current (updated for every new scanbeam)
private final LongPoint top;
private final LongPoint delta;
double deltaX;
PolyType polyTyp;
Side side; //side only refers to current side of solution poly
int windDelta; //1 or -1 depending on winding direction
int windCnt;
int windCnt2; //winding count of the opposite polytype
int outIdx;
Edge next;
Edge prev;
Edge nextInLML;
Edge nextInAEL;
Edge prevInAEL;
Edge nextInSEL;
Edge prevInSEL;
protected final static int SKIP = -2;
protected final static int UNASSIGNED = -1;
protected final static double HORIZONTAL = -3.4E+38;
private final static Logger LOGGER = Logger.getLogger( Edge.class.getName() );
public Edge() {
delta = new LongPoint();
top = new LongPoint();
bot = new LongPoint();
current = new LongPoint();
}
public Edge findNextLocMin() {
Edge e = this;
Edge e2;
for (;;) {
while (!e.bot.equals( e.prev.bot ) || e.current.equals( e.top )) {
e = e.next;
}
if (e.deltaX != Edge.HORIZONTAL && e.prev.deltaX != Edge.HORIZONTAL) {
break;
}
while (e.prev.deltaX == Edge.HORIZONTAL) {
e = e.prev;
}
e2 = e;
while (e.deltaX == Edge.HORIZONTAL) {
e = e.next;
}
if (e.top.getY() == e.prev.bot.getY()) {
continue; //ie just an intermediate horz.
}
if (e2.prev.bot.getX() < e.bot.getX()) {
e = e2;
}
break;
}
return e;
}
public LongPoint getBot() {
return bot;
}
public LongPoint getCurrent() {
return current;
}
public LongPoint getDelta() {
return delta;
}
public Edge getMaximaPair() {
if (next.top.equals( top ) && next.nextInLML == null) {
return next;
}
else if (prev.top.equals( top ) && prev.nextInLML == null) {
return prev;
}
return null;
}
Edge getMaximaPairEx() {
//as above but returns null if MaxPair isn't in AEL (unless it's horizontal)
Edge result = getMaximaPair();
if (result == null || result.outIdx == Edge.SKIP ||
((result.nextInAEL == result.prevInAEL) && !result.isHorizontal())) {
return null;
}
return result;
}
public Edge getNextInAEL(Direction direction ) {
return direction == Direction.LEFT_TO_RIGHT ? nextInAEL : prevInAEL;
}
public LongPoint getTop() {
return top;
}
public boolean isContributing(PolyFillType clipFillType, PolyFillType subjFillType, ClipType clipType ) {
LOGGER.entering( Edge.class.getName(), "isContributing" );
PolyFillType pft, pft2;
if (polyTyp == PolyType.SUBJECT) {
pft = subjFillType;
pft2 = clipFillType;
}
else {
pft = clipFillType;
pft2 = subjFillType;
}
switch (pft) {
case EVEN_ODD:
//return false if a subj line has been flagged as inside a subj polygon
if (windDelta == 0 && windCnt != 1) {
return false;
}
break;
case NON_ZERO:
if (Math.abs( windCnt ) != 1) {
return false;
}
break;
case POSITIVE:
if (windCnt != 1) {
return false;
}
break;
default: //PolyFillType.pftNegative
if (windCnt != -1) {
return false;
}
break;
}
switch (clipType) {
case INTERSECTION:
switch (pft2) {
case EVEN_ODD:
case NON_ZERO:
return windCnt2 != 0;
case POSITIVE:
return windCnt2 > 0;
default:
return windCnt2 < 0;
}
case UNION:
switch (pft2) {
case EVEN_ODD:
case NON_ZERO:
return windCnt2 == 0;
case POSITIVE:
return windCnt2 <= 0;
default:
return windCnt2 >= 0;
}
case DIFFERENCE:
if (polyTyp == PolyType.SUBJECT) {
switch (pft2) {
case EVEN_ODD:
case NON_ZERO:
return windCnt2 == 0;
case POSITIVE:
return windCnt2 <= 0;
default:
return windCnt2 >= 0;
}
}
else {
switch (pft2) {
case EVEN_ODD:
case NON_ZERO:
return windCnt2 != 0;
case POSITIVE:
return windCnt2 > 0;
default:
return windCnt2 < 0;
}
}
case XOR:
if (windDelta == 0) {
switch (pft2) {
case EVEN_ODD:
case NON_ZERO:
return windCnt2 == 0;
case POSITIVE:
return windCnt2 <= 0;
default:
return windCnt2 >= 0;
}
}
else {
return true;
}
}
return true;
}
public boolean isEvenOddAltFillType(PolyFillType clipFillType, PolyFillType subjFillType ) {
if (polyTyp == PolyType.SUBJECT) {
return clipFillType == PolyFillType.EVEN_ODD;
}
else {
return subjFillType == PolyFillType.EVEN_ODD;
}
}
public boolean isEvenOddFillType(PolyFillType clipFillType, PolyFillType subjFillType ) {
if (polyTyp == PolyType.SUBJECT) {
return subjFillType == PolyFillType.EVEN_ODD;
}
else {
return clipFillType == PolyFillType.EVEN_ODD;
}
}
public boolean isHorizontal() {
return delta.getY() == 0;
}
public boolean isIntermediate( double y ) {
return top.getY() == y && nextInLML != null;
}
public boolean isMaxima( double Y ) {
return top.getY() == Y && nextInLML == null;
}
public void reverseHorizontal() {
//swap horizontal edges' top and bottom x's so they follow the natural
//progression of the bounds - ie so their xbots will align with the
//adjoining lower edge. [Helpful in the ProcessHorizontal() method.]
long temp = top.getX();
top.setX( bot.getX() );
bot.setX( temp );
temp = top.getZ();
top.setZ( bot.getZ() );
bot.setZ( temp );
}
public void setBot( LongPoint bot ) {
this.bot.set( bot );
}
public void setCurrent( LongPoint current ) {
this.current.set( current );
}
public void setTop( LongPoint top ) {
this.top.set( top );
}
@Override
public String toString() {
return "TEdge [Bot=" + bot + ", Curr=" + current + ", Top=" + top + ", Delta=" + delta + ", Dx=" + deltaX + ", PolyTyp=" + polyTyp + ", Side=" + side
+ ", WindDelta=" + windDelta + ", WindCnt=" + windCnt + ", WindCnt2=" + windCnt2 + ", OutIdx=" + outIdx + ", Next=" + next + ", Prev="
+ prev + ", NextInLML=" + nextInLML + ", NextInAEL=" + nextInAEL + ", PrevInAEL=" + prevInAEL + ", NextInSEL=" + nextInSEL
+ ", PrevInSEL=" + prevInSEL + "]";
}
public void updateDeltaX() {
delta.setX( top.getX() - bot.getX() );
delta.setY( top.getY() - bot.getY() );
if (delta.getY() == 0) {
deltaX = Edge.HORIZONTAL;
}
else {
deltaX = (double) delta.getX() / delta.getY();
}
}
}

26
open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/LongRect.java

@ -0,0 +1,26 @@
package com.visual.open.anpr.core.clipper;
public class LongRect {
public long left;
public long top;
public long right;
public long bottom;
public LongRect() {
}
public LongRect( long l, long t, long r, long b ) {
left = l;
top = t;
right = r;
bottom = b;
}
public LongRect( LongRect ir ) {
left = ir.left;
top = ir.top;
right = ir.right;
bottom = ir.bottom;
}
}

414
open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/Path.java

@ -0,0 +1,414 @@
package com.visual.open.anpr.core.clipper;
import com.visual.open.anpr.core.clipper.Point.LongPoint;
import java.util.ArrayList;
import java.util.Collections;
/**
* A pure convenience class to avoid writing List<IntPoint> everywhere.
*
* @author Tobias Mahlmann
*
*/
public class Path extends ArrayList<LongPoint> {
static class Join {
OutPt outPt1;
OutPt outPt2;
private LongPoint offPt;
public LongPoint getOffPt() {
return offPt;
}
public void setOffPt( LongPoint offPt ) {
this.offPt = offPt;
}
}
static class OutPt {
public static OutRec getLowerMostRec( OutRec outRec1, OutRec outRec2 ) {
//work out which polygon fragment has the correct hole state ...
if (outRec1.bottomPt == null) {
outRec1.bottomPt = outRec1.pts.getBottomPt();
}
if (outRec2.bottomPt == null) {
outRec2.bottomPt = outRec2.pts.getBottomPt();
}
final OutPt bPt1 = outRec1.bottomPt;
final OutPt bPt2 = outRec2.bottomPt;
if (bPt1.getPt().getY() > bPt2.getPt().getY()) {
return outRec1;
}
else if (bPt1.getPt().getY() < bPt2.getPt().getY()) {
return outRec2;
}
else if (bPt1.getPt().getX() < bPt2.getPt().getX()) {
return outRec1;
}
else if (bPt1.getPt().getX() > bPt2.getPt().getX()) {
return outRec2;
}
else if (bPt1.next == bPt1) {
return outRec2;
}
else if (bPt2.next == bPt2) {
return outRec1;
}
else if (isFirstBottomPt( bPt1, bPt2 )) {
return outRec1;
}
else {
return outRec2;
}
}
private static boolean isFirstBottomPt(OutPt btmPt1, OutPt btmPt2 ) {
OutPt p = btmPt1.prev;
while (p.getPt().equals( btmPt1.getPt() ) && !p.equals( btmPt1 )) {
p = p.prev;
}
final double dx1p = Math.abs( LongPoint.getDeltaX( btmPt1.getPt(), p.getPt() ) );
p = btmPt1.next;
while (p.getPt().equals( btmPt1.getPt() ) && !p.equals( btmPt1 )) {
p = p.next;
}
final double dx1n = Math.abs( LongPoint.getDeltaX( btmPt1.getPt(), p.getPt() ) );
p = btmPt2.prev;
while (p.getPt().equals( btmPt2.getPt() ) && !p.equals( btmPt2 )) {
p = p.prev;
}
final double dx2p = Math.abs( LongPoint.getDeltaX( btmPt2.getPt(), p.getPt() ) );
p = btmPt2.next;
while (p.getPt().equals( btmPt2.getPt() ) && p.equals( btmPt2 )) {
p = p.next;
}
final double dx2n = Math.abs( LongPoint.getDeltaX( btmPt2.getPt(), p.getPt() ) );
if (Math.max( dx1p, dx1n ) == Math.max( dx2p, dx2n ) && Math.min( dx1p, dx1n ) == Math.min( dx2p, dx2n )) {
return btmPt1.area() > 0; //if otherwise identical use orientation
}
else {
return dx1p >= dx2p && dx1p >= dx2n || dx1n >= dx2p && dx1n >= dx2n;
}
}
int idx;
private LongPoint pt;
OutPt next;
OutPt prev;
public OutPt duplicate(boolean InsertAfter ) {
final OutPt result = new OutPt();
result.setPt( new LongPoint( getPt() ) );
result.idx = idx;
if (InsertAfter) {
result.next = next;
result.prev = this;
next.prev = result;
next = result;
}
else {
result.prev = prev;
result.next = this;
prev.next = result;
prev = result;
}
return result;
}
OutPt getBottomPt() {
OutPt dups = null;
OutPt p = next;
OutPt pp = this;
while (p != pp) {
if (p.getPt().getY() > pp.getPt().getY()) {
pp = p;
dups = null;
}
else if (p.getPt().getY() == pp.getPt().getY() && p.getPt().getX() <= pp.getPt().getX()) {
if (p.getPt().getX() < pp.getPt().getX()) {
dups = null;
pp = p;
}
else {
if (p.next != pp && p.prev != pp) {
dups = p;
}
}
}
p = p.next;
}
if (dups != null) {
//there appears to be at least 2 vertices at bottomPt so ...
while (dups != p) {
if (!isFirstBottomPt( p, dups )) {
pp = dups;
}
dups = dups.next;
while (!dups.getPt().equals( pp.getPt() )) {
dups = dups.next;
}
}
}
return pp;
}
public static int getPointCount( OutPt pts ) {
if (pts == null) return 0;
int result = 0;
OutPt p = pts;
do {
result++;
p = p.next;
}
while (p != pts);
return result;
}
public LongPoint getPt() {
return pt;
}
public void reversePolyPtLinks() {
OutPt pp1;
OutPt pp2;
pp1 = this;
do {
pp2 = pp1.next;
pp1.next = pp1.prev;
pp1.prev = pp2;
pp1 = pp2;
}
while (pp1 != this);
}
public void setPt( LongPoint pt ) {
this.pt = pt;
}
private double area() {
OutPt op = this;
double a = 0;
do {
a = a + (double) (op.prev.getPt().getX() + op.getPt().getX()) * (double) (op.prev.getPt().getY() - op.getPt().getY());
op = op.next;
} while (op != this);
return a * 0.5;
}
}
/** OutRec: contains a path in the clipping solution. Edges in the AEL will
carry a pointer to an OutRec when they are part of the clipping solution.*/
static class OutRec {
int Idx;
boolean isHole;
boolean isOpen;
OutRec firstLeft; //see comments in clipper.pas
private OutPt pts;
OutPt bottomPt;
PolyNode polyNode;
public double area() {
return pts.area();
}
public void fixHoleLinkage() {
//skip if an outermost polygon or
//already already points to the correct FirstLeft ...
if (firstLeft == null || isHole != firstLeft.isHole && firstLeft.pts != null) {
return;
}
OutRec orfl = firstLeft;
while (orfl != null && (orfl.isHole == isHole || orfl.pts == null)) {
orfl = orfl.firstLeft;
}
firstLeft = orfl;
}
public OutPt getPoints() {
return pts;
}
public static OutRec parseFirstLeft( OutRec firstLeft ) {
while (firstLeft != null && firstLeft.getPoints() == null) {
firstLeft = firstLeft.firstLeft;
}
return firstLeft;
}
public void setPoints( OutPt pts ) {
this.pts = pts;
}
}
private static OutPt excludeOp(OutPt op ) {
final OutPt result = op.prev;
result.next = op.next;
op.next.prev = result;
result.idx = 0;
return result;
}
/**
*
*/
private static final long serialVersionUID = -7120161578077546673L;
public Path() {
super();
}
public Path( int cnt ) {
super( cnt );
}
public double area() {
final int cnt = size();
if (cnt < 3) {
return 0;
}
double a = 0;
for (int i = 0, j = cnt - 1; i < cnt; ++i) {
a += ((double) get( j ).getX() + get( i ).getX()) * ((double) get( j ).getY() - get( i ).getY());
j = i;
}
return -a * 0.5;
}
public Path cleanPolygon() {
return cleanPolygon( 1.415 );
}
public Path cleanPolygon(double distance ) {
//distance = proximity in units/pixels below which vertices will be stripped.
//Default ~= sqrt(2) so when adjacent vertices or semi-adjacent vertices have
//both x & y coords within 1 unit, then the second vertex will be stripped.
int cnt = size();
if (cnt == 0) {
return new Path();
}
OutPt[] outPts = new OutPt[cnt];
for (int i = 0; i < cnt; ++i) {
outPts[i] = new OutPt();
}
for (int i = 0; i < cnt; ++i) {
outPts[i].pt = get( i );
outPts[i].next = outPts[(i + 1) % cnt];
outPts[i].next.prev = outPts[i];
outPts[i].idx = 0;
}
final double distSqrd = distance * distance;
OutPt op = outPts[0];
while (op.idx == 0 && op.next != op.prev) {
if (Point.arePointsClose( op.pt, op.prev.pt, distSqrd )) {
op = excludeOp( op );
cnt--;
}
else if (Point.arePointsClose( op.prev.pt, op.next.pt, distSqrd )) {
excludeOp( op.next );
op = excludeOp( op );
cnt -= 2;
}
else if (Point.slopesNearCollinear( op.prev.pt, op.pt, op.next.pt, distSqrd )) {
op = excludeOp( op );
cnt--;
}
else {
op.idx = 1;
op = op.next;
}
}
if (cnt < 3) {
cnt = 0;
}
final Path result = new Path( cnt );
for (int i = 0; i < cnt; ++i) {
result.add( op.pt );
op = op.next;
}
outPts = null;
return result;
}
public int isPointInPolygon( LongPoint pt ) {
//returns 0 if false, +1 if true, -1 if pt ON polygon boundary
//See "The Point in Polygon Problem for Arbitrary Polygons" by Hormann & Agathos
//http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf
int result = 0;
final int cnt = size();
if (cnt < 3) {
return 0;
}
LongPoint ip = get( 0 );
for (int i = 1; i <= cnt; ++i) {
final LongPoint ipNext = i == cnt ? get( 0 ) : get( i );
if (ipNext.getY() == pt.getY()) {
if (ipNext.getX() == pt.getX() || ip.getY() == pt.getY() && ipNext.getX() > pt.getX() == ip.getX() < pt.getX()) {
return -1;
}
}
if (ip.getY() < pt.getY() != ipNext.getY() < pt.getY()) {
if (ip.getX() >= pt.getX()) {
if (ipNext.getX() > pt.getX()) {
result = 1 - result;
}
else {
final double d = (double) (ip.getX() - pt.getX()) * (ipNext.getY() - pt.getY()) - (double) (ipNext.getX() - pt.getX())
* (ip.getY() - pt.getY());
if (d == 0) {
return -1;
}
else if (d > 0 == ipNext.getY() > ip.getY()) {
result = 1 - result;
}
}
}
else {
if (ipNext.getX() > pt.getX()) {
final double d = (double) (ip.getX() - pt.getX()) * (ipNext.getY() - pt.getY()) - (double) (ipNext.getX() - pt.getX())
* (ip.getY() - pt.getY());
if (d == 0) {
return -1;
}
else if (d > 0 == ipNext.getY() > ip.getY()) {
result = 1 - result;
}
}
}
}
ip = ipNext;
}
return result;
}
public boolean orientation() {
return area() >= 0;
}
public void reverse() {
Collections.reverse( this );
}
public Path TranslatePath(LongPoint delta ) {
final Path outPath = new Path( size() );
for (int i = 0; i < size(); i++) {
outPath.add( new LongPoint( get( i ).getX() + delta.getX(), get( i ).getY() + delta.getY() ) );
}
return outPath;
}
}

125
open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/Paths.java

@ -0,0 +1,125 @@
package com.visual.open.anpr.core.clipper;
import java.util.ArrayList;
/**
* A pure convenience class to avoid writing List<Path> everywhere.
*
* @author Tobias Mahlmann
*
*/
public class Paths extends ArrayList<Path> {
public static Paths closedPathsFromPolyTree(PolyTree polytree ) {
final Paths result = new Paths();
// result.Capacity = polytree.Total;
result.addPolyNode( polytree, PolyNode.NodeType.CLOSED );
return result;
}
public static Paths makePolyTreeToPaths(PolyTree polytree ) {
final Paths result = new Paths();
// result.Capacity = polytree.Total;
result.addPolyNode( polytree, PolyNode.NodeType.ANY );
return result;
}
public static Paths openPathsFromPolyTree(PolyTree polytree ) {
final Paths result = new Paths();
// result.Capacity = polytree.ChildCount;
for (final PolyNode c : polytree.getChilds()) {
if (c.isOpen()) {
result.add( c.getPolygon() );
}
}
return result;
}
/**
*
*/
private static final long serialVersionUID = 1910552127810480852L;
public Paths() {
super();
}
public Paths( int initialCapacity ) {
super( initialCapacity );
}
public void addPolyNode(PolyNode polynode, PolyNode.NodeType nt ) {
boolean match = true;
switch (nt) {
case OPEN:
return;
case CLOSED:
match = !polynode.isOpen();
break;
default:
break;
}
if (polynode.getPolygon().size() > 0 && match) {
add( polynode.getPolygon() );
}
for (final PolyNode pn : polynode.getChilds()) {
addPolyNode( pn, nt );
}
}
public Paths cleanPolygons() {
return cleanPolygons( 1.415 );
}
public Paths cleanPolygons(double distance ) {
final Paths result = new Paths( size() );
for (int i = 0; i < size(); i++) {
result.add( get( i ).cleanPolygon( distance ) );
}
return result;
}
public LongRect getBounds() {
int i = 0;
final int cnt = size();
final LongRect result = new LongRect();
while (i < cnt && get( i ).isEmpty()) {
i++;
}
if (i == cnt) {
return result;
}
result.left = get( i ).get( 0 ).getX();
result.right = result.left;
result.top = get( i ).get( 0 ).getY();
result.bottom = result.top;
for (; i < cnt; i++) {
for (int j = 0; j < get( i ).size(); j++) {
if (get( i ).get( j ).getX() < result.left) {
result.left = get( i ).get( j ).getX();
}
else if (get( i ).get( j ).getX() > result.right) {
result.right = get( i ).get( j ).getX();
}
if (get( i ).get( j ).getY() < result.top) {
result.top = get( i ).get( j ).getY();
}
else if (get( i ).get( j ).getY() > result.bottom) {
result.bottom = get( i ).get( j ).getY();
}
}
}
return result;
}
public void reversePaths() {
for (final Path poly : this) {
poly.reverse();
}
}
}

220
open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/Point.java

@ -0,0 +1,220 @@
package com.visual.open.anpr.core.clipper;
import java.util.Comparator;
public abstract class Point<T extends Number & Comparable<T>> {
public static class DoublePoint extends Point<Double> {
public DoublePoint() {
this( 0, 0 );
}
public DoublePoint( double x, double y ) {
this( x, y, 0 );
}
public DoublePoint( double x, double y, double z ) {
super( x, y, z );
}
public DoublePoint( DoublePoint other ) {
super( other );
}
public double getX() {
return x;
}
public double getY() {
return y;
}
public double getZ() {
return z;
}
}
public static class LongPoint extends Point<Long> {
public static double getDeltaX(LongPoint pt1, LongPoint pt2 ) {
if (pt1.getY() == pt2.getY()) {
return Edge.HORIZONTAL;
}
else {
return (double) (pt2.getX() - pt1.getX()) / (pt2.getY() - pt1.getY());
}
}
public LongPoint() {
this( 0, 0 );
}
public LongPoint( long x, long y ) {
this( x, y, 0 );
}
public LongPoint( long x, long y, long z ) {
super( x, y, z );
}
public LongPoint( LongPoint other ) {
super( other );
}
public long getX() {
return x;
}
public long getY() {
return y;
}
public long getZ() {
return z;
}
}
private static class NumberComparator<T extends Number & Comparable<T>> implements Comparator<T> {
@Override
public int compare( T a, T b ) throws ClassCastException {
return a.compareTo( b );
}
}
static boolean arePointsClose(Point<? extends Number> pt1, Point<? extends Number> pt2, double distSqrd ) {
final double dx = pt1.x.doubleValue() - pt2.x.doubleValue();
final double dy = pt1.y.doubleValue() - pt2.y.doubleValue();
return dx * dx + dy * dy <= distSqrd;
}
static double distanceFromLineSqrd(Point<? extends Number> pt, Point<? extends Number> ln1, Point<? extends Number> ln2 ) {
//The equation of a line in general form (Ax + By + C = 0)
//given 2 points (x¹,y¹) & (x²,y²) is ...
//(y¹ - y²)x + (x² - x¹)y + (y² - y¹)x¹ - (x² - x¹)y¹ = 0
//A = (y¹ - y²); B = (x² - x¹); C = (y² - y¹)x¹ - (x² - x¹)y¹
//perpendicular distance of point (x³,y³) = (Ax³ + By³ + C)/Sqrt(A² + B²)
//see http://en.wikipedia.org/wiki/Perpendicular_distance
final double A = ln1.y.doubleValue() - ln2.y.doubleValue();
final double B = ln2.x.doubleValue() - ln1.x.doubleValue();
double C = A * ln1.x.doubleValue() + B * ln1.y.doubleValue();
C = A * pt.x.doubleValue() + B * pt.y.doubleValue() - C;
return C * C / (A * A + B * B);
}
static DoublePoint getUnitNormal( LongPoint pt1, LongPoint pt2 ) {
double dx = pt2.x - pt1.x;
double dy = pt2.y - pt1.y;
if (dx == 0 && dy == 0) {
return new DoublePoint();
}
final double f = 1 * 1.0 / Math.sqrt( dx * dx + dy * dy );
dx *= f;
dy *= f;
return new DoublePoint( dy, -dx );
}
protected static boolean isPt2BetweenPt1AndPt3( LongPoint pt1, LongPoint pt2, LongPoint pt3 ) {
if (pt1.equals( pt3 ) || pt1.equals( pt2 ) || pt3.equals( pt2 )) {
return false;
}
else if (pt1.x != pt3.x) {
return pt2.x > pt1.x == pt2.x < pt3.x;
}
else {
return pt2.y > pt1.y == pt2.y < pt3.y;
}
}
protected static boolean slopesEqual( LongPoint pt1, LongPoint pt2, LongPoint pt3 ) {
return (pt1.y - pt2.y) * (pt2.x - pt3.x) - (pt1.x - pt2.x) * (pt2.y - pt3.y) == 0;
}
protected static boolean slopesEqual( LongPoint pt1, LongPoint pt2, LongPoint pt3, LongPoint pt4 ) {
return (pt1.y - pt2.y) * (pt3.x - pt4.x) - (pt1.x - pt2.x) * (pt3.y - pt4.y) == 0;
}
static boolean slopesNearCollinear( LongPoint pt1, LongPoint pt2, LongPoint pt3, double distSqrd ) {
//this function is more accurate when the point that's GEOMETRICALLY
//between the other 2 points is the one that's tested for distance.
//nb: with 'spikes', either pt1 or pt3 is geometrically between the other pts
if (Math.abs( pt1.x - pt2.x ) > Math.abs( pt1.y - pt2.y )) {
if (pt1.x > pt2.x == pt1.x < pt3.x) {
return distanceFromLineSqrd( pt1, pt2, pt3 ) < distSqrd;
}
else if (pt2.x > pt1.x == pt2.x < pt3.x) {
return distanceFromLineSqrd( pt2, pt1, pt3 ) < distSqrd;
}
else {
return distanceFromLineSqrd( pt3, pt1, pt2 ) < distSqrd;
}
}
else {
if (pt1.y > pt2.y == pt1.y < pt3.y) {
return distanceFromLineSqrd( pt1, pt2, pt3 ) < distSqrd;
}
else if (pt2.y > pt1.y == pt2.y < pt3.y) {
return distanceFromLineSqrd( pt2, pt1, pt3 ) < distSqrd;
}
else {
return distanceFromLineSqrd( pt3, pt1, pt2 ) < distSqrd;
}
}
}
private final static NumberComparator NUMBER_COMPARATOR = new NumberComparator();
protected T x;
protected T y;
protected T z;
protected Point( Point<T> pt ) {
this( pt.x, pt.y, pt.z );
}
protected Point( T x, T y, T z ) {
this.x = x;
this.y = y;
this.z = z;
}
@Override
public boolean equals( Object obj ) {
if (obj == null) {
return false;
}
if (obj instanceof Point<?>) {
final Point<?> a = (Point<?>) obj;
return NUMBER_COMPARATOR.compare( x, a.x ) == 0 && NUMBER_COMPARATOR.compare( y, a.y ) == 0;
}
else {
return false;
}
}
public void set( Point<T> other ) {
x = other.x;
y = other.y;
z = other.z;
}
public void setX( T x ) {
this.x = x;
}
public void setY( T y ) {
this.y = y;
}
public void setZ( T z ) {
this.z = z;
}
@Override
public String toString() {
return "Point [x=" + x + ", y=" + y + ", z=" + z + "]";
}
}// end struct IntPoint

116
open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/PolyNode.java

@ -0,0 +1,116 @@
package com.visual.open.anpr.core.clipper;
import com.visual.open.anpr.core.clipper.Clipper.EndType;
import com.visual.open.anpr.core.clipper.Clipper.JoinType;
import com.visual.open.anpr.core.clipper.Point.LongPoint;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class PolyNode {
enum NodeType {
ANY, OPEN, CLOSED
}
private PolyNode parent;
private final Path polygon = new Path();
private int index;
private JoinType joinType;
private EndType endType;
final List<PolyNode> childs = new ArrayList<>();
private boolean isOpen;
public void addChild( PolyNode child ) {
final int cnt = childs.size();
childs.add( child );
child.parent = this;
child.index = cnt;
}
public int getChildCount() {
return childs.size();
}
public List<PolyNode> getChilds() {
return Collections.unmodifiableList( childs );
}
public List<LongPoint> getContour() {
return polygon;
}
public EndType getEndType() {
return endType;
}
public JoinType getJoinType() {
return joinType;
}
public PolyNode getNext() {
if (!childs.isEmpty()) {
return childs.get( 0 );
}
else {
return getNextSiblingUp();
}
}
private PolyNode getNextSiblingUp() {
if (parent == null) {
return null;
}
else if (index == parent.childs.size() - 1) {
return parent.getNextSiblingUp();
}
else {
return parent.childs.get( index + 1 );
}
}
public PolyNode getParent() {
return parent;
}
public Path getPolygon() {
return polygon;
}
public boolean isHole() {
return isHoleNode();
}
private boolean isHoleNode() {
boolean result = true;
PolyNode node = parent;
while (node != null) {
result = !result;
node = node.parent;
}
return result;
}
public boolean isOpen() {
return isOpen;
}
public void setEndType( EndType value ) {
endType = value;
}
public void setJoinType( JoinType value ) {
joinType = value;
}
public void setOpen( boolean isOpen ) {
this.isOpen = isOpen;
}
public void setParent( PolyNode n ) {
parent = n;
}
}

37
open-anpr-core/src/main/java/com/visual/open/anpr/core/clipper/PolyTree.java

@ -0,0 +1,37 @@
package com.visual.open.anpr.core.clipper;
import java.util.ArrayList;
import java.util.List;
public class PolyTree extends PolyNode {
private final List<PolyNode> allPolys = new ArrayList<PolyNode>();
public void Clear() {
allPolys.clear();
childs.clear();
}
public List<PolyNode> getAllPolys() {
return allPolys;
}
public PolyNode getFirst() {
if (!childs.isEmpty()) {
return childs.get( 0 );
}
else {
return null;
}
}
public int getTotalSize() {
int result = allPolys.size();
//with negative offsets, ignore the hidden outer polygon ...
if (result > 0 && childs.get( 0 ) != allPolys.get( 0 )) {
result--;
}
return result;
}
}

692
open-anpr-core/src/main/java/com/visual/open/anpr/core/domain/ImageMat.java

@ -0,0 +1,692 @@
package com.visual.open.anpr.core.domain;
import ai.onnxruntime.OnnxTensor;
import ai.onnxruntime.OrtEnvironment;
import com.visual.open.anpr.core.utils.MatUtil;
import org.opencv.core.Point;
import org.opencv.core.*;
import org.opencv.dnn.Dnn;
import org.opencv.highgui.HighGui;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Base64;
/**
* 图片加载工具
*/
public class ImageMat implements Serializable {
//静态加载动态链接库
static{ nu.pattern.OpenCV.loadShared(); }
private OrtEnvironment env = OrtEnvironment.getEnvironment();
//对象成员
private Mat mat;
private ImageMat(Mat mat){
this.mat = mat;
}
/**
* 读取图片并转换为Mat
* @param imagePath 图片地址
* @return
*/
public static ImageMat fromImage(String imagePath){
return new ImageMat(Imgcodecs.imread(imagePath));
}
/**
* 直接读取Mat
* @param mat 图片mat值
* @return
*/
public static ImageMat fromCVMat(Mat mat){
try {
return new ImageMat(mat);
}catch (Exception e){
throw new RuntimeException(e);
}
}
/**
* 读取图片并转换为Mat
* @param base64Str 图片Base64编码值
* @return
*/
public static ImageMat fromBase64(String base64Str){
InputStream inputStream = null;
try {
Base64.Decoder decoder = Base64.getMimeDecoder();
byte[] data = decoder.decode(base64Str);
inputStream = new ByteArrayInputStream(data);
return fromInputStream(inputStream);
}catch (Exception e){
throw new RuntimeException(e);
}finally {
if(null != inputStream){
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 读取图片并转换为Mat
* @param inputStream 图片数据
* @return
*/
public static ImageMat fromInputStream(InputStream inputStream){
try {
BufferedImage image = ImageIO.read(inputStream);
return fromBufferedImage(image);
}catch (Exception e){
throw new RuntimeException(e);
}
}
/**
* 读取图片并转换为Mat
* @param image 图片数据
* @return
*/
public static ImageMat fromBufferedImage(BufferedImage image){
try {
if(image.getType() != BufferedImage.TYPE_3BYTE_BGR){
BufferedImage temp = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
Graphics2D g = temp.createGraphics();
try {
g.setComposite(AlphaComposite.Src);
g.drawImage(image, 0, 0, null);
} finally {
g.dispose();
}
image = temp;
}
byte[] pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
Mat mat = Mat.eye(image.getHeight(), image.getWidth(), CvType.CV_8UC3);
mat.put(0, 0, pixels);
return new ImageMat(mat);
}catch (Exception e){
throw new RuntimeException(e);
}
}
/**
* 显示图片用于数据调试
*/
public void imShow() {
HighGui.imshow("image", mat);
HighGui.waitKey();
}
/**
*获取数据的宽度
* @return
*/
public int getWidth(){
return (int) mat.size().width;
}
/**
* 获取数据的高度
* @return
*/
public int getHeight(){
return (int) mat.size().height;
}
/**
* 克隆ImageMat
* @return
*/
public ImageMat clone(){
return ImageMat.fromCVMat(this.mat.clone());
}
/**
* 获取图像的中心点
* @return
*/
public Point center(){
return new Point(mat.size(1)/2, mat.size(0)/2);
}
/**
* 获取当前的CV Mat
* @return
*/
public Mat toCvMat() {
return mat;
}
/**
* 数据格式转换,不释放原始图片数据
* @param code Imgproc.COLOR_*
* @param release 是否释放参数mat
*/
public ImageMat cvtColorAndNoReleaseMat(int code, boolean release) {
return this.cvtColor(code, false);
}
/**
* 数据格式转换,并释放原始图片数据
* @param code Imgproc.COLOR_*
* @param release 是否释放参数mat
*/
public ImageMat cvtColorAndDoReleaseMat(int code, boolean release) {
return this.cvtColor(code, true);
}
/**
* 数据格式转换
* @param code Imgproc.COLOR_*
* @param release 是否释放参数mat
*/
private ImageMat cvtColor(int code, boolean release) {
try {
Mat dst = new Mat();
Imgproc.cvtColor(mat, dst, code);
return new ImageMat(dst);
}finally {
if(release){
this.release();
}
}
}
/**
* 重新设置图片尺寸,不释放原始图片数据
* @param width 图片宽度
* @param height 图片高度
* @return
*/
public ImageMat resizeAndNoReleaseMat(int width, int height){
return this.resize(width, height, false);
}
/**
* 重新设置图片尺寸,并释放原始图片数据
* @param width 图片宽度
* @param height 图片高度
* @return
*/
public ImageMat resizeAndDoReleaseMat(int width, int height){
return this.resize(width, height, true);
}
/**
* 重新设置图片尺寸
* @param width 图片宽度
* @param height 图片高度
* @param release 是否释放参数mat
* @return
*/
private ImageMat resize(int width, int height, boolean release){
try {
Mat dst = new Mat();
Imgproc.resize(mat, dst, new Size(width,height), 0, 0, Imgproc.INTER_AREA);
return new ImageMat(dst);
}finally {
if(release){
this.release();
}
}
}
/**
* 对图像进行预处理,不释放原始图片数据
* @param scale 图像各通道数值的缩放比例
* @param mean 用于各通道减去的值以降低光照的影响
* @param swapRB 交换RB通道默认为False.
* @return
*/
public ImageMat blobFromImageAndNoReleaseMat(double scale, Scalar mean, boolean swapRB){
return this.blobFromImage(scale, mean, swapRB, false);
}
/**
* 对图像进行预处理,并释放原始图片数据
* @param scale 图像各通道数值的缩放比例
* @param mean 用于各通道减去的值以降低光照的影响
* @param swapRB 交换RB通道默认为False.
* @return
*/
public ImageMat blobFromImageAndDoReleaseMat(double scale, Scalar mean, boolean swapRB){
return this.blobFromImage(scale, mean, swapRB, true);
}
/**
* 对图像进行预处理
* @param scale 图像各通道数值的缩放比例
* @param mean 用于各通道减去的值以降低光照的影响
* @param swapRB 交换RB通道默认为False.
* @param release 是否释放参数mat
* @return
*/
private ImageMat blobFromImage(double scale, Scalar mean, boolean swapRB, boolean release){
try {
Mat dst = Dnn.blobFromImage(mat, scale, new Size( mat.cols(), mat.rows()), mean, swapRB);
java.util.List<Mat> mats = new ArrayList<>();
Dnn.imagesFromBlob(dst, mats);
dst.release();
return new ImageMat(mats.get(0));
}finally {
if(release){
this.release();
}
}
}
/**
* 转换为base64,不释放原始图片数据
* @return
*/
public String toBase64AndNoReleaseMat(){
return toBase64(false);
}
/**
* 转换为base64,并释放原始图片数据
* @return
*/
public String toBase64AndDoReleaseMat(){
return toBase64(true);
}
/**
* 转换为base64
* @param release 是否释放参数mat
* @return
*/
private String toBase64(boolean release){
if(null != mat){
try {
return MatUtil.matToBase64(mat);
}finally {
if(release){
this.release();
}
}
}else{
return null;
}
}
/**
* 转换为整形数组,不释放原始图片数据
* @param firstChannel
* @return
*/
public int[][][][] to4dIntArrayAndNoReleaseMat(boolean firstChannel){
return this.to4dIntArray(firstChannel, false);
}
/**
* 转换为整形数组,并释放原始图片数据
* @param firstChannel
* @return
*/
public int[][][][] to4dIntArrayAndDoReleaseMat(boolean firstChannel){
return this.to4dIntArray(firstChannel, true);
}
/**
* 转换为整形数组
* @param firstChannel
* @param release 是否释放参数mat
* @return
*/
private int[][][][] to4dIntArray(boolean firstChannel, boolean release){
try {
int width = this.mat.cols();
int height = this.mat.rows();
int channel = this.mat.channels();
int[][][][] array;
if(firstChannel){
array = new int[1][channel][height][width];
for(int i=0; i<height; i++){
for(int j=0; j<width; j++){
double[] c = mat.get(i, j);
for(int k=0; k< channel; k++){
array[0][k][i][j] = (int) Math.round(c[k]);
}
}
}
}else{
array = new int[1][height][width][channel];
for(int i=0; i<height; i++){
for(int j=0; j<width; j++){
double[] c = mat.get(i, j);
for(int k=0; k< channel; k++){
array[0][i][j][k] = (int) Math.round(c[k]);
}
}
}
}
return array;
}finally {
if(release){
this.release();
}
}
}
/**
* 转换为长整形数组,不释放原始图片数据
* @param firstChannel
* @return
*/
public long[][][][] to4dLongArrayAndNoReleaseMat(boolean firstChannel){
return this.to4dLongArray(firstChannel, false);
}
/**
* 转换为长整形数组,并释放原始图片数据
* @param firstChannel
* @return
*/
public long[][][][] to4dLongArrayAndDoReleaseMat(boolean firstChannel){
return this.to4dLongArray(firstChannel, true);
}
/**
* 转换为长整形数组
* @param firstChannel
* @param release 是否释放参数mat
* @return
*/
private long[][][][] to4dLongArray(boolean firstChannel, boolean release){
try {
int width = this.mat.cols();
int height = this.mat.rows();
int channel = this.mat.channels();
long[][][][] array;
if(firstChannel){
array = new long[1][channel][height][width];
for(int i=0; i<height; i++){
for(int j=0; j<width; j++){
double[] c = mat.get(i, j);
for(int k=0; k< channel; k++){
array[0][k][i][j] = Math.round(c[k]);
}
}
}
}else{
array = new long[1][height][width][channel];
for(int i=0; i<height; i++){
for(int j=0; j<width; j++){
double[] c = mat.get(i, j);
for(int k=0; k< channel; k++){
array[0][i][j][k] = Math.round(c[k]);
}
}
}
}
return array;
}finally {
if(release){
this.release();
}
}
}
/**
* 转换为单精度形数组,不释放原始图片数据
* @param firstChannel
* @return
*/
public float[][][][] to4dFloatArrayAndNoReleaseMat(boolean firstChannel){
return this.to4dFloatArray(firstChannel, false);
}
/**
* 转换为单精度形数组,并释放原始图片数据
* @param firstChannel
* @return
*/
public float[][][][] to4dFloatArrayAndDoReleaseMat(boolean firstChannel){
return this.to4dFloatArray(firstChannel, true);
}
/**
* 转换为单精度形数组
* @param firstChannel
* @param release 是否释放参数mat
* @return
*/
private float[][][][] to4dFloatArray(boolean firstChannel, boolean release){
try {
float std[] = {0.229f, 0.224f, 0.225f};
int width = this.mat.cols();
int height = this.mat.rows();
int channel = this.mat.channels();
float[][][][] array;
if(firstChannel){
array = new float[1][channel][height][width];
for(int i=0; i<height; i++){
for(int j=0; j<width; j++){
double[] c = mat.get(i, j);
for(int k=0; k< channel; k++){
array[0][k][i][j] = (float) c[k]/std[k];
}
}
}
}else{
array = new float[1][height][width][channel];
for(int i=0; i<height; i++){
for(int j=0; j<width; j++){
double[] c = mat.get(i, j);
for(int k=0; k< channel; k++){
array[0][i][j][k] = (float) c[k]/std[k];
}
}
}
}
return array;
}finally {
if(release){
this.release();
}
}
}
/**
* 转换为双精度形数组,不释放原始图片数据
* @param firstChannel
* @return
*/
public double[][][][] to4dDoubleArrayAndNoReleaseMat(boolean firstChannel){
return this.to4dDoubleArray(firstChannel, false);
}
/**
* 转换为双精度形数组,并释放原始图片数据
* @param firstChannel
* @return
*/
public double[][][][] to4dDoubleArrayAndDoReleaseMat(boolean firstChannel){
return this.to4dDoubleArray(firstChannel, true);
}
/**
* 转换为双精度形数组
* @param firstChannel
* @param release 是否释放参数mat
* @return
*/
private double[][][][] to4dDoubleArray(boolean firstChannel, boolean release){
try {
int width = this.mat.cols();
int height = this.mat.rows();
int channel = this.mat.channels();
double[][][][] array;
if(firstChannel){
array = new double[1][channel][height][width];
for(int i=0; i<height; i++){
for(int j=0; j<width; j++){
double[] c = mat.get(i, j);
for(int k=0; k< channel; k++){
array[0][k][i][j] = c[k];
}
}
}
}else{
array = new double[1][height][width][channel];
for(int i=0; i<height; i++){
for(int j=0; j<width; j++){
double[] c = mat.get(i, j);
for(int k=0; k< channel; k++){
array[0][i][j][k] = c[k];
}
}
}
}
return array;
}finally {
if(release){
this.release();
}
}
}
/**
* 转换为整形OnnxTensor,不释放原始图片数据
* @param firstChannel
* @return
*/
public OnnxTensor to4dIntOnnxTensorAndNoReleaseMat(boolean firstChannel){
try {
return OnnxTensor.createTensor(env, this.to4dIntArrayAndNoReleaseMat(firstChannel));
}catch (Exception e){
throw new RuntimeException(e);
}
}
/**
* 转换为整形OnnxTensor,并释放原始图片数据
* @param firstChannel
* @return
*/
public OnnxTensor to4dIntOnnxTensorAndDoReleaseMat(boolean firstChannel){
try {
return OnnxTensor.createTensor(env, this.to4dIntArrayAndDoReleaseMat(firstChannel));
}catch (Exception e){
throw new RuntimeException(e);
}
}
/**
* 转换为长整形OnnxTensor,不释放原始图片数据
* @param firstChannel
* @return
*/
public OnnxTensor to4dLongOnnxTensorAndNoReleaseMat(boolean firstChannel) {
try {
return OnnxTensor.createTensor(env, this.to4dLongArrayAndNoReleaseMat(firstChannel));
}catch (Exception e){
throw new RuntimeException(e);
}
}
/**
* 转换为长整形OnnxTensor,并释放原始图片数据
* @param firstChannel
* @return
*/
public OnnxTensor to4dLongOnnxTensorAndDoReleaseMat(boolean firstChannel) {
try {
return OnnxTensor.createTensor(env, this.to4dLongArrayAndDoReleaseMat(firstChannel));
}catch (Exception e){
throw new RuntimeException(e);
}
}
/**
* 转换为单精度形OnnxTensor,不释放原始图片数据
* @param firstChannel
* @return
*/
public OnnxTensor to4dFloatOnnxTensorAndNoReleaseMat(boolean firstChannel) {
try {
return OnnxTensor.createTensor(env, this.to4dFloatArrayAndNoReleaseMat(firstChannel));
}catch (Exception e){
throw new RuntimeException(e);
}
}
/**
* 转换为单精度形OnnxTensor,并释放原始图片数据
* @param firstChannel
* @return
*/
public OnnxTensor to4dFloatOnnxTensorAndDoReleaseMat(boolean firstChannel) {
try {
return OnnxTensor.createTensor(env, this.to4dFloatArrayAndDoReleaseMat(firstChannel));
}catch (Exception e){
throw new RuntimeException(e);
}
}
/**
* 转换为双精度形OnnxTensor,不释放原始图片数据
* @param firstChannel
* @return
*/
public OnnxTensor to4dDoubleOnnxTensorAndNoReleaseMat(boolean firstChannel) {
try {
return OnnxTensor.createTensor(env, this.to4dDoubleArrayAndNoReleaseMat(firstChannel));
}catch (Exception e){
throw new RuntimeException(e);
}
}
/**
* 转换为双精度形OnnxTensor,并释放原始图片数据
* @param firstChannel
* @return
*/
public OnnxTensor to4dDoubleOnnxTensorAndDoReleaseMat(boolean firstChannel) {
try {
return OnnxTensor.createTensor(env, this.to4dDoubleArrayAndDoReleaseMat(firstChannel));
}catch (Exception e){
throw new RuntimeException(e);
}
}
/**
* 释放资源
*/
public void release(){
if(this.mat != null){
try {
this.mat.release();
this.mat = null;
}catch (Exception e){
e.printStackTrace();
}
}
}
}

1
open-anpr-core/src/main/java/com/visual/open/anpr/core/package-info.java

@ -0,0 +1 @@
package com.visual.open.anpr.core;

122
open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/AlignUtil.java

@ -0,0 +1,122 @@
package com.visual.open.anpr.core.utils;
import org.apache.commons.math3.linear.RealMatrix;
import org.apache.commons.math3.linear.RealVector;
import org.apache.commons.math3.linear.SingularValueDecomposition;
import org.opencv.core.*;
import org.opencv.imgproc.Imgproc;
/**
* 图像对齐工具
*/
public class AlignUtil {
/**
* 人脸对齐
* @param image 图像数据
* @param imagePoint 图像中的关键点
* @param stdWidth 定义的标准图像的宽度
* @param stdHeight 定义的标准图像的高度
* @param stdPoint 定义的标准关键点
*/
public static Mat alignedImage(Mat image, double[][] imagePoint, int stdWidth, int stdHeight, double[][] stdPoint){
Mat warp = null;
Mat rectMat = null;
try {
warp = warpAffine(image, imagePoint, stdPoint);
double imgWidth = warp.size().width;
double imgHeight = warp.size().height;
if(stdWidth <= imgWidth && stdHeight <= imgHeight){
Mat crop = new Mat(warp, new Rect(0, 0, stdWidth, stdHeight));
return crop;
}
//计算需要裁剪的宽和高
int h, w;
if((1.0*imgWidth/imgHeight) >= (1.0 * stdWidth/stdHeight)){
h = (int) Math.floor(1.0 * imgHeight);
w = (int) Math.floor(1.0 * stdWidth * imgHeight / stdHeight);
}else{
w = (int) Math.floor(1.0 * imgWidth);
h = (int) Math.floor(1.0 * stdHeight * imgWidth / stdWidth);
}
//需要裁剪图片
rectMat = new Mat(warp, new Rect(0, 0, w, h));
Mat crop = new Mat();
Imgproc.resize(rectMat, crop, new Size(stdWidth, stdHeight), 0, 0, Imgproc.INTER_NEAREST);
return crop;
}finally {
if(null != rectMat){
rectMat.release();
}
if(null != warp){
warp.release();
}
}
}
/**
* 图像仿射变换
* @param image 图像数据
* @param imgPoint 图像中的关键点
* @param stdPoint 定义的标准关键点
* @return 图像的仿射结果图
*/
public static Mat warpAffine(Mat image, double[][] imgPoint, double[][] stdPoint){
Mat matM = null;
Mat matMTemp = null;
try {
//转换为矩阵
RealMatrix imgPointMatrix = MathUtil.createMatrix(imgPoint);
RealMatrix stdPointMatrix = MathUtil.createMatrix(stdPoint);
//判断数据的行列是否一致
int row = imgPointMatrix.getRowDimension();
int col = imgPointMatrix.getColumnDimension();
if(row <= 0 || col <=0 || row != stdPointMatrix.getRowDimension() || col != stdPointMatrix.getColumnDimension()){
throw new RuntimeException("row or col is not equal");
}
//求列的均值
RealVector imgPointMeanVector = MathUtil.mean(imgPointMatrix, 0);
RealVector stdPointMeanVector = MathUtil.mean(stdPointMatrix, 0);
//对关键点进行减去均值
RealMatrix imgPointMatrix1 = imgPointMatrix.subtract(MathUtil.createMatrix(row, imgPointMeanVector.toArray()));
RealMatrix stdPointMatrix1 = stdPointMatrix.subtract(MathUtil.createMatrix(row, stdPointMeanVector.toArray()));
//计算关键点的标准差
double imgPointStd = MathUtil.std(imgPointMatrix1);
double stdPointStd = MathUtil.std(stdPointMatrix1);
//对关键点除以标准差
RealMatrix imgPointMatrix2 = MathUtil.scalarDivision(imgPointMatrix1, imgPointStd);
RealMatrix stdPointMatrix2 = MathUtil.scalarDivision(stdPointMatrix1, stdPointStd);
//获取矩阵的分量
RealMatrix pointsT = imgPointMatrix2.transpose().multiply(stdPointMatrix2);
SingularValueDecomposition svdH = new SingularValueDecomposition(pointsT);
RealMatrix U = svdH.getU(); RealMatrix S = svdH.getS(); RealMatrix Vt = svdH.getVT();
//计算仿射矩阵
RealMatrix R = U.multiply(Vt).transpose();
RealMatrix R1 = R.scalarMultiply(stdPointStd/imgPointStd);
RealMatrix v21 = MathUtil.createMatrix(1, stdPointMeanVector.toArray()).transpose();
RealMatrix v22 = R.multiply(MathUtil.createMatrix(1, imgPointMeanVector.toArray()).transpose());
RealMatrix v23 = v22.scalarMultiply(stdPointStd/imgPointStd);
RealMatrix R2 = v21.subtract(v23);
RealMatrix M = MathUtil.hstack(R1, R2);
//变化仿射矩阵为Mat
matMTemp = new MatOfDouble(MathUtil.flatMatrix(M, 1).toArray());
matM = new Mat(2, 3, CvType.CV_32FC3);
matMTemp.reshape(1,2).copyTo(matM);
//使用open cv做仿射变换
Mat dst = new Mat();
Imgproc.warpAffine(image, dst, matM, image.size());
return dst;
}finally {
if(null != matM){
matM.release();
}
if(null != matMTemp){
matMTemp.release();
}
}
}
}

37
open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/ArrayUtil.java

@ -0,0 +1,37 @@
package com.visual.open.anpr.core.utils;
public class ArrayUtil {
public static double [] floatToDouble(float[] input){
if (input == null){
return null;
}
double[] output = new double[input.length];
for (int i = 0; i < input.length; i++){
output[i] = input[i];
}
return output;
}
public static float [] doubleToFloat(double[] input){
if (input == null){
return null;
}
float[] output = new float[input.length];
for (int i = 0; i < input.length; i++){
output[i] = Double.valueOf(input[i]).floatValue();
}
return output;
}
public static double matrixNorm(double[][] matrix){
double sum=0.0;
for(double[] temp1:matrix){
for(double temp2:temp1){
sum+=Math.pow(temp2,2);
}
}
return Math.sqrt(sum);
}
}

59
open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/CropUtil.java

@ -0,0 +1,59 @@
package com.visual.open.anpr.core.utils;
import com.visual.face.search.core.domain.FaceInfo;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Point;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;
import org.opencv.utils.Converters;
import java.util.ArrayList;
import java.util.List;
/**
* 图像裁剪工具
*/
public class CropUtil {
/**
* 根据4个点裁剪图像
* @param image
* @param faceBox
* @return
*/
public static Mat crop(Mat image, FaceInfo.FaceBox faceBox){
Mat endM = null;
Mat startM = null;
Mat perspectiveTransform = null;
try {
List<Point> dest = new ArrayList<>();
dest.add(new Point(faceBox.leftTop.x, faceBox.leftTop.y));
dest.add(new Point(faceBox.rightTop.x, faceBox.rightTop.y));
dest.add(new Point(faceBox.rightBottom.x, faceBox.rightBottom.y));
dest.add(new Point(faceBox.leftBottom.x, faceBox.leftBottom.y));
startM = Converters.vector_Point2f_to_Mat(dest);
List<Point> ends = new ArrayList<>();
ends.add(new Point(0, 0));
ends.add(new Point(faceBox.width(), 0));
ends.add(new Point(faceBox.width(), faceBox.height()));
ends.add(new Point(0, faceBox.height()));
endM = Converters.vector_Point2f_to_Mat(ends);
perspectiveTransform = Imgproc.getPerspectiveTransform(startM, endM);
Mat outputMat = new Mat((int)faceBox.height() , (int)faceBox.width(), CvType.CV_8UC4);
Imgproc.warpPerspective(image, outputMat, perspectiveTransform, new Size((int)faceBox.width(), (int)faceBox.height()), Imgproc.INTER_CUBIC);
return outputMat;
}finally {
if(null != endM){
endM.release();
}
if(null != startM){
startM.release();
}
if(null != perspectiveTransform){
perspectiveTransform.release();
}
}
}
}

128
open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/JsonUtil.java

@ -0,0 +1,128 @@
package com.visual.open.anpr.core.utils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.alibaba.fastjson.serializer.SerializerFeature;
import java.util.List;
import java.util.Map;
public class JsonUtil {
/**
* 将Bean转化为json字符串
*
* @param obj bean对象
* @return json
*/
public static String toString(Object obj) {
return toString(obj, false, false);
}
public static String toSimpleString(Object obj) {
return toString(obj, false, true);
}
/**
* 将Bean转化为json字符串
*
* @param obj bean对象
* @param prettyFormat 是否格式化
* @return json
*/
public static String toString(Object obj, boolean prettyFormat, boolean noNull) {
if (prettyFormat) {
if (noNull) {
return JSON.toJSONString(obj, SerializerFeature.DisableCircularReferenceDetect, SerializerFeature.PrettyFormat);
} else {
return JSON.toJSONString(obj, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteNullListAsEmpty, SerializerFeature.DisableCircularReferenceDetect, SerializerFeature.PrettyFormat);
}
} else {
if (noNull) {
return JSON.toJSONString(obj, SerializerFeature.DisableCircularReferenceDetect);
} else {
return JSON.toJSONString(obj, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteNullListAsEmpty, SerializerFeature.DisableCircularReferenceDetect);
}
}
}
/**
* 将字符串转换为Entity
*
* @param json 数据字符串
* @param clazz Entity class
* @return
*/
public static <T> T toEntity(String json, Class<T> clazz) {
return JSON.parseObject(json, clazz);
}
/**
* 将字符串转换为Entity
*
* @param json 数据字符串
* @param typeReference Entity class
* @return
*/
public static <T> T toEntity(String json, TypeReference<T> typeReference) {
return JSON.parseObject(json, typeReference);
}
/**
* 将字符串转换为Map
*
* @param json 数据字符串
* @return Map
*/
public static Map<String, Object> toMap(String json) {
return JSON.parseObject(json, new TypeReference<Map<String, Object>>() {
});
}
/**
* 将字符串转换为List<T>
*
* @param json 数据字符串
* @param collectionClass 泛型
* @return list<T>
*/
public static <T> List<T> toList(String json, Class<T> collectionClass) {
return JSON.parseArray(json, collectionClass);
}
/**
* 将字符串转换为List<Map<String, Object>>
*
* @param json 数据字符串
* @return list<map>
*/
public static List<Map<String, Object>> toListMap(String json) {
return JSON.parseObject(json, new TypeReference<List<Map<String, Object>>>() {
});
}
/**
* 将字符串转换为Object
*
* @param json 数据字符串
* @return list<map>
*/
public static JSONObject toJsonObject(String json) {
return JSON.parseObject(json);
}
/**
* 将字符串转换为Array
*
* @param json 数据字符串
* @return list<map>
*/
public static JSONArray toJsonArray(String json) {
return JSON.parseArray(json);
}
}

111
open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/MaskUtil.java

@ -0,0 +1,111 @@
package com.visual.open.anpr.core.utils;
import com.visual.face.search.core.domain.FaceInfo;
import com.visual.face.search.core.domain.ImageMat;
import org.opencv.core.*;
import org.opencv.imgproc.Imgproc;
import java.util.ArrayList;
import java.util.List;
public class MaskUtil {
/**添加遮罩层所需要的索引号:InsightCoordFaceKeyPoint**/
private static int [] MASK_106_IST_ROUND_INDEX = new int[]{
1,9,10,11,12,13,14,15,16,2,3,4,5,6,7,8,0,
24,23,22,21,20,19,18,32,31,30,29,28,27,26,25,17,
101,105,104,103,102,50,51,49,48,43
};
/**
* 添加遮罩层
* @param image 原始图像
* @param pts 指定不不需要填充的区域
* @param release 是否释放参数image
* @return
*/
public static Mat mask(Mat image, List<MatOfPoint> pts, boolean release){
Mat pattern = null;
try {
pattern = MatOfPoint.zeros(image.size(), CvType.CV_8U);
Imgproc.fillPoly(pattern, pts, new Scalar(1,1,1));
Mat dst = new Mat();
image.copyTo(dst, pattern);
return dst;
}finally {
if(null != pattern){
pattern.release();
}
if(release && null != pts){
for(MatOfPoint pt : pts){
pt.release();
}
}
if(release && null != image){
image.release();
}
}
}
/**
* 添加遮罩层
* @param image 原始图像
* @param fillPoints 指定不不需要填充的区域的点
* @param release 是否释放参数image
* @return
*/
public static Mat mask(Mat image, Point[] fillPoints, boolean release){
List<MatOfPoint> pts = null;
try {
pts = new ArrayList<>();
pts.add(new MatOfPoint(fillPoints));
return mask(image, pts, false);
}finally {
if(null != pts){
for(MatOfPoint pt : pts){
pt.release();
}
}
if(release && null != image){
image.release();
}
}
}
/**
* 添加遮罩层:InsightCoordFaceKeyPoint
* @param image 原始图像
* @param points 人脸标记点
* @param release 是否释放参数image
* @return
*/
public static Mat maskFor106InsightCoordModel(Mat image, FaceInfo.Points points, boolean release){
try {
Point[] fillPoints = PointUtil.convert(points.select(MASK_106_IST_ROUND_INDEX));
return mask(image, fillPoints, false);
}finally {
if(release && null != image){
image.release();
}
}
}
/**
* 添加遮罩层:InsightCoordFaceKeyPoint
* @param image 原始图像
* @param points 人脸标记点
* @param release 是否释放参数image
* @return
*/
public static ImageMat maskFor106InsightCoordModel(ImageMat image, FaceInfo.Points points, boolean release){
try {
Mat mat = maskFor106InsightCoordModel(image.toCvMat(), points, false);
return ImageMat.fromCVMat(mat);
}finally {
if(release && null != image){
image.release();
}
}
}
}

97
open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/MatUtil.java

@ -0,0 +1,97 @@
package com.visual.open.anpr.core.utils;
import org.opencv.core.Mat;
import org.opencv.core.Range;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.util.Base64;
import java.util.Objects;
public class MatUtil {
/**
* 将Mat转换为BufferedImage
* @param mat
* @return BufferedImage
*/
public static BufferedImage matToBufferedImage(Mat mat) {
int dataSize = mat.cols() * mat.rows() * (int) mat.elemSize();
byte[] data = new byte[dataSize];
mat.get(0, 0, data);
int type = mat.channels() == 1 ? BufferedImage.TYPE_BYTE_GRAY : BufferedImage.TYPE_3BYTE_BGR;
if (type == BufferedImage.TYPE_3BYTE_BGR) {
for (int i = 0; i < dataSize; i += 3) {
byte blue = data[i + 0];
data[i + 0] = data[i + 2];
data[i + 2] = blue;
}
}
BufferedImage image = new BufferedImage(mat.cols(), mat.rows(), type);
image.getRaster().setDataElements(0, 0, mat.cols(), mat.rows(), data);
return image;
}
/**
* 将Mat转换为 Base64
* @param mat
* @return Base64
*/
public static String matToBase64(Mat mat) {
ByteArrayOutputStream byteArrayOutputStream = null;
try {
byteArrayOutputStream = new ByteArrayOutputStream();
ImageIO.write(matToBufferedImage(mat), "jpg", byteArrayOutputStream);
byte[] bytes = byteArrayOutputStream.toByteArray();
Base64.Encoder encoder = Base64.getMimeEncoder();
return encoder.encodeToString(Objects.requireNonNull(bytes));
}catch (Exception e){
throw new RuntimeException(e);
}finally {
if(null != byteArrayOutputStream){
try {
byteArrayOutputStream.close();
} catch (Exception e) {}
}
}
}
/**
* 横向拼接两个图像的数据Mat该两个图像的类型必须是相同的类型均为CvType.CV_8UC3类型
* @author bailichun
* @since 2020.02.20 15:00
* @param m1 要合并的图像1左图
* @param m2 要合并的图像2右图
* @return 拼接好的Mat图像数据集其高度等于两个图像中高度较大者的高度其宽度等于两个图像的宽度之和类型与两个输入图像相同
* @throws Exception 当两个图像数据的类型不同时抛出异常
*/
public static Mat concat(Mat m1, Mat m2){
if(m1.type() != m2.type()){
throw new RuntimeException("concat:两个图像数据的类型不同!");
}
long time = System.currentTimeMillis();
//宽度为两图的宽度之和
double w = m1.size().width + m2.size().width;
//高度取两个矩阵中的较大者的高度
double h = m1.size().height > m2.size().height ? m1.size().height : m2.size().height;
//创建一个大矩阵对象
Mat des = Mat.zeros((int)h, (int)w, m1.type());
//在最终的大图上标记一块区域,用于存放复制图1(左图)的数据,大小为从第0列到m1.cols()列
Mat rectForM1 = des.colRange(new Range(0, m1.cols()));
//标记出位于rectForM1的垂直方向上中间位置的区域,高度为图1的高度,此时该区域的大小已经和图1的大小相同。(用于存放复制图1(左图)的数据)
int rowOffset1 = (int)(rectForM1.size().height-m1.rows())/2;
rectForM1 = rectForM1.rowRange(rowOffset1, rowOffset1 + m1.rows());
//在最终的大图上标记一块区域,用于存放复制图2(右图)的数据
Mat rectForM2 = des.colRange(new Range(m1.cols(), des.cols()));
//标记出位于rectForM2的垂直方向上中间位置的区域,高度为图2的高度,此时该区域的大小已经和图2的大小相同。(用于存放复制图2(右图)的数据)
int rowOffset2 = (int)(rectForM2.size().height-m2.rows())/2;
rectForM2 = rectForM2.rowRange(rowOffset2, rowOffset2 + m2.rows());
//将图1拷贝到des的指定区域 rectForM1
m1.copyTo(rectForM1);
//将图2拷贝到des的指定区域 rectForM2
m2.copyTo(rectForM2);
return des;
}
}

289
open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/MathUtil.java

@ -0,0 +1,289 @@
package com.visual.open.anpr.core.utils;
import org.apache.commons.math3.linear.Array2DRowRealMatrix;
import org.apache.commons.math3.linear.ArrayRealVector;
import org.apache.commons.math3.linear.RealMatrix;
import org.apache.commons.math3.linear.RealVector;
import org.apache.commons.math3.stat.descriptive.moment.Mean;
import org.apache.commons.math3.stat.descriptive.moment.StandardDeviation;
public class MathUtil {
/**
* 创建向量
* @param array 数组
* @return 向量
*/
public static RealVector createVector(double[] array){
return new ArrayRealVector(array);
}
/**
* 创建向量
* @param array 数组
* @return 向量
*/
public static RealVector createVector(Double[] array){
return new ArrayRealVector(array);
}
/**
* 创建矩阵
* @param array 矩阵数组
* @return 矩阵
*/
public static RealMatrix createMatrix(double[][] array){
return new Array2DRowRealMatrix(array);
}
/**
* 创建矩阵
* @param array 矩阵数组
* @return 矩阵
*/
public static RealMatrix createMatrix(Double[][] array){
double[][] data = new double[array.length][];
for(int i=0; i< array.length; i++){
double [] item = new double[array[i].length];
for(int j=0; j<array[i].length; j++){
item[j] = array[i][j];
}
data[i] = item;
}
return new Array2DRowRealMatrix(data);
}
/**
* 创建矩阵
* @param rows 重复的行数
* @param array 矩阵数组
* @return 矩阵
*/
public static RealMatrix createMatrix(int rows, double[] array){
double[][] data = new double[rows][array.length];
for(int i=0; i<rows;i++){
data[i] = array;
}
return new Array2DRowRealMatrix(data);
}
/**
* 将矩阵的每个值都加上value值
* @param matrix 矩阵
* @param value 加值
* @return 矩阵
*/
public static RealMatrix scalarAdd(RealMatrix matrix, double value){
return matrix.scalarAdd(value);
}
/**
* 将矩阵的每个值都减去value值
* @param matrix 矩阵
* @param value 减值
* @return 矩阵
*/
public static RealMatrix scalarSub(RealMatrix matrix, double value){
return matrix.scalarAdd(-value);
}
/**
* 将矩阵的每个值都乘以value值
* @param matrix 矩阵
* @param value 乘值
* @return 矩阵
*/
public static RealMatrix scalarMultiply(RealMatrix matrix, double value){
return matrix.scalarMultiply(value);
}
/**
* 将矩阵的每个值都除以value值
* @param matrix 矩阵
* @param value 除值
* @return 矩阵
*/
public static RealMatrix scalarDivision(RealMatrix matrix, double value){
return matrix.scalarMultiply(1.0/value);
}
/**
* 求矩阵的均值分坐标轴0Y轴 1X轴
* @param matrix 数据矩阵
* @param axis 0Y轴 1X轴
* @return 均值
*/
public static RealVector mean(RealMatrix matrix, int axis){
if(axis == 0){
double[] means = new double[matrix.getColumnDimension()];
for(int i=0;i<matrix.getColumnDimension(); i++){
means[i] = new Mean().evaluate(matrix.getColumn(i));
}
return new ArrayRealVector(means);
}else {
double[] means = new double[matrix.getRowDimension()];
for(int i=0;i<matrix.getRowDimension(); i++){
means[i] = new Mean().evaluate(matrix.getRow(i));
}
return new ArrayRealVector(means);
}
}
/**
* 计算矩阵的整体标准差
* @param matrix 数据矩阵
* @return 整体标准差
*/
public static double std(RealMatrix matrix){
double[] data = new double[matrix.getColumnDimension() * matrix.getRowDimension()];
for(int i=0;i<matrix.getRowDimension(); i++){
for(int j=0;j<matrix.getColumnDimension(); j++){
data[i*matrix.getColumnDimension()+j] = matrix.getEntry(i, j);
}
}
return new StandardDeviation(false).evaluate(data);
}
/**
* 矩阵列拼接
* @param matrix1 数据矩阵1
* @param matrix2 数据矩阵2
* @return 数据矩阵
*/
public static RealMatrix hstack(RealMatrix matrix1, RealMatrix matrix2){
int row = matrix1.getRowDimension();
int col = matrix1.getColumnDimension()+matrix2.getColumnDimension();
double[][] data = new double[row][col];
for(int i=0;i<matrix1.getRowDimension(); i++){
for(int j=0;j<matrix1.getColumnDimension(); j++){
data[i][j] = matrix1.getEntry(i, j);
}
for(int j=0;j<matrix2.getColumnDimension(); j++){
data[i][matrix1.getColumnDimension()+j] = matrix2.getEntry(i, j);
}
}
return new Array2DRowRealMatrix(data);
}
/**
* 矩阵行拼接
* @param matrix1 数据矩阵1
* @param matrix2 数据矩阵2
* @return 数据矩阵
*/
public static RealMatrix vstack(RealMatrix matrix1, RealMatrix matrix2){
int row = matrix1.getRowDimension()+matrix2.getRowDimension();
int col = matrix1.getColumnDimension();
double[][] data = new double[row][col];
for(int i=0;i<matrix1.getRowDimension(); i++){
for(int j=0;j<matrix1.getColumnDimension(); j++){
data[i][j] = matrix1.getEntry(i, j);
}
}
for(int i=0;i<matrix2.getRowDimension(); i++){
for(int j=0;j<matrix2.getColumnDimension(); j++){
data[i+matrix1.getRowDimension()][j] = matrix2.getEntry(i, j);
}
}
return new Array2DRowRealMatrix(data);
}
/**
* 将矩阵拉平
* @param matrix 矩阵
* @param axis 0Y轴 1X轴
* @return
*/
public static RealVector flatMatrix(RealMatrix matrix, int axis){
RealVector vector = new ArrayRealVector();
if(0 == axis){
for(int i=0; i< matrix.getColumnDimension(); i++){
vector = vector.append(matrix.getColumnVector(i));
}
}else{
for(int i=0; i< matrix.getRowDimension(); i++){
vector = vector.append(matrix.getRowVector(i));
}
}
return vector;
}
/**
* 向量点积
* @param vector1 向量1
* @param vector2 向量2
* @return 点积
*/
public static double dotProduct(RealVector vector1, RealVector vector2){
return vector1.dotProduct(vector2);
}
/**
* 矩阵点积
* @param matrix1 矩阵1
* @param matrix2 矩阵2
* @return 点积矩阵
*/
public static RealMatrix dotProduct(RealMatrix matrix1, RealMatrix matrix2){
double[][] data = new double[matrix1.getRowDimension()][matrix1.getColumnDimension()];
for(int row = 0; row < matrix1.getRowDimension(); row ++){
for(int col=0; col < matrix1.getColumnDimension(); col ++){
data[row][col] = matrix1.getRowVector(row).dotProduct(matrix2.getColumnVector(col));
}
}
return createMatrix(data);
}
/**
* 矩阵相似变换
* @param matrix
* @param scale
* @param rotation
* @param translation
* @return
*/
public static RealMatrix similarityTransform(Double[][] matrix, Double scale, Double rotation, Double[] translation){
if(matrix == null && translation == null){
return similarityTransform((RealMatrix)null, scale, rotation, null);
}else if(matrix == null){
return similarityTransform(null, scale, rotation, createVector(translation));
}else if(translation == null){
return similarityTransform(createMatrix(matrix), scale, rotation, null);
}else{
return similarityTransform(createMatrix(matrix), scale, rotation, createVector(translation));
}
}
/**
* 矩阵相似变换
* @param matrix
* @param scale
* @param rotation
* @param translation
* @return
*/
public static RealMatrix similarityTransform(RealMatrix matrix, Double scale, Double rotation, RealVector translation){
boolean hasParams = (scale != null || rotation!= null || translation!= null);
if(hasParams && matrix != null){
throw new RuntimeException("You cannot specify the transformation matrix and the implicit parameters at the same time.");
}else if(matrix != null){
if(matrix.getColumnDimension() != 3 && matrix.getRowDimension() != 3){
throw new RuntimeException("Invalid shape of transformation matrix.");
}else {
return matrix;
}
}else if(hasParams){
scale = scale == null ? 1 : scale;
rotation = rotation == null ? 0 : rotation;
translation = translation == null ? createVector(new double[]{0, 0}) : translation;
return createMatrix(new double[][]{
{Math.cos(rotation) * scale, -Math.sin(rotation) * scale, translation.getEntry(0)},
{Math.sin(rotation) * scale, Math.cos(rotation) * scale, translation.getEntry(1)},
{0, 0, 1}
});
}else {
return createMatrix(new double[][]{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}});
}
}
}

39
open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/PointUtil.java

@ -0,0 +1,39 @@
package com.visual.open.anpr.core.utils;
import com.visual.face.search.core.domain.FaceInfo;
import org.opencv.core.Point;
public class PointUtil {
/**
* 转换点对象
* @param point
* @return
*/
public static FaceInfo.Point convert(Point point){
return FaceInfo.Point.build((float)point.x, (float)point.y);
}
/**
* 转换点对象
* @param point
* @return
*/
public static Point convert(FaceInfo.Point point){
return new Point(point.x, point.y);
}
/**
* 转换点对象
* @param points
* @return
*/
public static Point[] convert(FaceInfo.Points points){
Point[] result = new Point[points.size()];
for(int i=0; i< points.size(); i++){
result[i] = convert(points.get(i));
}
return result;
}
}

98
open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/ReleaseUtil.java

@ -0,0 +1,98 @@
package com.visual.open.anpr.core.utils;
import ai.onnxruntime.OnnxTensor;
import ai.onnxruntime.OrtSession;
import com.visual.face.search.core.domain.ImageMat;
import com.visual.face.search.core.domain.Mats;
import org.opencv.core.Mat;
public class ReleaseUtil {
public static void release(Mat ...mats){
for(Mat mat : mats){
if(null != mat){
try {
mat.release();
}catch (Exception e){
e.printStackTrace();
}finally {
mat = null;
}
}
}
}
public static void release(Mats mats){
if(null == mats || mats.isEmpty()){
return;
}
try {
mats.release();
}catch (Exception e){
e.printStackTrace();
}finally {
mats = null;
}
}
public static void release(ImageMat ...imageMats){
for(ImageMat imageMat : imageMats){
if(null != imageMat){
try {
imageMat.release();
}catch (Exception e){
e.printStackTrace();
}finally {
imageMat = null;
}
}
}
}
public static void release(OnnxTensor ...tensors){
if(null == tensors || tensors.length == 0){
return;
}
try {
for(OnnxTensor tensor : tensors){
try {
if(null != tensor){
tensor.close();
}
}catch (Exception e) {
e.printStackTrace();
}finally {
tensor = null;
}
}
}catch (Exception e){
e.printStackTrace();
}finally {
tensors = null;
}
}
public static void release(OrtSession.Result ...results){
if(null == results || results.length == 0){
return;
}
try {
for(OrtSession.Result result : results){
try {
if(null != result){
result.close();
}
}catch (Exception e) {
e.printStackTrace();
}finally {
result = null;
}
}
}catch (Exception e){
e.printStackTrace();
}finally {
results = null;
}
}
}

39
open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/SigmoidUtil.java

@ -0,0 +1,39 @@
package com.visual.open.anpr.core.utils;
import java.util.function.DoubleUnaryOperator;
/**
* SigmoidUtil
*/
public class SigmoidUtil {
private static final DoubleUnaryOperator sigmoid = p ->1/(1+ Math.exp(-1 * p));
/**
* sigmoid
* @param tensor
* @return
*/
public static double[] sigmoid(double[] tensor){
double[] result = new double[tensor.length];
for (int i = 0; i < result.length; i++){
result[i] = sigmoid.applyAsDouble(tensor[i]);
}
return result;
}
/**
* sigmoid
* @param tensor
* @return
*/
public static double[][] sigmoid(double[][] tensor){
double[][] result = new double[tensor.length][];
for (int i = 0; i < result.length; i++){
result[i] = sigmoid(tensor[i]);
}
return result;
}
}

96
open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/Similarity.java

@ -0,0 +1,96 @@
package com.visual.open.anpr.core.utils;
import org.apache.commons.math3.linear.RealMatrix;
import static com.visual.face.search.core.utils.ArrayUtil.matrixNorm;
public class Similarity {
/**
* 向量余弦相似度
* @param leftVector
* @param rightVector
* @return
*/
public static float cosineSimilarity(float[] leftVector, float[] rightVector) {
double dotProduct = 0;
for (int i=0; i< leftVector.length; i++) {
dotProduct += leftVector[i] * rightVector[i];
}
double d1 = 0.0d;
for (float value : leftVector) {
d1 += Math.pow(value, 2);
}
double d2 = 0.0d;
for (float value : rightVector) {
d2 += Math.pow(value, 2);
}
double cosineSimilarity;
if (d1 <= 0.0 || d2 <= 0.0) {
cosineSimilarity = 0.0;
} else {
cosineSimilarity = (dotProduct / (Math.sqrt(d1) * Math.sqrt(d2)));
}
return (float) cosineSimilarity;
}
/**
* 两个向量可以为任意维度但必须保持维度相同表示n维度中的两点
* 欧式距离
* @param vector1
* @param vector2
* @return 两点间距离
*/
public static float euclideanDistance(float[] vector1, float[] vector2) {
double distance = 0;
if (vector1.length == vector2.length) {
for (int i = 0; i < vector1.length; i++) {
double temp = Math.pow((vector1[i] - vector2[i]), 2);
distance += temp;
}
distance = Math.sqrt(distance);
}else {
throw new RuntimeException("vector length not equal");
}
return (float) distance;
}
/**
* 向量余弦相似度,加入了norm变换
* @param leftVector
* @param rightVector
* @return
*/
public static float cosineSimilarityNorm(float[] leftVector, float[] rightVector) {
RealMatrix rm1 = MathUtil.createMatrix(1, ArrayUtil.floatToDouble(leftVector));
RealMatrix rm2 = MathUtil.createMatrix(1, ArrayUtil.floatToDouble(rightVector));
RealMatrix num = rm1.multiply(rm2.transpose());
double deco = matrixNorm(rm1.getData()) * matrixNorm(rm2.getData());
double cos = num.getEntry(0, 0) / deco;
double sim = cos;
if(cos >= 0.5){
sim = cos + 2 * (cos - 0.5) * (1 - cos);
}else if(cos >= 0){
sim = cos - 2 * (cos - 0.5) * (0 - cos);
}
return Double.valueOf(sim).floatValue();
}
/**
* 对cos的原始值进行进行增强
* @param cos
* @return
*/
public static float cosEnhance(float cos){
double sim = cos;
if(cos >= 0.5){
sim = cos + 2 * (cos - 0.5) * (1 - cos);
}else if(cos >= 0){
sim = cos - 2 * (cos - 0.5) * (0 - cos);
}
return Double.valueOf(sim).floatValue();
}
}

40
open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/SoftMaxUtil.java

@ -0,0 +1,40 @@
package com.visual.open.anpr.core.utils;
import java.util.Arrays;
import java.util.NoSuchElementException;
/**
* SoftMaxUtil
*/
public class SoftMaxUtil {
/**
* softMax
* @param tensor
* @return
*/
public static double[] softMax(double[] tensor){
if(Arrays.stream(tensor).max().isPresent()){
double maxValue = Arrays.stream(tensor).max().getAsDouble();
double[] value = Arrays.stream(tensor).map(y-> Math.exp(y - maxValue)).toArray();
double total = Arrays.stream(value).sum();
return Arrays.stream(value).map(p -> p/total).toArray();
}else{
throw new NoSuchElementException("No value present");
}
}
/**
* softMax
* @param tensor
* @return
*/
public double[][] softMax(double[][] tensor){
double[][] result = new double[tensor.length][];
for (int i = 0; i < result.length; i++){
result[i] = softMax(tensor[i]);
}
return result;
}
}

9
open-anpr-core/src/main/java/com/visual/open/anpr/core/utils/ThreadUtil.java

@ -0,0 +1,9 @@
package com.visual.open.anpr.core.utils;
public class ThreadUtil {
public static void run(Runnable runnable){
new Thread(runnable).start();
}
}

BIN
open-anpr-core/src/main/resources/models/det.onnx

Binary file not shown.
Loading…
Cancel
Save