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; import org.opencv.dnn.Net; import org.opencv.imgcodecs.Imgcodecs; import org.opencv.imgproc.Imgproc; import org.opencv.videoio.VideoCapture; import org.opencv.videoio.Videoio; public class CameraApp extends JFrame { static { // Load OpenCV native library nu.pattern.OpenCV.loadLocally(); } // GUI Components private JLabel videoLabel; private JComboBox cameraSelect; private JCheckBox detectionCheckbox; private JLabel statusLabel; // Camera and detection attributes private int cameraIndex = 1; private VideoCapture cap; private Timer timer; private final List availableCameras; private DetectionThread detectionThread; private Net net; private List classes; private List outputLayers; private boolean detectionEnabled = true; private boolean modelLoaded = false; private int frameCount = 0; private long lastFpsUpdate = System.currentTimeMillis(); public CameraApp() { setTitle("Advanced Object Detection Camera Viewer"); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setBounds(100, 100, 1000, 800); // Initialize attributes availableCameras = new ArrayList<>(); // Initialize UI initUI(); // Load YOLO after UI is ready SwingUtilities.invokeLater(this::loadYolo); } private void initUI() { Container contentPane = getContentPane(); contentPane.setLayout(new BorderLayout()); // Main panel JPanel mainPanel = new JPanel(new BorderLayout()); // Video display videoLabel = new JLabel(); videoLabel.setHorizontalAlignment(SwingConstants.CENTER); videoLabel.setVerticalAlignment(SwingConstants.CENTER); videoLabel.setPreferredSize(new Dimension(640, 480)); videoLabel.setBorder(BorderFactory.createLoweredBevelBorder()); mainPanel.add(videoLabel, BorderLayout.CENTER); // 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; controlsPanel.add(new JLabel("Camera:"), gbc); gbc.gridx = 1; cameraSelect = new JComboBox<>(); 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(_ -> toggleDetection()); controlsPanel.add(detectionCheckbox, gbc); // Buttons gbc.gridwidth = 1; gbc.gridx = 0; gbc.gridy = 2; JButton startButton = new JButton("Start Camera"); startButton.addActionListener(_ -> startCamera()); controlsPanel.add(startButton, 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; JButton snapshotButton = new JButton("Take Snapshot"); snapshotButton.addActionListener(_ -> takeSnapshot()); controlsPanel.add(snapshotButton, gbc); mainPanel.add(controlsPanel, BorderLayout.SOUTH); contentPane.add(mainPanel, BorderLayout.CENTER); // Status bar statusLabel = new JLabel("Initializing..."); statusLabel.setBorder(BorderFactory.createLoweredBevelBorder()); contentPane.add(statusLabel, BorderLayout.SOUTH); // Set up timer timer = new Timer(30, _ -> updateFrame()); // Detect cameras after UI is ready SwingUtilities.invokeLater(this::detectCameras); } private void toggleDetection() { detectionEnabled = detectionCheckbox.isSelected(); statusLabel.setText("Detection " + (detectionEnabled ? "enabled" : "disabled")); } private void detectCameras() { cameraSelect.removeAllItems(); availableCameras.clear(); // Check for available cameras int maxCamerasToCheck = 10; 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 cameraInfo = new CameraInfo(i, name); availableCameras.add(cameraInfo); cameraSelect.addItem(cameraInfo); testCap.release(); } } catch (Exception e) { // Ignore errors for non-existent cameras } } if (availableCameras.isEmpty()) { statusLabel.setText("No cameras detected"); JOptionPane.showMessageDialog(this, "No cameras detected!", "Warning", JOptionPane.WARNING_MESSAGE); } else { statusLabel.setText(String.format("Found %d cameras", availableCameras.size())); } } private void changeCamera() { CameraInfo selected = (CameraInfo) cameraSelect.getSelectedItem(); if (selected != null) { cameraIndex = selected.index(); if (cap != null && cap.isOpened()) { stopCamera(); startCamera(); } } } private void startCamera() { if (cap != null) { stopCamera(); } if (availableCameras.isEmpty()) { JOptionPane.showMessageDialog(this, "No cameras available", "Error", JOptionPane.ERROR_MESSAGE); return; } try { CameraInfo selected = (CameraInfo) cameraSelect.getSelectedItem(); if (selected != null) { cameraIndex = selected.index(); } cap = new VideoCapture(cameraIndex); if (!cap.isOpened()) { JOptionPane.showMessageDialog(this, String.format("Cannot open camera %d", cameraIndex), "Error", JOptionPane.ERROR_MESSAGE); statusLabel.setText(String.format("Failed to open camera %d", cameraIndex)); 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); timer.start(); statusLabel.setText(String.format("Camera %d started", cameraIndex)); } catch (Exception e) { JOptionPane.showMessageDialog(this, String.format("Error starting camera: %s", e.getMessage()), "Error", JOptionPane.ERROR_MESSAGE); statusLabel.setText(String.format("Error: %s", e.getMessage())); } } private void stopCamera() { if (cap != null) { timer.stop(); cap.release(); cap = null; videoLabel.setIcon(null); statusLabel.setText("Camera stopped"); } if (detectionThread != null) { detectionThread.stop(); detectionThread = null; } } 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"; String weightsPath = "./yolov4-tiny.weights"; String configPath = "./yolov4-tiny.cfg"; String classesPath = "./coco.names"; boolean filesValid = true; try { 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 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 br = new BufferedReader(new FileReader(classesPath))) { String line; while ((line = br.readLine()) != null) { classes.add(line.trim()); } } // Get output layer names List layerNames = net.getLayerNames(); MatOfInt unconnectedOutLayers = new MatOfInt(); net.getUnconnectedOutLayers(); int[] indices = unconnectedOutLayers.toArray(); outputLayers = new ArrayList<>(); for (int idx : indices) { outputLayers.add(layerNames.get(idx - 1)); } modelLoaded = true; statusLabel.setText("YOLOv4 model loaded successfully"); } catch (Exception e) { 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); } } private void downloadYoloFiles(String weightsUrl, String configUrl, String classesUrl) { String[][] files = { {"yolov4-tiny.weights", weightsUrl}, {"yolov4-tiny.cfg", configUrl}, {"coco.names", classesUrl} }; DownloadProgressBar progressDialog = new DownloadProgressBar(this); progressDialog.setVisible(true); try { for (String[] fileInfo : files) { String fileName = fileInfo[0]; String url = fileInfo[1]; final Path path = Paths.get(fileName); if (Files.exists(path)) { try { 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.printf("Existing file %s is invalid: %s%n", fileName, e.getMessage()); } } System.out.printf("Downloading %s...%n", fileName); progressDialog.updateLabel(fileName); 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; } } } finally { progressDialog.setVisible(false); } } private void downloadFile(String urlString, String fileName, DownloadProgressBar progressDialog) throws IOException { URL url = new URL(urlString); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestProperty("User-Agent", "Mozilla/5.0"); // 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(); } int totalSize = connection.getContentLength(); try (InputStream in = new BufferedInputStream(connection.getInputStream()); FileOutputStream out = new FileOutputStream(fileName)) { byte[] buffer = new byte[8192]; int bytesRead; int totalBytesRead = 0; long lastUpdate = 0; while ((bytesRead = in.read(buffer)) != -1) { out.write(buffer, 0, bytesRead); totalBytesRead += bytesRead; 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); } } } // Apply Non-Maximum Suppression MatOfRect2d boxesMat = new MatOfRect2d(); boxesMat.fromList(boxes); MatOfFloat confidencesMat = new MatOfFloat(); confidencesMat.fromList(confidences); MatOfInt indices = new MatOfInt(); Dnn.NMSBoxes(boxesMat, confidencesMat, 0.5f, 0.4f, indices); // 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); 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); } } 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(); } }