ты мать эта говно и пидарас

This commit is contained in:
rattatwinko
2025-05-26 20:29:02 +02:00
parent 3c847cee01
commit 64b472ad8e
7 changed files with 1330 additions and 4 deletions

Binary file not shown.

17
logs/access.log Normal file
View File

@@ -0,0 +1,17 @@
127.0.0.1 - - [26/May/2025:20:25:21 +0200] "GET / HTTP/1.1" 200 12917 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:138.0) Gecko/20100101 Firefox/138.0"
127.0.0.1 - - [26/May/2025:20:25:22 +0200] "GET /cameras HTTP/1.1" 200 192 "http://localhost:5000/" "Mozilla/5.0 (X11; Linux x86_64; rv:138.0) Gecko/20100101 Firefox/138.0"
127.0.0.1 - - [26/May/2025:20:25:22 +0200] "GET /favicon.ico HTTP/1.1" 404 207 "http://localhost:5000/" "Mozilla/5.0 (X11; Linux x86_64; rv:138.0) Gecko/20100101 Firefox/138.0"
127.0.0.1 - - [26/May/2025:20:25:27 +0200] "GET /cameras HTTP/1.1" 200 192 "http://localhost:5000/" "Mozilla/5.0 (X11; Linux x86_64; rv:138.0) Gecko/20100101 Firefox/138.0"
127.0.0.1 - - [26/May/2025:20:25:32 +0200] "GET /cameras HTTP/1.1" 200 192 "http://localhost:5000/" "Mozilla/5.0 (X11; Linux x86_64; rv:138.0) Gecko/20100101 Firefox/138.0"
127.0.0.1 - - [26/May/2025:20:25:33 +0200] "GET /add_camera/0 HTTP/1.1" 200 17 "http://localhost:5000/" "Mozilla/5.0 (X11; Linux x86_64; rv:138.0) Gecko/20100101 Firefox/138.0"
127.0.0.1 - - [26/May/2025:20:25:37 +0200] "GET /cameras HTTP/1.1" 200 3 "http://localhost:5000/" "Mozilla/5.0 (X11; Linux x86_64; rv:138.0) Gecko/20100101 Firefox/138.0"
127.0.0.1 - - [26/May/2025:20:25:40 +0200] "GET /cameras HTTP/1.1" 200 3 "http://localhost:5000/" "Mozilla/5.0 (X11; Linux x86_64; rv:138.0) Gecko/20100101 Firefox/138.0"
127.0.0.1 - - [26/May/2025:20:25:42 +0200] "GET /cameras HTTP/1.1" 200 3 "http://localhost:5000/" "Mozilla/5.0 (X11; Linux x86_64; rv:138.0) Gecko/20100101 Firefox/138.0"
127.0.0.1 - - [26/May/2025:20:25:44 +0200] "GET /video_feed/0 HTTP/1.1" 200 8815858 "http://localhost:5000/" "Mozilla/5.0 (X11; Linux x86_64; rv:138.0) Gecko/20100101 Firefox/138.0"
127.0.0.1 - - [26/May/2025:20:28:42 +0200] "GET / HTTP/1.1" 200 10364 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:138.0) Gecko/20100101 Firefox/138.0"
127.0.0.1 - - [26/May/2025:20:28:42 +0200] "GET /check_model HTTP/1.1" 404 207 "http://localhost:5000/" "Mozilla/5.0 (X11; Linux x86_64; rv:138.0) Gecko/20100101 Firefox/138.0"
127.0.0.1 - - [26/May/2025:20:28:44 +0200] "GET /scan_cameras HTTP/1.1" 404 207 "http://localhost:5000/" "Mozilla/5.0 (X11; Linux x86_64; rv:138.0) Gecko/20100101 Firefox/138.0"
127.0.0.1 - - [26/May/2025:20:28:46 +0200] "GET /scan_cameras HTTP/1.1" 404 207 "http://localhost:5000/" "Mozilla/5.0 (X11; Linux x86_64; rv:138.0) Gecko/20100101 Firefox/138.0"
127.0.0.1 - - [26/May/2025:20:28:46 +0200] "GET /scan_cameras HTTP/1.1" 404 207 "http://localhost:5000/" "Mozilla/5.0 (X11; Linux x86_64; rv:138.0) Gecko/20100101 Firefox/138.0"
127.0.0.1 - - [26/May/2025:20:28:46 +0200] "GET /scan_cameras HTTP/1.1" 404 207 "http://localhost:5000/" "Mozilla/5.0 (X11; Linux x86_64; rv:138.0) Gecko/20100101 Firefox/138.0"
127.0.0.1 - - [26/May/2025:20:28:50 +0200] "GET /scan_cameras HTTP/1.1" 404 207 "http://localhost:5000/" "Mozilla/5.0 (X11; Linux x86_64; rv:138.0) Gecko/20100101 Firefox/138.0"

88
logs/error.log Normal file
View File

