diff --git a/src/main/java/org/jdetect/CameraApp.java b/src/main/java/org/jdetect/CameraApp.java index 1bacd3e..1b1a82f 100644 --- a/src/main/java/org/jdetect/CameraApp.java +++ b/src/main/java/org/jdetect/CameraApp.java @@ -7,6 +7,7 @@ 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.*; @@ -17,15 +18,15 @@ import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.image.BufferedImage; import java.awt.image.DataBufferByte; -import java.io.BufferedReader; -import java.io.FileOutputStream; -import java.io.FileReader; -import java.io.InputStream; +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; @@ -106,29 +107,6 @@ class DownloadProgressBar extends JDialog { } } -// Download progress handler class -class DownloadProgressHandler { - private DownloadProgressBar progressDialog; - private AtomicLong currentSize = new AtomicLong(0); - private AtomicLong totalSize = new AtomicLong(0); - private AtomicLong lastUpdate = new AtomicLong(0); - - public DownloadProgressHandler(DownloadProgressBar progressDialog) { - this.progressDialog = progressDialog; - } - - public void handleProgress(long current, long total) { - this.totalSize.set(total); - this.currentSize.set(current); - - long currentTime = System.currentTimeMillis(); - if (currentTime - lastUpdate.get() > 100) { // Update every 100ms - progressDialog.updateProgress(current, total); - lastUpdate.set(currentTime); - } - } -} - // Detection thread class class DetectionThread extends Thread { private Net net; @@ -258,6 +236,10 @@ public class CameraApp extends JFrame { 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; @@ -303,7 +285,7 @@ public class CameraApp extends JFrame { private void initUI() { setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - setSize(1000, 800); + setSize(1200, 900); setLocationRelativeTo(null); // Main panel @@ -314,13 +296,16 @@ public class CameraApp extends JFrame { videoLabel = new JLabel(); videoLabel.setHorizontalAlignment(JLabel.CENTER); videoLabel.setVerticalAlignment(JLabel.CENTER); - videoLabel.setPreferredSize(new Dimension(640, 480)); + videoLabel.setPreferredSize(new Dimension(800, 600)); videoLabel.setBorder(BorderFactory.createLoweredBevelBorder()); videoLabel.setText("Camera feed will appear here"); mainPanel.add(videoLabel, BorderLayout.CENTER); // Control panel - JPanel controlPanel = new JPanel(new GridBagLayout()); + JPanel controlPanel = new JPanel(new BorderLayout()); + + // Basic controls + JPanel basicControlPanel = new JPanel(new GridBagLayout()); GridBagConstraints gbc = new GridBagConstraints(); gbc.insets = new Insets(5, 5, 5, 5); gbc.fill = GridBagConstraints.HORIZONTAL; @@ -329,30 +314,36 @@ public class CameraApp extends JFrame { gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 2; cameraSelect = new JComboBox<>(); cameraSelect.addActionListener(this::changeCamera); - controlPanel.add(cameraSelect, gbc); + basicControlPanel.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); - controlPanel.add(detectionCheckbox, gbc); + basicControlPanel.add(detectionCheckbox, gbc); // Buttons gbc.gridwidth = 1; gbc.gridx = 0; gbc.gridy = 2; startButton = new JButton("Start Camera"); startButton.addActionListener(e -> startCamera()); - controlPanel.add(startButton, gbc); + basicControlPanel.add(startButton, gbc); gbc.gridx = 1; gbc.gridy = 2; stopButton = new JButton("Stop Camera"); stopButton.addActionListener(e -> stopCamera()); - controlPanel.add(stopButton, gbc); + basicControlPanel.add(stopButton, gbc); gbc.gridx = 0; gbc.gridy = 3; gbc.gridwidth = 2; snapshotButton = new JButton("Take Snapshot"); snapshotButton.addActionListener(e -> takeSnapshot()); - controlPanel.add(snapshotButton, gbc); + basicControlPanel.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); @@ -385,6 +376,82 @@ public class CameraApp extends JFrame { }); } + 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")); @@ -461,7 +528,13 @@ public class CameraApp extends JFrame { selected = availableCameras.get(0); } - cap = new VideoCapture(selected.index, selected.backend); + // 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 (!cap.isOpened()) { JOptionPane.showMessageDialog(this, String.format("Cannot open camera %d", selected.index), @@ -470,14 +543,37 @@ public class CameraApp extends JFrame { return; } - // 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); + // 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)); timer.start(); - statusLabel.setText(String.format("Camera %d started", selected.index)); } catch (Exception ex) { JOptionPane.showMessageDialog(this, @@ -503,11 +599,161 @@ public class CameraApp extends JFrame { } } - private void loadYolo() { - 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"; + 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"; @@ -525,28 +771,45 @@ public class CameraApp extends JFrame { 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.YES_OPTION) { - statusLabel.setText("Object detection disabled - model not loaded"); - return; - } - - downloadYoloFiles(weightsUrl, configUrl, classesUrl); + 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); + } + + private void initializeYoloModel() { try { + String weightsPath = "yolov4-tiny.weights"; + String configPath = "yolov4-tiny.cfg"; + String classesPath = "coco.names"; + net = Dnn.readNetFromDarknet(configPath, weightsPath); - // Try to use CUDA if available (OpenCV Java bindings may not support this fully) + // Try to use CUDA if available try { net.setPreferableBackend(Dnn.DNN_BACKEND_CUDA); net.setPreferableTarget(Dnn.DNN_TARGET_CUDA); - statusLabel.setText("Attempting CUDA acceleration"); + statusLabel.setText("Using CUDA acceleration"); } catch (Exception e) { net.setPreferableBackend(Dnn.DNN_BACKEND_OPENCV); net.setPreferableTarget(Dnn.DNN_TARGET_CPU); @@ -572,12 +835,16 @@ public class CameraApp extends JFrame { } modelLoaded.set(true); - statusLabel.setText("YOLOv4 model loaded successfully"); + statusLabel.setText("YOLOv4-Tiny model loaded successfully (" + classes.size() + " classes)"); } catch (Exception e) { modelLoaded.set(false); statusLabel.setText("Error loading model: " + e.getMessage()); - JOptionPane.showMessageDialog(this, "Failed to load object detection model", "Error", JOptionPane.WARNING_MESSAGE); + JOptionPane.showMessageDialog(this, + "Failed to load object detection model: " + e.getMessage(), + "Error", JOptionPane.WARNING_MESSAGE); + detectionCheckbox.setSelected(false); + detectionEnabled.set(false); } } @@ -601,17 +868,19 @@ public class CameraApp extends JFrame { // Download in background thread CompletableFuture.runAsync(() -> { + boolean allSuccess = true; try { for (String[] file : files) { String fileName = file[0]; String url = file[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 >= 10000) || - (fileName.equals("coco.names") && size >= 1000)) { + (fileName.equals("yolov4-tiny.cfg") && size >= 1000) || + (fileName.equals("coco.names") && size >= 500)) { continue; } } catch (Exception e) { @@ -620,6 +889,7 @@ public class CameraApp extends JFrame { } if (progressDialog.isCancelled()) { + allSuccess = false; break; } @@ -631,14 +901,34 @@ public class CameraApp extends JFrame { System.out.println("Downloaded " + fileName); } catch (Exception e) { System.out.println("Error downloading " + fileName + ": " + e.getMessage()); - SwingUtilities.invokeLater(() -> { - JOptionPane.showMessageDialog(this, "Failed to download " + fileName, "Error", JOptionPane.ERROR_MESSAGE); - }); + allSuccess = false; break; } } - } finally { - SwingUtilities.invokeLater(() -> progressDialog.dispose()); + + 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); + } + }); + + } 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); + }); } }, executorService); } @@ -647,17 +937,25 @@ public class CameraApp extends JFrame { URL url = new URL(urlString); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); - connection.connect(); + connection.setConnectTimeout(10000); + connection.setReadTimeout(30000); - int contentLength = connection.getContentLength(); + // 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); + } + + long fileSize = connection.getContentLengthLong(); try (InputStream inputStream = connection.getInputStream(); FileOutputStream outputStream = new FileOutputStream(fileName)) { byte[] buffer = new byte[8192]; - int bytesRead; long totalBytesRead = 0; - long lastUpdate = 0; + int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { if (progressDialog.isCancelled()) { @@ -667,150 +965,53 @@ public class CameraApp extends JFrame { outputStream.write(buffer, 0, bytesRead); totalBytesRead += bytesRead; - long currentTime = System.currentTimeMillis(); - if (currentTime - lastUpdate > 100) { // Update every 100ms - progressDialog.updateProgress(totalBytesRead, contentLength); - lastUpdate = currentTime; + if (fileSize > 0) { + progressDialog.updateProgress(totalBytesRead, fileSize); } } } - } - private void updateFrame(ActionEvent e) { - 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; - } - - frameCount++; - long currentTime = System.currentTimeMillis(); - if (currentTime - lastFpsUpdate >= 1000) { - fps = frameCount * 1000.0 / (currentTime - lastFpsUpdate); - lastFpsUpdate = currentTime; - frameCount = 0; - statusLabel.setText(String.format("Running at %.1f FPS", fps)); - } - - // Convert BGR to RGB - Mat rgbFrame = new Mat(); - Imgproc.cvtColor(frame, rgbFrame, Imgproc.COLOR_BGR2RGB); - - if (detectionEnabled.get() && modelLoaded.get()) { - if (detectionThread == null || !detectionThread.isAlive()) { - detectionThread = new DetectionThread(net, classes, outputLayers, rgbFrame); - detectionThread.start(); - - // Handle detection result asynchronously - detectionThread.getDetectionResult().thenAccept(this::displayFrame); - } else { - displayFrame(rgbFrame); - } - } else { - displayFrame(rgbFrame); - } - } - - private void displayFrame(Mat frame) { - SwingUtilities.invokeLater(() -> { - BufferedImage bufferedImage = matToBufferedImage(frame); - if (bufferedImage != null) { - // Scale image to fit label while maintaining aspect ratio - Dimension labelSize = videoLabel.getSize(); - ImageIcon scaledIcon = new ImageIcon(bufferedImage.getScaledInstance( - labelSize.width, labelSize.height, Image.SCALE_SMOOTH)); - videoLabel.setIcon(scaledIcon); - videoLabel.setText(null); - } - }); - } - - private BufferedImage matToBufferedImage(Mat mat) { - try { - 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; - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - - 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"); - fileChooser.setFileFilter(new javax.swing.filechooser.FileNameExtensionFilter("JPEG Images", "jpg", "jpeg")); - - int result = fileChooser.showSaveDialog(this); - if (result == JFileChooser.APPROVE_OPTION) { - String fileName = fileChooser.getSelectedFile().getAbsolutePath(); - if (!fileName.toLowerCase().endsWith(".jpg") && !fileName.toLowerCase().endsWith(".jpeg")) { - 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("Snapshot saved to " + fileName); - } else { - JOptionPane.showMessageDialog(this, "Failed to save snapshot", "Error", JOptionPane.WARNING_MESSAGE); - } - } + connection.disconnect(); } private void closeEvent() { - stopCamera(); + if (cap != null) { + cap.release(); + } + if (detectionThread != null) { detectionThread.stopDetection(); } + if (executorService != null) { - executorService.shutdown(); + executorService.shutdownNow(); } + System.exit(0); } + // Main method public static void main(String[] args) { try { + // Set system look and feel UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception e) { - e.printStackTrace(); + System.out.println("Could not set system look and feel: " + e.getMessage()); } SwingUtilities.invokeLater(() -> { - CameraApp app = new CameraApp(); - app.setVisible(true); - }); - } -} + 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); + } + }); + }} \ No newline at end of file diff --git a/src/main/resources/coco.names b/src/main/resources/coco.names new file mode 100644 index 0000000..ca76c80 --- /dev/null +++ b/src/main/resources/coco.names @@ -0,0 +1,80 @@ +person +bicycle +car +motorbike +aeroplane +bus +train +truck +boat +traffic light +fire hydrant +stop sign +parking meter +bench +bird +cat +dog +horse +sheep +cow +elephant +bear +zebra +giraffe +backpack +umbrella +handbag +tie +suitcase +frisbee +skis +snowboard +sports ball +kite +baseball bat +baseball glove +skateboard +surfboard +tennis racket +bottle +wine glass +cup +fork +knife +spoon +bowl +banana +apple +sandwich +orange +broccoli +carrot +hot dog +pizza +donut +cake +chair +sofa +pottedplant +bed +diningtable +toilet +tvmonitor +laptop +mouse +remote +keyboard +cell phone +microwave +oven +toaster +sink +refrigerator +book +clock +vase +scissors +teddy bear +hair drier +toothbrush diff --git a/src/main/resources/yolov4-tiny.cfg b/src/main/resources/yolov4-tiny.cfg new file mode 100644 index 0000000..d990b51 --- /dev/null +++ b/src/main/resources/yolov4-tiny.cfg @@ -0,0 +1,294 @@ +[net] +# Testing +#batch=1 +#subdivisions=1 +# Training +batch=64 +subdivisions=1 +width=416 +height=416 +channels=3 +momentum=0.9 +decay=0.0005 +angle=0 +saturation = 1.5 +exposure = 1.5 +hue=.1 + +learning_rate=0.00261 +burn_in=1000 + +max_batches = 2000200 +policy=steps +steps=1600000,1800000 +scales=.1,.1 + + +#weights_reject_freq=1001 +#ema_alpha=0.9998 +#equidistant_point=1000 +#num_sigmas_reject_badlabels=3 +#badlabels_rejection_percentage=0.2 + + +[convolutional] +batch_normalize=1 +filters=32 +size=3 +stride=2 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=64 +size=3 +stride=2 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=64 +size=3 +stride=1 +pad=1 +activation=leaky + +[route] +layers=-1 +groups=2 +group_id=1 + +[convolutional] +batch_normalize=1 +filters=32 +size=3 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=32 +size=3 +stride=1 +pad=1 +activation=leaky + +[route] +layers = -1,-2 + +[convolutional] +batch_normalize=1 +filters=64 +size=1 +stride=1 +pad=1 +activation=leaky + +[route] +layers = -6,-1 + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=128 +size=3 +stride=1 +pad=1 +activation=leaky + +[route] +layers=-1 +groups=2 +group_id=1 + +[convolutional] +batch_normalize=1 +filters=64 +size=3 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=64 +size=3 +stride=1 +pad=1 +activation=leaky + +[route] +layers = -1,-2 + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=leaky + +[route] +layers = -6,-1 + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=leaky + +[route] +layers=-1 +groups=2 +group_id=1 + +[convolutional] +batch_normalize=1 +filters=128 +size=3 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=128 +size=3 +stride=1 +pad=1 +activation=leaky + +[route] +layers = -1,-2 + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=leaky + +[route] +layers = -6,-1 + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=512 +size=3 +stride=1 +pad=1 +activation=leaky + +################################## + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=512 +size=3 +stride=1 +pad=1 +activation=leaky + +[convolutional] +size=1 +stride=1 +pad=1 +filters=255 +activation=linear + + + +[yolo] +mask = 3,4,5 +anchors = 10,14, 23,27, 37,58, 81,82, 135,169, 344,319 +classes=80 +num=6 +jitter=.3 +scale_x_y = 1.05 +cls_normalizer=1.0 +iou_normalizer=0.07 +iou_loss=ciou +ignore_thresh = .7 +truth_thresh = 1 +random=0 +resize=1.5 +nms_kind=greedynms +beta_nms=0.6 +#new_coords=1 +#scale_x_y = 2.0 + +[route] +layers = -4 + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=leaky + +[upsample] +stride=2 + +[route] +layers = -1, 23 + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=leaky + +[convolutional] +size=1 +stride=1 +pad=1 +filters=255 +activation=linear + +[yolo] +mask = 1,2,3 +anchors = 10,14, 23,27, 37,58, 81,82, 135,169, 344,319 +classes=80 +num=6 +jitter=.3 +scale_x_y = 1.05 +cls_normalizer=1.0 +iou_normalizer=0.07 +iou_loss=ciou +ignore_thresh = .7 +truth_thresh = 1 +random=0 +resize=1.5 +nms_kind=greedynms +beta_nms=0.6 +#new_coords=1 +#scale_x_y = 2.0 diff --git a/src/main/resources/yolov4-tiny.weights b/src/main/resources/yolov4-tiny.weights new file mode 100644 index 0000000..27edc5d Binary files /dev/null and b/src/main/resources/yolov4-tiny.weights differ