Files
ErhaCam/src/main/java/id/co/gtc/erhacam/Cameradetail.java
2025-08-27 13:02:06 +07:00

1430 lines
53 KiB
Java

package id.co.gtc.erhacam;
import Camera.CameraProperty;
import Camera.LiveCamEvent;
import Camera.ObsbotMeet2Preset;
import Config.CameraConfigEnum;
import ErhaAPI.PhotoResult;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.Result;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.HybridBinarizer;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.control.Slider;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;
import lombok.Getter;
import lombok.Setter;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.FrameGrabber;
import org.bytedeco.javacv.OpenCVFrameGrabber;
import org.bytedeco.opencv.global.opencv_core;
import org.bytedeco.opencv.global.opencv_imgcodecs;
import org.bytedeco.opencv.global.opencv_imgproc;
import org.bytedeco.opencv.opencv_core.*;
import org.opencv.videoio.Videoio;
import org.tinylog.Logger;
import java.awt.image.BufferedImage;
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.CountDownLatch;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import static Config.SomeCodes.*;
import static org.bytedeco.opencv.global.opencv_imgproc.*;
@SuppressWarnings({"unused"})
public class Cameradetail {
private static final boolean isCudaAvailable ;
static{
isCudaAvailable = opencv_core.getCudaEnabledDeviceCount()>0;
if (isCudaAvailable){
System.out.println("CUDA is available");
opencv_core.printCudaDeviceInfo(0);
} else {
System.out.println("CUDA is not available");
}
}
private final AtomicBoolean Capturing = new AtomicBoolean(false);
private CountDownLatch TakingPhoto = null;
private final Semaphore IsGrabbingLiveView = new Semaphore(0);
private OpenCVFrameGrabber mGrabber = null;
private LiveCamEvent event = null;
private @Getter @Setter CameraConfigEnum cameraConfigEnum = CameraConfigEnum.CameraConfigCenter;
private @Getter int LiveFPS = 0;
private double topcrop = 0.0;
private double bottomcrop = 0.0;
private double leftcrop = 0.0;
private double rightcrop = 0.0;
private String title = "";
@FXML
private Label cameratitle;
@FXML
private ImageView camerastream;
@FXML
private AnchorPane streamanchor;
@FXML
private Label camerastatus;
@FXML
private Slider brightnessSlider;
@FXML
private Slider contrastSlider;
@FXML
private Slider saturationSlider;
@FXML
private Slider hueSlider;
@FXML
private Slider gainSlider;
@FXML
private Slider exposureSlider;
@FXML
private Label face_indicator;
@FXML
private Label eye_indicator;
@FXML
private Label BlinkCounterLabel;
@FXML
private Label sharpness_indicator;
private @Getter final UMat BestMat = new UMat();
private @Getter final UMat LiveMat = new UMat();
private @Getter final UMat ReducedMat = new UMat();
private @Getter final Mat GrayMat = new Mat();
// private @Getter Rect BestMatROI;
// private @Getter Rect ReducedMatROI;
// private @Getter Rect LiveMatROI;
private boolean IsPortrait = false;
// putar portrait
private @Getter Size LiveSize = new Size(360, 640);
private @Getter Size ReducedSize = new Size(720, 1280);
private @Getter Size BestSize = new Size(2160, 3840);
private int realwidth = 0;
private int realheight = 0;
//TODO ini angka dari Erha, cek apakah masih cocok atau tidak
private @Getter @Setter Size FullCropSize = new Size(1036,1036);
public int getBestWidth(){
return BestSize.width();
}
public int getBestHeight(){
return BestSize.height();
}
public int getRealWidth(){
return realwidth;
}
public int getRealHeight(){
return realheight;
}
int[] paramjpeg = {opencv_imgcodecs.IMWRITE_JPEG_QUALITY, 100};
int[] parampng = {opencv_imgcodecs.IMWRITE_PNG_COMPRESSION, 0};
private boolean use_qr = false;
private boolean use_face = false;
private @Getter Detectors detector;
private void setSliderValue(Slider sld, CameraProperty prop, double value){
if (sld!=null){
if (prop!=null){
if (Platform.isFxApplicationThread()){
sld.setMin(prop.Min);
sld.setMax(prop.Max);
sld.setValue(value);
} else {
Platform.runLater(()->{
sld.setMin(prop.Min);
sld.setMax(prop.Max);
sld.setValue(value);
});
}
}
}
}
private void resize_streamanchor(){
if (streamanchor!=null){
if (streamanchor.getHeight()!=0){
if (streamanchor.getWidth()!=0){
camerastream.setFitHeight(streamanchor.getHeight()-10);
//camerastream.setFitWidth(streamanchor.getWidth());
camerastream.setPreserveRatio(true);
}
}
}
}
@FXML
public void initialize(){
streamanchor.heightProperty().addListener(obs -> resize_streamanchor());
streamanchor.widthProperty().addListener(obs -> resize_streamanchor());
Platform.runLater(()->{
setSliderValue(brightnessSlider, ObsbotMeet2Preset.Brightness, config.getBrightness(cameraConfigEnum));
setSliderValue(contrastSlider, ObsbotMeet2Preset.Contrast, config.getContrast(cameraConfigEnum));
setSliderValue(saturationSlider, ObsbotMeet2Preset.Saturation, config.getSaturation(cameraConfigEnum));
setSliderValue(hueSlider, ObsbotMeet2Preset.Hue, config.getHue(cameraConfigEnum));
setSliderValue(gainSlider, ObsbotMeet2Preset.Gain, config.getGain(cameraConfigEnum));
setSliderValue(exposureSlider, ObsbotMeet2Preset.ExposureTime, config.getExposure(cameraConfigEnum));
});
brightnessSlider.valueProperty().addListener((obs, oldVal, newVal) -> {
setBrightness(newVal.doubleValue());
config.setBrightness(cameraConfigEnum, newVal.doubleValue());
raise_log("Brightness for "+getCameraTitle()+" changed to "+newVal);
});
contrastSlider.valueProperty().addListener((obs, oldVal, newVal) -> {
setContrast(newVal.doubleValue());
config.setContrast(cameraConfigEnum, newVal.doubleValue());
raise_log("Contrast for "+getCameraTitle()+" changed to "+newVal);
});
saturationSlider.valueProperty().addListener((obs, oldVal, newVal) -> {
setSaturation(newVal.doubleValue());
config.setSaturation(cameraConfigEnum, newVal.doubleValue());
raise_log("Saturation for "+getCameraTitle()+" changed to "+newVal);
});
hueSlider.valueProperty().addListener((obs, oldVal, newVal) -> {
setHue(newVal.doubleValue());
config.setHue(cameraConfigEnum, newVal.doubleValue());
raise_log("Hue for "+getCameraTitle()+" changed to "+newVal);
});
gainSlider.valueProperty().addListener((obs, oldVal, newVal) -> {
setGain(newVal.doubleValue());
config.setGain(cameraConfigEnum, newVal.doubleValue());
raise_log("Gain for "+getCameraTitle()+" changed to "+newVal);
});
exposureSlider.valueProperty().addListener((obs, oldVal, newVal) -> {
setExposure(newVal.doubleValue());
config.setExposure(cameraConfigEnum, newVal.doubleValue());
raise_log("Exposure for "+getCameraTitle()+" changed to "+newVal);
});
detector = new Detectors();
}
@FXML
public void resetClick(){
brightnessSlider.adjustValue(ObsbotMeet2Preset.Brightness.Default);
contrastSlider.adjustValue(ObsbotMeet2Preset.Contrast.Default);
saturationSlider.adjustValue(ObsbotMeet2Preset.Saturation.Default);
hueSlider.adjustValue(ObsbotMeet2Preset.Hue.Default);
gainSlider.adjustValue(ObsbotMeet2Preset.Gain.Default);
exposureSlider.adjustValue(ObsbotMeet2Preset.ExposureTime.Default);
}
public boolean isCapturing(){
return Capturing.get();
}
/**
* Set Camera Title
* @param title Title of the Camera
*/
public void setCameraTitle(String title){
if (ValidString(title)){
this.title = title;
LabelSetText(cameratitle, title,null);
}
}
public void setSharpness_indicator(double value){
if (value < 0){
// not defined
LabelSetText(sharpness_indicator, "","");
} else if (value >= config.getSharpnessThreshold()){
// sharpness is good
LabelSetText(sharpness_indicator, "OK","-fx-text-fill: green; -fx-border-color: black");
} else {
// sharpness is bad
LabelSetText(sharpness_indicator,"BAD","-fx-text-fill: red; -fx-border-color: black");
}
}
public void setSaturation(double value){
if (mGrabber!=null){
mGrabber.setOption(Videoio.CAP_PROP_SATURATION, value);
}
}
public double getSaturation(){
if (mGrabber!=null){
return mGrabber.getOption(Videoio.CAP_PROP_SATURATION);
}
return 0;
}
public void setHue(double value){
if (mGrabber!=null){
mGrabber.setOption(Videoio.CAP_PROP_HUE, value);
}
}
public double getHue(){
if (mGrabber!=null){
return mGrabber.getOption(Videoio.CAP_PROP_HUE);
}
return 0;
}
public void setGain(double value){
if (mGrabber!=null){
mGrabber.setOption(Videoio.CAP_PROP_GAIN, value);
}
}
public double getGain(){
if (mGrabber!=null){
return mGrabber.getOption(Videoio.CAP_PROP_GAIN);
}
return 0;
}
/**
* Get Camera Title
* @return Title of the Camera, or empty string if not set
*/
public String getCameraTitle(){
if (cameratitle!=null){
return cameratitle.getText();
}
return "";
}
/**
* Set Camera Status
* @param status Status of the Camera
*/
public void setCameraStatus(String status){
LabelSetText(camerastatus, status,null);
}
/**
* Get Camera Status
* @return Status of the Camera, or empty string if not set
*/
public String getCameraStatus(){
if (camerastatus!=null){
return camerastatus.getText();
}
return "";
}
/**
* Set Camera Stream
* @param image Image to be displayed
*/
public void setCameraStream(Image image){
if (image!=null){
if (camerastream!=null){
if (Platform.isFxApplicationThread()){
camerastream.setImage(image);
} else {
Platform.runLater(()->camerastream.setImage(image));
}
}
}
}
/**
* Get Camera Stream
* @return Image of the Camera Stream, or null if not set
*/
public Image getCameraStream(){
if (camerastream!=null){
return camerastream.getImage();
}
return null;
}
public void setFPS(double value){
if (mGrabber!=null){
mGrabber.setOption(Videoio.CAP_PROP_FPS, value);
}
}
public double getFPS(){
if (mGrabber!=null){
return mGrabber.getOption(Videoio.CAP_PROP_FPS);
}
return 0;
}
/**
* Set Camera Grabber and Target Width and Height
* @param hardwareID Hardware ID of the Camera
* @param grabber Camera Grabber
* @param livewidth Width used on live view
* @param liveheight Height used on live view
* @param photowidth Width used on photo capture
* @param photoheight Height used on photo capture
* @param reducedwidth Width used on reduced resolution
* @param reducedheight Height used on reduced resolution
* @param isPotrait if true, set to portrait mode, otherwise landscape
*/
public void SetGrabber(int hardwareID, OpenCVFrameGrabber grabber, int livewidth, int liveheight, int photowidth, int photoheight, int reducedwidth, int reducedheight, boolean isPotrait){
if (mGrabber!=null) {
StopLiveView();
}
IsPortrait = isPotrait;
if (IsPortrait){
// putar portrait
LiveSize = new Size(liveheight, livewidth);
BestSize = new Size(photoheight, photowidth);
ReducedSize = new Size(reducedheight, reducedwidth);
} else {
LiveSize = new Size(livewidth, liveheight);
BestSize = new Size(photowidth, photoheight);
ReducedSize = new Size(reducedwidth, reducedheight);
}
mGrabber = grabber;
}
//Exposure and Focus Tricks :
// https://stackoverflow.com/questions/53545945/how-to-set-camera-to-auto-exposure-with-opencv-3-4-2
// https://github.com/opencv/opencv/issues/9738
/**
* Set Auto Exposure Mode
* @param ON if true, set autoexposure on, otherwise off
*/
public void setAutoExposure(boolean ON){
if (mGrabber!=null){
mGrabber.setOption(Videoio.CAP_PROP_AUTO_EXPOSURE, ON?ObsbotMeet2Preset.AutoExposure.On:ObsbotMeet2Preset.AutoExposure.Off);
}
}
/**
* Get Auto Exposure Mode
* @return true if autoexposure is on, otherwise off
*/
public boolean getAutoExposure(){
if (mGrabber!=null){
return mGrabber.getOption(Videoio.CAP_PROP_AUTO_EXPOSURE)==ObsbotMeet2Preset.AutoExposure.On;
}
return false;
}
/**
* Set Exposure when Auto Exposure is Off
* @param value exposure value
*/
public void setExposure(double value){
if (mGrabber!=null){
mGrabber.setOption(Videoio.CAP_PROP_EXPOSURE, value);
}
}
/**
* Get Exposure when Auto Exposure is Off
* @return exposure value
*/
public double getExposure(){
if (mGrabber!=null){
return mGrabber.getOption(Videoio.CAP_PROP_EXPOSURE);
}
return 0;
}
/**
* Set Auto Focus
* @param ON if true, set autofocus on, otherwise off
*/
public void setAutoFocus(boolean ON){
if (mGrabber!=null){
mGrabber.setOption(Videoio.CAP_PROP_AUTOFOCUS, ON?ObsbotMeet2Preset.AutoFocus.On:ObsbotMeet2Preset.AutoFocus.Off);
}
}
/**
* Get Auto Focus
* @return true if autofocus is on, otherwise off
*/
public boolean getAutoFocus(){
if (mGrabber!=null){
return mGrabber.getOption(Videoio.CAP_PROP_AUTOFOCUS)==ObsbotMeet2Preset.AutoFocus.On;
}
return false;
}
public void setAutoWB(boolean ON){
if (mGrabber!=null){
mGrabber.setOption(Videoio.CAP_PROP_AUTO_WB, ON?ObsbotMeet2Preset.AutoWhiteBalance.On:ObsbotMeet2Preset.AutoWhiteBalance.Off);
}
}
public boolean getAutoWB(){
if (mGrabber!=null){
return mGrabber.getOption(Videoio.CAP_PROP_AUTO_WB)==ObsbotMeet2Preset.AutoWhiteBalance.On;
}
return false;
}
/**
* Set Focus when Auto Focus is Off
* @param value focus value
*/
public void setFocus(double value){
if (mGrabber!=null){
mGrabber.setOption(Videoio.CAP_PROP_FOCUS, value);
}
}
/**
* Get Focus when Auto Focus is Off
* @return focus value
*/
public double getFocus(){
if (mGrabber!=null){
return mGrabber.getOption(Videoio.CAP_PROP_FOCUS);
}
return 0;
}
public void setBrightness(double value){
if (mGrabber!=null){
mGrabber.setOption(Videoio.CAP_PROP_BRIGHTNESS, value);
}
}
public double getBrightness(){
if (mGrabber!=null){
return mGrabber.getOption(Videoio.CAP_PROP_BRIGHTNESS);
}
return 0;
}
public void setContrast(double value){
if (mGrabber!=null){
mGrabber.setOption(Videoio.CAP_PROP_CONTRAST, value);
}
}
public double getContrast(){
if (mGrabber!=null){
return mGrabber.getOption(Videoio.CAP_PROP_CONTRAST);
}
return 0;
}
public void setFrameWidth(int width){
if (mGrabber!=null){
mGrabber.setOption(Videoio.CAP_PROP_FRAME_WIDTH, width);
}
}
public double getFrameWidth(){
if (mGrabber!=null){
return mGrabber.getOption(Videoio.CAP_PROP_FRAME_WIDTH);
}
return 0;
}
public void setFrameHeight(int height){
if (mGrabber!=null){
mGrabber.setOption(Videoio.CAP_PROP_FRAME_HEIGHT, height);
}
}
public double getFrameHeight(){
if (mGrabber!=null){
return mGrabber.getOption(Videoio.CAP_PROP_FRAME_HEIGHT);
}
return 0;
}
public void setSharpness(double value){
if (mGrabber!=null){
mGrabber.setOption(Videoio.CAP_PROP_SHARPNESS, value);
}
}
public double getSharpness(){
if (mGrabber!=null){
return mGrabber.getOption(Videoio.CAP_PROP_SHARPNESS);
}
return 0;
}
public void setGamma(double value){
if (mGrabber!=null){
mGrabber.setOption(Videoio.CAP_PROP_GAMMA, value);
}
}
public double getGamma(){
if (mGrabber!=null){
return mGrabber.getOption(Videoio.CAP_PROP_GAMMA);
}
return 0;
}
public boolean PutText(String filename, String text, double fontScale, Scalar textColor, int thickness){
if (ValidString(filename)){
Mat mat = opencv_imgcodecs.imread(filename);
if (PutText(mat, text, fontScale, textColor, thickness)){
return opencv_imgcodecs.imwrite(filename, mat);
}
}
return false;
}
public boolean PutText(UMat Mat, String text, double fontScale, Scalar textColor, int thickness){
if (!Mat.empty()){
if (text!=null && !text.isBlank()){
//String timestamp = prefix+" "+SomeCodes.GetDateTimeString();
int fontFace = FONT_HERSHEY_SIMPLEX;
//double fontScale = 4.0;
//int thickness = 2;
//Scalar textColor = new Scalar(255, 255, 255, 0); // white color in BGR format
int[] baseline = {0};
Size textSize = getTextSize(text, fontFace, fontScale, thickness, baseline);
// position of the text in the bottom right corner
int textX = Mat.cols() - textSize.width() - 10; // 10 pixels from the right
int textY = Mat.rows() - 10; // 10 pixels from the bottom
opencv_imgproc.putText(Mat, text, new Point(textX, textY), fontFace, fontScale, textColor, thickness, LINE_8, false);
return true;
}
}
return false;
}
public boolean PutText(Mat Mat, String text, double fontScale, Scalar textColor, int thickness){
if (!Mat.empty()){
if (text!=null && !text.isBlank()){
//String timestamp = prefix+" "+SomeCodes.GetDateTimeString();
int fontFace = FONT_HERSHEY_SIMPLEX;
//double fontScale = 4.0;
//int thickness = 2;
//Scalar textColor = new Scalar(255, 255, 255, 0); // white color in BGR format
int[] baseline = {0};
Size textSize = getTextSize(text, fontFace, fontScale, thickness, baseline);
// position of the text in the bottom right corner
int textX = Mat.cols() - textSize.width() - 10; // 10 pixels from the right
int textY = Mat.rows() - 10; // 10 pixels from the bottom
opencv_imgproc.putText(Mat, text, new Point(textX, textY), fontFace, fontScale, textColor, thickness, LINE_8, false);
return true;
}
}
return false;
}
public String GetFullQualityPhotoPath(String directory, String prefix){
//if (!ValidDirectory(directory)) directory = currentDirectory;
//return Path.of(directory, "FullQuality", makeFileName(prefix,".png")).toString();
return Path.of(config.getFullQualityDirectory(), makeFileName(prefix,".png")).toString();
}
public String GetReducedPhotoPath(String directory, String prefix){
//if (!ValidDirectory(directory)) directory = currentDirectory;
//return Path.of(directory, "Compressed", makeReducedFileName(prefix,".jpg")).toString();
return Path.of(config.getCompressedDirectory(), makeFileName(prefix,".jpg")).toString();
}
public String GetFullQualityCropPhotoPath(String directory, String prefix){
//if (!ValidDirectory(directory)) directory = currentDirectory;
//return Path.of(directory, "FullQualityCrop", makeFileName(prefix,".png")).toString();
return Path.of(config.getFullQualityCropDirectory(), makeFileName(prefix,".png")).toString();
}
public String GetReducedCropPhotoPath(String directory, String prefix){
//if (!ValidDirectory(directory)) directory = currentDirectory;
//return Path.of(directory, "CompressedCrop", makeReducedFileName(prefix,".jpg")).toString();
return Path.of(config.getCompressedCropDirectory(), makeFileName(prefix,".jpg")).toString();
}
/**
* Take Photo from Camera
* @param directory directory to save the photo, if null, will use default directory
* @param prefix filename prefix
* @return filename path of the saved photo, or null if failed
*/
public PhotoResult TakePhoto(String directory, String prefix) {
PhotoResult result = new PhotoResult(title);
if (!ValidDirectory(directory)) directory = currentDirectory;
if (mGrabber!=null){
try{
// wait if the camera is still capturing
IsGrabbingLiveView.acquire();
TakingPhoto = new CountDownLatch(1);
if (!BestMat.empty()){
UMat cloned = new UMat();
synchronized (BestMat){
BestMat.copyTo(cloned);
}
// save BestMat at quality 9 PNG
String fullresfile = GetFullQualityPhotoPath(directory, prefix);
if (opencv_imgcodecs.imwrite(fullresfile, cloned, parampng)){
result.setFullres(fullresfile);
} else System.out.println("TakePhoto failed, Unable to Save FullQUality Photo for camera "+title);
// save ReducedMat at 100% JPEG
String reducedfile = GetReducedPhotoPath(directory, prefix);
opencv_imgproc.resize(cloned, ReducedMat, ReducedSize);
if (opencv_imgcodecs.imwrite(reducedfile, ReducedMat, paramjpeg)){
result.setCompressedfile(reducedfile);
} else {
System.out.println("TakePhoto failed, Unable to Save Reduced Photo for camera "+title);
}
String fullcropfile = GetFullQualityCropPhotoPath(directory, prefix);
UMat croppedfull = CropMat(cloned);
if (croppedfull!=null && !croppedfull.empty()){
if (opencv_imgcodecs.imwrite(fullcropfile, croppedfull, parampng)){
result.setFullcrop(fullcropfile);
} else {
System.out.println("TakePhoto failed, Unable to Save FullCrop Photo for camera "+title);
}
} else System.out.println("TakePhoto failed, Unable to Save FullCrop Photo for camera "+title);
String reducedcropfile = GetReducedCropPhotoPath(directory, prefix);
UMat reducedcrop = CropMat(ReducedMat);
if (reducedcrop!=null && !reducedcrop.empty()){
if (opencv_imgcodecs.imwrite(reducedcropfile, reducedcrop, paramjpeg)){
result.setCompressedcrop(reducedcropfile);
} else {
System.out.println("TakePhoto failed, Unable to Save ReducedCrop Photo for camera "+title);
}
} else System.out.println("TakePhoto failed, Unable to Save ReducedCrop Photo for camera "+title);
cloned.release();
} else raise_log("TakePhoto failed, Live View is Empty");
} catch (InterruptedException e){
System.out.println("TakePhoto IsGrabbingLiveView interrupted");
}
TakingPhoto.countDown();
} else raise_log("TakePhoto failed, Grabber is null");
return result;
}
public String CropBestMat(String directory, String prefix, Rect ROI){
UMat cloned = new UMat();
synchronized (BestMat){
BestMat.copyTo(cloned);
}
if (!cloned.empty()) {
if (ValidROI(ROI)){
if (ROIInsideUMat(ROI, cloned)){
UMat cropped = CropUMat(cloned, ROI);
if (cropped != null) {
String filename = GetFullQualityCropPhotoPath(directory, prefix);
if (opencv_imgcodecs.imwrite(filename, cropped, parampng)) {
Logger.info("CropBestMat success, saved as " + filename);
return filename;
} else Logger.error("CropBestMat failed, Unable to Save BestMat as ",filename);
} else Logger.error("CropBestMat failed, Unable to Crop BestMat");
} else Logger.error("CropBestMat failed, ROI is outside BestMat");
} else Logger.error("CropBestMat failed, ROI is invalid");
} else Logger.error("CropBestMat failed, BestMat is empty");
cloned.release();
return null;
}
public String CropReducedMat(String directory, String prefix, Rect ROI){
if (!ReducedMat.empty()){
if (ValidROI(ROI)){
if (ROIInsideUMat(ROI,ReducedMat)){
UMat cropped = CropUMat(ReducedMat, ROI);
if (cropped!=null){
String filename = GetReducedCropPhotoPath(directory, prefix);
if (opencv_imgcodecs.imwrite(filename, cropped, paramjpeg)){
Logger.info("CropReducedMat success, saved as ",filename);
return filename;
} else Logger.error("CropReducedMat failed, Unable to Save ReducedMat as ",filename);
} else Logger.error("CropReducedMat failed, Unable to Crop ReducedMat");
} else Logger.error("CropReducedMat failed, ROI is outside ReducedMat");
} else Logger.error("CropReducedMat failed, ROI is invalid");
} else Logger.error("CropReducedMat failed, ReducedMat is empty");
return null;
}
@SuppressWarnings("SameParameterValue")
private String makeFileName(String prefix, String extension){
LocalDateTime ldt = LocalDateTime.now();
String timetag = ldt.getYear() + "-" + ldt.getMonthValue() + "-" + ldt.getDayOfMonth() + "_" + ldt.getHour() + "-" + ldt.getMinute() + "-" + ldt.getSecond();
return prefix+" "+timetag+" "+title + extension;
}
@SuppressWarnings("SameParameterValue")
private String makeReducedFileName(String prefix, String extension){
LocalDateTime ldt = LocalDateTime.now();
String timetag = ldt.getYear() + "-" + ldt.getMonthValue() + "-" + ldt.getDayOfMonth() + "_" + ldt.getHour() + "-" + ldt.getMinute() + "-" + ldt.getSecond();
return prefix+" "+timetag+" "+title + "_reduced" + extension;
}
// public void Release(){
// if (mGrabber!=null){
// try{
// StopLiveView();
// mGrabber.release();
// mGrabber = null;
// } catch (Exception e){
// System.out.println("Release failed, Unable to Release Camera, Error: " + e.getMessage());
// }
// }
// }
public void StopLiveView(){
Capturing.set(false);
if (mGrabber!=null){
try{
mGrabber.close();
System.out.println("Camera "+title+" stopped");
setCameraStatus("Camera Stopped");
} catch (Exception e){
raise_log("StopLiveView failed, Unable to Stop Camera, Error: " + e.getMessage());
}
}
TakingPhoto = null;
IsGrabbingLiveView.drainPermits();
// stop FPS calculation
timer.cancel();
// stop camera capture thread
cam_capture.interrupt();
// stop qr detection thread
qr_detect.interrupt();
// stop face detection thread
face_detect.interrupt();
}
Timer timer = new java.util.Timer();
// FPS Calculator
AtomicInteger fps = new AtomicInteger(0);
// use for locking
final Object lockObject = new Object();
// QR Detection Thread
Semaphore qr_semaphore = new Semaphore(0);
Thread qr_detect = new Thread(()->{
while(Capturing.get()){
try {
qr_semaphore.acquire();
String qr = DetectQRFromMat(GrayMat.clone());
if (ValidBarCode(qr)){
if (event!=null) event.onDetectedQRCode(qr);
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()+" interrupted");
}
}
});
// Face Detection Thread
Semaphore face_semaphore = new Semaphore(0);
Thread face_detect = new Thread(()->{
// eye state = -1 means unknown, 0 means closed, 1 means open
final AtomicInteger eye_state = new AtomicInteger(-1);
final AtomicBoolean waiting_for_second_blink = new AtomicBoolean(false);
final AtomicLong last_blink = new AtomicLong(0);
final AtomicInteger no_face_counter = new AtomicInteger(0);
final AtomicInteger face_counter = new AtomicInteger(0);
final AtomicInteger blink_counter = new AtomicInteger(0);
//final AtomicInteger no_eye_counter = new AtomicInteger(0);
//final AtomicInteger have_eye_counter = new AtomicInteger(0);
while(Capturing.get()){
try {
face_semaphore.acquire();
DetectorResult theface = null;
boolean have_frontal_face = false;
boolean have_left_45_face = false;
int _face_width = 0;
int _face_height = 0;
List<DetectorResult> frontalfaces = detector.HaveFrontalFace(GrayMat.clone());
//System.out.println("camera "+title+ (frontalfaces.isEmpty() ? " no frontal face detected" : " found "+frontalfaces.size()+" frontal faces"));
if (!frontalfaces.isEmpty()){
for(DetectorResult rect : frontalfaces){
if (rect.haveFace() ){
rect.FaceRectangle(LiveMat);
if (rect.getFaceWidth()>_face_width) _face_width = rect.getFaceWidth();
if (rect.getFaceHeight()>_face_height) _face_height = rect.getFaceHeight();
theface = rect;
have_frontal_face = true;
if (rect.haveEyes()){
rect.EyesRectangle(LiveMat);
}
}
}
} else {
// gak punya frontal face
// coba cek punya profile left face 45 gak
List<DetectorResult> Left45Faces = detector.HaveLeft45Face(GrayMat.clone());
//System.out.println("camera "+title+ (Left45Faces.isEmpty() ? " no left 45 face detected" : " found "+Left45Faces.size()+" left 45 faces"));
if (!Left45Faces.isEmpty()){
for(DetectorResult rect : Left45Faces){
if (rect.haveFace()){
rect.FaceRectangle(LiveMat);
if (rect.getFaceWidth()>_face_width) _face_width = rect.getFaceWidth();
if (rect.getFaceHeight()>_face_height) _face_height = rect.getFaceHeight();
have_left_45_face = true;
if (rect.haveEyes()){
rect.EyesRectangle(LiveMat);
}
}
}
}
}
if (have_frontal_face){
if (face_counter.incrementAndGet()<5) continue;
no_face_counter.set(0);
if (event!=null) event.onFrontalFaceDetector(true, _face_width, _face_height);
LabelVisible(face_indicator,true);
// Revisi 26/05/2025, LiveMatROI dihitung dari topcrop, bottomcrop, leftcrop dan rightcrop
// if (theface.getFace()!=null){
// LiveMatROI = new Rect(theface.getFace().x(), theface.getFace().y(), theface.getFace().width(), theface.getFace().height());
// }
if (theface.getEyesCount()>=2){
// ada mata (buka mata)
// if (have_eye_counter.incrementAndGet()<5) continue;
// no_eye_counter.set(0);
if (event!=null) event.onEyeDetector(true);
LabelVisible(eye_indicator,true);
// Valid eye condition
if (eye_state.get()!=1){
// transisi dari tutup mata ke buka mata
if (eye_state.get()==-1) {
//System.out.println("First Eye Detected from camera "+title);
eye_state.set(1);
} else {
eye_state.set(1);
blink_counter.incrementAndGet();
if (event!=null) event.onBlink(blink_counter.get());
LabelSetText(BlinkCounterLabel, String.valueOf(blink_counter.get()),null);
long now = System.currentTimeMillis();
if (waiting_for_second_blink.get()){
long diff = now - last_blink.get();
// kalau beda waktu antara blink 1 dan blink 2 kurang dari 3 detik
if (diff<=3000){
//System.out.println("Double Blink Detected from camera "+title);
if (event!=null) event.onDoubleBlink((int)diff);
}
waiting_for_second_blink.set(false);
} else {
waiting_for_second_blink.set(true);
//System.out.println("First Blink Detected from camera "+title);
}
last_blink.set(now);
}
}
} else {
// ada muka, tidak ada mata
// transisi dari buka mata ke tutup mata
// if (no_eye_counter.incrementAndGet()<5) continue;
// have_eye_counter.set(0);
if (event!=null) event.onEyeDetector(false);
LabelVisible(eye_indicator,false);
// Valid no eye condition
if (eye_state.get()!=0){
eye_state.set(0);
}
}
} else if (have_left_45_face ){
if (event!=null) event.onProfileFaceDetector(true, _face_width, _face_height);
LabelVisible(face_indicator,true);
} else {
// no face detected, but let's not cancel the previous state immediately
if (no_face_counter.incrementAndGet()<30) continue;
// beneran dianggap no face detected
eye_state.set(-1);
last_blink.set(0);
waiting_for_second_blink.set(false);
face_counter.set(0);
blink_counter.set(0);
// no_eye_counter.set(0);
// have_eye_counter.set(0);
if (event!=null) {
event.onFrontalFaceDetector(false, _face_width, _face_height);
event.onProfileFaceDetector(false, _face_width, _face_height);
event.onEyeDetector(false);
event.onBlink(blink_counter.get());
LabelSetText(BlinkCounterLabel, "",null);
LabelVisible(face_indicator,false);
LabelVisible(eye_indicator,false);
}
}
if (LiveMat != null && !LiveMat.empty()){
Mat imgmat = new Mat();
// copy back to CPU
LiveMat.copyTo(imgmat);
setCameraStream(MatToImage(imgmat));
imgmat.release();
}
} catch (Exception e) {
//System.out.println(Thread.currentThread().getName()+" interrupted");
}
}
});
private void flush(){
if (mGrabber!=null){
long now = System.currentTimeMillis();
long delta = 0;
while (delta<32){
// flushing stale frames
// 30 fps means 33 ms per frame
// so if grab is quicker than 30 ms , its stale frame
try{
if (mGrabber==null) throw new Exception("Grabber is null");
Frame frame = mGrabber.grab(); // grab frame
delta = System.currentTimeMillis() - now;
now = System.currentTimeMillis();
} catch (Exception ignored) {
}
}
}
}
// Camera Capture Thread
Thread cam_capture = new Thread(()->{
while (Capturing.get()) {
try {
// selama proses pengambilan foto, jangan ambil frame
if (TakingPhoto!=null) {
TakingPhoto.await();
flush();
TakingPhoto = null;
}
IsGrabbingLiveView.drainPermits();
IsGrabbingLiveView.release();
//IsGrabbingLiveView.set(true);
Frame frame = null;
if (Capturing.get()) {
try{
if (mGrabber==null) throw new Exception("Grabber is null");
frame = mGrabber.grab();
} catch (Exception e){
if (Capturing.get()){
// kalau ada exception padahal masih capturing. Kalau sudah tidak capturing, tidak peduli
if (ValidString(e.getMessage())){
String msg = e.getMessage();
System.out.println("Exception on "+Thread.currentThread().getName()+" :"+msg);
if (msg.contains("start() been called")){
if (Capturing.get()){
System.out.println("Camera "+Thread.currentThread().getName()+" has been stopped, restarting");
mGrabber.restart();
} else {
System.out.println("Camera "+Thread.currentThread().getName()+" has been stopped, not restarting");
}
}
}
}
}
}
//IsGrabbingLiveView.set(false);
if (frame==null) continue;
if (frame.image==null) continue;
if (frame.image.length==0) continue;
if (realwidth!=frame.imageWidth || realheight!=frame.imageHeight) {
realwidth = frame.imageWidth;
realheight = frame.imageHeight;
}
Mat mat = matconverter.convert(frame); // convert to Mat
if (mat.empty()) continue;
fps.incrementAndGet();
UMat originalmat = new UMat();
mat.copyTo(originalmat); // copy to originalmat for using OpenCL
if (config.isMirrorCamera()){
// revisi 18/03/2025
UMat flippedmat = new UMat();
opencv_core.flip(originalmat, flippedmat, 0); // flip vertical
flippedmat.copyTo(originalmat);
flippedmat.release();
}
if (config.isFlipCamera()){
// revisi 18/03/2025
UMat flippedmat = new UMat();
opencv_core.flip(originalmat, flippedmat, 1); // flip horizontal
flippedmat.copyTo(originalmat);
flippedmat.release();
}
// rotate 90 degree counter clockwise karena kamera potrait
opencv_core.rotate(originalmat, BestMat, opencv_core.ROTATE_90_COUNTERCLOCKWISE);
originalmat.release();
if (!BestMat.empty()) {
// LiveMat and GrayMat are synchronized
synchronized (lockObject){
// resize BestMat to LiveSize
UMat _liveMat = new UMat();
opencv_imgproc.resize(BestMat, _liveMat, LiveSize);
// crop LiveMat
UMat croppedLiveMat = CropMat(_liveMat);
if (croppedLiveMat!=null && !croppedLiveMat.empty()){
croppedLiveMat.copyTo(LiveMat);
croppedLiveMat.release();
} else {
_liveMat.copyTo(LiveMat);
_liveMat.release();
}
// convert to grayscale
Mat xx = new Mat();
LiveMat.copyTo(xx);
opencv_imgproc.cvtColor(xx,GrayMat, COLOR_BGR2GRAY);
}
if (use_qr){
qr_semaphore.release();
}
if (use_face){
face_semaphore.release();
}
}
} catch ( FrameGrabber.Exception fe){
System.out.println("FrameGrabber Exception in" + Thread.currentThread().getName() + " : " + fe.getMessage());
//fe.printStackTrace();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()+" interrupted");
} catch (Exception e){
System.out.println(Thread.currentThread().getName()+" exception : "+e.getMessage());
//e.printStackTrace();
}
}
});
private UMat CropMat(UMat mat){
if (mat != null && !mat.empty()){
int width = mat.cols();
int height = mat.rows();
int x = (int) (width * leftcrop / 100);
int y = (int) (height * topcrop / 100);
int w = (int) (width * (100 - leftcrop - rightcrop) / 100);
int h = (int) (height * (100 - topcrop - bottomcrop) / 100);
// Ensure the crop dimensions are valid
if (x >= 0 && y >= 0 && w > 0 && h > 0 && x + w <= width && y + h <= height) {
Rect roi = new Rect(x, y, w, h);
UMat crop = new UMat();
mat.apply(roi).copyTo(crop);
return crop;
}
}
return null;
}
public void ChangeCropValue(){
if ("01".equals(title)){
topcrop = config.getCam1TopCrop();
leftcrop = config.getCam1LeftCrop();
rightcrop = config.getCam1RightCrop();
bottomcrop = config.getCam1BottomCrop();
} else if ("02".equals(title)){
topcrop = config.getCam2TopCrop();
leftcrop = config.getCam2LeftCrop();
rightcrop = config.getCam2RightCrop();
bottomcrop = config.getCam2BottomCrop();
} else if ("03".equals(title)){
topcrop = config.getCam3TopCrop();
leftcrop = config.getCam3LeftCrop();
rightcrop = config.getCam3RightCrop();
bottomcrop = config.getCam3BottomCrop();
} else if ("04".equals(title)){
topcrop = config.getCam4TopCrop();
leftcrop = config.getCam4LeftCrop();
rightcrop = config.getCam4RightCrop();
bottomcrop = config.getCam4BottomCrop();
} else if ("05".equals(title)){
topcrop = config.getCam5TopCrop();
leftcrop = config.getCam5LeftCrop();
rightcrop = config.getCam5RightCrop();
bottomcrop = config.getCam5BottomCrop();
}
}
public boolean StartLiveView(LiveCamEvent event, String cameratitle, boolean use_qr , boolean use_face) {
this.event = event;
this.title = cameratitle;
ChangeCropValue();
if (mGrabber != null) {
try {
if (use_qr) raise_log("QR Reader loaded");
if (use_face) raise_log("Face detector loaded");
// capture with best resolution
if (IsPortrait){
setFrameHeight(BestSize.width());
setFrameWidth(BestSize.height());
} else {
setFrameHeight(BestSize.height());
setFrameWidth(BestSize.width());
}
LiveFPS = 0;
mGrabber.start();
System.out.println("Camera "+cameratitle+" started");
Capturing.set(true);
if (event!=null) event.onStartCapturing();
TimerTask fpsTask = new TimerTask() {
@Override
public void run() {
if (Capturing.get()){
int fpsval = fps.getAndSet(0);
if (fpsval!=LiveFPS){
LiveFPS = fpsval;
if (event!=null) event.onIntervalUpdate();
AutoCloseAlert.ChangeCamStatus(switch (cameratitle){
case "01" -> 1;
case "02" -> 2;
case "03" -> 3;
case "04" -> 4;
case "05" -> 5;
default -> 0;
}, LiveFPS>0 );
}
} else {
fps.set(0);
this.cancel();
}
}
};
timer.scheduleAtFixedRate(fpsTask, 1000, 1000);
this.use_qr = use_qr;
this.use_face = use_face;
cam_capture.setName("cam_capture "+cameratitle);
cam_capture.setDaemon(true);
cam_capture.start();
//System.out.println("Starting cam_capture thread");
qr_detect.setName("qr_detect "+cameratitle);
qr_detect.setDaemon(true);
qr_detect.start();
//System.out.println("Starting qr_detect thread");
face_detect.setName("face_detect "+cameratitle);
face_detect.setDaemon(true);
face_detect.start();
//System.out.println("Starting face_detect thread");
return true;
} catch (Exception e) {
raise_log("StartLiveView failed, Unable to Start Camera, Error: " + e.getMessage());
}
} else raise_log("StartLiveView failed, grabber is null");
return false;
}
// /**
// * Remap LiveMatROI to BestMatROI and ReducedMatROI Resolution
// * @param scaleX scale factor for width
// * @param scaleY scale factor for height
// */
// public void RemapROI(double scaleX, double scaleY, boolean printdebug){
// BestMatROI = null;
// ReducedMatROI = null;
// if (ValidROI(LiveMatROI)){
// if (ROIInsideUMat(LiveMatROI, LiveMat)){
// if (printdebug) System.out.println("LiveMatROI camera "+cameratitle.getText()+" = "+RectToString(LiveMatROI));
//
// double scaleXBest = 1.0*BestSize.width()/LiveSize.width();
// double scaleYBest = 1.0*BestSize.height()/LiveSize.height();
// int XBest = (int) (LiveMatROI.x()*scaleXBest);
// int YBest = (int) (LiveMatROI.y()*scaleYBest);
// int WBest = (int) (LiveMatROI.width()*scaleXBest);
// int HBest = (int) (LiveMatROI.height()*scaleYBest);
// int deltaWBest = (int) (BestSize.width() * scaleX);
// int deltaHBest = (int) (BestSize.height() * scaleY);
// XBest = XBest - deltaWBest/2;
// if (XBest<0) XBest = 0;
// YBest = YBest - deltaHBest/2;
// if (YBest<0) YBest = 0;
// WBest = WBest + deltaWBest;
// if (WBest>BestSize.width()) WBest = BestSize.width();
// HBest = HBest + deltaHBest;
// if (HBest>BestSize.height()) HBest = BestSize.height();
// BestMatROI = new Rect(XBest, YBest, WBest, HBest);
//
// if (printdebug){
// System.out.println("scaleXBest = "+scaleXBest+" scaleYBest = "+scaleYBest);
// System.out.println("BestMatROI camera "+cameratitle.getText()+" = "+RectToString(BestMatROI));
// }
//
// double scaleXReduced = 1.0*ReducedSize.width()/LiveSize.width();
// double scaleYReduced = 1.0*ReducedSize.height()/LiveSize.height();
// int XReduced = (int) (LiveMatROI.x()*scaleXReduced);
// int YReduced = (int) (LiveMatROI.y()*scaleYReduced);
// int WReduced = (int) (LiveMatROI.width()*scaleXReduced);
// int HReduced = (int) (LiveMatROI.height()*scaleYReduced);
// int deltaWReduced = (int) (ReducedSize.width() * scaleX);
// int deltaHReduced = (int) (ReducedSize.height() * scaleY);
// XReduced = XReduced - deltaWReduced/2;
// if (XReduced<0) XReduced = 0;
// YReduced = YReduced - deltaHReduced/2;
// if (YReduced<0) YReduced = 0;
// WReduced = WReduced + deltaWReduced;
// if (WReduced>ReducedSize.width()) WReduced = ReducedSize.width();
// HReduced = HReduced + deltaHReduced;
// if (HReduced>ReducedSize.height()) HReduced = ReducedSize.height();
// ReducedMatROI = new Rect(XReduced, YReduced, WReduced, HReduced);
// if (printdebug){
// System.out.println("scaleXReduced = "+scaleXReduced+" scaleYReduced = "+scaleYReduced);
// System.out.println("ReducedMatROI camera "+cameratitle.getText()+" = "+RectToString(ReducedMatROI));
// }
// }
// }
//
// }
/**
* Detect QR Code from Mat
* @param mat Mat in Gray Scale
* @return QR Code Text, or null if not detected
*/
private String DetectQRFromMat(Mat mat){
if (qrreader!=null){
if (mat!=null && !mat.empty()){
BufferedImage bufferedImage = matToBufferedImage(mat);
BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(new BufferedImageLuminanceSource(bufferedImage)));
try{
Result result = qrreader.decode(binaryBitmap);
if (result!=null){
return result.getText();
}
} catch (Exception ignored) {
}
mat.release();
}
}
return null;
}
private void raise_log(String msg){
if (event!=null) event.onLog(msg);
}
}