From e3ee4e88786747dc7060a3cb26841bff5d4f9666 Mon Sep 17 00:00:00 2001 From: davpapp Date: Fri, 23 Feb 2018 09:26:15 -0500 Subject: [PATCH] Refactored object detection, added new classifier class (ores) --- src/Cursor.java | 6 ++ src/DetectedObject.java | 49 ++++++++++ src/ImageCollector.java | 13 +-- src/IronMiner.java | 75 +++++++++------ src/ObjectDetector.java | 88 ++++++++++-------- src/Paths.java | 2 + src/cascadeTrainingImageCollector.java | 17 ++-- target/classes/Cursor.class | Bin 8326 -> 8498 bytes target/classes/DetectedObject.class | Bin 0 -> 2134 bytes target/classes/ImageCollector$1.class | Bin 843 -> 843 bytes target/classes/ImageCollector.class | Bin 3800 -> 3837 bytes target/classes/IronMiner.class | Bin 4326 -> 4200 bytes target/classes/ObjectDetector.class | Bin 5241 -> 6233 bytes target/classes/Paths.class | Bin 821 -> 1013 bytes .../cascadeTrainingImageCollector.class | Bin 4133 -> 3328 bytes 15 files changed, 165 insertions(+), 85 deletions(-) create mode 100644 src/DetectedObject.java create mode 100644 target/classes/DetectedObject.class diff --git a/src/Cursor.java b/src/Cursor.java index 3a3fd17..f9f9b44 100644 --- a/src/Cursor.java +++ b/src/Cursor.java @@ -125,6 +125,12 @@ public class Cursor { return randomizedGoalPoint; // Return the point we moved to in case we need precise movement afterwards } + public Point moveCursorToCoordinatesWithRandomness(Point goalPoint, int xTolerance, int yTolerance) throws Exception { + Point randomizedGoalPoint = randomizePoint(goalPoint, xTolerance, yTolerance); + moveCursorToCoordinates(randomizedGoalPoint); + return randomizedGoalPoint; // Return the point we moved to in case we need precise movement afterwards + } + public void moveCursorToCoordinates(Point goalPoint) throws Exception { Point startingPoint = getCurrentCursorPoint(); int distanceToMoveCursor = getDistanceBetweenPoints(startingPoint, goalPoint); diff --git a/src/DetectedObject.java b/src/DetectedObject.java new file mode 100644 index 0000000..a3eb6b3 --- /dev/null +++ b/src/DetectedObject.java @@ -0,0 +1,49 @@ +import java.awt.Point; +import java.awt.Rectangle; + + + +public class DetectedObject { + + private Rectangle boundingBox; + private float detectionScore; + private String detectionClass; + + public DetectedObject(float detectionScore, float detectionClass, float[] detectionBox) { + this.boundingBox = initializeBoundingBox(detectionBox); + this.detectionScore = detectionScore; + this.detectionClass = initializeLabel(detectionClass); + } + + private Rectangle initializeBoundingBox(float[] detectionBox) { + int offset_x = (int) (detectionBox[1] * Constants.GAME_WINDOW_WIDTH); + int offset_y = (int) (detectionBox[0] * Constants.GAME_WINDOW_HEIGHT); + int width = (int) (detectionBox[3] * Constants.GAME_WINDOW_WIDTH) - offset_x; + int height = (int) (detectionBox[2] * Constants.GAME_WINDOW_HEIGHT) - offset_y; + + //System.out.println(detectionBox[0] + ", " + detectionBox[1] + ", " + detectionBox[2] + ", " + detectionBox[3]); + return new Rectangle(offset_x, offset_y, width, height); + } + + private String initializeLabel(float detectionClass) { + // TODO: actually load these from a file + String[] labels = {"NA", "ironOre", "ore"}; + return labels[(int) detectionClass]; + } + + public String getDetectionClass() { + return detectionClass; + } + + public Rectangle getBoundingBox() { + return boundingBox; + } + + public Point getCenterForClicking() { + return new Point(boundingBox.x + boundingBox.width / 2 + Constants.GAME_WINDOW_OFFSET_X, boundingBox.y + boundingBox.height / 2 + Constants.GAME_WINDOW_OFFSET_Y); + } + + public void display() { + System.out.println(detectionClass + " with score " + detectionScore + " at (" + getCenterForClicking().x + "," + getCenterForClicking().y + ")"); + } +} diff --git a/src/ImageCollector.java b/src/ImageCollector.java index 037b648..9339eec 100644 --- a/src/ImageCollector.java +++ b/src/ImageCollector.java @@ -12,6 +12,7 @@ public class ImageCollector { public String screenshotOutputDirectory; public Rectangle gameWindowRectangle; + public Robot robot; /* * Methods needed: @@ -23,13 +24,14 @@ public class ImageCollector { * detect last file name */ - public ImageCollector(String screenshotOutputDirectory) { + public ImageCollector(String screenshotOutputDirectory) throws AWTException { initializeGameWindowRectangle(); this.screenshotOutputDirectory = screenshotOutputDirectory; + this.robot = new Robot(); } private void initializeGameWindowRectangle() { - this.gameWindowRectangle = new Rectangle(103, 85, 510, 330); + this.gameWindowRectangle = new Rectangle(Constants.GAME_WINDOW_OFFSET_X, Constants.GAME_WINDOW_OFFSET_Y, Constants.GAME_WINDOW_WIDTH, Constants.GAME_WINDOW_HEIGHT); } public void collectImages(String itemName) throws IOException, InterruptedException, AWTException { @@ -66,7 +68,6 @@ public class ImageCollector { } private void captureAndSaveGameWindow(String itemName, int fileCounter) throws IOException, InterruptedException, AWTException { - Robot robot = new Robot(); BufferedImage imageCaptured = robot.createScreenCapture(gameWindowRectangle); String fileName = getFileName(itemName, fileCounter); ImageIO.write(imageCaptured, "jpg", new File(fileName)); @@ -85,8 +86,8 @@ public class ImageCollector { public static void main(String[] args) throws Exception { - ImageCollector imageCollector = new ImageCollector("/home/dpapp/Desktop/RunescapeAI/TensorFlow/IronOre/"); - //imageCollector.collectImages("ironOre"); - imageCollector.generateInventoryImages(); + ImageCollector imageCollector = new ImageCollector("/home/dpapp/Desktop/RunescapeAI/TensorFlow/Ores/Images/"); + imageCollector.collectImages("ore"); + //imageCollector.generateInventoryImages(); } } diff --git a/src/IronMiner.java b/src/IronMiner.java index 3054cef..13eaf54 100644 --- a/src/IronMiner.java +++ b/src/IronMiner.java @@ -9,11 +9,13 @@ import java.util.ArrayList; import javax.imageio.ImageIO; +import org.opencv.core.Rect2d; + public class IronMiner { public static final int IRON_ORE_MINING_TIME_MILLISECONDS = 2738; public static final int MAXIMUM_DISTANCE_TO_WALK_TO_IRON_ORE = 400; - public static final Point GAME_WINDOW_CENTER = new Point(510 / 2, 330 / 2); + public static final Point GAME_WINDOW_CENTER = new Point(Constants.GAME_WINDOW_WIDTH / 2, Constants.GAME_WINDOW_HEIGHT / 2); Cursor cursor; CursorTask cursorTask; @@ -35,13 +37,15 @@ public class IronMiner { public void run() throws Exception { while (true) { - //Thread.sleep(250); - - String filename = "/home/dpapp/Desktop/RunescapeAI/temp/screenshot.jpg"; - BufferedImage image = captureScreenshotGameWindow(); - ImageIO.write(image, "jpg", new File(filename)); - mineClosestIronOre(filename); + objectDetector.update(); + ArrayList ironOres = objectDetector.getRecognizedObjectsOfClassFromImage("ironOre"); + ArrayList ores = objectDetector.getRecognizedObjectsOfClassFromImage("ore"); + System.out.println(ironOres.size() + " ironOres, " + ores.size() + " ores."); + /*for (DetectedObject ironOre : ironOres) { + ironOre.display(); + }*/ + mineClosestIronOre(ironOres, ores); dropInventoryIfFull(); } } @@ -53,47 +57,56 @@ public class IronMiner { } } - private void mineClosestIronOre(String filename) throws Exception { + + private void mineClosestIronOre(ArrayList ironOres, ArrayList ores) throws Exception { + DetectedObject closestIronOre = getClosestObjectToCharacter(ironOres); + if (closestIronOre != null) { + cursor.moveAndLeftClickAtCoordinatesWithRandomness(closestIronOre.getCenterForClicking(), 10, 10); + Thread.sleep(84, 219); + DetectedObject closestOre = getClosestObjectToCharacter(ores); + if (closestOre != null) { + cursor.moveCursorToCoordinatesWithRandomness(closestOre.getCenterForClicking(), 10, 10); + } + Thread.sleep(randomizer.nextGaussianWithinRange(IRON_ORE_MINING_TIME_MILLISECONDS - 250, IRON_ORE_MINING_TIME_MILLISECONDS + -50)); + } + //Thread.sleep(randomizer.nextGaussianWithinRange(150, 350)); + + //cursor.moveCursorToCoordinates(goalPoint); + } + /*private void mineClosestIronOre(String filename) throws Exception { Point ironOreLocation = getClosestIronOre(filename); - /*if (ironOreLocation == null) { - Thread.sleep(1000); - }*/ if (ironOreLocation != null) { System.out.println("Mineable iron at (" + (ironOreLocation.x + 103) + "," + (ironOreLocation.y + 85) + ")"); Point actualIronOreLocation = new Point(ironOreLocation.x + 103, ironOreLocation.y + 85); + Rect2d trackerBoundingBox = new Rec2d(); + //Rectangle trackerBoundingBox = new Rectangle(ironOreLocation.x - 10, ironOreLocation.x + 10, ironOreLocation.y - 10, ironOreLocation.y + 10); + //tracker.init(image, trackerBoundingBox); cursor.moveAndLeftClickAtCoordinatesWithRandomness(actualIronOreLocation, 12, 12); Thread.sleep(randomizer.nextGaussianWithinRange(IRON_ORE_MINING_TIME_MILLISECONDS - 350, IRON_ORE_MINING_TIME_MILLISECONDS + 150)); } } - private Point getClosestIronOre(String filename) throws IOException { - ArrayList ironOreLocations = objectDetector.getIronOreLocationsFromImage(filename); - System.out.println(ironOreLocations.size()); - int closestDistanceToIronOreFromCharacter = Integer.MAX_VALUE; - Point closestIronOreToCharacter = null; - for (Point ironOreLocation : ironOreLocations) { - int distanceToIronOreFromCharacter = getDistanceBetweenPoints(GAME_WINDOW_CENTER, ironOreLocation); - if (distanceToIronOreFromCharacter < closestDistanceToIronOreFromCharacter) { - closestDistanceToIronOreFromCharacter = distanceToIronOreFromCharacter; - closestIronOreToCharacter = new Point(ironOreLocation.x, ironOreLocation.y); + }*/ + + private DetectedObject getClosestObjectToCharacter(ArrayList detectedObjects) { + int closestDistanceToCharacter = Integer.MAX_VALUE; + DetectedObject closestObjectToCharacter = null; + + for (DetectedObject detectedObject : detectedObjects) { + int objectDistanceToCharacter = getDistanceBetweenPoints(GAME_WINDOW_CENTER, detectedObject.getCenterForClicking()); + if (objectDistanceToCharacter < closestDistanceToCharacter) { + closestDistanceToCharacter = objectDistanceToCharacter; + closestObjectToCharacter = detectedObject; } } - - if (closestIronOreToCharacter != null && closestDistanceToIronOreFromCharacter < MAXIMUM_DISTANCE_TO_WALK_TO_IRON_ORE) { - return closestIronOreToCharacter; + if (closestObjectToCharacter != null && closestDistanceToCharacter < MAXIMUM_DISTANCE_TO_WALK_TO_IRON_ORE) { + return closestObjectToCharacter; } return null; } - public int getDistanceBetweenPoints(Point startingPoint, Point goalPoint) { return (int) (Math.hypot(goalPoint.x - startingPoint.x, goalPoint.y - startingPoint.y)); } - - private BufferedImage captureScreenshotGameWindow() throws IOException { - Rectangle area = new Rectangle(103, 85, 510, 330); - return robot.createScreenCapture(area); - - } } diff --git a/src/ObjectDetector.java b/src/ObjectDetector.java index 95d0240..b2ab4bc 100644 --- a/src/ObjectDetector.java +++ b/src/ObjectDetector.java @@ -13,7 +13,10 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ +import java.awt.AWTException; import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Robot; import java.awt.image.BufferedImage; import java.awt.image.DataBufferByte; import java.io.File; @@ -24,6 +27,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import javax.imageio.ImageIO; @@ -38,16 +42,28 @@ import org.tensorflow.types.UInt8; public class ObjectDetector { SavedModelBundle model; + ArrayList detectedObjects; + Robot robot; - public ObjectDetector() { - model = SavedModelBundle.load("/home/dpapp/tensorflow-1.5.0/models/raccoon_dataset/results/checkpoint_23826/saved_model/", "serve"); + public ObjectDetector() throws AWTException { + this.model = SavedModelBundle.load("/home/dpapp/tensorflow-1.5.0/models/raccoon_dataset/results/checkpoint_22948/saved_model/", "serve"); + this.detectedObjects = new ArrayList(); + this.robot = new Robot(); } - public ArrayList getIronOreLocationsFromImage(String filename) throws IOException { + public void update() throws Exception { + // TODO: eliminate IO and pass BufferedImage directly. + String fileName = "/home/dpapp/Desktop/RunescapeAI/temp/screenshot.jpg"; + BufferedImage image = captureScreenshotGameWindow(); + ImageIO.write(image, "jpg", new File(fileName)); + this.detectedObjects = getRecognizedObjectsFromImage(fileName); + } + + private ArrayList getRecognizedObjectsFromImage(String fileName) throws Exception { List> outputs = null; - ArrayList ironOreLocations = new ArrayList(); - - try (Tensor input = makeImageTensor(filename)) { + ArrayList detectedObjectsInImage = new ArrayList(); + + try (Tensor input = makeImageTensor(fileName)) { outputs = model .session() @@ -62,47 +78,30 @@ public class ObjectDetector { try (Tensor scoresT = outputs.get(0).expect(Float.class); Tensor classesT = outputs.get(1).expect(Float.class); Tensor boxesT = outputs.get(2).expect(Float.class)) { - // All these tensors have: - // - 1 as the first dimension - // - maxObjects as the second dimension - // While boxesT will have 4 as the third dimension (2 sets of (x, y) coordinates). - // This can be verified by looking at scoresT.shape() etc. + int maxObjects = (int) scoresT.shape()[1]; float[] scores = scoresT.copyTo(new float[1][maxObjects])[0]; float[] classes = classesT.copyTo(new float[1][maxObjects])[0]; float[][] boxes = boxesT.copyTo(new float[1][maxObjects][4])[0]; - // Print all objects whose score is at least 0.5. - boolean foundSomething = false; + for (int i = 0; i < scores.length; ++i) { - if (scores[i] < 0.75) { - continue; + if (scores[i] > 0.80) { + detectedObjectsInImage.add(new DetectedObject(scores[i], classes[i], boxes[i])); } - foundSomething = true; - //System.out.printf("\tFound %-20s (score: %.4f)\n", "ironOre", scores[i]); - //System.out.println("X:" + 510 * boxes[i][1] + ", Y:" + 330 * boxes[i][0] + ", width:" + 510 * boxes[i][3] + ", height:" + 330 * boxes[i][2]); - ironOreLocations.add(getCenterPointFromBox(boxes[i])); - } - if (!foundSomething) { - System.out.println("No objects detected with a high enough score."); } } - return ironOreLocations; + return detectedObjectsInImage; } - private Point getCenterPointFromBox(float[] box) { - int x = (int) (510 * (box[1] + box[3]) / 2); - int y = (int) (330 * (box[0] + box[2]) / 2); - return new Point(x, y); + public ArrayList getRecognizedObjectsOfClassFromImage(String objectClass) { + ArrayList detectedObjectsOfType = new ArrayList(); + for (DetectedObject detectedObject : this.detectedObjects) { + if (detectedObject.getDetectionClass().equals(objectClass)) { + detectedObjectsOfType.add(detectedObject); + } + } + return detectedObjectsOfType; } - - - private static void bgr2rgb(byte[] data) { - for (int i = 0; i < data.length; i += 3) { - byte tmp = data[i]; - data[i] = data[i + 2]; - data[i + 2] = tmp; - } - } private static Tensor makeImageTensor(String filename) throws IOException { BufferedImage img = ImageIO.read(new File(filename)); @@ -111,8 +110,6 @@ public class ObjectDetector { String.format( "Expected 3-byte BGR encoding in BufferedImage, found %d (file: %s). This code could be made more robust")); } - //System.out.println("Image is of type RGB? " + (img.getType() == BufferedImage.TYPE_INT_RGB)); - //System.out.println("Image is of type RGB? " + (img.getType() == BufferedImage.TYPE_3BYTE_BGR)); byte[] data = ((DataBufferByte) img.getData().getDataBuffer()).getData(); // ImageIO.read seems to produce BGR-encoded images, but the model expects RGB. @@ -122,4 +119,17 @@ public class ObjectDetector { long[] shape = new long[] {BATCH_SIZE, img.getHeight(), img.getWidth(), CHANNELS}; return Tensor.create(UInt8.class, shape, ByteBuffer.wrap(data)); } -} + + private static void bgr2rgb(byte[] data) { + for (int i = 0; i < data.length; i += 3) { + byte tmp = data[i]; + data[i] = data[i + 2]; + data[i + 2] = tmp; + } + } + + private BufferedImage captureScreenshotGameWindow() throws IOException { + Rectangle area = new Rectangle(103, 85, 510, 330); + return robot.createScreenCapture(area); + } +} \ No newline at end of file diff --git a/src/Paths.java b/src/Paths.java index 9eb84fe..9c864d7 100644 --- a/src/Paths.java +++ b/src/Paths.java @@ -7,4 +7,6 @@ public class Paths { public static final String CURSOR_COORDINATES_FILE_PATH = "/home/dpapp/GhostMouse/coordinates.txt"; public static final String TENSORFLOW_MODEL_DIRECTORY = "/home/dpapp/tensorflow-1.5.0/models/raccoon_dataset/results/checkpoint_23826/saved_model/"; + public static final String TENSORFLOW_TRAINING_IMAGE_OUTPUT_DIRECTORY = "/home/dpapp/tensorflow-1.5.0/models/raccoon_dataset/results/checkpoint_23826/saved_model/"; + public static final String TENSORFLOW_MODEL_LABELS_FILE_PATH = "/home/dpapp/tensorflow-1.5.0/models/raccoon_dataset/training/labels.pbtxt"; } diff --git a/src/cascadeTrainingImageCollector.java b/src/cascadeTrainingImageCollector.java index 11507cb..24f7bad 100644 --- a/src/cascadeTrainingImageCollector.java +++ b/src/cascadeTrainingImageCollector.java @@ -33,9 +33,8 @@ public class cascadeTrainingImageCollector { public void captureWindowEveryNMilliseconds(int n) throws InterruptedException, IOException { for (int i = 0; i < 1000; i++) { captureScreenshotGameWindow(i); - System.out.println(i); - //System.out.println("Created image: " + getImageName(i)); - Thread.sleep(n * 1000); + System.out.println("Created image: " + getImageName(i)); + Thread.sleep(n); } } @@ -63,7 +62,7 @@ public class cascadeTrainingImageCollector { return imageOutputDirectory + "screenshot" + counter + ".jpg"; } - private void resizeImagesInDirectory() throws IOException { + /*private void resizeImagesInDirectory() throws IOException { File folder = new File("/home/dpapp/Desktop/RunescapeAIPics/CascadeTraining/Testing/"); File[] listOfFiles = folder.listFiles(); @@ -73,21 +72,21 @@ public class cascadeTrainingImageCollector { System.out.println("Cropped " + listOfFiles[i].getName()); } } - } + }*/ - private void resizeImage(File imageFile, int counter) throws IOException { + /*private void resizeImage(File imageFile, int counter) throws IOException { BufferedImage screenshot = ImageIO.read(imageFile); //Rectangle resizeRectangle = new Rectangle(103, 85, 510, 330); BufferedImage resizedImage = screenshot.getSubimage(103, 85, 510, 330); ImageIO.write(resizedImage, "jpg", new File(getImageName(counter))); - } + }*/ public static void main(String[] args) throws Exception { System.out.println("Starting image collection..."); - cascadeTrainingImageCollector imageCollector = new cascadeTrainingImageCollector("/home/dpapp/Desktop/RunescapeAIPics/CascadeTraining/CoalNegative/"); + cascadeTrainingImageCollector imageCollector = new cascadeTrainingImageCollector(Paths.TENSORFLOW_TRAINING_IMAGE_OUTPUT_DIRECTORY); //imageCollector.resizeImagesInDirectory(); - imageCollector.captureWindowEveryNMilliseconds(5);; + imageCollector.captureWindowEveryNMilliseconds(2000); //cascadeTrainingImageCollector imageCollector = new cascadeTrainingImageCollector("/home/dpapp/Desktop/RunescapeAIPics/CascadeTraining/Testing/"); //imageCollector.captureWindowEveryNMilliseconds(1); } diff --git a/target/classes/Cursor.class b/target/classes/Cursor.class index 5323db5cd621f64518ec31b730ac8e9bc585a4d0..b7c03b73aa24c060e2c8ff37db5ee5b571b265e2 100644 GIT binary patch delta 2042 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 YmO8?xk>UJeEa2$$uu0)Z`doPZKm4h)UjP6A literal 0 HcmV?d00001 diff --git a/target/classes/ImageCollector$1.class b/target/classes/ImageCollector$1.class index e3fbe9facb4ad281593bcc829a5a6fc2f9fcf404..3217e3953d9958b4543e010dc0882cd5ba1081b6 100644 GIT binary patch delta 19 bcmX@jcA9O&ekMlC$p@Gm7_BEWGJ634M&Jej delta 19 bcmX@jcA9O&ekMlq$p@Gm7%e9=GJ634M!*I9 diff --git a/target/classes/ImageCollector.class b/target/classes/ImageCollector.class index d8851a8a058c6094082aca73d63ba4a9ea8cd520..e4a1e08f679a2d0792eddf736cc116f8c62a204c 100644 GIT binary patch literal 3837 zcmZ`*`(G5-760C4ah7#ZSRRT3u}TzJbk!P~2%$xcW(|*2P=dzD0K+o6yR)606-?i) zP5OS=NBT@_U-prL1Zx`cG)TXJ!~c$nIx%9`~MezUOG?oij4#fSt{n)2{7kh$_@vF|HY@tdY;8My9Uty+TuQ+A+<1an5!}N^YU# zp0pg#;JQL$aCQEu>sa|rk3wC>$eH6-e#V|Z%Zc=rH5Kapsm8pU3g2>arDIRoj8d~^ zSl)1GORtr;+&+bd_KjgW&MU;9T9`Hqu4U(o8n$zu2Sy_FkB^083ef?3#*Cu^2_0Ke zr%=(}aXt5-5P4F?rky#cSv>AY(?PO0FUGvOSeCZ*#r zjxgJd>84#XH(-}!<~tN4Ehg{QaSV@#mA;3*xaa9ZIJCVk4vn#EI&ojYY`XIRQP!{vO(9k<*$g?*bO z(s9w3DG3+jo;cEYM#C8eeWeicUdJGYh=VbF;Pvet8}%)4Msz$YHE0NG;ET$`L_cwy z#h8ZC6-9dG95UQF9p^x5DssjG`Jz-h$i>#IRdh#Yy{xFwhoF+rS-PaF^(kn0eqAa2 zHLl?~$?dYJQP*H_%6bvE9f@NS(=tAz@M!zP8sC_%tj9EFb!0$xs|0KSwIg6UU^UNL zbA(dtd6;i@j9IT%F@u*3#3ncSjR{3Q7%k&RKqn# zy~Y|U^H`AS`W6|$k(ZYA>(q(T?5ydSGals>nZn0)d;*_jjRV>Gu@7t_(an;J;ZqdL zl|m+tm+=`5pAH)7f*()Xhr~@&ji1HmsHk~|St<0df$JkXYm(#;*+v@IYpdfz6~2Hk zY53xLiCr&p9qafqzCu`biG((UhRxOQZ&NHrp+LY22R9*VqX+`WB>X16tK)mJhGVYnM~&nAxTWC- zYkKY)s5*WKof<3%`fQ$s5=ddIs`l6mWI#Nve!A~m@r$^v<7fCeU0yS?C39p}VgK4B z{H?oZV-g2j@huNX3?`vY5|ZMzk>2-C)MRST)vEio>P#7JcX41fPTcdg7Y*3w`PO}_ zz_$`YIYZKj5qu}Qf>eIOCqdZNnJnYblQl>Cnxm85(c7r&Og2QzI9iYHB|Ls3sv4rr zEtAp7TMzyxQ7gRcT8R$WPx21%9M+Cn#@R{lt-Mb%hGcon_aA22g+9(_E1=zb@jjxe zBIbVHRS;7PPVg>@0a{ieav!xysqLJ^gHb@q+ngv>>K4c+hbsEICXQ?Oy0=txSKh|% zCat0J=<#SvXH#?$PnR*gg!lK|s8kJ=4O?zK_;1T=wDiI6@@k&q|6%$&!U7#7^dkg( zYz^@45O{Y8{4mDx95Kak$b(x!+ijS@g#h?2L>{mbF_uolMGcp@{qgP+u!B}S?F5-* zNllb^nwOED=zJ49%6MU@YZ;RhlG@ZF%;AG~IPObn8*u}Vvye|<7qJMroxXjDu{pq~ zQ#=;&L^w0zp%umQU=u_Zq~dL7GMTuth-`E+nXq|X#0Q4DmQWIw{HVULPEe2oUc^1V zT^MmEUi8XY$6P+hy9mELk@A0|*k0uv5nC^FZRYR5B0e8L20p)xn-htzmGKR!SPLq$PQO|=+8_u8y&#b9kZ-}xt;DxdA zBTsa4)c!F<{`C~A=vj8cAMO#Yk~ z%U+`BN$GQ0b=5^5s{?6wJ{(*mb?-eF_<>&v}AZRxL literal 3800 zcmZ`*>0cYy6+KT#9w85#Fb-gFaf59Ti(<#6EhIF6jU$I80RqNu8wNCpu_cW<8Uaq1 zbaD6AUDADb+tgiBH=89IQg{E-PyHkMrT>K8^Jaz-0;=Cnv%Gol-gD1A_rCY92XEg8 z@E-hAK|r83kVg&Gyrs1>N{>Yk5)P*pU@_jYN((9*_>`o;Z@t2wh#MF@uk>L!e= zK2^vp>(-pMEI~yl%oQy?uUUqC_XdOZnvtjdiAsKk6tr-K6L3Wo5j5kN1lJ~@bS3!f zo|kTqt56UYs9A9-cFfUm(#Zc|Iw60vkR6!l1$_+bIC?;u7;lKW;!pW^1ZQzh!Q&NDnp?AUEvZs@0|KFZTGw+-Y}X~3->^Y!Q%sz| zVfpC86bZ7OEo5BU=FBk{6FFYliKJ%M)>f`hYB?u!GJ;_hAHY)r?_$v7Mq1C0TV`h5 zOedN0HO=OH%U&?-HGxMD2&DU}t3l>ME*^?t6z3I;38*_clH)2aV4OJU!v%h%t9!qA zC7h>KBxDYnyd1dVb1>0P1kYen!31+u&h(^auc?^AG?kvwHpv$y)lDuAr;WTlz3N0o zE$@L!K38dyrYb{F@O(uo+$vS@oQ&a?7fNwxFW+njt4^-MND7D-BACOfij<^KVMEb3r&q%; z_^Drhx~?LP44vf*%Xz2!NPUv6{pcbv@eu`-T|=2_SyUgqK84A{SGUe32xop}I6i_2CH&ncckFuEAEHzu7b$X^R*>fHR6@6pK!zl+1*^1+% zh1FHv(vwaus6K&@tM~+7V)8vbyP!@VpxuL_4&zf)(Y0JEg3sWy3T}G+V$<~}TaNUb zm{##Qe4bLyQ zmm^?-z5|HbuMW>J3BQH!srbGu?yzmTUL*J+eyrd}yZZDls49Mf2*sI`=+jvyN<#8` zSjdthAqj4nzHGSh)i}g@3L(_v0mRJ_O?5U?s)}& z+)0D;!Qbz~*cNy^zGi0hcrwTA$It8e>$aJT&lIwHp4*u|l8Ddo6k}TBX>%iu6Mi$j`FPJ**;H?Le7nCqj|ADRsVd0c+em&mC$+* z?PVVk$5sdJo^Wid?kdkV_gJD0po5mejOhd(#YqSID}0mZf!4$}f=jm_{#Safaw0oUbU-J$IY~ZF5%+1HuexxY&pn7a zNY1(T{V03!Ue0ITbI$wlexj--nn6BQG3s^<@hONWXtx@H2WSvN)NvAzM;;~baH34Y zfQ0#_<#@mF+7Xsh$+mK5^fdO)?x;;9`IHK9@0dZ zsZ%hgfF~!xr+F~Z0eR-}B1w}PDUG&mj4sCBLbQa1$=+=&F3QlZ7V+V!zSsEOl~FDI zFlwm8(Mp0O#+bX_$Jpj!R8fy*;t6nOz(HHZJC8S>=PO(%tu^=bL|2N?gG)WpHQtN3 zHrcy{tYpdc>I&;AG-AZ@*x|18JvJlnq_dH+*nEv=qhBxKn=PRtUM}LhMf{+w zd^e33=xUsmc98);P3x(EQ&MuIetNMS}8ag zScrjeQ}<|F<{U{|r-UREwuN63R}W!H8eeWJ;@8pNxgg7llWGShchT4Bqv`a~bUIR! z^s%>j^hr8TvRY-AN$^>AnVN0fC3&w+(Ch1E8eDbEkcL?*WX^HsHkcaES(w4Rg?j{3 zjUq~(iy+^(90$)ZH0hvj8)7l~b_wrHP=$Zm>*6vy$rU2Jy30i!{*1qHX@oBRN|DPR T_BXx<@OS*f85caEI=}w`L^)q4 diff --git a/target/classes/IronMiner.class b/target/classes/IronMiner.class index 9a6918f6c888194af7d756f86c905df5acc1ca15..3ddfe4610901e393666f018ea1ce975ec59d3447 100644 GIT binary patch literal 4200 zcma)9X>=3U75>JSJ+>H(Z4ALch}nX?VUh+&Y_ka2CL)pywqu91i5W|SJs{7hqY>bA zNw<`4NlEvlZQ4R9U2+-%SSF=S(~Y)Cx~6-&r2GD_zj{vVe(#MWOU5~;IkDb4_kQ=g z%ey@L-!D83U@QKqqD(<`*s-msX&H_RpMu&W`Z2vDsaxY6{bNUrxT|1oc%VNv+CR`c z8V$$7vAv^1;i#BLBH_W_P=BmvP=OLwusCE}X;-)0VLh2K$^ric1skHlyTZ{sqoY0H z!J%L*)H^!VKRObO+#$xiVBS?uDX8BYq~MWotfzluG}Id#>K!1Uh(x3xcRTjmrsZ}i zsElWvwCyOUiiG5hdwy?kNKYSCpyl?(if7fPbSrEizD8@vN#lo-{4f-Kn5F&^)Zb7qSzE zitDMh)FMAt;|2{)SWDBobKg`1!r|uOYA9H*VG%S1OCoeqEHgP~I79ka(jZ?EJFX{( zb;lIz+@`IRJ zqfLOhNyB2)sCWg9_l^X|e%$OGZ_}^@wJL5Eah2!UkL})RmxemjtJpybN@@0^+q<$` z!%{S;*i+1sq}z|6ccoXuGAviImk{O{_oL4{-lyR@tRUU)gqfE9&c`>MQto6d75#bB zWtbP4-i-qq2C$NLXHp5>HL5VAKs70$-!TNpVGSd=i(x%(xC2Jq9=GWHgl9~;|8OX& zr_+6oJsFt8^Tg15`zh9?a6_N$QZc6BI_cLUoqIB7GGRD=Byd;-ae1CE zg`i;^6XY#Ti)74({WyZ83MN!hRq(9PFbRv)922SDf0%Fr;hbSbdb|A4krL7LYH?!$ zE{3ZhoYa(~INjDHKFMe}hU4^to=O>3LP7gN-(DjwXQtFTB}Ck(VD&<{@)xV{S}JKG z>*>ux@9Q<(k2f%PUE4ELK|^3cv)+WasCaXp$WkY0ctF^zri5RTmY->JQp4Nuc4q7( zb2yZ=(?;4Ab(c{!XI!Z}x)uzR=3yc3of_UHqI7A(u~UUS3m@*wB$Gs+C$kVF^CZ*B z!ej-@OAE+UDqlo4Eu_3V;#I|bym#$nMlBw6_4v`Ko|5H9r1}Bz^L`Bt zhmOVYZc#5D(eNlftf0weu@WUT(L-Z`$z+(3&W{S85oRD?=ZlKBD5=$h=D4N18NuR# zT!@s~x&Y??(=?Cik7@WgKA~V0^F8M`FWHCe(1h;jaaWZ7O$+K~rq3hHk25%{;*%_H zr6(Fbg>wuxp@lD%;q=)~C~3xzvS2W3@-aB~2#SLI84aJs=M=O~+Q*Ebm53OJiIE=# z-H>fN36s7-$#I0p9d!4kNpHDm>@jRIHhk+5GDQRVJ4DXFV+_gOhYl=3fX_!6FGDYJ|d z?p{5UPMf+VOffAQBT8#Upr@yqCF%)0BanQBK^@p9f}$oqFTZN%yFqjd-W{2Lw=L+s z1tRAff0o}rJ3A|?>bEp}8_$Z=qwjRtNi5GhO$H=`TJCo(!t?m9ito^?vPzenYWN<$ zPeX*!^GlBxcC2j*eyHIE{7AtHhD#4058a9zdkpuuVOX+7qLFRmxgP$*Z`!2|jsNcLl2t)?u~|XJ@eHBGx_0?J{iO zuK)px+$eY0C5y(4XE(~TGHgP#6!jp1i<^5}X3*Ml1?>lC&@qiIoZigoEz{V_>2^+U zn?@(6-JI^6#x72SoQ9^+!)YIBnRtP7o|rT7P|go) zX;Ehu@AZcFJ%W19w(@U_xAP&vaJj@>FQZ@^F>j~FTZyl;h|#q1q59tn}13ld7 z!vVq;WZ&YU$r(XhIkLsfBfF9T^HqF}7PZhpU+2tMaDaDTIKaD~cR&rw{)62r&hY<# zGx5s6H=4g56W|6 literal 4326 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 literal 5241 zcmb7I349dg75{(PV>X*ffHe^our94gPBtchkZ?$Vz+wU^iD*bMPIf1oh25R)>?|Z| zt!>rXdi1ci)=JS$L_ws7*lKTWZR<~axAs15)%q*_e>0n7L!vG0&V0vv-@E?r zy>Iixe;z&tU^!k^P$E#VePE9swKnP&pN6TxEiikJwoeNuwRAk}>3ut!y|tq(}E=j6~Y% zZ)#rIv?839I`rFIVFj~_4CuDZL^|%n9NLi8&3)A5G*l_56gcyBOi^(<=2HKpp~VEY zRCi3c#W}UL@6@H5*L6@S1cE9&@CwZ8NTl^`xzvDe_Gkl1T_Dh5M788j%}mI7VbN_3 zC9*WDqc~Xf^}@JrMNA{T-P9$cl`zuTcGE~jQd*p+)l8#wjiYqVN+iSUO;a1`NMtP@ z<=-?M)id@93f?Ginr&{v2uHS$?+BE3C*o<%%9%X9^?yBmwIfp7Z-u*L+*;SRDmZ&g zM6o7*EJm51KB`esi#qx!rR~*idwOix3Diw1cSZh?XXE&eK=P@O~{b+JE`NxD$oTj z0%uGwq`<0THP#SR8+$e|Y;^k3 ziR}uu2~^grf;hPhRfC0*DfXOQLz^+ z*xsm-8R;6e|&H<(E3X)VP71SZ*4px_DAI+L^P%3X{dmfP8h zDP?=yzT$dFQcohyV+7{TknFOu+4{_*qz@Ww=G|=3Y&_{WnRfA;U`Z_QP9vGI4pr~iKc2Cxr&o1#?ON7;fhCj3$9vE; zT|!{_3lQac-M;__}wi|*~zGIqaJaI<^EN$}!w z7kFd2Yb^7}f$hhgI3zDKpQg*IBN2%O7hOjm$?phVIuJLT%s4aLQ{C5Q--6^VMmoHW zv{ouZI*uZf^I;5GGig4h@)cXG-~sj+8)ZauJAvuP{m4r#ukm9P4=Z?x2$KZEVY?N{ z&MsS0WV?zZILfSNIx$C=7IZ2cGgc&|^)_F)EjKu*n|jRVX1_d!;|d<-UBs3?1D%R5 z;mdRk>qiemMJH6(M11(FKzx&31DUVQ4Fe;V9%|dXgXtPIVuW5Okq#Bfs}I@O3@wg@ zs%84MgcfIOmWFzGK?;#Y=OdSlg$DFcO5-fWwiYstfn1h#w*n_rJdUq10R{~-rC9>2 zrU}60TirB;vJfUe79lTuSsUJ_{8L8(FonFxd>&u$I7J>V zY0L8}ej>wEN(Hv)tef=OG5k!$&*iF*s~09>){uf<@(wjK9{u<=UQqCxvHF@OmZTbv zJt}^S-%*eKrp7b~$^>!BK`|Mf)rmi-_#^%#+Ze%V5jN{52h3@deOsqxpQ(QmdWjTk z+8Z3Mi4YXwj77@dw!UY>mj3R@u1(C=t+ao`mi61VZR+67qNI;=f47k{qqOI8^_W4nF_}UevL9e^2^fAOs@<<#<+||_L4=Pia%7Q*F zDUTuLvdK~;Q(Y^{T`MXMVM)+mRnd4uC5&5cgZB_B-7Cr)YJ+N3MblxtHILkdS5=6r zilG149VefuD!&zF_hBUP_B`GZ^qs)`pwG5EK+8Gx*;ASpR8C-7P_g&wU(*dF^x1@x zi_YsV`11VM~OR$olTg9JDEnI2ksF|bH$YCw6$2uIqdAJ+x z>_40FB(~srBKvPRA1|T<|3;_q@k(EYE>XeT`a*PwCAdJ;^LoAv7l~)lYs2|Qf}8*? zz*&yojcWmVe@1<^j@f5D3JQwv_gBx%o?Y&+!;C=S!0E^N4IZ~*}L41HCAN9E$ zAH+>efhUo`hj25kd!ByzFh^z7unix<5EJP|+Hea#igLU}FWgFw3Ve(nSxX6y{M*Lm z9~hVa@p1VFIdbG59G8EP@^9xEcVfXwx?u!Qw0uUpkPBR8E2XV~hJjBq;dKq#KBs{*zSqbZ<474=4 z0{7rvuH4Rb{Xy#CH&t zN3f_j@D1-R#)eM8E<`K2wZ+nvTrS7K0T(%^zYGQ*%F4c9=;z zLI7Wi-MGxQUxJwm+i4}c$8P)?f1&-&Olj$p5^^-+uS^RU`Red@jy$B2zd`>&zuZo4 cHm+ACe2d-^)MyT<#6 zh6IF$#JhL~xjKjV2SqY6C?YBL^>=agiT81Ia`g$0ck}ddjSp}P@!(*PWMuH1e4Nol zw4^98GcPkQT|Xx=DK)29uOO+UqGYlHlN6gA4}%ng^kfI7Xh!zQbC?1%BpJjQ*cccY z*ntjXVh~}FWDrN3{ZtWn9sn#sEkC8zhtV#g@vPTXu diff --git a/target/classes/cascadeTrainingImageCollector.class b/target/classes/cascadeTrainingImageCollector.class index 1e937c43521d3533289fcc73ac56dee3e69f1419..30e6ea6239b5dd45d4f5e981b764b16683c22bf9 100644 GIT binary patch delta 1327 zcmaKsS#KLv6vzK}#`f5gX^4k{!4A|35NbQ|Gzp2S&<3Qar72Dunl#33Ax!Ow?bPwO zp2<=+7ih~?*2}(y0HweKqEOipk&uvh;ssuK;tTL0pykXsN~0n%lINa#eeUo4&thMQ zo({{uUitAS00a2uk{lAb;JYQ}$6kgTMyiHx8@Xt4PR|=dQ5hrHr$E6L84UXsJctzk zMU44Ja6rL9jPvupduek>!2~9`Aa#;sAEp#SZ;|jY-zJ9I(>UTFjxtEwN9l=8$q3;Q z1&?By9}*9C=?vm*B`+b%pv>wEx>hu`y~UCt<2d%om_b28p8q3;#Uf^T*t6n0sh|pr z|Loc6n^&-aMSj(zZ9j!4Bs|X0>O%BITN^ivWn00MXklPW0i43q3Tk}H8;d@Nvl7lQ zs2k2>=DcN$mS;=_=kPpV_9n+)#LE(1V%WOjBG6G=VaR9&bI#Cm6}?i?Y$BS~nUcBK)0-Sf?$GAU zoKdp0s-Df7W_dcN+qz}gTGg=TOSYwD3r6-t#VOKsU;oa&LCw+^jNG()OLKZoR9*xQCnU4 zJ81HIDV@GeDMdF@ilP}qX_6_1;LJ5886-tn_-GF#;tklTHKa3%%UGhE+P$?>j_jvg zyOeVl=R+xfNB+&MWcTg>ycl$2-`r%a$M&KNr}2ziPWt(*N9?7Q5kXqTvvs_14d!!? zhg^_CcFie#voV76r$9%J;z`QcQ5<#JPKs_?#Vgc%l~dy*b-b;<*PMXs!NZ#rM*mZt z06xHn#BC+Aha!E1V1djd>%qb_X6h$2RBK=v_0tAEOC^HR?{$2BeT%2yN1EE4!L2y9 ZDP$NI@Ffvd`u-wKn^T0ZXfNRFe*w#i4QT)X delta 2088 zcmbVNYj+b>6x}zIWYXyXO)M6Y6w>loP0}{2MUf>BVWm7$+muI335Yr*({xIjge2vq z64avj21gWpS4DgwPm@X&1>Y!s^cVQS-{5kA=T2fz32 zu3Wot5x||edPb@dS{wt)P9>o1$_3V|(VUWuCsYYRtdb#PHG|j?qK|i@gc^iowBufV zk-2M5CxbAMj*IAGkc|vlDzHl9dl}ZrSdR_*Ho;pJMh{OkMM5tkA~xwK%}!x6w&+*P zBi3y)qKN4`EQ_ptGUDjhPgw#D14xM2&M&aoK zh#`iFE;>^~Sv8dGqb0krTko2`WY(YYOW&{RdQM?DI%}W z7MvalkK%C=k15;j#?&2Je=Rl9>6Dnv40;H)Xtqx2X{= z%NLUJ4c^UM=XYehi}#4TNk~bGhH{Q_3Nnuk?=#qfnG`V*BU;1wK*onSLiQrs!^$Ap zrWGH{WXdU2r!SS*5>qo|fg3YXC62bMuZ(xvKWNL9M4aM2J1gTH&J(Q}RUz7&Z^Gl% zLl#d(0iz;{3~s&M;&#jU92ZI^*f6wB_Gs(Hkj`?t+9&^!v*|gJB z+D~I0-BBhiK_!(j(VC?cnXrszMVS7Ah1KX0sXiZ!RK=@$?ChGIwHL9n({~=u#sb!a zn;acu2uFObBGwksJ&KLJXK1qtv*_x8nXY~s8{t9|n(4~{_>B3$IDi1|GXOYHi>#`Jp~vY|nG89j}nk z9sUB2bqdQ)ptZ5KwSYH{VZM)|+9F2CjvSQZIN>oKo+v>n;G> zfyoWmP%R=X;xzMfo$2tprMH8mxP<`t{G&J%Zkn)p0iWG%o@p-ojf5tD=l|8CgHNZOUr=O51-`_^NdUhx$lp{JdIr(j z>$pTDj)6rS-xTp(hu>Df_cy<9y+r6HdgnINKls*3YR|xr_=&(dsN*a2>T}=yO!p@I F@;5ETwtD~o