
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