@@ -0,0 +1,88 @@
[2025-05-26 20:24:22 +0200] [824587] [INFO] Starting gunicorn 23.0.0
[2025-05-26 20:24:22 +0200] [824587] [INFO] Listening at: http://0.0.0.0:5000 (824587)
[2025-05-26 20:24:22 +0200] [824587] [INFO] Using worker: gthread
[2025-05-26 20:24:22 +0200] [825529] [INFO] Booting worker with pid: 825529
[2025-05-26 20:24:22 +0200] [825544] [INFO] Booting worker with pid: 825544
[2025-05-26 20:24:22 +0200] [825556] [INFO] Booting worker with pid: 825556
[2025-05-26 20:24:22 +0200] [825571] [INFO] Booting worker with pid: 825571
[2025-05-26 20:24:23 +0200] [825584] [INFO] Booting worker with pid: 825584
[2025-05-26 20:24:23 +0200] [825600] [INFO] Booting worker with pid: 825600
[2025-05-26 20:24:23 +0200] [825601] [INFO] Booting worker with pid: 825601
[2025-05-26 20:24:23 +0200] [825602] [INFO] Booting worker with pid: 825602
[2025-05-26 20:24:23 +0200] [825639] [INFO] Booting worker with pid: 825639
[2025-05-26 20:24:23 +0200] [825651] [INFO] Booting worker with pid: 825651
[2025-05-26 20:24:23 +0200] [825666] [INFO] Booting worker with pid: 825666
Found YOLO model in directory: mucapy/models
[2025-05-26 20:24:23 +0200] [825682] [INFO] Booting worker with pid: 825682
Found YOLO model in directory: mucapy/models
Found YOLO model in directory: mucapy/models
Found YOLO model in directory: mucapy/models
Found YOLO model in directory: mucapy/models
Found YOLO model in directory: mucapy/models
Found YOLO model in directory: mucapy/models
Found YOLO model in directory: mucapy/models
Found YOLO model in directory: mucapy/models
Found YOLO model in directory: mucapy/models
Found YOLO model in directory: mucapy/models
Found YOLO model in directory: mucapy/models
[ WARN:0@58.649] global cap_v4l.cpp:913 open VIDEOIO(V4L2:/dev/video1): can't open camera by index
[ERROR:0@58.649] global obsensor_uvc_stream_channel.cpp:158 getStreamChannelGroup Camera index out of range
[ WARN:0@64.731] global cap_v4l.cpp:913 open VIDEOIO(V4L2:/dev/video1): can't open camera by index
[ERROR:0@64.732] global obsensor_uvc_stream_channel.cpp:158 getStreamChannelGroup Camera index out of range
[ WARN:0@69.536] global cap_v4l.cpp:913 open VIDEOIO(V4L2:/dev/video1): can't open camera by index
[ERROR:0@69.537] global obsensor_uvc_stream_channel.cpp:158 getStreamChannelGroup Camera index out of range
[ WARN:0@74.431] global cap_v4l.cpp:913 open VIDEOIO(V4L2:/dev/video0): can't open camera by index
[ERROR:0@74.431] global obsensor_uvc_stream_channel.cpp:158 getStreamChannelGroup Camera index out of range
[ WARN:0@74.431] global cap_v4l.cpp:913 open VIDEOIO(V4L2:/dev/video1): can't open camera by index
[ERROR:0@74.431] global obsensor_uvc_stream_channel.cpp:158 getStreamChannelGroup Camera index out of range
[ WARN:0@76.868] global cap_v4l.cpp:913 open VIDEOIO(V4L2:/dev/video0): can't open camera by index
[ERROR:0@76.868] global obsensor_uvc_stream_channel.cpp:158 getStreamChannelGroup Camera index out of range
[ WARN:0@76.868] global cap_v4l.cpp:913 open VIDEOIO(V4L2:/dev/video1): can't open camera by index
[ERROR:0@76.868] global obsensor_uvc_stream_channel.cpp:158 getStreamChannelGroup Camera index out of range
[ WARN:0@79.438] global cap_v4l.cpp:913 open VIDEOIO(V4L2:/dev/video0): can't open camera by index
[ERROR:0@79.438] global obsensor_uvc_stream_channel.cpp:158 getStreamChannelGroup Camera index out of range
[ WARN:0@79.438] global cap_v4l.cpp:913 open VIDEOIO(V4L2:/dev/video1): can't open camera by index
[ERROR:0@79.438] global obsensor_uvc_stream_channel.cpp:158 getStreamChannelGroup Camera index out of range
[2025-05-26 20:25:47 +0200] [824587] [INFO] Handling signal: int
[2025-05-26 20:25:47 +0200] [825556] [INFO] Worker exiting (pid: 825556)
[2025-05-26 20:25:47 +0200] [825600] [INFO] Worker exiting (pid: 825600)
[2025-05-26 20:25:47 +0200] [825602] [INFO] Worker exiting (pid: 825602)
[2025-05-26 20:25:47 +0200] [825584] [INFO] Worker exiting (pid: 825584)
[2025-05-26 20:25:47 +0200] [825639] [INFO] Worker exiting (pid: 825639)
[2025-05-26 20:25:47 +0200] [825544] [INFO] Worker exiting (pid: 825544)
[2025-05-26 20:25:47 +0200] [825666] [INFO] Worker exiting (pid: 825666)
[2025-05-26 20:25:47 +0200] [825571] [INFO] Worker exiting (pid: 825571)
[2025-05-26 20:25:47 +0200] [825651] [INFO] Worker exiting (pid: 825651)
[2025-05-26 20:25:47 +0200] [825682] [INFO] Worker exiting (pid: 825682)
[2025-05-26 20:25:47 +0200] [825529] [INFO] Worker exiting (pid: 825529)
[2025-05-26 20:25:47 +0200] [825601] [INFO] Worker exiting (pid: 825601)
terminate called without an active exception
[2025-05-26 20:25:49 +0200] [824587] [ERROR] Worker (pid:825651) was sent code 134!
[2025-05-26 20:25:49 +0200] [824587] [INFO] Shutting down: Master
[2025-05-26 20:28:26 +0200] [833260] [INFO] Starting gunicorn 23.0.0
[2025-05-26 20:28:26 +0200] [833260] [INFO] Listening at: http://0.0.0.0:5000 (833260)
[2025-05-26 20:28:26 +0200] [833260] [INFO] Using worker: gthread
[2025-05-26 20:28:26 +0200] [833415] [INFO] Booting worker with pid: 833415
[2025-05-26 20:28:26 +0200] [833428] [INFO] Booting worker with pid: 833428
[2025-05-26 20:28:26 +0200] [833448] [INFO] Booting worker with pid: 833448
[2025-05-26 20:28:26 +0200] [833464] [INFO] Booting worker with pid: 833464
[2025-05-26 20:28:26 +0200] [833481] [INFO] Booting worker with pid: 833481
[2025-05-26 20:28:26 +0200] [833493] [INFO] Booting worker with pid: 833493
[2025-05-26 20:28:26 +0200] [833508] [INFO] Booting worker with pid: 833508
[2025-05-26 20:28:26 +0200] [833509] [INFO] Booting worker with pid: 833509
Found YOLO model in directory: mucapy/models
[2025-05-26 20:28:26 +0200] [833537] [INFO] Booting worker with pid: 833537
[2025-05-26 20:28:27 +0200] [833556] [INFO] Booting worker with pid: 833556
Found YOLO model in directory: mucapy/models
[2025-05-26 20:28:27 +0200] [833570] [INFO] Booting worker with pid: 833570
[2025-05-26 20:28:27 +0200] [833585] [INFO] Booting worker with pid: 833585
Found YOLO model in directory: mucapy/models
Found YOLO model in directory: mucapy/models
Found YOLO model in directory: mucapy/models
Found YOLO model in directory: mucapy/models
Found YOLO model in directory: mucapy/models
Found YOLO model in directory: mucapy/models
Found YOLO model in directory: mucapy/models
Found YOLO model in directory: mucapy/models
Found YOLO model in directory: mucapy/models
Found YOLO model in directory: mucapy/models

