Trial 04022025

This commit is contained in:
2025-02-04 08:48:28 +07:00
parent a60fe56510
commit 29884c03ed
24 changed files with 16423 additions and 158594 deletions

View File

@@ -2,8 +2,13 @@ package Camera;
public interface LiveCamEvent {
void onDetectedQRCode(String qrCode);
void onFaceDetector(boolean hasface, int width, int height);
void onFrontalFaceDetector(boolean hasface, int width, int height);
void onProfileFaceDetector(boolean hasface, int width, int height);
void onEyeDetector(boolean hasEye, int width, int height);
void onLeftEarDetector(boolean hasLeftEar, int width, int height);
void onRightEarDetector(boolean hasRightEar, int width, int height);
void onLeftEyeDetector(boolean hasLeftEye, int width, int height);
void onRightEyeDetector(boolean hasRightEye, int width, int height);
void onLog(String log);
void onBlink(int counter);
}

View File

@@ -1,7 +1,7 @@
package Config;
import id.co.gtc.erhacam.Detectors;
import lombok.Getter;
import lombok.val;
import org.tinylog.Logger;
import java.io.File;
@@ -40,6 +40,11 @@ public class ConfigFile {
private String PhotoDirectory;
private @Getter double cascadeScaleFactor;
private @Getter int cascadeMinNeighbors;
private @Getter int cascadeMinSize;
private @Getter int cascadeMaxSize;
private boolean needsave = false;
public ConfigFile(){
@@ -48,6 +53,34 @@ public class ConfigFile {
Load();
}
public void setCascadeScaleFactor(double value){
if (cascadeScaleFactor != value){
cascadeScaleFactor = value;
needsave = true;
}
}
public void setCascadeMinNeighbors(int value){
if (cascadeMinNeighbors != value){
cascadeMinNeighbors = value;
needsave = true;
}
}
public void setCascadeMinSize(int value){
if (cascadeMinSize != value){
cascadeMinSize = value;
needsave = true;
}
}
public void setCascadeMaxSize(int value){
if (cascadeMaxSize != value){
cascadeMaxSize = value;
needsave = true;
}
}
public void SetPhotoDirectory(String path){
if (ValidString(path)){
if (!path.equals(PhotoDirectory)){
@@ -528,6 +561,11 @@ public class ConfigFile {
if (prop.getProperty(CameraConfigEnum.CameraConfigRight45.toString()) == null) allcorrect = false;
if (prop.getProperty(CameraConfigEnum.CameraConfigRight90.toString()) == null) allcorrect = false;
if (prop.getProperty("cascadeScaleFactor") == null) allcorrect = false;
if (prop.getProperty("cascadeMinNeighbors") == null) allcorrect = false;
if (prop.getProperty("cascadeMinSize") == null) allcorrect = false;
if (prop.getProperty("cascadeMaxSize") == null) allcorrect = false;
if (allcorrect){
AudioPhase1 = prop.getProperty("AudioPhase1");
AudioPhase2 = prop.getProperty("AudioPhase2");
@@ -554,6 +592,15 @@ public class ConfigFile {
ConfigRight45 = gson.fromJson(prop.getProperty(CameraConfigEnum.CameraConfigRight45.toString()), CameraConfig.class);
ConfigRight90 = gson.fromJson(prop.getProperty(CameraConfigEnum.CameraConfigRight90.toString()), CameraConfig.class);
cascadeScaleFactor = toDouble(prop.getProperty("cascadeScaleFactor"));
cascadeMinNeighbors = toInt(prop.getProperty("cascadeMinNeighbors"));
cascadeMinSize = toInt(prop.getProperty("cascadeMinSize"));
cascadeMaxSize = toInt(prop.getProperty("cascadeMaxSize"));
Detectors.setMaxSize(cascadeMaxSize);
Detectors.setMinSize(cascadeMinSize);
Detectors.setScaleFactor(cascadeScaleFactor);
Logger.info("Config Loaded");
return;
} else {
@@ -568,6 +615,14 @@ public class ConfigFile {
CreateDefault();
}
private double toDouble(String cascadeScaleFactor) {
try{
return Double.parseDouble(cascadeScaleFactor);
} catch (Exception e){
return 1.1;
}
}
private void CreateDefault(){
AudioPhase1 = Path.of(currentDirectory, "audio", "phase1.mp3").toString();
AudioPhase2 = Path.of(currentDirectory, "audio", "phase2.mp3").toString();
@@ -590,6 +645,13 @@ public class ConfigFile {
SetDefaultCameraConfig(ConfigCenter);
SetDefaultCameraConfig(ConfigRight45);
SetDefaultCameraConfig(ConfigRight90);
cascadeScaleFactor = 1.1;
cascadeMinNeighbors = 3;
cascadeMinSize = 200;
cascadeMaxSize = 400;
Detectors.setMaxSize(cascadeMaxSize);
Detectors.setMinSize(cascadeMinSize);
Detectors.setScaleFactor(cascadeScaleFactor);
Logger.info("Default Config Created");
needsave = true;
Save();
@@ -640,6 +702,11 @@ public class ConfigFile {
prop.setProperty(CameraConfigEnum.CameraConfigRight45.toString(), gson.toJson(ConfigRight45));
prop.setProperty(CameraConfigEnum.CameraConfigRight90.toString(), gson.toJson(ConfigRight90));
prop.setProperty("cascadeScaleFactor", String.valueOf(cascadeScaleFactor));
prop.setProperty("cascadeMinNeighbors", String.valueOf(cascadeMinNeighbors));
prop.setProperty("cascadeMinSize", String.valueOf(cascadeMinSize));
prop.setProperty("cascadeMaxSize", String.valueOf(cascadeMaxSize));
try{
prop.store(new FileOutputStream(Path.of(currentDirectory, "config.properties").toString()), null);
Logger.info("Config Saved");

View File

@@ -11,7 +11,6 @@ import org.bytedeco.opencv.global.opencv_imgcodecs;
import org.bytedeco.opencv.global.opencv_imgproc;
import org.bytedeco.opencv.opencv_core.Mat;
import org.bytedeco.opencv.opencv_core.Size;
import org.bytedeco.opencv.opencv_objdetect.CascadeClassifier;
import org.tinylog.Logger;
import java.awt.image.BufferedImage;

View File

@@ -38,13 +38,13 @@ import java.awt.image.DataBufferByte;
import java.nio.ByteBuffer;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import static Config.SomeCodes.*;
import static id.co.gtc.erhacam.Detectors.*;
import static org.bytedeco.opencv.global.opencv_core.CV_8UC3;
import static org.bytedeco.opencv.global.opencv_core.mean;
import static org.bytedeco.opencv.global.opencv_imgcodecs.imwrite;
@@ -60,6 +60,7 @@ public class Cameradetail {
} else {
System.out.println("CUDA is not available");
}
}
private final AtomicBoolean Capturing = new AtomicBoolean(false);
private final AtomicBoolean TakingPhoto = new AtomicBoolean(false);
@@ -684,49 +685,116 @@ public class Cameradetail {
}
}
if (use_face){
RectVector face = DetectFace(graymat);
if (face!=null && face.size()>0){
// ada muka
if (event!=null) event.onFaceDetector(true, BestSize.width(), BestSize.height());
for(int i=0; i<face.size(); i++){
Rect rect = face.get(i);
List<Rect> frontalfaces = Detectors.HaveFrontalFace(graymat);
if (!frontalfaces.isEmpty()){
if (event!=null) event.onFrontalFaceDetector(true, BestSize.width(), BestSize.height());
for(Rect rect : frontalfaces){
rectangle(LiveMat, rect, Scalar.GREEN);
}
// kalau detect muka, baru coba detect mata
RectVector eye = DetectEye(graymat);
if (eye!=null && eye.size()>=2){
// ada mata
if (eye_was_closed.get()){
eye_was_closed.set(false);
if (blink_counter.get()>=2){
blink_counter.set(0);
if (event!=null) event.onBlink(blink_counter.get());
}
}
if (event!=null) event.onEyeDetector(true, BestSize.width(), BestSize.height());
for(int i=0; i<eye.size(); i++){
Rect rect = eye.get(i);
rectangle(LiveMat, rect, Scalar.RED);
}
} else {
// mata tidak ada
if (!eye_was_closed.get()){
eye_was_closed.set(true);
blink_counter.incrementAndGet();
}
if (event!=null) event.onEyeDetector(false, BestSize.width(), BestSize.height());
}
} else {
eye_was_closed.set(false);
blink_counter.set(0);
// tidak ada muka, tidak ada mata
if (event!=null) event.onFaceDetector(false, BestSize.width(), BestSize.height());
if (event!=null) event.onEyeDetector(false, BestSize.width(), BestSize.height());
if (event!=null) event.onFrontalFaceDetector(false, BestSize.width(), BestSize.height());
}
List<Rect> Left45Faces = Detectors.HaveLeft45Face(graymat);
if (!Left45Faces.isEmpty()){
if (event!=null){
event.onLeftEyeDetector(true, BestSize.width(), BestSize.height());
event.onProfileFaceDetector(true, BestSize.width(), BestSize.height());
}
for(Rect rect : Left45Faces){
rectangle(LiveMat, rect, Scalar.BLUE);
}
} else {
if (event!=null) {
event.onLeftEyeDetector(false, BestSize.width(), BestSize.height());
event.onProfileFaceDetector(false, BestSize.width(), BestSize.height());
}
}
List<Rect> Right45Faces = Detectors.HaveRight45Face(graymat);
if (!Right45Faces.isEmpty()){
if (event!=null){
event.onRightEyeDetector(true, BestSize.width(), BestSize.height());
event.onProfileFaceDetector(true, BestSize.width(), BestSize.height());
}
for(Rect rect : Right45Faces){
rectangle(LiveMat, rect, Scalar.BLUE);
}
} else {
if (event!=null) {
event.onRightEyeDetector(false, BestSize.width(), BestSize.height());
event.onProfileFaceDetector(false, BestSize.width(), BestSize.height());
}
}
// List<Rect> Left90Faces = Detectors.HaveLeft90Face(graymat);
// if (!Left90Faces.isEmpty()){
// if (event!=null){
// event.onLeftEyeDetector(true, BestSize.width(), BestSize.height());
// event.onLeftEarDetector(true, BestSize.width(), BestSize.height());
// }
// for(Rect rect : Left90Faces){
// rectangle(LiveMat, rect, Scalar.YELLOW);
// }
// } else {
// if (event!=null) {
// event.onLeftEyeDetector(false, BestSize.width(), BestSize.height());
// event.onLeftEarDetector(false, BestSize.width(), BestSize.height());
// }
// }
//
// List<Rect> Right90Faces = Detectors.HaveRight90Face(graymat);
// if (!Right90Faces.isEmpty()){
// if (event!=null){
// event.onRightEyeDetector(true, BestSize.width(), BestSize.height());
// event.onRightEarDetector(true, BestSize.width(), BestSize.height());
// }
// for(Rect rect : Right90Faces){
// rectangle(LiveMat, rect, Scalar.YELLOW);
// }
// } else {
// if (event!=null) {
// event.onRightEyeDetector(false, BestSize.width(), BestSize.height());
// event.onRightEarDetector(false, BestSize.width(), BestSize.height());
// }
// }
// if (have_face){
// // kalau detect muka, baru coba detect mata
// RectVector eye = DetectEye(graymat);
// if (eye!=null && eye.size()>=2){
// // ada mata
// if (eye_was_closed.get()){
// eye_was_closed.set(false);
// if (blink_counter.get()>=2){
// blink_counter.set(0);
// if (event!=null) event.onBlink(blink_counter.get());
// }
// }
//
// if (event!=null) event.onEyeDetector(true, BestSize.width(), BestSize.height());
// for(int i=0; i<eye.size(); i++){
// Rect rect = eye.get(i);
// rectangle(LiveMat, rect, Scalar.RED);
// }
// } else {
// // mata tidak ada
// if (!eye_was_closed.get()){
// eye_was_closed.set(true);
// blink_counter.incrementAndGet();
// }
// if (event!=null) event.onEyeDetector(false, BestSize.width(), BestSize.height());
// }
//
// } else {
// eye_was_closed.set(false);
// blink_counter.set(0);
// // tidak ada muka, tidak ada mata
//
// }
}

View File

@@ -69,6 +69,11 @@ public class CaptureView {
private final AtomicBoolean[] have_face = new AtomicBoolean[5];
private final AtomicBoolean[] have_eyes = new AtomicBoolean[5];
private final AtomicBoolean[] have_profile = new AtomicBoolean[5];
private final AtomicBoolean[] have_left_ear = new AtomicBoolean[5];
private final AtomicBoolean[] have_right_ear = new AtomicBoolean[5];
private final AtomicBoolean[] have_left_eye = new AtomicBoolean[5];
private final AtomicBoolean[] have_right_eye = new AtomicBoolean[5];
private final AtomicBoolean isTakingPhoto = new AtomicBoolean(false);
@FXML
@@ -463,6 +468,14 @@ public class CaptureView {
case CameraConfigRight90 -> have_face[4];
};
final AtomicBoolean _have_profile = switch (image.getCameraConfigEnum()){
case CameraConfigCenter -> have_profile[2];
case CameraConfigLeft45 -> have_profile[1];
case CameraConfigLeft90 -> have_profile[0];
case CameraConfigRight45 -> have_profile[3];
case CameraConfigRight90 -> have_profile[4];
};
final AtomicBoolean _have_eye = switch (image.getCameraConfigEnum()){
case CameraConfigCenter -> have_eyes[2];
case CameraConfigLeft45 -> have_eyes[1];
@@ -471,6 +484,38 @@ public class CaptureView {
case CameraConfigRight90 -> have_eyes[4];
};
final AtomicBoolean _have_left_ear = switch (image.getCameraConfigEnum()){
case CameraConfigCenter -> have_left_ear[2];
case CameraConfigLeft45 -> have_left_ear[1];
case CameraConfigLeft90 -> have_left_ear[0];
case CameraConfigRight45 -> have_left_ear[3];
case CameraConfigRight90 -> have_left_ear[4];
};
final AtomicBoolean _have_right_ear = switch (image.getCameraConfigEnum()){
case CameraConfigCenter -> have_right_ear[2];
case CameraConfigLeft45 -> have_right_ear[1];
case CameraConfigLeft90 -> have_right_ear[0];
case CameraConfigRight45 -> have_right_ear[3];
case CameraConfigRight90 -> have_right_ear[4];
};
final AtomicBoolean _have_left_eye = switch (image.getCameraConfigEnum()){
case CameraConfigCenter -> have_left_eye[2];
case CameraConfigLeft45 -> have_left_eye[1];
case CameraConfigLeft90 -> have_left_eye[0];
case CameraConfigRight45 -> have_left_eye[3];
case CameraConfigRight90 -> have_left_eye[4];
};
final AtomicBoolean _have_right_eye = switch (image.getCameraConfigEnum()){
case CameraConfigCenter -> have_right_eye[2];
case CameraConfigLeft45 -> have_right_eye[1];
case CameraConfigLeft90 -> have_right_eye[0];
case CameraConfigRight45 -> have_right_eye[3];
case CameraConfigRight90 -> have_right_eye[4];
};
Platform.runLater(()-> image.setCameraTitle(title));
if (devicenumber!=-1){
OpenCVFrameGrabber grabber = new OpenCVFrameGrabber(devicenumber);
@@ -521,17 +566,47 @@ public class CaptureView {
}
@Override
public void onFaceDetector(boolean hasface, int width, int height) {
public void onFrontalFaceDetector(boolean hasface, int width, int height) {
_have_face.set(hasface);
update_status(image);
}
@Override
public void onProfileFaceDetector(boolean hasface, int width, int height) {
_have_profile.set(hasface);
update_status(image);
}
@Override
public void onEyeDetector(boolean hasEye, int width, int height) {
_have_eye.set(hasEye);
update_status(image);
}
@Override
public void onLeftEarDetector(boolean hasLeftEar, int width, int height) {
_have_left_ear.set(hasLeftEar);
update_status(image);
}
@Override
public void onRightEarDetector(boolean hasRightEar, int width, int height) {
_have_right_ear.set(hasRightEar);
update_status(image);
}
@Override
public void onLeftEyeDetector(boolean hasLeftEye, int width, int height) {
_have_left_eye.set(hasLeftEye);
update_status(image);
}
@Override
public void onRightEyeDetector(boolean hasRightEye, int width, int height) {
_have_right_eye.set(hasRightEye);
update_status(image);
}
@Override
public void onLog(String log) {
String ss = String.format("[%s] : %s", title, log);
@@ -556,8 +631,14 @@ public class CaptureView {
sb.append(image.getBestHeight());
sb.append("@");
sb.append(image.getLiveFPS());
if (_have_face.get()) sb.append(", Face Detected");
if (_have_eye.get()) sb.append(", Eye Detected");
if (_have_face.get()) sb.append(", Face");
if (_have_profile.get()) sb.append(", Profile");
if (_have_eye.get()) sb.append(", Eye");
if (_have_left_eye.get()) sb.append(", Left Eye");
if (_have_right_eye.get()) sb.append(", Right Eye");
if (_have_left_ear.get()) sb.append(", Left Ear");
if (_have_right_ear.get()) sb.append(", Right Ear");
image.setCameraStatus(sb.toString());
});
@@ -578,6 +659,11 @@ public class CaptureView {
have_face[camid-1] = new AtomicBoolean(false);
have_eyes[camid-1] = new AtomicBoolean(false);
have_profile[camid-1] = new AtomicBoolean(false);
have_left_ear[camid-1] = new AtomicBoolean(false);
have_right_ear[camid-1] = new AtomicBoolean(false);
have_left_eye[camid-1] = new AtomicBoolean(false);
have_right_eye[camid-1] = new AtomicBoolean(false);
FXMLLoader loader = new FXMLLoader(getClass().getResource("cameradetail.fxml"));
AnchorPane child = loader.load();

View File

@@ -1,23 +1,43 @@
package id.co.gtc.erhacam;
import Config.SomeCodes;
import lombok.NonNull;
import org.bytedeco.opencv.opencv_core.Rect;
import org.bytedeco.opencv.opencv_core.RectVector;
import org.bytedeco.opencv.opencv_core.Size;
import org.bytedeco.opencv.opencv_core.UMat;
import org.bytedeco.opencv.opencv_objdetect.CascadeClassifier;
import org.tinylog.Logger;
public class Detectors {
public static CascadeClassifier faceDetector;
public static CascadeClassifier eyeDetector;
import java.util.ArrayList;
import java.util.List;
public static void LoadFaceDetector(){
String filename = SomeCodes.ExtractResource("/haarcascade_profileface.xml");
public class Detectors {
public static CascadeClassifier frontalfaceDetector;
public static CascadeClassifier eyeDetector;
public static CascadeClassifier leftEyeDetector;
public static CascadeClassifier rightEyeDetector;
public static CascadeClassifier leftEarDetector;
public static CascadeClassifier rightEarDetector;
public static CascadeClassifier profilefaceDetector;
public static void LoadAllDetectors(){
LoadFrontalFaceDetector();
LoadProfileFaceDetector();
LoadEyeDetector();
LoadLeftEarDetector();
LoadRightEarDetector();
LoadLeftEyeDetector();
LoadRightEyeDetector();
}
private static void LoadFrontalFaceDetector(){
String filename = SomeCodes.ExtractResource("/haarcascade_frontalface_default.xml");
if (filename!=null) {
System.out.println("Face Detector file : " + filename);
if (faceDetector==null) {
if (frontalfaceDetector==null) {
try{
faceDetector = new CascadeClassifier(filename);
frontalfaceDetector = new CascadeClassifier(filename);
Logger.info("FaceDetector loaded");
} catch (Exception e){
Logger.error("Exception on loading FaceDetector : " + e.getMessage());
@@ -27,28 +47,23 @@ public class Detectors {
} else Logger.error("Unable to extract face detector file");
}
static double scaleFactor = 1.1;
static int minNeighbors = 3;
static int flags = 0;
static Size minSize = new Size(30, 30);
static Size maxSize = new Size(300, 300);
private static void LoadProfileFaceDetector(){
String filename = SomeCodes.ExtractResource("/haarcascade_profileface.xml");
if (filename!=null) {
System.out.println("Profile Face Detector file : " + filename);
if (profilefaceDetector==null) {
try{
profilefaceDetector = new CascadeClassifier(filename);
Logger.info("ProfileFaceDetector loaded");
} catch (Exception e){
Logger.error("Exception on loading ProfileFaceDetector : " + e.getMessage());
}
/**
* Detect Face from Mat
* @param graymat Mat in Gray Scale
* @return RectVector if face detected, otherwise false
*/
public static RectVector DetectFace(UMat graymat){
if (faceDetector!=null){
RectVector face = new RectVector();
//faceDetector.detectMultiScale(graymat, face);
faceDetector.detectMultiScale(graymat, face, scaleFactor, minNeighbors,flags, minSize, maxSize);
return face;
}
return null;
} else Logger.info("ProfileFaceDetector already loaded");
} else Logger.error("Unable to extract profile face detector file");
}
public static void LoadEyeDetector(){
private static void LoadEyeDetector(){
String filename = SomeCodes.ExtractResource("/haarcascade_eye_tree_eyeglasses.xml");
if (filename!=null) {
System.out.println("Eye Detector file : " + filename);
@@ -65,13 +80,272 @@ public class Detectors {
} else Logger.error("Unable to extract eye detector file");
}
private static void LoadLeftEyeDetector(){
String filename = SomeCodes.ExtractResource("/haarcascade_lefteye_2splits.xml");
if (filename!=null) {
System.out.println("Left Eye Detector file : " + filename);
if (leftEyeDetector==null) {
try{
leftEyeDetector = new CascadeClassifier(filename);
Logger.info("LeftEyeDetector loaded");
} catch (Exception e){
Logger.error("Exception on loading LeftEyeDetector : " + e.getMessage());
}
} else Logger.info("LeftEyeDetector already loaded");
} else Logger.error("Unable to extract left eye detector file");
}
private static void LoadRightEyeDetector(){
String filename = SomeCodes.ExtractResource("/haarcascade_righteye_2splits.xml");
if (filename!=null) {
System.out.println("Right Eye Detector file : " + filename);
if (rightEyeDetector==null) {
try{
rightEyeDetector = new CascadeClassifier(filename);
Logger.info("RightEyeDetector loaded");
} catch (Exception e){
Logger.error("Exception on loading RightEyeDetector : " + e.getMessage());
}
} else Logger.info("RightEyeDetector already loaded");
} else Logger.error("Unable to extract right eye detector file");
}
private static void LoadLeftEarDetector(){
String filename = SomeCodes.ExtractResource("/haarcascade_mcs_leftear.xml");
if (filename!=null) {
System.out.println("Left Ear Detector file : " + filename);
if (leftEarDetector==null) {
try{
leftEarDetector = new CascadeClassifier(filename);
Logger.info("LeftEarDetector loaded");
} catch (Exception e){
Logger.error("Exception on loading LeftEarDetector : " + e.getMessage());
}
} else Logger.info("LeftEarDetector already loaded");
} else Logger.error("Unable to extract left ear detector file");
}
private static void LoadRightEarDetector(){
String filename = SomeCodes.ExtractResource("/haarcascade_mcs_rightear.xml");
if (filename!=null) {
System.out.println("Right Ear Detector file : " + filename);
if (rightEarDetector==null) {
try{
rightEarDetector = new CascadeClassifier(filename);
Logger.info("RightEarDetector loaded");
} catch (Exception e){
Logger.error("Exception on loading RightEarDetector : " + e.getMessage());
}
} else Logger.info("RightEarDetector already loaded");
} else Logger.error("Unable to extract right ear detector file");
}
//TODO buat kalkulasi range minSize dan maxSize kalau pakai BestMat (3840x2160) dan LiveMat (1280x720)
/**
* Detect if there is a frontal face, containing 2 eyes
* @param graymat Mat in Gray Scale
* @return List of Rect if face detected, otherwise empty list
*/
public static @NonNull List<Rect> HaveFrontalFace(UMat graymat){
List<Rect> result = new ArrayList<>();
RectVector faces = DetectFrontalFace(graymat);
if (faces!=null && faces.size()>0){
for(Rect face : faces.get()){
UMat faceMat = new UMat(graymat, face);
RectVector eyes = DetectEye(faceMat);
if (eyes!=null && eyes.size()>=2){
result.add(face);
}
}
}
return result;
}
public static @NonNull List<Rect> HaveLeft45Face(UMat graymat){
List<Rect> result = new ArrayList<>();
RectVector faces = DetectProfileFace(graymat);
if (faces!=null && faces.size()>0){
for(Rect face : faces.get()){
UMat faceMat = new UMat(graymat, face);
RectVector eyes = DetectLeftEye(faceMat);
if (eyes!=null && eyes.size()>0){
result.add(face);
}
}
}
return result;
}
public static @NonNull List<Rect> HaveRight45Face(UMat graymat){
List<Rect> result = new ArrayList<>();
RectVector faces = DetectProfileFace(graymat);
if (faces!=null && faces.size()>0){
for(Rect face : faces.get()){
UMat faceMat = new UMat(graymat, face);
RectVector eyes = DetectRightEye(faceMat);
if (eyes!=null && eyes.size()>0){
result.add(face);
}
}
}
return result;
}
public static @NonNull List<Rect> HaveLeft90Face(UMat graymat){
List<Rect> result = new ArrayList<>();
RectVector leftears = DetectLeftEar(graymat);
RectVector lefteyes = DetectLeftEye(graymat);
if (leftears!=null && leftears.size()>0 && lefteyes!=null && lefteyes.size()>0){
for(Rect ear : leftears.get()){
result.add(ear);
}
}
return result;
}
public static @NonNull List<Rect> HaveRight90Face(UMat graymat){
List<Rect> result = new ArrayList<>();
RectVector rightears = DetectRightEar(graymat);
RectVector righteyes = DetectRightEye(graymat);
if (rightears!=null && rightears.size()>0 && righteyes!=null && righteyes.size()>0){
for(Rect ear : rightears.get()){
result.add(ear);
}
}
return result;
}
private static double scaleFactor = 1.1;
private final static int minNeighbors = 3;
private final static int flags = 0;
private static Size minSize = new Size(200,200);
private static Size maxSize = new Size(400,400);
public static void setScaleFactor(double value){
if (scaleFactor!=value) scaleFactor = value;
}
public static void setMinSize(int value){
if (minSize.width()!=value || minSize.height()!=value) minSize = new Size(value, value);
}
public static void setMaxSize(int value){
if (maxSize.width()!=value || maxSize.height()!=value) maxSize = new Size(value, value);
}
public static RectVector DetectProfileFace(UMat graymat){
return Detect(graymat, profilefaceDetector, scaleFactor, minNeighbors, flags, minSize, maxSize);
}
/**
* Detect Face from Mat
* @param graymat Mat in Gray Scale
* @return RectVector if face detected, otherwise null
*/
public static RectVector DetectFrontalFace(UMat graymat){
return Detect(graymat, frontalfaceDetector, scaleFactor, minNeighbors, flags, minSize, maxSize);
}
/**
* Detect Eye from Mat
* If Eye detected, it will return RectVector with size is number of eyes detected
* @param graymat Mat in Gray Scale
* @return RectVector if eye detected, otherwise null
*/
public static RectVector DetectEye(UMat graymat){
if (eyeDetector!=null){
RectVector eye = new RectVector();
eyeDetector.detectMultiScale(graymat, eye);
return eye;
return Detect(graymat, eyeDetector);
}
/**
* Detect Left Eye from Mat
* @param graymat Mat in Gray Scale
* @return RectVector if left eye detected, otherwise null
*/
public static RectVector DetectLeftEye(UMat graymat){
return Detect(graymat, leftEyeDetector);
}
/**
* Detect Right Eye from Mat
* @param graymat Mat in Gray Scale
* @return RectVector if right eye detected, otherwise null
*/
public static RectVector DetectRightEye(UMat graymat){
return Detect(graymat, rightEyeDetector);
}
/**
* Detect Left Ear from Mat
* @param graymat Mat in Gray Scale
* @return RectVector if left ear detected, otherwise null
*/
public static RectVector DetectLeftEar(UMat graymat){
return Detect(graymat, leftEarDetector);
}
/**
* Detect Right Ear from Mat
* @param graymat Mat in Gray Scale
* @return RectVector if right ear detected, otherwise null
*/
public static RectVector DetectRightEar(UMat graymat){
return Detect(graymat, rightEarDetector);
}
private static RectVector Detect(UMat graymat, CascadeClassifier detector){
if (detector!=null){
if (graymat!=null){
if (!graymat.empty()){
RectVector detected = new RectVector();
detector.detectMultiScale(graymat, detected);
return detected;
}
}
}
return null;
}
@SuppressWarnings("SameParameterValue")
private static RectVector Detect(UMat graymat, CascadeClassifier detector, double scaleFactor, int minNeighbors, int flags, Size minSize, Size maxSize){
if (detector!=null){
if (graymat!=null){
if (!graymat.empty()){
RectVector detected = new RectVector();
detector.detectMultiScale(graymat, detected, scaleFactor, minNeighbors, flags, minSize, maxSize);
return detected;
}
}
}
return null;
}
}

View File

@@ -21,7 +21,7 @@ public class MainApplication extends Application {
Screen screen = Screen.getPrimary();
Rectangle2D screenbound = screen.getVisualBounds();
Scene scene = new Scene(fxmlLoader.load(), screenbound.getWidth(), screenbound.getHeight());
stage.setTitle("MultiCam Capture App for ERHA");
stage.setTitle("MultiCam Capture App for ERHA 03022025-011");
stage.setScene(scene);
stage.setResizable(true);
stage.setMaximized(true);
@@ -32,9 +32,10 @@ public class MainApplication extends Application {
Logger.info("Application closed");
});
SomeCodes.LoadQRReader();
Detectors.LoadFaceDetector();
Detectors.LoadEyeDetector();
Detectors.LoadAllDetectors();
stage.show();
Logger.info("Application started");
}

View File

@@ -59,7 +59,7 @@ public class MainView {
@FXML
private void initialize(){
ReviewClick(null);
CaptureClick(null);
}
public void Unload(){

View File

@@ -13,6 +13,7 @@ import org.tinylog.Logger;
import java.io.File;
import static Config.SomeCodes.ValidString;
import static Config.SomeCodes.config;
public class SettingView {
@@ -53,6 +54,64 @@ public class SettingView {
String[] cameranames = null;
@FXML
private TextField cascadeMinSize;
@FXML
private TextField cascadeScaleFactor;
@FXML
private TextField cascadeMaxSize;
@FXML
private void CascadeSettingApply(){
String minsize = cascadeMinSize.getText();
String scalefactor = cascadeScaleFactor.getText();
String maxsize = cascadeMaxSize.getText();
if (ValidString(minsize)){
if (ValidString(maxsize)){
if (ValidString(scalefactor)){
try{
int min = Integer.parseInt(minsize);
double scale = Double.parseDouble(scalefactor);
int max = Integer.parseInt(maxsize);
if (scale> 1.0){
if (min>0){
if (max>min){
config.setCascadeMaxSize(max);
config.setCascadeMinSize(min);
config.setCascadeScaleFactor(scale);
config.Save();
Detectors.setMaxSize(max);
Detectors.setMinSize(min);
Detectors.setScaleFactor(scale);
val alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle("Cascade Setting");
alert.setHeaderText("Cascade Setting Saved");
alert.setContentText("Cascade Setting Saved Successfully");
alert.showAndWait();
} else show_cascade_alert("Max Size must be greater than Min Size");
} else show_cascade_alert("Min Size must be greater than 0");
} else show_cascade_alert("Scale Factor must be greater than 1.0");
} catch (NumberFormatException e){
show_cascade_alert("Min Size, Scale Factor, and Max Size must be a number");
}
} else show_cascade_alert("Scale Factor must not empty");
} else show_cascade_alert("Max Size must not empty");
} else show_cascade_alert("Min Size must not empty");
}
private void show_cascade_alert(String content){
val alert = new Alert(Alert.AlertType.ERROR);
alert.setTitle("Cascade Setting Error");
alert.setHeaderText("Cascade Setting Error");
alert.setContentText(content);
alert.showAndWait();
}
@FXML
public void initialize(){
FileChooser.ExtensionFilter filter = new FileChooser.ExtensionFilter("Audio File", "wav","mp3");
@@ -108,6 +167,10 @@ public class SettingView {
FTPUser.setText(config.getFTPUser());
FTPPass.setText(config.getFTPPass());
FTPPath.setText(config.getFTPPath());
cascadeScaleFactor.setText(String.valueOf(config.getCascadeScaleFactor()));
cascadeMinSize.setText(String.valueOf(config.getCascadeMinSize()));
cascadeMaxSize.setText(String.valueOf(config.getCascadeMaxSize()));
});
}

View File

@@ -39,7 +39,7 @@ import java.time.LocalDateTime;
import java.util.concurrent.atomic.AtomicBoolean;
import static Config.SomeCodes.*;
import static id.co.gtc.erhacam.Detectors.faceDetector;
import static id.co.gtc.erhacam.Detectors.frontalfaceDetector;
import static org.bytedeco.opencv.global.opencv_core.CV_8UC3;
import static org.bytedeco.opencv.global.opencv_core.mean;
import static org.bytedeco.opencv.global.opencv_imgcodecs.imwrite;
@@ -706,12 +706,12 @@ public class Cameradetail_Arducam {
if (use_face){
RectVector face = DetectFace(graymat);
if (face!=null && face.size()>0){
if (event!=null) event.onFaceDetector(true,photoWidth, photoHeight);
if (event!=null) event.onFrontalFaceDetector(true,photoWidth, photoHeight);
for(int i=0; i<face.size(); i++){
val rect = face.get(i);
rectangle(umat, rect, Scalar.GREEN);
}
} else if (event!=null) event.onFaceDetector(false, photoWidth, photoHeight);
} else if (event!=null) event.onFrontalFaceDetector(false, photoWidth, photoHeight);
}
@@ -783,9 +783,9 @@ public class Cameradetail_Arducam {
* @return true if face detected, otherwise false
*/
private RectVector DetectFace(UMat graymat){
if (faceDetector!=null){
if (frontalfaceDetector!=null){
val face = new RectVector();
faceDetector.detectMultiScale(graymat, face);
frontalfaceDetector.detectMultiScale(graymat, face);
return face;
}
return null;

View File

@@ -19,16 +19,19 @@
<RowConstraints minHeight="36.0" prefHeight="36.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<AnchorPane prefHeight="200.0" prefWidth="200.0">
<children>
<Button fx:id="ReviewButton" layoutX="99.0" layoutY="2.0" mnemonicParsing="false" onAction="#ReviewClick" text="Review" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1">
<children>
<Button fx:id="CaptureButton" layoutX="103.0" layoutY="6.0" mnemonicParsing="false" onAction="#CaptureClick" text="Capture" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="0.0" />
</children>
<children>
<Button fx:id="CaptureButton" layoutX="103.0" layoutY="6.0" mnemonicParsing="false" onAction="#CaptureClick" text="Capture" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="2">
<children>
<Button fx:id="SettingButton" layoutX="64.0" layoutY="2.0" mnemonicParsing="false" onAction="#SettingClick" text="Setting" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="0.0" />

View File

@@ -1,16 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minHeight="-Infinity" minWidth="-Infinity" prefWidth="1024.0" xmlns="http://javafx.com/javafx/22" xmlns:fx="http://javafx.com/fxml/1" fx:controller="id.co.gtc.erhacam.SettingView">
<AnchorPane maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minHeight="-Infinity" minWidth="-Infinity" prefWidth="1024.0" xmlns="http://javafx.com/javafx/11.0.14-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="id.co.gtc.erhacam.SettingView">
<children>
<GridPane layoutX="70.0" layoutY="78.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columnConstraints>
@@ -137,97 +130,185 @@
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.rowIndex="3">
<children>
<GridPane layoutX="166.0" layoutY="-29.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" percentWidth="15.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" percentWidth="15.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<AnchorPane prefHeight="200.0" prefWidth="200.0">
<GridPane AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" percentWidth="40.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<GridPane>
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" percentWidth="15.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" percentWidth="15.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label layoutX="30.0" layoutY="12.0" prefHeight="40.8" prefWidth="87.2" text="Left 90" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="0.0" />
<AnchorPane prefHeight="200.0" prefWidth="200.0">
<children>
<Label layoutX="30.0" layoutY="12.0" prefHeight="40.8" prefWidth="87.2" text="Left 90" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.rowIndex="1">
<children>
<Label layoutX="44.0" layoutY="12.0" prefHeight="40.8" prefWidth="87.2" text="Left 45" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.rowIndex="2">
<children>
<Label layoutX="30.0" layoutY="6.0" prefHeight="40.8" prefWidth="87.2" text="Center" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.rowIndex="3">
<children>
<Label layoutX="24.0" layoutY="6.0" prefHeight="40.0" prefWidth="87.2" text="Right 45" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.rowIndex="4">
<children>
<Label layoutX="36.0" layoutY="11.0" prefHeight="40.0" prefWidth="87.2" text="Right 90" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1">
<children>
<ComboBox fx:id="CameraLeft90" layoutX="54.0" layoutY="8.0" prefHeight="40.8" prefWidth="408.0" AnchorPane.bottomAnchor="2.0" AnchorPane.leftAnchor="2.0" AnchorPane.rightAnchor="2.0" AnchorPane.topAnchor="2.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1" GridPane.rowIndex="1">
<children>
<ComboBox fx:id="CameraLeft45" layoutX="26.0" layoutY="14.0" prefHeight="40.8" prefWidth="408.0" AnchorPane.bottomAnchor="2.0" AnchorPane.leftAnchor="2.0" AnchorPane.rightAnchor="2.0" AnchorPane.topAnchor="2.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1" GridPane.rowIndex="2">
<children>
<ComboBox fx:id="CameraCenter" layoutX="88.0" layoutY="8.0" prefHeight="40.8" prefWidth="408.0" AnchorPane.bottomAnchor="2.0" AnchorPane.leftAnchor="2.0" AnchorPane.rightAnchor="2.0" AnchorPane.topAnchor="2.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1" GridPane.rowIndex="3">
<children>
<ComboBox fx:id="CameraRight45" layoutX="54.0" layoutY="8.0" prefHeight="40.0" prefWidth="408.0" AnchorPane.bottomAnchor="2.0" AnchorPane.leftAnchor="2.0" AnchorPane.rightAnchor="2.0" AnchorPane.topAnchor="2.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1" GridPane.rowIndex="4">
<children>
<ComboBox fx:id="CameraRight90" layoutX="75.0" layoutY="1.0" prefHeight="40.0" prefWidth="408.0" AnchorPane.bottomAnchor="2.0" AnchorPane.leftAnchor="2.0" AnchorPane.rightAnchor="2.0" AnchorPane.topAnchor="2.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="2">
<children>
<Button layoutY="8.0" mnemonicParsing="false" onAction="#ApplyCameraLeft90" prefHeight="40.8" prefWidth="88.0" text="Apply" AnchorPane.bottomAnchor="2.0" AnchorPane.leftAnchor="2.0" AnchorPane.rightAnchor="2.0" AnchorPane.topAnchor="2.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="2" GridPane.rowIndex="1">
<children>
<Button layoutX="22.0" layoutY="2.0" mnemonicParsing="false" onAction="#ApplyCameraLeft45" prefHeight="40.8" prefWidth="88.0" text="Apply" AnchorPane.bottomAnchor="2.0" AnchorPane.leftAnchor="2.0" AnchorPane.rightAnchor="2.0" AnchorPane.topAnchor="2.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="2" GridPane.rowIndex="2">
<children>
<Button layoutX="18.0" layoutY="2.0" mnemonicParsing="false" onAction="#ApplyCameraFront" prefHeight="40.8" prefWidth="88.0" text="Apply" AnchorPane.bottomAnchor="2.0" AnchorPane.leftAnchor="2.0" AnchorPane.rightAnchor="2.0" AnchorPane.topAnchor="2.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="2" GridPane.rowIndex="3">
<children>
<Button layoutX="22.0" layoutY="8.0" mnemonicParsing="false" onAction="#ApplyCameraRight45" prefHeight="40.0" prefWidth="88.0" text="Apply" AnchorPane.bottomAnchor="2.0" AnchorPane.leftAnchor="2.0" AnchorPane.rightAnchor="2.0" AnchorPane.topAnchor="2.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="2" GridPane.rowIndex="4">
<children>
<Button layoutX="22.0" layoutY="8.0" mnemonicParsing="false" onAction="#ApplyCameraRight90" prefHeight="40.0" prefWidth="88.0" text="Apply" AnchorPane.bottomAnchor="2.0" AnchorPane.leftAnchor="2.0" AnchorPane.rightAnchor="2.0" AnchorPane.topAnchor="2.0" />
</children>
</AnchorPane>
</children>
</GridPane>
<GridPane GridPane.columnIndex="1">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints maxHeight="-Infinity" minHeight="-Infinity" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="-Infinity" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<AnchorPane GridPane.rowIndex="2">
<children>
<Button mnemonicParsing="false" onAction="#CascadeSettingApply" prefHeight="40.0" prefWidth="410.4" text="Apply" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
<AnchorPane>
<children>
<Label alignment="CENTER" prefHeight="30.4" prefWidth="410.4" text="Detection Parameters" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
<AnchorPane GridPane.rowIndex="1">
<children>
<ScrollPane layoutY="0.7999992370605469" prefHeight="200.8" prefWidth="410.4" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="0.0">
<content>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="200.0" minWidth="0.0" prefWidth="350.0">
<children>
<GridPane prefHeight="200.0" prefWidth="300.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints maxHeight="-Infinity" minHeight="-Infinity" prefHeight="40.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="-Infinity" minHeight="-Infinity" prefHeight="40.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="-Infinity" minHeight="-Infinity" prefHeight="40.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<AnchorPane>
<children>
<Label prefHeight="40.0" prefWidth="175.2" text="Scale Factor" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
<AnchorPane GridPane.rowIndex="1">
<children>
<Label prefHeight="40.0" prefWidth="175.2" text="Minimum Size" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
<AnchorPane GridPane.rowIndex="2">
<children>
<Label prefHeight="40.0" prefWidth="175.2" text="Maximum Size" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
<AnchorPane GridPane.columnIndex="1">
<children>
<TextField fx:id="cascadeScaleFactor" alignment="CENTER" prefHeight="40.0" prefWidth="175.2" text="1.2" AnchorPane.bottomAnchor="5.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
<AnchorPane GridPane.columnIndex="1" GridPane.rowIndex="1">
<children>
<TextField fx:id="cascadeMinSize" alignment="CENTER" prefHeight="40.0" prefWidth="175.2" text="200" AnchorPane.bottomAnchor="5.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
<AnchorPane GridPane.columnIndex="1" GridPane.rowIndex="2">
<children>
<TextField fx:id="cascadeMaxSize" alignment="CENTER" prefHeight="40.0" prefWidth="175.2" text="400" AnchorPane.bottomAnchor="5.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
</children>
</GridPane>
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.rowIndex="1">
<children>
<Label layoutX="44.0" layoutY="12.0" prefHeight="40.8" prefWidth="87.2" text="Left 45" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.rowIndex="2">
<children>
<Label layoutX="30.0" layoutY="6.0" prefHeight="40.8" prefWidth="87.2" text="Center" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.rowIndex="3">
<children>
<Label layoutX="24.0" layoutY="6.0" prefHeight="40.0" prefWidth="87.2" text="Right 45" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.rowIndex="4">
<children>
<Label layoutX="36.0" layoutY="11.0" prefHeight="40.0" prefWidth="87.2" text="Right 90" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1">
<children>
<ComboBox fx:id="CameraLeft90" layoutX="54.0" layoutY="8.0" prefHeight="40.8" prefWidth="408.0" AnchorPane.bottomAnchor="2.0" AnchorPane.leftAnchor="2.0" AnchorPane.rightAnchor="2.0" AnchorPane.topAnchor="2.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1" GridPane.rowIndex="1">
<children>
<ComboBox fx:id="CameraLeft45" layoutX="26.0" layoutY="14.0" prefHeight="40.8" prefWidth="408.0" AnchorPane.bottomAnchor="2.0" AnchorPane.leftAnchor="2.0" AnchorPane.rightAnchor="2.0" AnchorPane.topAnchor="2.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1" GridPane.rowIndex="2">
<children>
<ComboBox fx:id="CameraCenter" layoutX="88.0" layoutY="8.0" prefHeight="40.8" prefWidth="408.0" AnchorPane.bottomAnchor="2.0" AnchorPane.leftAnchor="2.0" AnchorPane.rightAnchor="2.0" AnchorPane.topAnchor="2.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1" GridPane.rowIndex="3">
<children>
<ComboBox fx:id="CameraRight45" layoutX="54.0" layoutY="8.0" prefHeight="40.0" prefWidth="408.0" AnchorPane.bottomAnchor="2.0" AnchorPane.leftAnchor="2.0" AnchorPane.rightAnchor="2.0" AnchorPane.topAnchor="2.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1" GridPane.rowIndex="4">
<children>
<ComboBox fx:id="CameraRight90" layoutX="75.0" layoutY="1.0" prefHeight="40.0" prefWidth="408.0" AnchorPane.bottomAnchor="2.0" AnchorPane.leftAnchor="2.0" AnchorPane.rightAnchor="2.0" AnchorPane.topAnchor="2.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="2">
<children>
<Button layoutY="8.0" mnemonicParsing="false" onAction="#ApplyCameraLeft90" prefHeight="40.8" prefWidth="88.0" text="Apply" AnchorPane.bottomAnchor="2.0" AnchorPane.leftAnchor="2.0" AnchorPane.rightAnchor="2.0" AnchorPane.topAnchor="2.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="2" GridPane.rowIndex="1">
<children>
<Button layoutX="22.0" layoutY="2.0" mnemonicParsing="false" onAction="#ApplyCameraLeft45" prefHeight="40.8" prefWidth="88.0" text="Apply" AnchorPane.bottomAnchor="2.0" AnchorPane.leftAnchor="2.0" AnchorPane.rightAnchor="2.0" AnchorPane.topAnchor="2.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="2" GridPane.rowIndex="2">
<children>
<Button layoutX="18.0" layoutY="2.0" mnemonicParsing="false" onAction="#ApplyCameraFront" prefHeight="40.8" prefWidth="88.0" text="Apply" AnchorPane.bottomAnchor="2.0" AnchorPane.leftAnchor="2.0" AnchorPane.rightAnchor="2.0" AnchorPane.topAnchor="2.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="2" GridPane.rowIndex="3">
<children>
<Button layoutX="22.0" layoutY="8.0" mnemonicParsing="false" onAction="#ApplyCameraRight45" prefHeight="40.0" prefWidth="88.0" text="Apply" AnchorPane.bottomAnchor="2.0" AnchorPane.leftAnchor="2.0" AnchorPane.rightAnchor="2.0" AnchorPane.topAnchor="2.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="2" GridPane.rowIndex="4">
<children>
<Button layoutX="22.0" layoutY="8.0" mnemonicParsing="false" onAction="#ApplyCameraRight90" prefHeight="40.0" prefWidth="88.0" text="Apply" AnchorPane.bottomAnchor="2.0" AnchorPane.leftAnchor="2.0" AnchorPane.rightAnchor="2.0" AnchorPane.topAnchor="2.0" />
</children>
</AnchorPane>
</children>
</GridPane>
</content>
</ScrollPane>
</children>
</AnchorPane>
</children>
</GridPane>
</children>
</GridPane>
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.rowIndex="5">