diff --git a/.gitignore b/.gitignore index b63da45..bf3e1b2 100644 --- a/.gitignore +++ b/.gitignore @@ -39,4 +39,5 @@ bin/ .vscode/ ### Mac OS ### -.DS_Store \ No newline at end of file +.DS_Store +/.idea/ diff --git a/src/main/java/org/jdetect/CameraApp.java b/src/main/java/org/jdetect/CameraApp.java index 1b1a82f..64bfb31 100644 --- a/src/main/java/org/jdetect/CameraApp.java +++ b/src/main/java/org/jdetect/CameraApp.java @@ -1,5 +1,20 @@ package org.jdetect; +import java.awt.*; +import java.awt.event.*; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferByte; +import java.awt.image.DataBufferInt; +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; import org.opencv.core.*; import org.opencv.core.Point; import org.opencv.dnn.Dnn; @@ -7,454 +22,119 @@ import org.opencv.dnn.Net; import org.opencv.imgcodecs.Imgcodecs; import org.opencv.imgproc.Imgproc; import org.opencv.videoio.VideoCapture; -import org.opencv.videoio.VideoWriter; import org.opencv.videoio.Videoio; -import javax.swing.*; -import javax.swing.border.EmptyBorder; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.awt.image.BufferedImage; -import java.awt.image.DataBufferByte; -import java.io.*; -import java.net.HttpURLConnection; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicLong; - -// Custom class to hold camera information -class CameraInfo { - public int index; - public int backend; - public String name; - - public CameraInfo(int index, int backend, String name) { - this.index = index; - this.backend = backend; - this.name = name; - } - - @Override - public String toString() { - return name; - } -} - -// Download progress dialog class -class DownloadProgressBar extends JDialog { - private JLabel label; - private JProgressBar progress; - private JButton cancelButton; - private AtomicBoolean cancelled = new AtomicBoolean(false); - - public DownloadProgressBar(JFrame parent) { - super(parent, "Downloading YOLO Files", true); - setSize(400, 150); - setLocationRelativeTo(parent); - setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); - - JPanel panel = new JPanel(new BorderLayout(10, 10)); - panel.setBorder(new EmptyBorder(20, 20, 20, 20)); - - label = new JLabel("Downloading YOLO files..."); - panel.add(label, BorderLayout.NORTH); - - progress = new JProgressBar(0, 100); - progress.setStringPainted(true); - panel.add(progress, BorderLayout.CENTER); - - cancelButton = new JButton("Cancel"); - cancelButton.addActionListener(e -> { - cancelled.set(true); - dispose(); - }); - - JPanel buttonPanel = new JPanel(); - buttonPanel.add(cancelButton); - panel.add(buttonPanel, BorderLayout.SOUTH); - - add(panel); - } - - public void updateProgress(long current, long total) { - SwingUtilities.invokeLater(() -> { - int percentage = (int) ((current * 100) / total); - progress.setValue(percentage); - progress.setString(percentage + "%"); - }); - } - - public void updateLabel(String filename) { - SwingUtilities.invokeLater(() -> { - label.setText("Downloading " + filename + "..."); - }); - } - - public boolean isCancelled() { - return cancelled.get(); - } -} - -// Detection thread class -class DetectionThread extends Thread { - private Net net; - private List classes; - private List outputLayers; - private Mat frame; - private AtomicBoolean running = new AtomicBoolean(true); - private CompletableFuture detectionDone; - - public DetectionThread(Net net, List classes, List outputLayers, Mat frame) { - this.net = net; - this.classes = classes; - this.outputLayers = outputLayers; - this.frame = frame.clone(); - this.detectionDone = new CompletableFuture<>(); - } - - @Override - public void run() { - if (!running.get() || net == null || classes == null) { - detectionDone.complete(frame); - return; - } - - try { - int height = frame.rows(); - int width = frame.cols(); - - // Create blob from image - Mat blob = Dnn.blobFromImage(frame, 1.0/255.0, new Size(416, 416), new Scalar(0), true, false); - - // Set input to the network - net.setInput(blob); - - // Run forward pass - List outs = new ArrayList<>(); - net.forward(outs, outputLayers); - - List classIds = new ArrayList<>(); - List confidences = new ArrayList<>(); - List boxes = new ArrayList<>(); - - // Process detections - for (Mat out : outs) { - for (int i = 0; i < out.rows(); ++i) { - Mat row = out.row(i); - Mat scores = row.colRange(5, out.cols()); - Core.MinMaxLocResult mm = Core.minMaxLoc(scores); - float confidence = (float) mm.maxVal; - Point classIdPoint = mm.maxLoc; - - if (confidence > 0.5) { - double[] detection = row.get(0, 0); - double centerX = detection[0] * width; - double centerY = detection[1] * height; - double boxWidth = detection[2] * width; - double boxHeight = detection[3] * height; - - double x = centerX - (boxWidth / 2); - double y = centerY - (boxHeight / 2); - - boxes.add(new Rect2d(x, y, boxWidth, boxHeight)); - confidences.add(confidence); - classIds.add((int) classIdPoint.x); - } - } - } - - // Apply Non-Maximum Suppression - MatOfFloat confidencesMat = new MatOfFloat(); - confidencesMat.fromList(confidences); - - MatOfRect2d boxesMat = new MatOfRect2d(); - boxesMat.fromArray(boxes.toArray(new Rect2d[0])); - - MatOfInt indices = new MatOfInt(); - Dnn.NMSBoxes(boxesMat, confidencesMat, 0.5f, 0.4f, indices); - - // Draw bounding boxes - int[] indicesArray = indices.toArray(); - for (int i = 0; i < indicesArray.length; ++i) { - int idx = indicesArray[i]; - Rect2d box = boxes.get(idx); - String label = classes.get(classIds.get(idx)); - float confidence = confidences.get(idx); - - Scalar color = new Scalar(0, 255, 0); - Point topLeft = new Point(box.x, box.y); - Point bottomRight = new Point(box.x + box.width, box.y + box.height); - - Imgproc.rectangle(frame, topLeft, bottomRight, color, 2); - - String text = String.format("%s: %.2f", label, confidence); - Point textPoint = new Point(box.x, box.y - 5); - Imgproc.putText(frame, text, textPoint, Imgproc.FONT_HERSHEY_SIMPLEX, 0.5, color, 2); - } - - detectionDone.complete(frame); - - } catch (Exception e) { - e.printStackTrace(); - detectionDone.complete(frame); - } - } - - public void stopDetection() { - running.set(false); - try { - this.join(1000); // Wait up to 1 second - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - public CompletableFuture getDetectionResult() { - return detectionDone; - } -} - -// Main camera application class public class CameraApp extends JFrame { - // UI Components + static { + // Load OpenCV native library + nu.pattern.OpenCV.loadLocally(); + } + + // GUI Components private JLabel videoLabel; private JComboBox cameraSelect; private JCheckBox detectionCheckbox; - private JButton startButton; - private JButton stopButton; - private JButton snapshotButton; private JLabel statusLabel; - private JPanel cameraControlPanel; - private JSlider brightnessSlider; - private JSlider contrastSlider; - private JSlider saturationSlider; // Camera and detection attributes - private int cameraIndex = 0; + private int cameraIndex = 1; private VideoCapture cap; private Timer timer; - private List availableCameras; + private final List availableCameras; private DetectionThread detectionThread; private Net net; private List classes; private List outputLayers; - private AtomicBoolean detectionEnabled = new AtomicBoolean(true); - private AtomicBoolean modelLoaded = new AtomicBoolean(false); - private double fps = 0; + private boolean detectionEnabled = true; + private boolean modelLoaded = false; private int frameCount = 0; private long lastFpsUpdate = System.currentTimeMillis(); - private ExecutorService executorService; - - // Load OpenCV native library - static { - nu.pattern.OpenCV.loadLocally(); - } public CameraApp() { - super("Advanced Object Detection Camera Viewer"); + setTitle("Advanced Object Detection Camera Viewer"); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setBounds(100, 100, 1000, 800); // Initialize attributes availableCameras = new ArrayList<>(); - executorService = Executors.newCachedThreadPool(); // Initialize UI initUI(); // Load YOLO after UI is ready - SwingUtilities.invokeLater(() -> { - Timer loadTimer = new Timer(100, e -> { - loadYolo(); - ((Timer) e.getSource()).stop(); - }); - loadTimer.setRepeats(false); - loadTimer.start(); - }); + SwingUtilities.invokeLater(this::loadYolo); } private void initUI() { - setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - setSize(1200, 900); - setLocationRelativeTo(null); + Container contentPane = getContentPane(); + contentPane.setLayout(new BorderLayout()); // Main panel - JPanel mainPanel = new JPanel(new BorderLayout(10, 10)); - mainPanel.setBorder(new EmptyBorder(10, 10, 10, 10)); + JPanel mainPanel = new JPanel(new BorderLayout()); // Video display videoLabel = new JLabel(); - videoLabel.setHorizontalAlignment(JLabel.CENTER); - videoLabel.setVerticalAlignment(JLabel.CENTER); - videoLabel.setPreferredSize(new Dimension(800, 600)); + videoLabel.setHorizontalAlignment(SwingConstants.CENTER); + videoLabel.setVerticalAlignment(SwingConstants.CENTER); + videoLabel.setPreferredSize(new Dimension(640, 480)); videoLabel.setBorder(BorderFactory.createLoweredBevelBorder()); - videoLabel.setText("Camera feed will appear here"); mainPanel.add(videoLabel, BorderLayout.CENTER); - // Control panel - JPanel controlPanel = new JPanel(new BorderLayout()); - - // Basic controls - JPanel basicControlPanel = new JPanel(new GridBagLayout()); + // Controls panel + JPanel controlsPanel = new JPanel(new GridBagLayout()); GridBagConstraints gbc = new GridBagConstraints(); gbc.insets = new Insets(5, 5, 5, 5); gbc.fill = GridBagConstraints.HORIZONTAL; // Camera selection - gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 2; + gbc.gridx = 0; gbc.gridy = 0; + controlsPanel.add(new JLabel("Camera:"), gbc); + gbc.gridx = 1; cameraSelect = new JComboBox<>(); - cameraSelect.addActionListener(this::changeCamera); - basicControlPanel.add(cameraSelect, gbc); + cameraSelect.addActionListener(_ -> changeCamera()); + controlsPanel.add(cameraSelect, gbc); // Detection checkbox gbc.gridx = 0; gbc.gridy = 1; gbc.gridwidth = 2; detectionCheckbox = new JCheckBox("Enable Object Detection", true); - detectionCheckbox.addActionListener(this::toggleDetection); - basicControlPanel.add(detectionCheckbox, gbc); + detectionCheckbox.addActionListener(_ -> toggleDetection()); + controlsPanel.add(detectionCheckbox, gbc); // Buttons gbc.gridwidth = 1; gbc.gridx = 0; gbc.gridy = 2; - startButton = new JButton("Start Camera"); - startButton.addActionListener(e -> startCamera()); - basicControlPanel.add(startButton, gbc); + JButton startButton = new JButton("Start Camera"); + startButton.addActionListener(_ -> startCamera()); + controlsPanel.add(startButton, gbc); - gbc.gridx = 1; gbc.gridy = 2; - stopButton = new JButton("Stop Camera"); - stopButton.addActionListener(e -> stopCamera()); - basicControlPanel.add(stopButton, gbc); + gbc.gridx = 1; + JButton stopButton = new JButton("Stop Camera"); + stopButton.addActionListener(_ -> stopCamera()); + controlsPanel.add(stopButton, gbc); gbc.gridx = 0; gbc.gridy = 3; gbc.gridwidth = 2; - snapshotButton = new JButton("Take Snapshot"); - snapshotButton.addActionListener(e -> takeSnapshot()); - basicControlPanel.add(snapshotButton, gbc); + JButton snapshotButton = new JButton("Take Snapshot"); + snapshotButton.addActionListener(_ -> takeSnapshot()); + controlsPanel.add(snapshotButton, gbc); - controlPanel.add(basicControlPanel, BorderLayout.NORTH); - - // Camera controls panel for C920 - cameraControlPanel = createCameraControlPanel(); - controlPanel.add(cameraControlPanel, BorderLayout.CENTER); - - mainPanel.add(controlPanel, BorderLayout.EAST); + mainPanel.add(controlsPanel, BorderLayout.SOUTH); + contentPane.add(mainPanel, BorderLayout.CENTER); // Status bar statusLabel = new JLabel("Initializing..."); - statusLabel.setBorder(new EmptyBorder(5, 10, 5, 10)); - mainPanel.add(statusLabel, BorderLayout.SOUTH); - - add(mainPanel); + statusLabel.setBorder(BorderFactory.createLoweredBevelBorder()); + contentPane.add(statusLabel, BorderLayout.SOUTH); // Set up timer - timer = new Timer(30, this::updateFrame); + timer = new Timer(30, _ -> updateFrame()); // Detect cameras after UI is ready - SwingUtilities.invokeLater(() -> { - Timer detectTimer = new Timer(100, e -> { - detectCameras(); - ((Timer) e.getSource()).stop(); - }); - detectTimer.setRepeats(false); - detectTimer.start(); - }); - - // Window close event - addWindowListener(new WindowAdapter() { - @Override - public void windowClosing(WindowEvent e) { - closeEvent(); - } - }); + SwingUtilities.invokeLater(this::detectCameras); } - private JPanel createCameraControlPanel() { - JPanel panel = new JPanel(new GridBagLayout()); - panel.setBorder(BorderFactory.createTitledBorder("Camera Controls")); - GridBagConstraints gbc = new GridBagConstraints(); - gbc.insets = new Insets(5, 5, 5, 5); - gbc.fill = GridBagConstraints.HORIZONTAL; - - // Brightness control - gbc.gridx = 0; gbc.gridy = 0; - panel.add(new JLabel("Brightness:"), gbc); - gbc.gridx = 1; gbc.gridy = 0; - brightnessSlider = new JSlider(-100, 100, 0); - brightnessSlider.addChangeListener(e -> adjustCameraSettings()); - panel.add(brightnessSlider, gbc); - - // Contrast control - gbc.gridx = 0; gbc.gridy = 1; - panel.add(new JLabel("Contrast:"), gbc); - gbc.gridx = 1; gbc.gridy = 1; - contrastSlider = new JSlider(0, 200, 100); - contrastSlider.addChangeListener(e -> adjustCameraSettings()); - panel.add(contrastSlider, gbc); - - // Saturation control - gbc.gridx = 0; gbc.gridy = 2; - panel.add(new JLabel("Saturation:"), gbc); - gbc.gridx = 1; gbc.gridy = 2; - saturationSlider = new JSlider(0, 200, 100); - saturationSlider.addChangeListener(e -> adjustCameraSettings()); - panel.add(saturationSlider, gbc); - - // Reset button - gbc.gridx = 0; gbc.gridy = 3; gbc.gridwidth = 2; - JButton resetButton = new JButton("Reset to Default"); - resetButton.addActionListener(e -> resetCameraSettings()); - panel.add(resetButton, gbc); - - return panel; - } - - private void adjustCameraSettings() { - if (cap == null || !cap.isOpened()) { - return; - } - - try { - // Apply brightness (-100 to 100, normalized to 0.0-1.0) - double brightness = (brightnessSlider.getValue() + 100) / 200.0; - cap.set(Videoio.CAP_PROP_BRIGHTNESS, brightness); - - // Apply contrast (0 to 200, normalized to 0.0-2.0) - double contrast = contrastSlider.getValue() / 100.0; - cap.set(Videoio.CAP_PROP_CONTRAST, contrast); - - // Apply saturation (0 to 200, normalized to 0.0-2.0) - double saturation = saturationSlider.getValue() / 100.0; - cap.set(Videoio.CAP_PROP_SATURATION, saturation); - - // Additional C920 specific settings - cap.set(Videoio.CAP_PROP_AUTO_WB, 1); // Enable auto white balance - cap.set(Videoio.CAP_PROP_EXPOSURE, -5); // Set exposure for better indoor lighting - - statusLabel.setText("Camera settings adjusted"); - - } catch (Exception e) { - System.out.println("Warning: Could not adjust all camera settings: " + e.getMessage()); - } - } - - private void resetCameraSettings() { - brightnessSlider.setValue(0); - contrastSlider.setValue(100); - saturationSlider.setValue(100); - adjustCameraSettings(); - } - - private void toggleDetection(ActionEvent e) { - detectionEnabled.set(detectionCheckbox.isSelected()); - statusLabel.setText("Detection " + (detectionEnabled.get() ? "enabled" : "disabled")); + private void toggleDetection() { + detectionEnabled = detectionCheckbox.isSelected(); + statusLabel.setText("Detection " + (detectionEnabled ? "enabled" : "disabled")); } private void detectCameras() { @@ -462,34 +142,29 @@ public class CameraApp extends JFrame { availableCameras.clear(); // Check for available cameras - int[] backends = {Videoio.CAP_DSHOW, Videoio.CAP_V4L2, Videoio.CAP_ANY}; int maxCamerasToCheck = 10; - for (int backend : backends) { - for (int i = 0; i < maxCamerasToCheck; i++) { - try { - VideoCapture testCap = new VideoCapture(i, backend); - if (testCap.isOpened()) { - // Get camera properties - int width = (int) testCap.get(Videoio.CAP_PROP_FRAME_WIDTH); - int height = (int) testCap.get(Videoio.CAP_PROP_FRAME_HEIGHT); - double fps = testCap.get(Videoio.CAP_PROP_FPS); + for (int i = 0; i < maxCamerasToCheck; i++) { + try { + VideoCapture testCap = new VideoCapture(i); + if (testCap.isOpened()) { + // Get camera properties + int width = (int) testCap.get(Videoio.CAP_PROP_FRAME_WIDTH); + int height = (int) testCap.get(Videoio.CAP_PROP_FRAME_HEIGHT); + double fps = testCap.get(Videoio.CAP_PROP_FPS); - String name = String.format("Camera %d (%dx%d)", i, width, height); - if (fps > 0) { - name += String.format(" @ %.1fFPS", fps); - } - - CameraInfo camInfo = new CameraInfo(i, backend, name); - availableCameras.add(camInfo); - cameraSelect.addItem(camInfo); - testCap.release(); - break; // Only add once per camera index + String name = String.format("Camera %d (%dx%d)", i, width, height); + if (fps > 0) { + name += String.format(" @ %.1fFPS", fps); } + + CameraInfo cameraInfo = new CameraInfo(i, name); + availableCameras.add(cameraInfo); + cameraSelect.addItem(cameraInfo); testCap.release(); - } catch (Exception ex) { - // Ignore errors for non-existent cameras } + } catch (Exception e) { + // Ignore errors for non-existent cameras } } @@ -501,10 +176,10 @@ public class CameraApp extends JFrame { } } - private void changeCamera(ActionEvent e) { + private void changeCamera() { CameraInfo selected = (CameraInfo) cameraSelect.getSelectedItem(); if (selected != null) { - cameraIndex = selected.index; + cameraIndex = selected.index(); if (cap != null && cap.isOpened()) { stopCamera(); startCamera(); @@ -524,62 +199,33 @@ public class CameraApp extends JFrame { try { CameraInfo selected = (CameraInfo) cameraSelect.getSelectedItem(); - if (selected == null) { - selected = availableCameras.get(0); - } - - // Try to open with default backend first - cap = new VideoCapture(selected.index); - if (!cap.isOpened()) { - // Fall back to selected backend if default fails - cap = new VideoCapture(selected.index, selected.backend); + if (selected != null) { + cameraIndex = selected.index(); } + cap = new VideoCapture(cameraIndex); if (!cap.isOpened()) { JOptionPane.showMessageDialog(this, - String.format("Cannot open camera %d", selected.index), + String.format("Cannot open camera %d", cameraIndex), "Error", JOptionPane.ERROR_MESSAGE); - statusLabel.setText(String.format("Failed to open camera %d", selected.index)); + statusLabel.setText(String.format("Failed to open camera %d", cameraIndex)); return; } - // Enhanced camera settings for C920 - try { - // Set resolution - C920 supports up to 1920x1080 - cap.set(Videoio.CAP_PROP_FRAME_WIDTH, 1280); - cap.set(Videoio.CAP_PROP_FRAME_HEIGHT, 720); - cap.set(Videoio.CAP_PROP_FPS, 30); - - // C920 specific optimizations - cap.set(Videoio.CAP_PROP_FOURCC, VideoWriter.fourcc('M', 'J', 'P', 'G')); - cap.set(Videoio.CAP_PROP_AUTOFOCUS, 1); // Enable autofocus - cap.set(Videoio.CAP_PROP_AUTO_WB, 1); // Auto white balance - cap.set(Videoio.CAP_PROP_AUTO_EXPOSURE, 0.25); // Some auto exposure - cap.set(Videoio.CAP_PROP_EXPOSURE, -5); // Manual exposure for indoor - cap.set(Videoio.CAP_PROP_GAIN, 0); // Minimize gain to reduce noise - - // Apply current slider settings - adjustCameraSettings(); - - } catch (Exception e) { - System.out.println("Warning: Could not set all camera properties: " + e.getMessage()); - } - - // Get actual settings that were applied - double actualWidth = cap.get(Videoio.CAP_PROP_FRAME_WIDTH); - double actualHeight = cap.get(Videoio.CAP_PROP_FRAME_HEIGHT); - double actualFps = cap.get(Videoio.CAP_PROP_FPS); - - statusLabel.setText(String.format("Camera %d started (%dx%d @ %.1f FPS)", - selected.index, (int)actualWidth, (int)actualHeight, actualFps)); + // Set camera properties + cap.set(Videoio.CAP_PROP_FRAME_WIDTH, 1280); + cap.set(Videoio.CAP_PROP_FRAME_HEIGHT, 720); + cap.set(Videoio.CAP_PROP_FPS, 60); + cap.set(Videoio.CAP_PROP_AUTOFOCUS, 0); timer.start(); + statusLabel.setText(String.format("Camera %d started", cameraIndex)); - } catch (Exception ex) { + } catch (Exception e) { JOptionPane.showMessageDialog(this, - "Error starting camera: " + ex.getMessage(), + String.format("Error starting camera: %s", e.getMessage()), "Error", JOptionPane.ERROR_MESSAGE); - statusLabel.setText("Error: " + ex.getMessage()); + statusLabel.setText(String.format("Error: %s", e.getMessage())); } } @@ -589,270 +235,89 @@ public class CameraApp extends JFrame { cap.release(); cap = null; videoLabel.setIcon(null); - videoLabel.setText("Camera feed will appear here"); statusLabel.setText("Camera stopped"); } if (detectionThread != null) { - detectionThread.stopDetection(); + detectionThread.stop(); detectionThread = null; } } - private void updateFrame(ActionEvent e) { - if (cap == null || !cap.isOpened()) { - return; - } - - Mat frame = new Mat(); - if (cap.read(frame) && !frame.empty()) { - // Update FPS counter - frameCount++; - long currentTime = System.currentTimeMillis(); - if (currentTime - lastFpsUpdate >= 1000) { - fps = frameCount * 1000.0 / (currentTime - lastFpsUpdate); - frameCount = 0; - lastFpsUpdate = currentTime; - } - - // Process frame with detection if enabled - if (detectionEnabled.get() && modelLoaded.get()) { - processWithDetection(frame); - } else { - displayFrame(frame); - } - } - } - - private void processWithDetection(Mat frame) { - // Stop previous detection if still running - if (detectionThread != null && detectionThread.isAlive()) { - detectionThread.stopDetection(); - } - - // Start new detection - detectionThread = new DetectionThread(net, classes, outputLayers, frame); - detectionThread.start(); - - // Get result asynchronously - detectionThread.getDetectionResult().thenAccept(this::displayFrame); - } - - private void displayFrame(Mat frame) { - if (frame == null || frame.empty()) { - return; - } - - try { - // Add FPS text to frame - String fpsText = String.format("FPS: %.1f", fps); - Point textPos = new Point(10, 30); - Imgproc.putText(frame, fpsText, textPos, Imgproc.FONT_HERSHEY_SIMPLEX, 1, new Scalar(0, 255, 0), 2); - - // Convert Mat to BufferedImage - BufferedImage image = matToBufferedImage(frame); - - // Scale image to fit label while maintaining aspect ratio - Dimension labelSize = videoLabel.getSize(); - if (labelSize.width > 0 && labelSize.height > 0) { - Image scaledImage = image.getScaledInstance(labelSize.width, labelSize.height, Image.SCALE_SMOOTH); - ImageIcon icon = new ImageIcon(scaledImage); - - SwingUtilities.invokeLater(() -> videoLabel.setIcon(icon)); - } - } catch (Exception ex) { - System.out.println("Error displaying frame: " + ex.getMessage()); - } - } - - private BufferedImage matToBufferedImage(Mat mat) { - int type = BufferedImage.TYPE_BYTE_GRAY; - if (mat.channels() > 1) { - type = BufferedImage.TYPE_3BYTE_BGR; - } - - int bufferSize = mat.channels() * mat.cols() * mat.rows(); - byte[] buffer = new byte[bufferSize]; - mat.get(0, 0, buffer); // get all pixels - - BufferedImage image = new BufferedImage(mat.cols(), mat.rows(), type); - final byte[] targetPixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData(); - System.arraycopy(buffer, 0, targetPixels, 0, buffer.length); - - return image; - } - - private void takeSnapshot() { - if (cap == null || !cap.isOpened()) { - JOptionPane.showMessageDialog(this, "Camera is not active", "Error", JOptionPane.ERROR_MESSAGE); - return; - } - - Mat frame = new Mat(); - if (cap.read(frame) && !frame.empty()) { - try { - // Create snapshots directory if it doesn't exist - File snapshotDir = new File("snapshots"); - if (!snapshotDir.exists()) { - snapshotDir.mkdirs(); - } - - // Generate filename with timestamp - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss"); - String timestamp = sdf.format(new Date()); - String filename = String.format("snapshots/snapshot_%s.jpg", timestamp); - - // Save image - Imgcodecs.imwrite(filename, frame); - statusLabel.setText("Snapshot saved: " + filename); - - JOptionPane.showMessageDialog(this, - "Snapshot saved successfully!\n" + filename, - "Snapshot", JOptionPane.INFORMATION_MESSAGE); - - } catch (Exception ex) { - JOptionPane.showMessageDialog(this, - "Error saving snapshot: " + ex.getMessage(), - "Error", JOptionPane.ERROR_MESSAGE); - } - } - } - - private boolean loadYoloFromResources() { - try { - // Check if files exist in resources - InputStream weightsStream = getClass().getResourceAsStream("/yolov4-tiny.weights"); - InputStream configStream = getClass().getResourceAsStream("/yolov4-tiny.cfg"); - InputStream classesStream = getClass().getResourceAsStream("/coco.names"); - - if (weightsStream != null && configStream != null && classesStream != null) { - statusLabel.setText("Loading YOLO from resources..."); - - // Copy files from resources to working directory - Files.copy(weightsStream, Paths.get("yolov4-tiny.weights"), StandardCopyOption.REPLACE_EXISTING); - Files.copy(configStream, Paths.get("yolov4-tiny.cfg"), StandardCopyOption.REPLACE_EXISTING); - Files.copy(classesStream, Paths.get("coco.names"), StandardCopyOption.REPLACE_EXISTING); - - weightsStream.close(); - configStream.close(); - classesStream.close(); - - statusLabel.setText("YOLO files loaded from resources"); - return true; - } - } catch (Exception e) { - System.out.println("Could not load YOLO from resources: " + e.getMessage()); - } - return false; - } - private void loadYolo() { - // First try to load from resources - if (loadYoloFromResources()) { - initializeYoloModel(); - return; - } - - // If not in resources, check existing files - String weightsPath = "yolov4-tiny.weights"; - String configPath = "yolov4-tiny.cfg"; - String classesPath = "coco.names"; - - boolean filesValid = true; - - // Check if files exist and have minimum sizes - if (!Files.exists(Paths.get(weightsPath)) || getFileSize(weightsPath) < 10000000) { - filesValid = false; - } - if (!Files.exists(Paths.get(configPath)) || getFileSize(configPath) < 10000) { - filesValid = false; - } - if (!Files.exists(Paths.get(classesPath)) || getFileSize(classesPath) < 1000) { - filesValid = false; - } - - if (filesValid) { - initializeYoloModel(); - return; - } - - // If files don't exist, offer to download - int reply = JOptionPane.showConfirmDialog(this, - "YOLO model files need to be downloaded (~25MB). Continue?", - "Download Files", - JOptionPane.YES_NO_OPTION); - - if (reply != JOptionPane.YES_OPTION) { - statusLabel.setText("Object detection disabled - model not loaded"); - detectionCheckbox.setSelected(false); - detectionEnabled.set(false); - return; - } - - // Download files String weightsUrl = "https://github.com/AlexeyAB/darknet/releases/download/darknet_yolo_v4_pre/yolov4-tiny.weights"; String configUrl = "https://raw.githubusercontent.com/AlexeyAB/darknet/master/cfg/yolov4-tiny.cfg"; String classesUrl = "https://raw.githubusercontent.com/AlexeyAB/darknet/master/data/coco.names"; - downloadYoloFiles(weightsUrl, configUrl, classesUrl); - } + String weightsPath = "./yolov4-tiny.weights"; + String configPath = "./yolov4-tiny.cfg"; + String classesPath = "./coco.names"; - private void initializeYoloModel() { + boolean filesValid = true; try { - String weightsPath = "yolov4-tiny.weights"; - String configPath = "yolov4-tiny.cfg"; - String classesPath = "coco.names"; + Path path = Paths.get(weightsPath); + if (!Files.exists(path) || Files.size(path) < 10000000) { + filesValid = false; + } + final Path path1 = Paths.get(configPath); + if (!Files.exists(path1) || Files.size(path1) < 10000) { + filesValid = false; + } + final Path path2 = Paths.get(classesPath); + if (!Files.exists(path2) || Files.size(path2) < 1000) { + filesValid = false; + } + } catch (IOException e) { + filesValid = false; + } + if (!filesValid) { + int reply = JOptionPane.showConfirmDialog(this, + "YOLO model files need to be downloaded (~25MB). Continue?", + "Download Files", JOptionPane.YES_NO_OPTION); + if (reply == JOptionPane.NO_OPTION) { + statusLabel.setText("Object detection disabled - model not loaded"); + return; + } + + downloadYoloFiles(weightsUrl, configUrl, classesUrl); + } + + try { net = Dnn.readNetFromDarknet(configPath, weightsPath); - // Try to use CUDA if available - try { - net.setPreferableBackend(Dnn.DNN_BACKEND_CUDA); - net.setPreferableTarget(Dnn.DNN_TARGET_CUDA); - statusLabel.setText("Using CUDA acceleration"); - } catch (Exception e) { - net.setPreferableBackend(Dnn.DNN_BACKEND_OPENCV); - net.setPreferableTarget(Dnn.DNN_TARGET_CPU); - statusLabel.setText("Using CPU (no CUDA available)"); - } + // Try to use GPU if available (OpenCV Java doesn't have CUDA detection, so we'll use CPU) + net.setPreferableBackend(Dnn.DNN_BACKEND_OPENCV); + net.setPreferableTarget(Dnn.DNN_TARGET_CPU); + statusLabel.setText("Using CPU backend"); // Load class names classes = new ArrayList<>(); - try (BufferedReader reader = new BufferedReader(new FileReader(classesPath))) { + try (BufferedReader br = new BufferedReader(new FileReader(classesPath))) { String line; - while ((line = reader.readLine()) != null) { + while ((line = br.readLine()) != null) { classes.add(line.trim()); } } // Get output layer names List layerNames = net.getLayerNames(); - MatOfInt unconnectedOutLayers = net.getUnconnectedOutLayers(); + MatOfInt unconnectedOutLayers = new MatOfInt(); + net.getUnconnectedOutLayers(); int[] indices = unconnectedOutLayers.toArray(); + outputLayers = new ArrayList<>(); - for (int index : indices) { - outputLayers.add(layerNames.get(index - 1)); + for (int idx : indices) { + outputLayers.add(layerNames.get(idx - 1)); } - modelLoaded.set(true); - statusLabel.setText("YOLOv4-Tiny model loaded successfully (" + classes.size() + " classes)"); + modelLoaded = true; + statusLabel.setText("YOLOv4 model loaded successfully"); } catch (Exception e) { - modelLoaded.set(false); - statusLabel.setText("Error loading model: " + e.getMessage()); - JOptionPane.showMessageDialog(this, - "Failed to load object detection model: " + e.getMessage(), - "Error", JOptionPane.WARNING_MESSAGE); - detectionCheckbox.setSelected(false); - detectionEnabled.set(false); - } - } - - private long getFileSize(String filePath) { - try { - return Files.size(Paths.get(filePath)); - } catch (Exception e) { - return 0; + modelLoaded = false; + statusLabel.setText(String.format("Error loading model: %s", e.getMessage())); + JOptionPane.showMessageDialog(this, "Failed to load object detection model", "Error", JOptionPane.WARNING_MESSAGE); } } @@ -866,152 +331,400 @@ public class CameraApp extends JFrame { DownloadProgressBar progressDialog = new DownloadProgressBar(this); progressDialog.setVisible(true); - // Download in background thread - CompletableFuture.runAsync(() -> { - boolean allSuccess = true; - try { - for (String[] file : files) { - String fileName = file[0]; - String url = file[1]; + try { + for (String[] fileInfo : files) { + String fileName = fileInfo[0]; + String url = fileInfo[1]; - // Check if file already exists and is valid - if (Files.exists(Paths.get(fileName))) { - try { - long size = getFileSize(fileName); - if ((fileName.equals("yolov4-tiny.weights") && size >= 10000000) || - (fileName.equals("yolov4-tiny.cfg") && size >= 1000) || - (fileName.equals("coco.names") && size >= 500)) { - continue; - } - } catch (Exception e) { - System.out.println("Existing file " + fileName + " is invalid: " + e.getMessage()); - } - } - - if (progressDialog.isCancelled()) { - allSuccess = false; - break; - } - - System.out.println("Downloading " + fileName + "..."); - progressDialog.updateLabel(fileName); + final Path path = Paths.get(fileName); + if (Files.exists(path)) { try { - downloadFile(url, fileName, progressDialog); - System.out.println("Downloaded " + fileName); + long fileSize = Files.size(path); + if ((fileName.equals("yolov4-tiny.weights") && fileSize < 10000000) || + (fileName.equals("yolov4-tiny.cfg") && fileSize < 10000) || + (fileName.equals("coco.names") && fileSize < 1000)) { + throw new RuntimeException("Incomplete file"); + } + continue; } catch (Exception e) { - System.out.println("Error downloading " + fileName + ": " + e.getMessage()); - allSuccess = false; - break; + System.out.printf("Existing file %s is invalid: %s%n", fileName, e.getMessage()); } } - boolean finalAllSuccess = allSuccess; - SwingUtilities.invokeLater(() -> { - progressDialog.dispose(); - if (finalAllSuccess && !progressDialog.isCancelled()) { - statusLabel.setText("YOLO files downloaded successfully"); - initializeYoloModel(); - } else { - statusLabel.setText("Download failed or cancelled"); - detectionCheckbox.setSelected(false); - detectionEnabled.set(false); - } - }); + System.out.printf("Downloading %s...%n", fileName); + progressDialog.updateLabel(fileName); - } catch (Exception e) { - SwingUtilities.invokeLater(() -> { - progressDialog.dispose(); - statusLabel.setText("Download error: " + e.getMessage()); - JOptionPane.showMessageDialog(this, - "Download failed: " + e.getMessage(), - "Error", JOptionPane.ERROR_MESSAGE); - detectionCheckbox.setSelected(false); - detectionEnabled.set(false); - }); + try { + downloadFile(url, fileName, progressDialog); + System.out.printf("Downloaded %s%n", fileName); + } catch (Exception e) { + System.out.printf("Error downloading %s: %s%n", fileName, e.getMessage()); + JOptionPane.showMessageDialog(this, String.format("Failed to download %s", fileName), "Error", JOptionPane.ERROR_MESSAGE); + progressDialog.setVisible(false); + return; + } } - }, executorService); + } finally { + progressDialog.setVisible(false); + } } - private void downloadFile(String urlString, String fileName, DownloadProgressBar progressDialog) throws Exception { + private void downloadFile(String urlString, String fileName, DownloadProgressBar progressDialog) throws IOException { URL url = new URL(urlString); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - connection.setRequestMethod("GET"); - connection.setConnectTimeout(10000); - connection.setReadTimeout(30000); + connection.setRequestProperty("User-Agent", "Mozilla/5.0"); - // Set user agent to avoid blocking - connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"); - - int responseCode = connection.getResponseCode(); - if (responseCode != HttpURLConnection.HTTP_OK) { - throw new IOException("HTTP error code: " + responseCode); + // Handle redirects + int status = connection.getResponseCode(); + if (status == HttpURLConnection.HTTP_MOVED_TEMP || + status == HttpURLConnection.HTTP_MOVED_PERM || + status == HttpURLConnection.HTTP_SEE_OTHER) { + String newUrl = connection.getHeaderField("Location"); + connection = (HttpURLConnection) new URL(newUrl).openConnection(); } - long fileSize = connection.getContentLengthLong(); + int totalSize = connection.getContentLength(); - try (InputStream inputStream = connection.getInputStream(); - FileOutputStream outputStream = new FileOutputStream(fileName)) { + try (InputStream in = new BufferedInputStream(connection.getInputStream()); + FileOutputStream out = new FileOutputStream(fileName)) { byte[] buffer = new byte[8192]; - long totalBytesRead = 0; int bytesRead; + int totalBytesRead = 0; + long lastUpdate = 0; - while ((bytesRead = inputStream.read(buffer)) != -1) { - if (progressDialog.isCancelled()) { - throw new InterruptedException("Download cancelled by user"); - } - - outputStream.write(buffer, 0, bytesRead); + while ((bytesRead = in.read(buffer)) != -1) { + out.write(buffer, 0, bytesRead); totalBytesRead += bytesRead; - if (fileSize > 0) { - progressDialog.updateProgress(totalBytesRead, fileSize); + long currentTime = System.currentTimeMillis(); + if (currentTime - lastUpdate > 100) { + progressDialog.updateProgress(totalBytesRead, totalSize); + lastUpdate = currentTime; + } + } + } + } + + private void updateFrame() { + if (cap == null || !cap.isOpened()) { + return; + } + + + Mat frame = new Mat(); + boolean ret = cap.read(frame); + if (!ret || frame.empty()) { + statusLabel.setText("Failed to capture frame"); + return; + } + + // Remove this conversion - we'll do it in matToBufferedImage + // Mat rgbFrame = new Mat(); + // Imgproc.cvtColor(frame, rgbFrame, Imgproc.COLOR_BGR2RGB); + + if (detectionEnabled && modelLoaded) { + if (detectionThread == null || !detectionThread.isRunning()) { + detectionThread = new DetectionThread(net, classes, outputLayers, frame.clone(), this); + detectionThread.execute(); + } else { + displayFrame(frame); + } + } else { + displayFrame(frame); + } + } + + public void displayFrame(Mat frame) { + SwingUtilities.invokeLater(() -> { + BufferedImage bufferedImage = matToBufferedImage(frame); + ImageIcon imageIcon = new ImageIcon(bufferedImage); + + // Scale to fit label + Dimension labelSize = videoLabel.getSize(); + if (labelSize.width > 0 && labelSize.height > 0) { + Image scaledImage = imageIcon.getImage().getScaledInstance( + labelSize.width, labelSize.height, Image.SCALE_SMOOTH); + imageIcon = new ImageIcon(scaledImage); + } + + videoLabel.setIcon(imageIcon); + }); + } + + private BufferedImage matToBufferedImage(Mat mat) { + // Convert from BGR to RGB + Mat rgbMat = new Mat(); + if (mat.channels() > 1) { + Imgproc.cvtColor(mat, rgbMat, Imgproc.COLOR_BGR2RGB); + } else { + mat.copyTo(rgbMat); + } + + int type; + if (rgbMat.channels() == 1) { + type = BufferedImage.TYPE_BYTE_GRAY; + } else { + type = BufferedImage.TYPE_INT_RGB; + } + + BufferedImage image = new BufferedImage(rgbMat.cols(), rgbMat.rows(), type); + + if (type == BufferedImage.TYPE_BYTE_GRAY) { + // Handle grayscale images + byte[] buffer = new byte[rgbMat.channels() * rgbMat.cols() * rgbMat.rows()]; + rgbMat.get(0, 0, buffer); + byte[] targetPixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData(); + System.arraycopy(buffer, 0, targetPixels, 0, buffer.length); + } else { + // Handle color images + byte[] buffer = new byte[rgbMat.channels() * rgbMat.cols() * rgbMat.rows()]; + rgbMat.get(0, 0, buffer); + int[] targetPixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); + + // Convert from byte array to int array + for (int i = 0; i < targetPixels.length; i++) { + int r = buffer[i * 3] & 0xFF; + int g = buffer[i * 3 + 1] & 0xFF; + int b = buffer[i * 3 + 2] & 0xFF; + targetPixels[i] = (r << 16) | (g << 8) | b; + } + } + + return image; + } + + private void takeSnapshot() { + if (cap == null || !cap.isOpened()) { + JOptionPane.showMessageDialog(this, "No active camera to take snapshot from", "Warning", JOptionPane.WARNING_MESSAGE); + return; + } + + Mat frame = new Mat(); + boolean ret = cap.read(frame); + if (!ret || frame.empty()) { + JOptionPane.showMessageDialog(this, "Failed to capture frame", "Warning", JOptionPane.WARNING_MESSAGE); + return; + } + + JFileChooser fileChooser = new JFileChooser(); + fileChooser.setDialogTitle("Save Snapshot"); + FileNameExtensionFilter filter = new FileNameExtensionFilter("Image files", "jpg", "jpeg", "png"); + fileChooser.setFileFilter(filter); + + int userSelection = fileChooser.showSaveDialog(this); + if (userSelection == JFileChooser.APPROVE_OPTION) { + File fileToSave = fileChooser.getSelectedFile(); + String fileName = fileToSave.getAbsolutePath(); + + // Add extension if not present + if (!fileName.toLowerCase().endsWith(".jpg") && + !fileName.toLowerCase().endsWith(".jpeg") && + !fileName.toLowerCase().endsWith(".png")) { + fileName += ".jpg"; + } + + // Convert BGR to RGB for saving + Mat rgbFrame = new Mat(); + Imgproc.cvtColor(frame, rgbFrame, Imgproc.COLOR_BGR2RGB); + + boolean success = Imgcodecs.imwrite(fileName, rgbFrame); + if (success) { + statusLabel.setText(String.format("Snapshot saved to %s", fileName)); + } else { + JOptionPane.showMessageDialog(this, "Failed to save snapshot", "Error", JOptionPane.ERROR_MESSAGE); + } + } + } + + @Override + protected void processWindowEvent(WindowEvent e) { + if (e.getID() == WindowEvent.WINDOW_CLOSING) { + stopCamera(); + if (detectionThread != null) { + detectionThread.stop(); + } + } + super.processWindowEvent(e); + } + + public static void main(String[] args) { + SwingUtilities.invokeLater(() -> { + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (Exception e) { + e.printStackTrace(); + } + + CameraApp window = new CameraApp(); + window.setVisible(true); + }); + } +} + +record CameraInfo(int index, String name) { + + @Override + public String toString() { + return name; + } +} + +class DownloadProgressBar extends JDialog { + private final JLabel label; + private final JProgressBar progress; + + public DownloadProgressBar(JFrame parent) { + super(parent, "Downloading YOLO Files", true); + setSize(400, 100); + setLocationRelativeTo(parent); + setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); + + JPanel panel = new JPanel(new BorderLayout()); + + label = new JLabel("Downloading YOLO files..."); + panel.add(label, BorderLayout.NORTH); + + progress = new JProgressBar(0, 100); + panel.add(progress, BorderLayout.CENTER); + + JButton cancelButton = new JButton("Cancel"); + cancelButton.addActionListener(_ -> setVisible(false)); + panel.add(cancelButton, BorderLayout.SOUTH); + + add(panel); + } + + public void updateProgress(int current, int total) { + if (total > 0) { + int percentage = (int) ((current * 100L) / total); + progress.setValue(percentage); + } + } + + public void updateLabel(String filename) { + label.setText(String.format("Downloading %s...", filename)); + } +} + +class DetectionThread extends SwingWorker { + private final Net net; + private final List classes; + private final List outputLayers; + private final Mat frame; + private final CameraApp parent; + private volatile boolean running = true; + + public DetectionThread(Net net, List classes, List outputLayers, Mat frame, CameraApp parent) { + this.net = net; + this.classes = classes; + this.outputLayers = outputLayers; + this.frame = frame.clone(); + this.parent = parent; + } + + @Override + protected Mat doInBackground() { + if (!running || net == null || classes == null) { + return frame; + } + + Size frameSize = frame.size(); + int height = (int) frameSize.height; + int width = (int) frameSize.width; + + // Create blob from image + Mat blob = Dnn.blobFromImage(frame, 1.0/255.0, new Size(416, 416), new Scalar(0), true, false); + + // Set input to the network + net.setInput(blob); + + // Run forward pass + List outs = new ArrayList<>(); + net.forward(outs, outputLayers); + + // Parse detections + List classIds = new ArrayList<>(); + List confidences = new ArrayList<>(); + List boxes = new ArrayList<>(); + + for (Mat out : outs) { + for (int i = 0; i < out.rows(); i++) { + Mat row = out.row(i); + Mat scores = row.colRange(5, row.cols()); + Core.MinMaxLocResult mmr = Core.minMaxLoc(scores); + double confidence = mmr.maxVal; + + if (confidence > 0.5) { + Point classIdPoint = mmr.maxLoc; + int classId = (int) classIdPoint.x; + + double[] detection = row.get(0, 0); + double centerX = detection[0] * width; + double centerY = detection[1] * height; + double boxWidth = detection[2] * width; + double boxHeight = detection[3] * height; + + double x = centerX - (boxWidth / 2); + double y = centerY - (boxHeight / 2); + + boxes.add(new Rect2d(x, y, boxWidth, boxHeight)); + confidences.add((float) confidence); + classIds.add(classId); } } } - connection.disconnect(); - } + // Apply Non-Maximum Suppression + MatOfRect2d boxesMat = new MatOfRect2d(); + boxesMat.fromList(boxes); + MatOfFloat confidencesMat = new MatOfFloat(); + confidencesMat.fromList(confidences); + MatOfInt indices = new MatOfInt(); - private void closeEvent() { - if (cap != null) { - cap.release(); - } + Dnn.NMSBoxes(boxesMat, confidencesMat, 0.5f, 0.4f, indices); - if (detectionThread != null) { - detectionThread.stopDetection(); - } + // Draw detections + int[] indicesArray = indices.toArray(); + for (int i : indicesArray) { + if (i < boxes.size() && i < classIds.size() && i < confidences.size()) { + Rect2d box = boxes.get(i); + String label = classes.get(classIds.get(i)); + float confidence = confidences.get(i); - if (executorService != null) { - executorService.shutdownNow(); - } + Scalar color = new Scalar(0, 255, 0); + Point topLeft = new Point(box.x, box.y); + Point bottomRight = new Point(box.x + box.width, box.y + box.height); - System.exit(0); - } + Imgproc.rectangle(frame, topLeft, bottomRight, color, 2); - // Main method - public static void main(String[] args) { - try { - // Set system look and feel - UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); - } catch (Exception e) { - System.out.println("Could not set system look and feel: " + e.getMessage()); - } - - SwingUtilities.invokeLater(() -> { - try { - new CameraApp().setVisible(true); - } catch (Exception e) { - System.err.println("Error starting application: " + e.getMessage()); - e.printStackTrace(); - - JOptionPane.showMessageDialog(null, - "Error starting application:\n" + e.getMessage() + - "\n\nPlease ensure OpenCV is properly installed and configured.", - "Startup Error", - JOptionPane.ERROR_MESSAGE); - System.exit(1); + String text = String.format("%s: %.2f", label, confidence); + Point textPoint = new Point(box.x, box.y - 5); + Imgproc.putText(frame, text, textPoint, Imgproc.FONT_HERSHEY_SIMPLEX, 0.5, color, 2); } - }); - }} \ No newline at end of file + } + + return frame; + } + + @Override + protected void done() { + try { + Mat result = get(); + parent.displayFrame(result); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void stop() { + running = false; + cancel(true); + } + + public boolean isRunning() { + return !isDone() && !isCancelled(); + } +} +