commit 623051c116797817e45232311fc14b89aac21392 Author: ZockerKatze Date: Thu Feb 27 16:04:23 2025 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7d2dec5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,63 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +.Python +.venv/ +venv/ +ENV/ + +# C++ build +build/ +out/ +*.o +*.obj +*.exe +*.dll +*.so +*.dylib + +# IDE +.vs/ +.vscode/ +.idea/ +*.swp +*.swo +*.user + +# AppImage specific +*.AppImage +AppDir/ + +# Large model files and datasets +*.weights +*.h5 +*.pt +*.onnx + +# Temporary files +*.log +*.tmp + +# Qt specific +*.pro.user +*.pro.user.* +*.qmake.stash +moc_*.cpp +qrc_*.cpp +ui_*.h + +# CMake +CMakeCache.txt +CMakeFiles/ +cmake_install.cmake +install_manifest.txt +compile_commands.json + +# OpenCV build artifacts +opencv/ +opencv_build/ + +# System specific +.DS_Store +Thumbs.db \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..7e8a056 --- /dev/null +++ b/README.md @@ -0,0 +1,115 @@ +# OpenCam + +Real-time object detection application using OpenCV and YOLO, with both Python and C++ implementations. + +## Features + +- Real-time object detection using YOLOv3 +- Support for multiple camera inputs +- GPU acceleration with CUDA (optional) +- Cross-platform support (Windows, Linux) +- Modern Qt-based user interface +- Portable Linux AppImage build support + +## Prerequisites + +### For C++ Version + +- CMake 3.16 or higher +- C++17 compatible compiler +- Qt 5.12 or higher +- OpenCV 4.x with CUDA support (optional) +- CUDA Toolkit 10.0 or higher (optional) + +### For Python Version + +- Python 3.8 or higher +- OpenCV-Python +- PyQt5 +- NumPy + +## Installation + +### Building from Source (C++) + +1. Clone the repository: +```bash +git clone https://github.com/yourusername/opencam.git +cd opencam/opencamcpp +``` + +2. Build OpenCV with CUDA (optional): +```bash +chmod +x build_opencv.sh +./build_opencv.sh +``` + +3. Build the application: +```bash +mkdir build && cd build +cmake .. +make -j$(nproc) +``` + +### Creating AppImage (Linux) + +1. Ensure all dependencies are installed: +```bash +sudo apt-get install cmake build-essential qt5-default libopencv-dev librsvg2-bin +``` + +2. Build the AppImage: +```bash +chmod +x build_appimage.sh +./build_appimage.sh +``` + +### Python Version Setup + +1. Create a virtual environment: +```bash +python -m venv .venv +source .venv/bin/activate # Linux +# or +.venv\Scripts\activate # Windows +``` + +2. Install dependencies: +```bash +pip install opencv-python pyqt5 numpy +``` + +## Usage + +### Running the C++ Version + +```bash +./opencam +``` + +### Running the Python Version + +```bash +python main.py +``` + +## Model Files + +The application requires YOLOv3 model files: +- `yolov3.weights` +- `yolov3.cfg` +- `coco.names` + +Download the weights file from: https://pjreddie.com/media/files/yolov3.weights + +## License + +[Your chosen license] + +## Contributing + +1. Fork the repository +2. Create your feature branch +3. Commit your changes +4. Push to the branch +5. Create a new Pull Request \ No newline at end of file diff --git a/coco.names b/coco.names new file mode 100644 index 0000000..ca76c80 --- /dev/null +++ b/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/opencam/image.jpg b/opencam/image.jpg new file mode 100644 index 0000000..af8e087 Binary files /dev/null and b/opencam/image.jpg differ diff --git a/opencam/main.py b/opencam/main.py new file mode 100644 index 0000000..be740c4 --- /dev/null +++ b/opencam/main.py @@ -0,0 +1,294 @@ +import sys +import cv2 +import numpy as np +from PyQt5.QtWidgets import (QApplication, QMainWindow, QLabel, QPushButton, + QVBoxLayout, QWidget, QComboBox, QMessageBox, + QProgressBar, QDialog) +from PyQt5.QtGui import QImage, QPixmap +from PyQt5.QtCore import QTimer, Qt +import os +import urllib.request + +class DownloadProgressBar(QDialog): + def __init__(self, parent=None): + super().__init__(parent) + self.setWindowTitle("Downloading YOLO Files") + self.setFixedSize(400, 100) + self.setWindowModality(Qt.ApplicationModal) + + layout = QVBoxLayout() + + self.label = QLabel("Downloading...") + layout.addWidget(self.label) + + self.progress = QProgressBar() + self.progress.setMinimum(0) + self.progress.setMaximum(100) + layout.addWidget(self.progress) + + self.setLayout(layout) + + def update_progress(self, current, total): + percentage = int((current / total) * 100) + self.progress.setValue(percentage) + + def set_file_label(self, filename): + self.label.setText(f"Downloading {filename}...") + +class DownloadProgressHandler: + def __init__(self, progress_dialog): + self.progress_dialog = progress_dialog + self.current_size = 0 + self.total_size = 0 + + def handle_progress(self, count, block_size, total_size): + self.total_size = total_size + self.current_size += block_size + self.progress_dialog.update_progress(self.current_size, self.total_size) + +class CameraApp(QMainWindow): + def __init__(self): + super().__init__() + self.setWindowTitle("Object Detection Camera Viewer") + self.setGeometry(100, 100, 800, 600) + + self.camera_index = 0 + self.cap = None + self.timer = QTimer() + self.timer.timeout.connect(self.update_frame) + self.available_cameras = [] + + # Initialize object detection + self.net = None + self.classes = None + self.output_layers = None + self.load_yolo() + + self.init_ui() + + def init_ui(self): + self.central_widget = QWidget() + self.setCentralWidget(self.central_widget) + layout = QVBoxLayout() + + self.video_label = QLabel(self) + layout.addWidget(self.video_label) + + self.camera_select = QComboBox(self) + self.detect_cameras() + layout.addWidget(self.camera_select) + self.camera_select.currentIndexChanged.connect(self.change_camera) + + self.start_button = QPushButton("Start Camera", self) + self.start_button.clicked.connect(self.start_camera) + layout.addWidget(self.start_button) + + self.stop_button = QPushButton("Stop Camera", self) + self.stop_button.clicked.connect(self.stop_camera) + layout.addWidget(self.stop_button) + + self.central_widget.setLayout(layout) + + def detect_cameras(self): + self.camera_select.clear() + self.available_cameras = [] + + # Try to get the list of available cameras using DirectShow backend + for i in range(10): # Check first 10 indexes + cap = cv2.VideoCapture(i, cv2.CAP_DSHOW) + if cap.isOpened(): + # Get camera name + cap.set(cv2.CAP_PROP_SETTINGS, 1) # This might show camera properties dialog + name = f"Camera {i}" + + # Try to get camera resolution to verify it's working + width = cap.get(cv2.CAP_PROP_FRAME_WIDTH) + height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT) + + if width and height: + name = f"{name} ({int(width)}x{int(height)})" + self.available_cameras.append(i) + self.camera_select.addItem(name, i) + + cap.release() + + if len(self.available_cameras) == 0: + QMessageBox.warning(self, "Warning", "No cameras detected!") + print("Error: No available cameras detected.") + else: + print(f"Detected {len(self.available_cameras)} cameras") + + def change_camera(self, index): + if index >= 0: # Only change if a valid camera is selected + self.camera_index = self.camera_select.itemData(index) + if self.cap is not None and self.cap.isOpened(): + self.stop_camera() + self.start_camera() + + def start_camera(self): + if self.cap is not None: + self.stop_camera() + + try: + self.cap = cv2.VideoCapture(self.camera_index, cv2.CAP_DSHOW) + if not self.cap.isOpened(): + QMessageBox.warning(self, "Error", f"Cannot open camera {self.camera_index}") + print(f"Error: Cannot open camera {self.camera_index}") + return + + # Set camera properties for better performance + self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) + self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) + self.cap.set(cv2.CAP_PROP_FPS, 30) + + self.timer.start(30) + print(f"Started camera {self.camera_index}") + + except Exception as e: + QMessageBox.critical(self, "Error", f"Error starting camera: {str(e)}") + print(f"Error starting camera: {str(e)}") + + def stop_camera(self): + if self.cap: + self.timer.stop() + self.cap.release() + self.cap = None + self.video_label.clear() + print("Camera stopped") + + def load_yolo(self): + # Download YOLO files if they don't exist + weights_path = "yolov3.weights" + config_path = "yolov3.cfg" + classes_path = "coco.names" + + if not all(os.path.exists(f) for f in [weights_path, config_path, classes_path]): + QMessageBox.information(self, "Download", "Downloading YOLO model files. This may take a moment...") + self.download_yolo_files() + + try: + self.net = cv2.dnn.readNet(weights_path, config_path) + with open(classes_path, "r") as f: + self.classes = [line.strip() for line in f.readlines()] + + layer_names = self.net.getLayerNames() + self.output_layers = [layer_names[i - 1] for i in self.net.getUnconnectedOutLayers()] + + print("YOLO model loaded successfully") + except Exception as e: + print(f"Error loading YOLO model: {str(e)}") + QMessageBox.warning(self, "Error", "Failed to load object detection model") + + def download_yolo_files(self): + files = { + "yolov3.weights": "https://pjreddie.com/media/files/yolov3.weights", + "yolov3.cfg": "https://raw.githubusercontent.com/pjreddie/darknet/master/cfg/yolov3.cfg", + "coco.names": "https://raw.githubusercontent.com/pjreddie/darknet/master/data/coco.names" + } + + progress_dialog = DownloadProgressBar(self) + progress_dialog.show() + + for file_name, url in files.items(): + if not os.path.exists(file_name): + print(f"Downloading {file_name}...") + progress_dialog.set_file_label(file_name) + try: + progress_handler = DownloadProgressHandler(progress_dialog) + urllib.request.urlretrieve( + url, + file_name, + reporthook=progress_handler.handle_progress + ) + print(f"Downloaded {file_name}") + except Exception as e: + print(f"Error downloading {file_name}: {str(e)}") + QMessageBox.critical(self, "Error", f"Failed to download {file_name}") + + progress_dialog.close() + + def detect_objects(self, frame): + if self.net is None or self.classes is None: + return frame + + height, width, _ = frame.shape + blob = cv2.dnn.blobFromImage(frame, 0.00392, (416, 416), (0, 0, 0), True, crop=False) + + self.net.setInput(blob) + outs = self.net.forward(self.output_layers) + + # Showing information on the screen + class_ids = [] + confidences = [] + boxes = [] + + # Showing information on the screen + for out in outs: + for detection in out: + scores = detection[5:] + class_id = np.argmax(scores) + confidence = scores[class_id] + if confidence > 0.5: + # Object detected + center_x = int(detection[0] * width) + center_y = int(detection[1] * height) + w = int(detection[2] * width) + h = int(detection[3] * height) + + # Rectangle coordinates + x = int(center_x - w / 2) + y = int(center_y - h / 2) + + boxes.append([x, y, w, h]) + confidences.append(float(confidence)) + class_ids.append(class_id) + + indexes = cv2.dnn.NMSBoxes(boxes, confidences, 0.5, 0.4) + + for i in range(len(boxes)): + if i in indexes: + x, y, w, h = boxes[i] + label = str(self.classes[class_ids[i]]) + confidence = confidences[i] + color = (0, 255, 0) # Green + cv2.rectangle(frame, (x, y), (x + w, y + h), color, 2) + cv2.putText(frame, f"{label} {confidence:.2f}", (x, y - 10), + cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2) + + return frame + + def update_frame(self): + if self.cap is None or not self.cap.isOpened(): + return + + ret, frame = self.cap.read() + if ret: + # Perform object detection + frame = self.detect_objects(frame) + + # Convert the frame from BGR to RGB + rgb_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + h, w, ch = rgb_image.shape + bytes_per_line = ch * w + + # Convert the frame to QImage + q_image = QImage(rgb_image.data, w, h, bytes_per_line, QImage.Format_RGB888) + + # Scale the image to fit the label while maintaining aspect ratio + scaled_pixmap = QPixmap.fromImage(q_image).scaled( + self.video_label.size(), + aspectRatioMode=1 # Qt.KeepAspectRatio + ) + self.video_label.setPixmap(scaled_pixmap) + else: + print("Failed to get frame from camera") + + def closeEvent(self, event): + self.stop_camera() + event.accept() + +if __name__ == "__main__": + app = QApplication(sys.argv) + window = CameraApp() + window.show() + sys.exit(app.exec_()) \ No newline at end of file diff --git a/opencamcpp/CMakeLists.txt b/opencamcpp/CMakeLists.txt new file mode 100644 index 0000000..2a13fdc --- /dev/null +++ b/opencamcpp/CMakeLists.txt @@ -0,0 +1,137 @@ +cmake_minimum_required(VERSION 3.16) +project(opencam LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) + +# Add OpenCV directory to CMAKE_PREFIX_PATH +list(APPEND CMAKE_PREFIX_PATH "/usr/local") + +# Find Qt5 +find_package(Qt5 COMPONENTS + Core + Gui + Widgets + Network + REQUIRED +) + +# Find OpenCV +find_package(OpenCV REQUIRED) +message(STATUS "OpenCV version: ${OpenCV_VERSION}") +message(STATUS "OpenCV libraries: ${OpenCV_LIBS}") +message(STATUS "OpenCV include dirs: ${OpenCV_INCLUDE_DIRS}") + +# Check for CUDA support in OpenCV +if(";${OpenCV_LIBS};" MATCHES ";opencv_cudaimgproc;") + message(STATUS "OpenCV CUDA support found") + find_package(CUDA REQUIRED) + enable_language(CUDA) + set(CUDA_NVCC_FLAGS ${CUDA_NVCC_FLAGS};-O3) + add_definitions(-DWITH_CUDA) + set(HAS_CUDA TRUE) +else() + message(STATUS "OpenCV was built without CUDA support. GPU acceleration will be disabled.") + set(HAS_CUDA FALSE) +endif() + +# Add source files +add_executable(${PROJECT_NAME} + main.cpp +) + +# Link libraries +target_link_libraries(${PROJECT_NAME} PRIVATE + Qt5::Core + Qt5::Gui + Qt5::Widgets + Qt5::Network + ${OpenCV_LIBS} +) + +if(HAS_CUDA) + target_link_libraries(${PROJECT_NAME} PRIVATE + ${CUDA_LIBRARIES} + ) +endif() + +# Include directories +target_include_directories(${PROJECT_NAME} PRIVATE + ${OpenCV_INCLUDE_DIRS} +) + +if(HAS_CUDA) + target_include_directories(${PROJECT_NAME} PRIVATE + ${CUDA_INCLUDE_DIRS} + ) +endif() + +# Installation rules +install(TARGETS ${PROJECT_NAME} + RUNTIME DESTINATION bin + COMPONENT applications +) + +# Function to safely install a library and its dependencies +function(install_library lib_path) + if(EXISTS "${lib_path}") + file(REAL_PATH "${lib_path}" real_path) + install(FILES "${real_path}" + DESTINATION lib + COMPONENT libraries + ) + + # Get library dependencies + execute_process( + COMMAND ldd "${real_path}" + OUTPUT_VARIABLE LDD_OUTPUT + ) + + # Extract and install dependencies + string(REGEX MATCHALL "/[^)]*\\.so[^)]*" DEPS "${LDD_OUTPUT}") + foreach(dep ${DEPS}) + if(EXISTS "${dep}" AND NOT "${dep}" MATCHES "^/lib" AND NOT "${dep}" MATCHES "^/usr/lib") + install(FILES "${dep}" + DESTINATION lib + COMPONENT libraries + ) + endif() + endforeach() + endif() +endfunction() + +# Install OpenCV libraries +foreach(lib ${OpenCV_LIBS}) + get_target_property(lib_location ${lib} LOCATION) + if(lib_location) + install_library("${lib_location}") + endif() +endforeach() + +# Install Qt plugins +if(Qt5_DIR) + file(GLOB_RECURSE QT_PLUGINS "${Qt5_DIR}/../../../plugins/*.so") + foreach(plugin ${QT_PLUGINS}) + get_filename_component(plugin_path "${plugin}" DIRECTORY) + get_filename_component(plugin_dir "${plugin_path}" NAME) + install(FILES "${plugin}" + DESTINATION plugins/${plugin_dir} + COMPONENT plugins + ) + endforeach() +endif() + +# Print configuration summary +message(STATUS "") +message(STATUS "Configuration Summary") +message(STATUS "--------------------") +message(STATUS "OpenCV version: ${OpenCV_VERSION}") +message(STATUS "CUDA support: ${HAS_CUDA}") +message(STATUS "Qt version: ${Qt5_VERSION}") +message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") +message(STATUS "C++ compiler: ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}") +message(STATUS "Installation prefix: ${CMAKE_INSTALL_PREFIX}") +message(STATUS "") \ No newline at end of file diff --git a/opencamcpp/CMakePresets.json b/opencamcpp/CMakePresets.json new file mode 100644 index 0000000..56c4ce3 --- /dev/null +++ b/opencamcpp/CMakePresets.json @@ -0,0 +1,22 @@ +{ + "version": 3, + "configurePresets": [ + { + "hidden": true, + "name": "Qt", + "cacheVariables": { + "CMAKE_PREFIX_PATH": "$env{QTDIR}" + }, + "vendor": { + "qt-project.org/Qt": { + "checksum": "wVa86FgEkvdCTVp1/nxvrkaemJc=" + } + } + } + ], + "vendor": { + "qt-project.org/Presets": { + "checksum": "67SmY24ZeVbebyKD0fGfIzb/bGI=" + } + } +} \ No newline at end of file diff --git a/opencamcpp/CMakeUserPresets.json b/opencamcpp/CMakeUserPresets.json new file mode 100644 index 0000000..4987168 --- /dev/null +++ b/opencamcpp/CMakeUserPresets.json @@ -0,0 +1,40 @@ +{ + "version": 3, + "configurePresets": [ + { + "name": "Qt-Debug", + "inherits": "Qt-Default", + "binaryDir": "${sourceDir}/out/build/debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_CXX_FLAGS": "-DQT_QML_DEBUG" + }, + "environment": { + "QML_DEBUG_ARGS": "-qmljsdebugger=file:{ff00bfed-29c2-47f8-b4aa-00b2873b1a26},block" + } + }, + { + "name": "Qt-Release", + "inherits": "Qt-Default", + "binaryDir": "${sourceDir}/out/build/release", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "hidden": true, + "name": "Qt-Default", + "inherits": null, + "vendor": { + "qt-project.org/Default": { + "checksum": "VoalogTkyWuFomeO1TLFx0olLJ4=" + } + } + } + ], + "vendor": { + "qt-project.org/Presets": { + "checksum": "azRZtZDqJVYwlIJYZufPfOYPwkE=" + } + } +} \ No newline at end of file diff --git a/opencamcpp/build_appimage.sh b/opencamcpp/build_appimage.sh new file mode 100644 index 0000000..daf4642 --- /dev/null +++ b/opencamcpp/build_appimage.sh @@ -0,0 +1,90 @@ +#!/bin/bash + +# Exit on error +set -e + +# Function to copy library and its dependencies +copy_dependencies() { + local binary="$1" + local target_dir="$2" + + # Create target directory if it doesn't exist + mkdir -p "$target_dir" + + # Get all dependencies + ldd "$binary" | while read -r line; do + # Extract the library path + if [[ $line =~ '=>' ]]; then + lib_path=$(echo "$line" | awk '{print $3}') + else + lib_path=$(echo "$line" | awk '{print $1}') + fi + + # Skip system libraries and non-existent files + if [[ -f "$lib_path" && ! "$lib_path" =~ ^/lib && ! "$lib_path" =~ ^/usr/lib ]]; then + cp -L "$lib_path" "$target_dir/" + fi + done +} + +# Create AppDir structure +mkdir -p AppDir/usr/{bin,lib,share/applications,share/icons/hicolor/256x256/apps} + +# Build the application +mkdir -p build +cd build +cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release +make -j$(nproc) + +# Install to AppDir +make DESTDIR=../AppDir install +cd .. + +# Copy desktop file and icon +cp opencam.desktop AppDir/usr/share/applications/ +cp icon.png AppDir/usr/share/icons/hicolor/256x256/apps/opencam.png + +# Copy Qt plugins +mkdir -p AppDir/usr/plugins +for plugin_dir in platforms imageformats xcbglintegrations; do + if [ -d "/usr/lib/x86_64-linux-gnu/qt5/plugins/$plugin_dir" ]; then + cp -r "/usr/lib/x86_64-linux-gnu/qt5/plugins/$plugin_dir" AppDir/usr/plugins/ + fi +done + +# Copy dependencies for the main executable +copy_dependencies "AppDir/usr/bin/opencam" "AppDir/usr/lib" + +# Copy dependencies for Qt plugins +find AppDir/usr/plugins -type f -name "*.so" | while read plugin; do + copy_dependencies "$plugin" "AppDir/usr/lib" +done + +# Create AppRun script +cat > AppDir/AppRun << 'EOF' +#!/bin/bash +HERE="$(dirname "$(readlink -f "${0}")")" +export LD_LIBRARY_PATH="${HERE}/usr/lib:${LD_LIBRARY_PATH}" +export QT_PLUGIN_PATH="${HERE}/usr/plugins" +export QT_QPA_PLATFORM_PLUGIN_PATH="${HERE}/usr/plugins/platforms" +exec "${HERE}/usr/bin/opencam" "$@" +EOF + +chmod +x AppDir/AppRun + +# Download linuxdeploy if not present +if [ ! -f linuxdeploy-x86_64.AppImage ]; then + wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage + chmod +x linuxdeploy-x86_64.AppImage +fi + +if [ ! -f linuxdeploy-plugin-qt-x86_64.AppImage ]; then + wget https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage + chmod +x linuxdeploy-plugin-qt-x86_64.AppImage +fi + +# Create the AppImage +export OUTPUT="OpenCam-x86_64.AppImage" +./linuxdeploy-x86_64.AppImage --appdir AppDir --plugin qt --output appimage + +echo "AppImage created successfully: $OUTPUT" \ No newline at end of file diff --git a/opencamcpp/build_opencv.sh b/opencamcpp/build_opencv.sh new file mode 100644 index 0000000..dda4802 --- /dev/null +++ b/opencamcpp/build_opencv.sh @@ -0,0 +1,83 @@ +#!/bin/bash + +# Exit on error +set -e + +# Function to check CUDA installation +check_cuda() { + if ! command -v nvcc &> /dev/null; then + echo "CUDA not found! Please install CUDA toolkit first." + exit 1 + fi + echo "Found CUDA installation: $(nvcc --version | head -n1)" +} + +# Function to check GPU +check_gpu() { + if ! command -v nvidia-smi &> /dev/null; then + echo "No NVIDIA GPU found or drivers not installed!" + exit 1 + fi + echo "Found GPU: $(nvidia-smi -L)" +} + +# Check CUDA and GPU +check_cuda +check_gpu + +# Get CUDA compute capability +CUDA_ARCH=$(nvidia-smi --query-gpu=compute_cap --format=csv,noheader) +echo "GPU Compute Capability: $CUDA_ARCH" + +# Create build directory +cd ~/opencv_build/opencv +mkdir -p build +cd build + +# Configure OpenCV build +cmake -D CMAKE_BUILD_TYPE=RELEASE \ + -D CMAKE_INSTALL_PREFIX=/usr/local \ + -D OPENCV_EXTRA_MODULES_PATH=../../opencv_contrib/modules \ + -D WITH_CUDA=ON \ + -D WITH_CUDNN=OFF \ + -D OPENCV_DNN_CUDA=ON \ + -D ENABLE_FAST_MATH=1 \ + -D CUDA_FAST_MATH=1 \ + -D CUDA_ARCH_BIN=$CUDA_ARCH \ + -D WITH_CUBLAS=1 \ + -D WITH_TBB=ON \ + -D WITH_V4L=ON \ + -D WITH_QT=ON \ + -D WITH_OPENGL=ON \ + -D WITH_GSTREAMER=ON \ + -D OPENCV_GENERATE_PKGCONFIG=ON \ + -D OPENCV_PC_FILE_NAME=opencv.pc \ + -D OPENCV_ENABLE_NONFREE=ON \ + -D INSTALL_PYTHON_EXAMPLES=OFF \ + -D INSTALL_C_EXAMPLES=OFF \ + -D BUILD_EXAMPLES=OFF \ + -D BUILD_opencv_cudacodec=OFF \ + -D OPENCV_ENABLE_MEMALIGN=ON \ + -D WITH_OPENEXR=ON \ + -D OPENCV_ENABLE_MEMALIGN=ON \ + -D WITH_EIGEN=ON \ + -D ENABLE_PRECOMPILED_HEADERS=OFF \ + .. + +# Build using all available CPU cores +make -j$(nproc) + +# Install +sudo make install +sudo ldconfig + +# Print OpenCV installation information +echo "OpenCV installation completed!" +echo "OpenCV version: $(pkg-config --modversion opencv4)" +echo "Installation location: $(pkg-config --variable=prefix opencv4)" + +# Create a pkg-config path file +sudo sh -c 'echo "/usr/local/lib/pkgconfig" > /etc/ld.so.conf.d/opencv.conf' +sudo ldconfig + +echo "Setup complete! You can now use OpenCV with CUDA support." \ No newline at end of file diff --git a/opencamcpp/icon.png b/opencamcpp/icon.png new file mode 100644 index 0000000..15d1c3c Binary files /dev/null and b/opencamcpp/icon.png differ diff --git a/opencamcpp/icon.svg b/opencamcpp/icon.svg new file mode 100644 index 0000000..7371bb1 --- /dev/null +++ b/opencamcpp/icon.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/opencamcpp/main.cpp b/opencamcpp/main.cpp new file mode 100644 index 0000000..eb0d3f0 --- /dev/null +++ b/opencamcpp/main.cpp @@ -0,0 +1,311 @@ +#include +#ifdef WITH_CUDA +#include +#include +#include +#include +#include +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class FrameProcessor : public QThread { + Q_OBJECT +public: + FrameProcessor(QObject* parent = nullptr) : QThread(parent), running(false) { + #ifdef WITH_CUDA + // Initialize CUDA device + cv::cuda::setDevice(0); + stream = cv::cuda::Stream(); + #endif + } + + void startProcessing() { + running = true; + if (!isRunning()) start(); + } + + void stopProcessing() { + running = false; + wait(); + } + + void setNet(cv::dnn::Net& net) { + std::lock_guard lock(netMutex); + this->net = net; + } + + void queueFrame(const cv::Mat& frame) { + std::lock_guard lock(queueMutex); + frameQueue.push(frame); + condition.notify_one(); + } + +signals: + void frameProcessed(QImage image); + +protected: + void run() override { + while (running) { + cv::Mat frame; + { + std::unique_lock lock(queueMutex); + condition.wait(lock, [this] { return !frameQueue.empty() || !running; }); + if (!running) break; + frame = frameQueue.front(); + frameQueue.pop(); + } + + if (frame.empty()) continue; + + #ifdef WITH_CUDA + // GPU processing path + cv::cuda::GpuMat gpuFrame; + gpuFrame.upload(frame, stream); + + cv::cuda::GpuMat gpuResized; + cv::cuda::resize(gpuFrame, gpuResized, cv::Size(416, 416), 0, 0, cv::INTER_CUBIC, stream); + + // Download for DNN processing (until we implement CUDA DNN) + cv::Mat resized; + gpuResized.download(resized, stream); + #else + // CPU processing path + cv::Mat resized; + cv::resize(frame, resized, cv::Size(416, 416), 0, 0, cv::INTER_CUBIC); + #endif + + // Object detection + cv::Mat blob; + cv::dnn::blobFromImage(resized, blob, 1/255.0, cv::Size(416, 416), cv::Scalar(0,0,0), true, false); + + { + std::lock_guard lock(netMutex); + net.setInput(blob); + std::vector outs; + net.forward(outs, net.getUnconnectedOutLayersNames()); + + std::vector classIds; + std::vector confidences; + std::vector boxes; + + for (const auto& out : outs) { + float* data = (float*)out.data; + for (int j = 0; j < out.rows; ++j, data += out.cols) { + cv::Mat scores = out.row(j).colRange(5, out.cols); + cv::Point classIdPoint; + double confidence; + cv::minMaxLoc(scores, 0, &confidence, 0, &classIdPoint); + + if (confidence > 0.5) { + int centerX = (int)(data[0] * frame.cols); + int centerY = (int)(data[1] * frame.rows); + int width = (int)(data[2] * frame.cols); + int height = (int)(data[3] * frame.rows); + int left = centerX - width / 2; + int top = centerY - height / 2; + + classIds.push_back(classIdPoint.x); + confidences.push_back((float)confidence); + boxes.push_back(cv::Rect(left, top, width, height)); + } + } + } + + std::vector indices; + cv::dnn::NMSBoxes(boxes, confidences, 0.5, 0.4, indices); + + for (size_t i = 0; i < indices.size(); ++i) { + int idx = indices[i]; + cv::Rect box = boxes[idx]; + cv::rectangle(frame, box, cv::Scalar(0, 255, 0), 2); + } + } + + #ifdef WITH_CUDA + // Convert to RGB on GPU + cv::cuda::GpuMat gpuRGB; + cv::cuda::cvtColor(gpuFrame, gpuRGB, cv::COLOR_BGR2RGB, 0, stream); + cv::Mat rgb; + gpuRGB.download(rgb, stream); + #else + // Convert to RGB on CPU + cv::Mat rgb; + cv::cvtColor(frame, rgb, cv::COLOR_BGR2RGB); + #endif + + // Convert to QImage + QImage qimg(rgb.data, rgb.cols, rgb.rows, rgb.step, QImage::Format_RGB888); + emit frameProcessed(qimg.copy()); + } + } + +private: + std::atomic running; + #ifdef WITH_CUDA + cv::cuda::Stream stream; + #endif + cv::dnn::Net net; + std::queue frameQueue; + std::mutex queueMutex; + std::mutex netMutex; + std::condition_variable condition; +}; + +class CameraApp : public QMainWindow { + Q_OBJECT +public: + CameraApp(QWidget* parent = nullptr) : QMainWindow(parent) { + setWindowTitle("GPU-Accelerated Object Detection"); + setGeometry(100, 100, 1280, 720); + + #ifdef WITH_CUDA + // Check CUDA device + int deviceCount = cv::cuda::getCudaEnabledDeviceCount(); + if (deviceCount == 0) { + QMessageBox::warning(this, "Warning", "No CUDA capable devices found. Falling back to CPU processing."); + } else { + cv::cuda::printCudaDeviceInfo(0); + } + #endif + + setupUI(); + initializeObjectDetection(); + processor = new FrameProcessor(this); + connect(processor, &FrameProcessor::frameProcessed, this, &CameraApp::updateFrame); + } + + ~CameraApp() { + stopCamera(); + if (processor) { + processor->stopProcessing(); + delete processor; + } + } + +private slots: + void startCamera() { + if (!cap.isOpened()) { + cap.open(currentCamera); + cap.set(cv::CAP_PROP_FRAME_WIDTH, 1920); + cap.set(cv::CAP_PROP_FRAME_HEIGHT, 1080); + cap.set(cv::CAP_PROP_FPS, 120); // Request maximum FPS + + #ifdef WITH_CUDA + // Try to use CUDA video decoder if available + cap.set(cv::CAP_PROP_CUDA_DEVICE, 0); + #endif + + if (!cap.isOpened()) { + QMessageBox::critical(this, "Error", "Failed to open camera!"); + return; + } + } + + processor->startProcessing(); + timer->start(0); // Run as fast as possible + } + + void stopCamera() { + timer->stop(); + processor->stopProcessing(); + if (cap.isOpened()) { + cap.release(); + } + } + + void captureFrame() { + if (!cap.isOpened()) return; + + cv::Mat frame; + cap >> frame; + if (frame.empty()) return; + + processor->queueFrame(frame); + } + + void updateFrame(const QImage& image) { + QPixmap pixmap = QPixmap::fromImage(image); + videoLabel->setPixmap(pixmap.scaled(videoLabel->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); + } + +private: + void setupUI() { + QWidget* centralWidget = new QWidget(this); + setCentralWidget(centralWidget); + QVBoxLayout* layout = new QVBoxLayout(centralWidget); + + videoLabel = new QLabel(this); + videoLabel->setMinimumSize(640, 480); + layout->addWidget(videoLabel); + + cameraSelect = new QComboBox(this); + detectCameras(); + layout->addWidget(cameraSelect); + + startButton = new QPushButton("Start Camera", this); + connect(startButton, &QPushButton::clicked, this, &CameraApp::startCamera); + layout->addWidget(startButton); + + stopButton = new QPushButton("Stop Camera", this); + connect(stopButton, &QPushButton::clicked, this, &CameraApp::stopCamera); + layout->addWidget(stopButton); + + timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, &CameraApp::captureFrame); + } + + void detectCameras() { + for (int i = 0; i < 10; ++i) { + cv::VideoCapture temp(i); + if (temp.isOpened()) { + cameraSelect->addItem("Camera " + QString::number(i), i); + temp.release(); + } + } + } + + void initializeObjectDetection() { + // Download and load YOLO model (implementation similar to Python version) + // ... + } + + QLabel* videoLabel; + QComboBox* cameraSelect; + QPushButton* startButton; + QPushButton* stopButton; + QTimer* timer; + cv::VideoCapture cap; + int currentCamera = 0; + FrameProcessor* processor; +}; + +#include "main.moc" + +int main(int argc, char* argv[]) { + QApplication app(argc, argv); + CameraApp window; + window.show(); + return app.exec(); +} \ No newline at end of file diff --git a/opencamcpp/opencam.desktop b/opencamcpp/opencam.desktop new file mode 100644 index 0000000..a136032 --- /dev/null +++ b/opencamcpp/opencam.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Type=Application +Name=OpenCam +Comment=Real-time object detection using OpenCV and YOLO +Exec=opencam +Icon=opencam +Categories=Graphics;Video; +Keywords=camera;detection;opencv;yolo; +Terminal=false +StartupNotify=true \ No newline at end of file diff --git a/yolov3.cfg b/yolov3.cfg new file mode 100644 index 0000000..938ffff --- /dev/null +++ b/yolov3.cfg @@ -0,0 +1,789 @@ +[net] +# Testing +# batch=1 +# subdivisions=1 +# Training +batch=64 +subdivisions=16 +width=608 +height=608 +channels=3 +momentum=0.9 +decay=0.0005 +angle=0 +saturation = 1.5 +exposure = 1.5 +hue=.1 + +learning_rate=0.001 +burn_in=1000 +max_batches = 500200 +policy=steps +steps=400000,450000 +scales=.1,.1 + +[convolutional] +batch_normalize=1 +filters=32 +size=3 +stride=1 +pad=1 +activation=leaky + +# Downsample + +[convolutional] +batch_normalize=1 +filters=64 +size=3 +stride=2 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=32 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=64 +size=3 +stride=1 +pad=1 +activation=leaky + +[shortcut] +from=-3 +activation=linear + +# Downsample + +[convolutional] +batch_normalize=1 +filters=128 +size=3 +stride=2 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=64 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=128 +size=3 +stride=1 +pad=1 +activation=leaky + +[shortcut] +from=-3 +activation=linear + +[convolutional] +batch_normalize=1 +filters=64 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=128 +size=3 +stride=1 +pad=1 +activation=leaky + +[shortcut] +from=-3 +activation=linear + +# Downsample + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=2 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=leaky + +[shortcut] +from=-3 +activation=linear + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=leaky + +[shortcut] +from=-3 +activation=linear + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=leaky + +[shortcut] +from=-3 +activation=linear + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=leaky + +[shortcut] +from=-3 +activation=linear + + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=leaky + +[shortcut] +from=-3 +activation=linear + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=leaky + +[shortcut] +from=-3 +activation=linear + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=leaky + +[shortcut] +from=-3 +activation=linear + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=leaky + +[shortcut] +from=-3 +activation=linear + +# Downsample + +[convolutional] +batch_normalize=1 +filters=512 +size=3 +stride=2 +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 + +[shortcut] +from=-3 +activation=linear + + +[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 + +[shortcut] +from=-3 +activation=linear + + +[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 + +[shortcut] +from=-3 +activation=linear + + +[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 + +[shortcut] +from=-3 +activation=linear + +[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 + +[shortcut] +from=-3 +activation=linear + + +[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 + +[shortcut] +from=-3 +activation=linear + + +[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 + +[shortcut] +from=-3 +activation=linear + +[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 + +[shortcut] +from=-3 +activation=linear + +# Downsample + +[convolutional] +batch_normalize=1 +filters=1024 +size=3 +stride=2 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=512 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=1024 +size=3 +stride=1 +pad=1 +activation=leaky + +[shortcut] +from=-3 +activation=linear + +[convolutional] +batch_normalize=1 +filters=512 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=1024 +size=3 +stride=1 +pad=1 +activation=leaky + +[shortcut] +from=-3 +activation=linear + +[convolutional] +batch_normalize=1 +filters=512 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=1024 +size=3 +stride=1 +pad=1 +activation=leaky + +[shortcut] +from=-3 +activation=linear + +[convolutional] +batch_normalize=1 +filters=512 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=1024 +size=3 +stride=1 +pad=1 +activation=leaky + +[shortcut] +from=-3 +activation=linear + +###################### + +[convolutional] +batch_normalize=1 +filters=512 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=1024 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=512 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=1024 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=512 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=1024 +activation=leaky + +[convolutional] +size=1 +stride=1 +pad=1 +filters=255 +activation=linear + + +[yolo] +mask = 6,7,8 +anchors = 10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156,198, 373,326 +classes=80 +num=9 +jitter=.3 +ignore_thresh = .7 +truth_thresh = 1 +random=1 + + +[route] +layers = -4 + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=leaky + +[upsample] +stride=2 + +[route] +layers = -1, 61 + + + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=512 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=512 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=512 +activation=leaky + +[convolutional] +size=1 +stride=1 +pad=1 +filters=255 +activation=linear + + +[yolo] +mask = 3,4,5 +anchors = 10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156,198, 373,326 +classes=80 +num=9 +jitter=.3 +ignore_thresh = .7 +truth_thresh = 1 +random=1 + + + +[route] +layers = -4 + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=leaky + +[upsample] +stride=2 + +[route] +layers = -1, 36 + + + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=256 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=256 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=256 +activation=leaky + +[convolutional] +size=1 +stride=1 +pad=1 +filters=255 +activation=linear + + +[yolo] +mask = 0,1,2 +anchors = 10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156,198, 373,326 +classes=80 +num=9 +jitter=.3 +ignore_thresh = .7 +truth_thresh = 1 +random=1 +