View File

@@ -1,4 +1,14 @@
opencv-python>=4.5.0
opencv-contrib-python>=4.5.0
numpy>=1.19.0
PyQt5>=5.15.0
# Web framework and extensions
Flask>=3.0.0
Flask-Cors>=4.0.0
Werkzeug>=3.0.0
gunicorn>=21.2.0
# Core dependencies
opencv-python-headless>=4.8.0
# Flask dependencies
click>=8.1.7
itsdangerous>=2.1.2
Jinja2>=3.1.2
MarkupSafe>=2.1.3

235
run_server.sh Executable file
View File

@@ -0,0 +1,235 @@
#!/bin/bash
# Exit on error
set -e
# Function to print error messages
error() {
echo -e "\e[31mERROR:\e[0m $1" >&2
exit 1
}
# Function to print success messages
success() {
echo -e "\e[32mSUCCESS:\e[0m $1"
}
# Function to print info messages
info() {
echo -e "\e[34mINFO:\e[0m $1"
}
# Function to check if a command exists
check_command() {
if ! command -v "$1" &> /dev/null; then
error "Required command '$1' not found. Please install it first."
fi
}
# Function to compare version numbers
version_compare() {
if [[ "$1" == "$2" ]]; then
echo 0
return
fi
local IFS=.
local i ver1=($1) ver2=($2)
# Fill empty positions in ver1 with zeros
for ((i=${#ver1[@]}; i<${#ver2[@]}; i++)); do
ver1[i]=0
done
for ((i=0; i<${#ver1[@]}; i++)); do
# Fill empty positions in ver2 with zeros
if [[ -z ${ver2[i]} ]]; then
ver2[i]=0
fi
if ((10#${ver1[i]} > 10#${ver2[i]})); then
echo 1
return
fi
if ((10#${ver1[i]} < 10#${ver2[i]})); then
echo -1
return
fi
done
echo 0
}
# Check for required system commands
check_command python3
check_command pip3
# Check Python version
PYTHON_VERSION=$(python3 -c 'import sys; print(".".join(map(str, sys.version_info[:2])))')
MIN_VERSION="3.8"
if [ $(version_compare "$PYTHON_VERSION" "$MIN_VERSION") -lt 0 ]; then
error "Python version must be $MIN_VERSION or higher (found $PYTHON_VERSION)"
fi
success "Python version check passed (found $PYTHON_VERSION)"
# Function to install packages on Fedora
install_fedora_deps() {
info "Installing Fedora dependencies..."
# Check which packages need to be installed
local packages=()
local check_packages=(
"python3-devel"
"gcc"
"python3-pip"
"python3-setuptools"
"python3-numpy"
"python3-opencv"
"python3-flask"
"python3-gunicorn"
"bc"
"lsof"
)
for pkg in "${check_packages[@]}"; do
if ! rpm -q "$pkg" &>/dev/null; then
packages+=("$pkg")
fi
done
# Only run dnf if there are packages to install
if [ ${#packages[@]} -gt 0 ]; then
info "Installing missing packages: ${packages[*]}"
sudo dnf install -y "${packages[@]}" || error "Failed to install required packages"
else
info "All required system packages are already installed"
fi
}
# Function to install packages on Ubuntu/Debian
install_ubuntu_deps() {
info "Installing Ubuntu/Debian dependencies..."
sudo apt-get update || error "Failed to update package lists"
sudo apt-get install -y \
python3-dev \
python3-pip \
python3-venv \
python3-numpy \
python3-opencv \
python3-flask \
python3-gunicorn \
bc \
lsof \
|| error "Failed to install required packages"
}
# Check and install system dependencies based on distribution
install_system_deps() {
if [ -f /etc/os-release ]; then
. /etc/os-release
case $ID in
fedora)
install_fedora_deps
;;
ubuntu|debian)
install_ubuntu_deps
;;
*)
info "Unknown distribution. Please ensure you have the following packages installed:"
echo "- Python development package (python3-devel/python3-dev)"
echo "- GCC compiler"
echo "- Python pip"
echo "- Python venv"
echo "- Python numpy"
echo "- Python OpenCV"
echo "- Python Flask"
echo "- Python Gunicorn"
echo "- bc"
echo "- lsof"
;;
esac
fi
}
# Install system dependencies
install_system_deps
# Create and activate virtual environment
if [ ! -d "venv" ]; then
info "Creating virtual environment..."
# Create venv with system packages to use system numpy and opencv
python3 -m venv venv --system-site-packages || error "Failed to create virtual environment"
success "Virtual environment created successfully"
fi
# Ensure virtual environment is activated
if [ -z "$VIRTUAL_ENV" ]; then
info "Activating virtual environment..."
source venv/bin/activate || error "Failed to activate virtual environment"
fi
# Upgrade pip to latest version
info "Upgrading pip..."
python3 -m pip install --upgrade pip setuptools wheel || error "Failed to upgrade pip and setuptools"
# Create or update requirements.txt with compatible package versions
if [ ! -f "requirements.txt" ]; then
info "Creating requirements.txt..."
cat > requirements.txt << EOF
# Web framework and extensions
Flask>=3.0.0
Flask-Cors>=4.0.0
Werkzeug>=3.0.0
gunicorn>=21.2.0
# Core dependencies
opencv-python-headless>=4.8.0
# Flask dependencies
click>=8.1.7
itsdangerous>=2.1.2
Jinja2>=3.1.2
MarkupSafe>=2.1.3
EOF
success "Created requirements.txt"
fi
# Install requirements with better error handling
info "Installing/updating requirements..."
# First ensure pip is up to date
pip install --upgrade pip
# Install packages with specific options for better compatibility
PYTHONWARNINGS="ignore" pip install \
--no-cache-dir \
--prefer-binary \
--only-binary :all: \
-r requirements.txt || error "Failed to install requirements"
success "Requirements installed successfully"
# Create necessary directories
info "Creating required directories..."
mkdir -p logs || error "Failed to create logs directory"
success "Created required directories"
# Function to check if port is available
check_port() {
if lsof -Pi :5000 -sTCP:LISTEN -t >/dev/null ; then
error "Port 5000 is already in use. Please stop the other process first."
fi
}
# Check if port is available
check_port
# Start Gunicorn with modern settings
info "Starting server with Gunicorn..."
exec gunicorn web_server:app \
--bind 0.0.0.0:5000 \
--workers $(nproc) \
--worker-class gthread \
--threads 2 \
--timeout 120 \
--access-logfile logs/access.log \
--error-logfile logs/error.log \
--capture-output \
--log-level info \
--reload \
--max-requests 1000 \
--max-requests-jitter 50 \
|| error "Failed to start Gunicorn server"

321
templates/index.html Normal file
View File

@@ -0,0 +1,321 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Camera Viewer</title>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<style>
:root {
--primary-color: #000080;
--bg-color: #c0c0c0;
--window-bg: #ffffff;
--border-light: #ffffff;
--border-dark: #808080;
--border-darker: #404040;
--text-color: #000000;
--button-face: #c0c0c0;
--title-bar: #000080;
--title-text: #ffffff;
--button-highlight: #ffffff;
--button-shadow: #808080;
--button-shadow-dark: #404040;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: 'IBM Plex Mono', monospace;
}
body {
background-color: var(--bg-color);
padding: 20px;
min-height: 100vh;
}
.window {
background-color: var(--window-bg);
border: 2px solid var(--border-darker);
border-top: 2px solid var(--border-light);
border-left: 2px solid var(--border-light);
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.2);
margin: 0 auto;
max-width: 1600px;
}
.title-bar {
background-color: var(--title-bar);
color: var(--title-text);
padding: 4px 8px;
display: flex;
justify-content: space-between;
align-items: center;
font-weight: bold;
}
.title-bar-controls {
display: flex;
gap: 4px;
}
.title-bar-button {
width: 20px;
height: 20px;
background-color: var(--button-face);
border: 1px solid var(--border-darker);
border-top: 1px solid var(--button-highlight);
border-left: 1px solid var(--button-highlight);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.content {
padding: 16px;
}
.camera-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 16px;
margin-bottom: 16px;
}
.camera-container {
position: relative;
background-color: var(--bg-color);
border: 2px solid var(--border-darker);
border-top: 2px solid var(--border-light);
border-left: 2px solid var(--border-light);
padding: 8px;
min-height: 300px;
display: flex;
flex-direction: column;
}
.camera-title {
background-color: var(--title-bar);
color: var(--title-text);
padding: 4px 8px;
margin-bottom: 8px;
font-size: 14px;
}
.camera-feed {
flex: 1;
background-color: #000;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
position: relative;
}
.camera-feed img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.status-bar {
background-color: var(--button-face);
border-top: 2px solid var(--border-darker);
padding: 4px 8px;
display: flex;
justify-content: space-between;
font-size: 12px;
margin-top: 8px;
}
.control-panel {
background-color: var(--button-face);
border: 2px solid var(--border-darker);
border-top: 2px solid var(--border-light);
border-left: 2px solid var(--border-light);
padding: 16px;
margin-bottom: 16px;
}
.button {
background-color: var(--button-face);
border: 2px solid var(--border-darker);
border-top: 2px solid var(--button-highlight);
border-left: 2px solid var(--button-highlight);
padding: 6px 12px;
cursor: pointer;
font-size: 14px;
min-width: 100px;
text-align: center;
}
.button:active {
border: 2px solid var(--border-darker);
border-bottom: 2px solid var(--button-highlight);
border-right: 2px solid var(--button-highlight);
}
.button.primary {
background-color: var(--primary-color);
color: var(--title-text);
}
.status-message {
font-family: 'IBM Plex Mono', monospace;
font-size: 12px;
margin-top: 8px;
}
@media (max-width: 768px) {
.camera-grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="window">
<div class="title-bar">
<span>Multi-Camera YOLO Detection</span>
<div class="title-bar-controls">
<div class="title-bar-button">_</div>
<div class="title-bar-button"></div>
<div class="title-bar-button">×</div>
</div>
</div>
<div class="content">
<div class="control-panel">
<button class="button primary" onclick="startSystem()">Start System</button>
<div class="status-message" id="modelStatus">Status: Initializing...</div>
</div>
<div class="camera-grid" id="cameraGrid">
<!-- Camera feeds will be added here dynamically -->
</div>
</div>
<div class="status-bar">
<span id="systemStatus">Ready</span>
<span id="modelName"></span>
</div>
</div>
<script>
let activeCameras = new Set();
let isSystemRunning = false;
async function startSystem() {
if (isSystemRunning) {
return;
}
const startButton = document.querySelector('.button.primary');
startButton.textContent = 'Starting...';
startButton.disabled = true;
try {
// Auto-scan for cameras
const cameras = await scanCameras();
// Initialize camera grid
initializeCameraGrid(cameras);
// Start camera streams
for (const camera of cameras) {
startCameraStream(camera);
}
isSystemRunning = true;
updateStatus('System running');
startButton.textContent = 'System Running';
} catch (error) {
console.error('Error starting system:', error);
updateStatus('Error starting system');
startButton.textContent = 'Start System';
startButton.disabled = false;
}
}
async function scanCameras() {
const response = await fetch('/scan_cameras');
const data = await response.json();
return data.cameras;
}
function initializeCameraGrid(cameras) {
const grid = document.getElementById('cameraGrid');
grid.innerHTML = '';
cameras.forEach((camera, index) => {
const container = document.createElement('div');
container.className = 'camera-container';
container.innerHTML = `
<div class="camera-title">Camera ${index + 1}</div>
<div class="camera-feed">
<img id="camera${camera.id}" src="" alt="Camera ${index + 1}"
style="width: ${camera.width}px; height: ${camera.height}px;">
</div>
<div class="status-bar">
<span>Resolution: ${camera.width}x${camera.height}</span>
<span id="status${camera.id}">Connecting...</span>
</div>
`;
grid.appendChild(container);
});
}
function startCameraStream(camera) {
const img = document.getElementById(`camera${camera.id}`);
const statusElement = document.getElementById(`status${camera.id}`);
function updateStream() {
if (!isSystemRunning) return;
img.src = `/video_feed/${camera.id}?t=${Date.now()}`;
statusElement.textContent = 'Connected';
activeCameras.add(camera.id);
img.onload = () => {
if (isSystemRunning) {
requestAnimationFrame(updateStream);
}
};
img.onerror = () => {
statusElement.textContent = 'Error';
activeCameras.delete(camera.id);
setTimeout(() => {
if (isSystemRunning) {
updateStream();
}
}, 1000);
};
}
updateStream();
}
function updateStatus(message) {
document.getElementById('systemStatus').textContent = message;
}
// Check for model on load
fetch('/check_model')
.then(response => response.json())
.then(data => {
const modelStatus = document.getElementById('modelStatus');
const modelName = document.getElementById('modelName');
if (data.model_loaded) {
modelStatus.textContent = 'Status: Model loaded';
modelName.textContent = `Model: ${data.model_name || 'Unknown'}`;
} else {
modelStatus.textContent = 'Status: No model loaded';
}
});
</script>
</body>
</html>
</html>

655
web_server.py Normal file
View File

@@ -0,0 +1,655 @@
import os
import cv2
import json
import numpy as np
from flask import Flask, Response, render_template, jsonify, request
from flask_cors import CORS
import threading
import time
import queue
import urllib.parse
import glob
import logging
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
app = Flask(__name__)
CORS(app) # Enable CORS for all routes
def find_yolo_model():
"""Scan current directory and subdirectories for YOLO model files"""
# Look for common YOLO file patterns
weights_files = glob.glob('**/*.weights', recursive=True) + glob.glob('**/*.onnx', recursive=True)
cfg_files = glob.glob('**/*.cfg', recursive=True)
names_files = glob.glob('**/*.names', recursive=True)
# Find directories containing all required files
model_dirs = set()
for weights in weights_files:
directory = os.path.dirname(weights)
if not directory:
directory = '.'
# Check if this directory has all required files
has_cfg = any(cfg for cfg in cfg_files if os.path.dirname(cfg) == directory)
has_names = any(names for names in names_files if os.path.dirname(names) == directory)
if has_cfg and has_names:
model_dirs.add(directory)
# Return the first valid directory found, or None
return next(iter(model_dirs), None)
class YOLODetector:
def __init__(self):
self.net = None
self.classes = []
self.colors = []
self.confidence_threshold = 0.35
self.cuda_available = self.check_cuda()
self.model_loaded = False
self.current_model = None
def check_cuda(self):
"""Check if CUDA is available"""
try:
count = cv2.cuda.getCudaEnabledDeviceCount()
return count > 0
except:
return False
def scan_for_model(self):
"""Auto-scan for YOLO model files in current directory"""
try:
# Look for model files in current directory
weights = [f for f in os.listdir('.') if f.endswith(('.weights', '.onnx'))]
configs = [f for f in os.listdir('.') if f.endswith('.cfg')]
classes = [f for f in os.listdir('.') if f.endswith('.names')]
if weights and configs and classes:
self.load_yolo_model('.', weights[0], configs[0], classes[0])
return True
return False
except Exception as e:
print(f"Error scanning for model: {e}")
return False
def load_yolo_model(self, model_dir, weights_file=None, config_file=None, classes_file=None):
"""Load YOLO model with specified files or auto-detect"""
try:
if not weights_file:
weights = [f for f in os.listdir(model_dir) if f.endswith(('.weights', '.onnx'))]
configs = [f for f in os.listdir(model_dir) if f.endswith('.cfg')]
classes = [f for f in os.listdir(model_dir) if f.endswith('.names')]
if not (weights and configs and classes):
return False
weights_file = weights[0]
config_file = configs[0]
classes_file = classes[0]
weights_path = os.path.join(model_dir, weights_file)
config_path = os.path.join(model_dir, config_file)
classes_path = os.path.join(model_dir, classes_file)
self.net = cv2.dnn.readNet(weights_path, config_path)
self.current_model = weights_file
if self.cuda_available:
try:
self.net.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA)
self.net.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA)
except:
self.net.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV)
self.net.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU)
with open(classes_path, 'r') as f:
self.classes = f.read().strip().split('\n')
np.random.seed(42)
self.colors = np.random.randint(0, 255, size=(len(self.classes), 3), dtype='uint8')
self.model_loaded = True
return True
except Exception as e:
print(f"Error loading model: {e}")
self.model_loaded = False
return False
def get_camera_resolution(self, cap):
"""Get the optimal resolution for a camera"""
try:
# Common resolutions to try
resolutions = [
(1920, 1080), # Full HD
(1280, 720), # HD
(800, 600), # SVGA
(640, 480) # VGA
]
best_width = 640
best_height = 480
for width, height in resolutions:
cap.set(cv2.CAP_PROP_FRAME_WIDTH, width)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height)
actual_width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
actual_height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
if actual_width > 0 and actual_height > 0:
best_width = actual_width
best_height = actual_height
break
return int(best_width), int(best_height)
except:
return 640, 480
def scan_cameras(self):
"""Scan for available cameras, skipping video0"""
cameras = []
# Start from video1 since video0 is often empty or system camera
for i in range(1, 10):
try:
cap = cv2.VideoCapture(i)
if cap.isOpened():
# Get optimal resolution
width, height = self.get_camera_resolution(cap)
cameras.append({
'id': i,
'width': width,
'height': height
})
cap.release()
except:
continue
# Check device paths
for i in range(1, 10):
path = f"/dev/video{i}"
if os.path.exists(path):
try:
cap = cv2.VideoCapture(path)
if cap.isOpened():
width, height = self.get_camera_resolution(cap)
cameras.append({
'id': path,
'width': width,
'height': height
})
cap.release()
except:
continue
return cameras
def detect(self, frame):
"""Perform object detection on frame"""
if self.net is None or not self.model_loaded:
return frame
try:
height, width = frame.shape[:2]
blob = cv2.dnn.blobFromImage(frame, 1/255.0, (416, 416), swapRB=True, crop=False)
self.net.setInput(blob)
try:
layer_names = self.net.getLayerNames()
output_layers = [layer_names[i - 1] for i in self.net.getUnconnectedOutLayers()]
except:
output_layers = self.net.getUnconnectedOutLayersNames()
outputs = self.net.forward(output_layers)
# Process detections
boxes = []
confidences = []
class_ids = []
for output in outputs:
for detection in output:
scores = detection[5:]
class_id = np.argmax(scores)
confidence = scores[class_id]
if confidence > self.confidence_threshold:
# Convert YOLO coords to screen coords
center_x = int(detection[0] * width)
center_y = int(detection[1] * height)
w = int(detection[2] * width)
h = int(detection[3] * height)
# Get top-left corner
x = max(0, int(center_x - w/2))
y = max(0, int(center_y - h/2))
boxes.append([x, y, w, h])
confidences.append(float(confidence))
class_ids.append(class_id)
# Apply non-maximum suppression
indices = cv2.dnn.NMSBoxes(boxes, confidences, self.confidence_threshold, 0.4)
if len(indices) > 0:
for i in indices.flatten():
try:
(x, y, w, h) = boxes[i]
# Ensure coordinates are within frame bounds
x = max(0, min(x, width - 1))
y = max(0, min(y, height - 1))
w = min(w, width - x)
h = min(h, height - y)
color = [int(c) for c in self.colors[class_ids[i]]]
cv2.rectangle(frame, (x, y), (x + w, y + h), color, 2)
# Draw label with background
text = f"{self.classes[class_ids[i]]}: {confidences[i]:.2f}"
font_scale = 0.5
font = cv2.FONT_HERSHEY_SIMPLEX
thickness = 1
(text_w, text_h), baseline = cv2.getTextSize(text, font, font_scale, thickness)
# Draw background rectangle for text
cv2.rectangle(frame, (x, y - text_h - baseline - 5), (x + text_w, y), color, -1)
# Draw text
cv2.putText(frame, text, (x, y - 5), font, font_scale, (255, 255, 255), thickness)
except Exception as e:
print(f"Error drawing detection {i}: {e}")
continue
return frame
except Exception as e:
print(f"Detection error: {e}")
return frame
class CameraStream:
def __init__(self, camera_id, detector):
self.camera_id = camera_id
self.cap = None
self.frame_queue = queue.Queue(maxsize=10)
self.running = False
self.thread = None
self.lock = threading.Lock()
self.detector = detector
self.is_network_camera = isinstance(camera_id, str) and camera_id.startswith('net:')
self.last_frame_time = time.time()
self.frame_timeout = 5.0 # Timeout after 5 seconds without frames
self.reconnect_interval = 5.0 # Try to reconnect every 5 seconds
self.last_reconnect_attempt = 0
def start(self):
"""Start the camera stream"""
if self.running:
return
try:
if self.is_network_camera:
# Handle network camera
name = self.camera_id[4:] # Remove 'net:' prefix
camera_info = camera_manager.network_cameras.get(name)
if not camera_info:
raise Exception(f"Network camera {name} not found")
if isinstance(camera_info, dict):
url = camera_info['url']
# Handle DroidCam URL formatting
if ':4747' in url and not url.endswith('/video'):
url = url.rstrip('/') + '/video'
if not url.startswith(('http://', 'https://')):
url = 'http://' + url
if 'username' in camera_info and 'password' in camera_info:
parsed = urllib.parse.urlparse(url)
netloc = f"{camera_info['username']}:{camera_info['password']}@{parsed.netloc}"
url = parsed._replace(netloc=netloc).geturl()
else:
url = camera_info
logger.info(f"Connecting to network camera: {url}")
self.cap = cv2.VideoCapture(url)
else:
# Handle local camera
self.cap = cv2.VideoCapture(self.camera_id)
if not self.cap.isOpened():
raise Exception(f"Failed to open camera {self.camera_id}")
# Set camera properties
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.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
self.running = True
self.thread = threading.Thread(target=self._capture_loop)
self.thread.daemon = True
self.thread.start()
return True
except Exception as e:
logger.error(f"Error starting camera {self.camera_id}: {e}")
if self.cap:
self.cap.release()
self.cap = None
return False
def stop(self):
"""Stop the camera stream"""
self.running = False
if self.thread:
self.thread.join()
if self.cap:
self.cap.release()
self.cap = None
while not self.frame_queue.empty():
try:
self.frame_queue.get_nowait()
except queue.Empty:
break
def _capture_loop(self):
"""Main capture loop with automatic reconnection"""
while self.running:
try:
if not self.cap or not self.cap.isOpened():
current_time = time.time()
if current_time - self.last_reconnect_attempt >= self.reconnect_interval:
logger.info(f"Attempting to reconnect camera {self.camera_id}")
self.last_reconnect_attempt = current_time
self.start()
time.sleep(1)
continue
ret, frame = self.cap.read()
if not ret:
current_time = time.time()
if current_time - self.last_frame_time > self.frame_timeout:
logger.warning(f"No frames received from camera {self.camera_id} for {self.frame_timeout} seconds")
self.cap.release()
self.cap = None
time.sleep(0.1)
continue
self.last_frame_time = time.time()
# Apply object detection
if self.detector and self.detector.net is not None:
frame = self.detector.detect(frame)
# Convert to JPEG
_, jpeg = cv2.imencode('.jpg', frame, [cv2.IMWRITE_JPEG_QUALITY, 85])
# Update queue
try:
self.frame_queue.put_nowait(jpeg.tobytes())
except queue.Full:
try:
self.frame_queue.get_nowait()
self.frame_queue.put_nowait(jpeg.tobytes())
except queue.Empty:
pass
except Exception as e:
logger.error(f"Error in capture loop for camera {self.camera_id}: {e}")
if self.cap:
self.cap.release()
self.cap = None
time.sleep(1)
def get_frame(self):
"""Get the latest frame"""
try:
return self.frame_queue.get_nowait()
except queue.Empty:
return None
class CameraManager:
def __init__(self):
self.cameras = {}
self.network_cameras = {}
self.lock = threading.Lock()
self.detector = YOLODetector()
# Auto-scan for model directory
model_dir = os.getenv('YOLO_MODEL_DIR')
if not model_dir or not os.path.exists(model_dir):
model_dir = find_yolo_model()
if model_dir:
print(f"Found YOLO model in directory: {model_dir}")
self.detector.load_yolo_model(model_dir)
else:
print("No YOLO model found in current directory")
def add_camera(self, camera_id):
"""Add a camera to the manager"""
with self.lock:
if camera_id not in self.cameras:
camera = CameraStream(camera_id, self.detector)
if camera.start():
self.cameras[camera_id] = camera
return True
return False
def remove_camera(self, camera_id):
"""Remove a camera from the manager"""
with self.lock:
if camera_id in self.cameras:
self.cameras[camera_id].stop()
del self.cameras[camera_id]
def get_camera(self, camera_id):
"""Get a camera by ID"""
return self.cameras.get(camera_id)
def get_all_cameras(self):
"""Get list of all camera IDs"""
return list(self.cameras.keys())
def add_network_camera(self, name, url, username=None, password=None):
"""Add a network camera"""
camera_info = {
'url': url,
'username': username,
'password': password
} if username and password else url
self.network_cameras[name] = camera_info
return True
def remove_network_camera(self, name):
"""Remove a network camera"""
if name in self.network_cameras:
camera_id = f"net:{name}"
if camera_id in self.cameras:
self.remove_camera(camera_id)
del self.network_cameras[name]
return True
return False
camera_manager = CameraManager()
def gen_frames(camera_id):
"""Generator function for camera frames"""
camera = camera_manager.get_camera(camera_id)
if not camera:
return
while True:
frame = camera.get_frame()
if frame is not None:
yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
else:
time.sleep(0.01)
@app.route('/')
def index():
"""Serve the main page"""
return render_template('index.html')
@app.route('/video_feed/<path:camera_id>')
def video_feed(camera_id):
"""Video streaming route"""
# Handle both local and network cameras
if camera_id.startswith('net:'):
camera_id = camera_id # Keep as string for network cameras
else:
try:
camera_id = int(camera_id) # Convert to int for local cameras
except ValueError:
camera_id = camera_id # Keep as string if not convertible
return Response(gen_frames(camera_id),
mimetype='multipart/x-mixed-replace; boundary=frame')
@app.route('/cameras')
def get_cameras():
"""Get list of available cameras"""
# Scan for local cameras silently
cameras = scan_cameras_silently()
# Add network cameras
for name, info in camera_manager.network_cameras.items():
url = info['url'] if isinstance(info, dict) else info
cameras.append({
'id': f'net:{name}',
'type': 'network',
'name': f'{name} ({url})'
})
# Add status information for active cameras
for camera in cameras:
camera_stream = camera_manager.get_camera(camera['id'])
if camera_stream:
camera['active'] = True
camera['status'] = 'connected' if camera_stream.cap and camera_stream.cap.isOpened() else 'reconnecting'
else:
camera['active'] = False
camera['status'] = 'disconnected'
return jsonify(cameras)
@app.route('/add_camera/<path:camera_id>')
def add_camera(camera_id):
"""Add a camera to the stream"""
if camera_id.startswith('net:'):
camera_id = camera_id # Keep as string for network cameras
else:
try:
camera_id = int(camera_id) # Convert to int for local cameras
except ValueError:
camera_id = camera_id # Keep as string if not convertible
success = camera_manager.add_camera(camera_id)
return jsonify({'success': success})
@app.route('/remove_camera/<path:camera_id>')
def remove_camera(camera_id):
"""Remove a camera from the stream"""
camera_manager.remove_camera(camera_id)
return jsonify({'success': True})
@app.route('/network_cameras', methods=['POST'])
def add_network_camera():
"""Add a network camera"""
data = request.json
name = data.get('name')
url = data.get('url')
username = data.get('username')
password = data.get('password')
if not name or not url:
return jsonify({'success': False, 'error': 'Name and URL required'})
try:
success = camera_manager.add_network_camera(name, url, username, password)
if success:
# Try to connect to the camera to verify it works
camera_id = f"net:{name}"
test_success = camera_manager.add_camera(camera_id)
if test_success:
camera_manager.remove_camera(camera_id) # Remove test connection
else:
camera_manager.remove_network_camera(name)
return jsonify({'success': False, 'error': 'Failed to connect to camera'})
return jsonify({'success': success})
except Exception as e:
return jsonify({'success': False, 'error': str(e)})
@app.route('/load_model', methods=['POST'])
def load_model():
"""Load YOLO model"""
data = request.json
model_dir = data.get('model_dir')
if not model_dir or not os.path.exists(model_dir):
return jsonify({'success': False, 'error': 'Invalid model directory'})
success = camera_manager.detector.load_yolo_model(model_dir)
return jsonify({'success': success})
# Reduce camera scanning noise
def scan_cameras_silently():
"""Scan for cameras while suppressing OpenCV warnings"""
import contextlib
with open(os.devnull, 'w') as devnull:
with contextlib.redirect_stderr(devnull):
cameras = []
# Check device paths first
for i in range(10):
device_path = f"/dev/video{i}"
if os.path.exists(device_path):
try:
cap = cv2.VideoCapture(device_path)
if cap.isOpened():
cameras.append({
'id': device_path,
'type': 'local',
'name': f'Camera {i} ({device_path})'
})
cap.release()
except Exception as e:
logger.debug(f"Error checking device {device_path}: {e}")
# Check numeric indices
for i in range(2): # Only check first two indices to reduce noise
try:
cap = cv2.VideoCapture(i)
if cap.isOpened():
cameras.append({
'id': str(i),
'type': 'local',
'name': f'Camera {i}'
})
cap.release()
except Exception as e:
logger.debug(f"Error checking camera {i}: {e}")
return cameras
if __name__ == '__main__':
# Create templates directory if it doesn't exist
os.makedirs('templates', exist_ok=True)
# Load model from environment variable if available
model_dir = os.getenv('YOLO_MODEL_DIR')
if model_dir and os.path.exists(model_dir):
camera_manager.detector.load_yolo_model(model_dir)
# Check if running with Gunicorn
if os.environ.get('GUNICORN_CMD_ARGS') is not None:
# Running with Gunicorn, let it handle the server
pass
else:
# Development server warning
logger.warning("Running in development mode. Use Gunicorn for production!")
app.run(host='0.0.0.0', port=5000, threaded=True)