
30 changed files with 7021 additions and 0 deletions
@ -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(); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,8 @@ |
|||
package com.visual.open.anpr.core.base; |
|||
|
|||
public abstract class OpenCVLoader { |
|||
|
|||
//静态加载动态链接库
|
|||
static{ nu.pattern.OpenCV.loadShared(); } |
|||
|
|||
} |
@ -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); |
|||
} |
@ -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" ); |
|||
} |
|||
} |
@ -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; |
|||
} |
|||
//------------------------------------------------------------------------------
|
|||
} |
File diff suppressed because it is too large
@ -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(); |
|||
} |
|||
} |
|||
|
|||
} |
@ -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; |
|||
} |
|||
} |
@ -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; |
|||
} |
|||
} |
@ -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(); |
|||
} |
|||
} |
|||
|
|||
} |
@ -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
|
@ -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; |
|||
|
|||
} |
|||
|
|||
} |
@ -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; |
|||
|
|||
} |
|||
|
|||
} |
@ -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(); |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1 @@ |
|||
package com.visual.open.anpr.core; |
@ -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(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
} |
@ -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); |
|||
} |
|||
|
|||
} |
@ -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(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
} |
@ -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); |
|||
} |
|||
|
|||
} |
|||
|
@ -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(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
} |
@ -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; |
|||
} |
|||
|
|||
} |
@ -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); |
|||
} |
|||
|
|||
/** |
|||
* 求矩阵的均值,分坐标轴:0:Y轴, 1:X轴 |
|||
* @param matrix 数据矩阵 |
|||
* @param axis 0:Y轴, 1:X轴 |
|||
* @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 0:Y轴, 1:X轴 |
|||
* @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}}); |
|||
} |
|||
} |
|||
|
|||
} |
@ -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; |
|||
} |
|||
|
|||
} |
@ -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; |
|||
} |
|||
} |
|||
|
|||
} |
@ -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; |
|||
} |
|||
|
|||
} |
@ -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(); |
|||
} |
|||
|
|||
|
|||
} |
@ -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; |
|||
} |
|||
|
|||
} |
@ -0,0 +1,9 @@ |
|||
package com.visual.open.anpr.core.utils; |
|||
|
|||
public class ThreadUtil { |
|||
|
|||
public static void run(Runnable runnable){ |
|||
new Thread(runnable).start(); |
|||
} |
|||
|
|||
} |
Binary file not shown.
Loading…
Reference in new issue