mirror of
https://github.com/ConsistentlyInconsistentYT/Pixeltovoxelprojector.git
synced 2025-10-13 04:12:07 +00:00
386 lines
No EOL
14 KiB
Python
386 lines
No EOL
14 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
AstraVoxel Simple Webcam Demo
|
|
=============================
|
|
|
|
Clean, simple version that focuses on basic USB webcam usage.
|
|
Avoids complex camera systems and focuses on essential functionality.
|
|
|
|
Features:
|
|
- Simple USB webcam detection
|
|
- Basic motion detection
|
|
- 3D voxel visualization
|
|
- No advanced camera systems (just standard webcams)
|
|
"""
|
|
|
|
import tkinter as tk
|
|
from tkinter import ttk, messagebox
|
|
import cv2
|
|
import numpy as np
|
|
import matplotlib.pyplot as plt
|
|
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
|
from mpl_toolkits.mplot3d import Axes3D
|
|
import threading
|
|
import time
|
|
|
|
class AstraVoxelSimpleWebcam:
|
|
"""
|
|
Simple webcam demo focusing on basic functionality
|
|
"""
|
|
|
|
def __init__(self, root):
|
|
self.root = root
|
|
self.root.title("AstraVoxel Webcam Demo")
|
|
self.root.geometry("1000x700")
|
|
|
|
# Camera and processing state
|
|
self.camera = None
|
|
self.camera_active = False
|
|
self.processing_active = False
|
|
self.previous_frame = None
|
|
self.voxel_grid = None
|
|
self.grid_size = 24
|
|
|
|
# Create simple interface
|
|
self.setup_interface()
|
|
self.detect_simple_camera()
|
|
|
|
def setup_interface(self):
|
|
"""Set up simple interface"""
|
|
# Title
|
|
title_label = ttk.Label(self.root, text="🎥 AstraVoxel Webcam Demo",
|
|
font=("Segoe UI", 14, "bold"))
|
|
title_label.pack(pady=10)
|
|
|
|
# Main frame
|
|
main_frame = ttk.Frame(self.root)
|
|
main_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=10)
|
|
|
|
# Left panel - Controls
|
|
left_panel = ttk.Frame(main_frame)
|
|
left_panel.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 20))
|
|
|
|
self.setup_controls(left_panel)
|
|
|
|
# Right panel - Visualization
|
|
right_panel = ttk.Frame(main_frame)
|
|
right_panel.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
|
|
|
|
self.setup_visualization(right_panel)
|
|
|
|
def setup_controls(self, parent):
|
|
"""Set up control panel"""
|
|
# Camera section
|
|
camera_frame = ttk.LabelFrame(parent, text="Camera", padding=10)
|
|
camera_frame.pack(fill=tk.X, pady=(0, 20))
|
|
|
|
self.camera_status_label = ttk.Label(camera_frame,
|
|
text="Detecting camera...",
|
|
foreground="orange")
|
|
self.camera_status_label.pack(anchor=tk.W)
|
|
|
|
self.start_camera_btn = ttk.Button(camera_frame,
|
|
text="▶️ Connect Camera",
|
|
command=self.start_simple_camera)
|
|
self.start_camera_btn.pack(fill=tk.X, pady=5)
|
|
|
|
self.stop_camera_btn = ttk.Button(camera_frame,
|
|
text="⏹️ Stop Camera",
|
|
command=self.stop_simple_camera,
|
|
state="disabled")
|
|
self.stop_camera_btn.pack(fill=tk.X, pady=5)
|
|
|
|
# Processing section
|
|
process_frame = ttk.LabelFrame(parent, text="Motion Processing", padding=10)
|
|
process_frame.pack(fill=tk.X, pady=(0, 20))
|
|
|
|
self.start_process_btn = ttk.Button(process_frame,
|
|
text="🚀 Start Processing",
|
|
command=self.start_processing)
|
|
self.start_process_btn.pack(fill=tk.X, pady=5)
|
|
|
|
self.stop_process_btn = ttk.Button(process_frame,
|
|
text="⏹️ Stop Processing",
|
|
command=self.stop_processing)
|
|
self.stop_process_btn.pack(fill=tk.X, pady=5)
|
|
|
|
# Parameters
|
|
param_frame = ttk.LabelFrame(parent, text="Parameters", padding=10)
|
|
param_frame.pack(fill=tk.X)
|
|
|
|
ttk.Label(param_frame, text="Motion Threshold:").pack(anchor=tk.W)
|
|
self.threshold_var = tk.IntVar(value=25)
|
|
threshold_scale = ttk.Scale(param_frame, from_=5, to=100,
|
|
variable=self.threshold_var, orient=tk.HORIZONTAL)
|
|
threshold_scale.pack(fill=tk.X, pady=(0, 10))
|
|
|
|
ttk.Label(param_frame, text="Grid Size:").pack(anchor=tk.W)
|
|
self.grid_size_var = tk.IntVar(value=self.grid_size)
|
|
grid_scale = ttk.Scale(param_frame, from_=16, to_=48,
|
|
variable=self.grid_size_var, orient=tk.HORIZONTAL)
|
|
grid_scale.pack(fill=tk.X)
|
|
|
|
# Status
|
|
self.control_status_label = ttk.Label(parent,
|
|
text="Ready to use",
|
|
foreground="green")
|
|
self.control_status_label.pack(pady=(20, 0))
|
|
|
|
def setup_visualization(self, parent):
|
|
"""Set up 3D visualization"""
|
|
viz_frame = ttk.LabelFrame(parent, text="3D Motion Visualization", padding=10)
|
|
viz_frame.pack(fill=tk.BOTH, expand=True)
|
|
|
|
# Create 3D plot
|
|
self.figure = plt.Figure(figsize=(6, 5))
|
|
self.ax = self.figure.add_subplot(111, projection='3d')
|
|
|
|
# Initialize empty plot
|
|
self.update_3d_visualization()
|
|
|
|
# Create canvas
|
|
self.canvas = FigureCanvasTkAgg(self.figure, master=viz_frame)
|
|
self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
|
|
|
|
# Status
|
|
self.viz_status_label = ttk.Label(viz_frame, text="No voxel data")
|
|
self.viz_status_label.pack(pady=(5, 0))
|
|
|
|
def detect_simple_camera(self):
|
|
"""Simple camera detection"""
|
|
try:
|
|
# Try to find a basic webcam (usually index 0 or 1)
|
|
for camera_index in [0, 1]:
|
|
cap = cv2.VideoCapture(camera_index, cv2.CAP_DSHOW) # Use DirectShow backend
|
|
|
|
if cap.isOpened():
|
|
ret, frame = cap.read()
|
|
cap.release()
|
|
|
|
if ret and frame is not None:
|
|
self.available_camera_index = camera_index
|
|
self.camera_status_label.config(
|
|
text=f"Camera {camera_index} detected",
|
|
foreground="green"
|
|
)
|
|
self.control_status_label.config(
|
|
text="Camera detected - ready to use",
|
|
foreground="green"
|
|
)
|
|
return True
|
|
|
|
# No camera found
|
|
self.camera_status_label.config(
|
|
text="No camera detected",
|
|
foreground="red"
|
|
)
|
|
self.control_status_label.config(
|
|
text="Please connect a USB webcam",
|
|
foreground="red"
|
|
)
|
|
return False
|
|
|
|
except Exception as e:
|
|
self.camera_status_label.config(
|
|
text="Camera detection failed",
|
|
foreground="red"
|
|
)
|
|
return False
|
|
|
|
def start_simple_camera(self):
|
|
"""Start webcam"""
|
|
try:
|
|
# Use CAP_DSHOW backend to avoid conflicts
|
|
self.camera = cv2.VideoCapture(self.available_camera_index, cv2.CAP_DSHOW)
|
|
|
|
if self.camera.isOpened():
|
|
self.camera_active = True
|
|
self.camera_status_label.config(
|
|
text="Camera Active",
|
|
foreground="green"
|
|
)
|
|
self.start_camera_btn.config(state="disabled")
|
|
self.stop_camera_btn.config(state="normal")
|
|
self.control_status_label.config(
|
|
text="Camera started - ready for motion detection",
|
|
foreground="green"
|
|
)
|
|
|
|
# Start camera preview thread
|
|
self.camera_thread = threading.Thread(target=self.camera_preview_loop, daemon=True)
|
|
self.camera_thread.start()
|
|
|
|
else:
|
|
messagebox.showerror("Camera Error", "Failed to open camera")
|
|
|
|
except Exception as e:
|
|
messagebox.showerror("Camera Error", f"Error starting camera: {e}")
|
|
|
|
def stop_simple_camera(self):
|
|
"""Stop camera"""
|
|
self.camera_active = False
|
|
if self.camera:
|
|
self.camera.release()
|
|
|
|
self.camera_status_label.config(
|
|
text="Camera Stopped",
|
|
foreground="orange"
|
|
)
|
|
self.start_camera_btn.config(state="normal")
|
|
self.stop_camera_btn.config(state="disabled")
|
|
self.control_status_label.config(
|
|
text="Camera stopped",
|
|
foreground="orange"
|
|
)
|
|
|
|
def camera_preview_loop(self):
|
|
"""Simple camera preview"""
|
|
while self.camera_active and self.camera.isOpened():
|
|
try:
|
|
ret, frame = self.camera.read()
|
|
if ret:
|
|
# Convert to grayscale for processing
|
|
self.current_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
|
|
|
time.sleep(0.033) # ~30 FPS
|
|
|
|
except Exception as e:
|
|
print(f"Camera preview error: {e}")
|
|
break
|
|
|
|
def start_processing(self):
|
|
"""Start motion processing"""
|
|
if not self.camera_active:
|
|
messagebox.showerror("Camera Required", "Please start camera first")
|
|
return
|
|
|
|
self.processing_active = True
|
|
self.start_process_btn.config(state="disabled")
|
|
self.stop_process_btn.config(state="normal")
|
|
|
|
# Initialize voxel grid
|
|
self.grid_size = self.grid_size_var.get()
|
|
self.voxel_grid = np.zeros((self.grid_size, self.grid_size, self.grid_size), dtype=np.float32)
|
|
|
|
self.control_status_label.config(
|
|
text="Processing motion...",
|
|
foreground="blue"
|
|
)
|
|
|
|
# Start processing thread
|
|
self.processing_thread = threading.Thread(target=self.processing_loop, daemon=True)
|
|
self.processing_thread.start()
|
|
|
|
def stop_processing(self):
|
|
"""Stop motion processing"""
|
|
self.processing_active = False
|
|
self.start_process_btn.config(state="normal")
|
|
self.stop_process_btn.config(state="disabled")
|
|
self.control_status_label.config(
|
|
text="Processing stopped",
|
|
foreground="orange"
|
|
)
|
|
|
|
def processing_loop(self):
|
|
"""Motion detection and voxel accumulation"""
|
|
while self.processing_active:
|
|
if hasattr(self, 'current_frame') and self.current_frame is not None:
|
|
current_frame = self.current_frame.copy()
|
|
|
|
if self.previous_frame is not None:
|
|
# Simple motion detection
|
|
diff = np.abs(current_frame.astype(np.float32) -
|
|
self.previous_frame.astype(np.float32))
|
|
|
|
threshold = self.threshold_var.get()
|
|
motion_mask = diff > threshold
|
|
motion_pixels = np.count_nonzero(motion_mask)
|
|
|
|
# If significant motion detected, add to voxel grid
|
|
if motion_pixels > 100: # Minimum motion threshold
|
|
self.add_motion_to_voxels(motion_pixels)
|
|
|
|
# Update visualization occasionally
|
|
self.root.after(0, self.update_3d_visualization)
|
|
|
|
self.previous_frame = current_frame
|
|
|
|
time.sleep(0.1)
|
|
|
|
def add_motion_to_voxels(self, motion_intensity):
|
|
"""Simple voxel accumulation"""
|
|
if self.voxel_grid is None:
|
|
return
|
|
|
|
# Add voxels in a simple pattern
|
|
base_intensity = motion_intensity / 5000.0
|
|
|
|
# Add some structure by distributing motion across grid
|
|
for _ in range(5): # Add multiple points per motion event
|
|
x = np.random.randint(0, self.grid_size)
|
|
y = np.random.randint(0, self.grid_size)
|
|
z = np.random.randint(self.grid_size // 4, 3 * self.grid_size // 4)
|
|
|
|
self.voxel_grid[x, y, z] += base_intensity * np.random.uniform(0.5, 1.5)
|
|
|
|
def update_3d_visualization(self):
|
|
"""Update 3D visualization"""
|
|
if self.voxel_grid is None:
|
|
return
|
|
|
|
try:
|
|
self.ax.clear()
|
|
|
|
# Get active voxels
|
|
coords = np.where(self.voxel_grid > 0.1)
|
|
if len(coords[0]) > 0:
|
|
intensities = self.voxel_grid[coords]
|
|
|
|
self.ax.scatter(
|
|
coords[0], coords[1], coords[2],
|
|
c=intensities, cmap='plasma', marker='o', s=10, alpha=0.8
|
|
)
|
|
|
|
self.ax.set_xlabel('X')
|
|
self.ax.set_ylabel('Y')
|
|
self.ax.set_zlabel('Z')
|
|
self.ax.set_title(f'Motion Detection Results\n{len(coords[0])} Points')
|
|
|
|
self.viz_status_label.config(text=f"Active Voxels: {len(coords[0])}")
|
|
|
|
else:
|
|
self.ax.text(0.5, 0.5, 0.5, 'Waiting for motion...\nMove objects in front of camera',
|
|
ha='center', va='center', transform=self.ax.transAxes)
|
|
self.ax.set_title('3D Motion Space')
|
|
self.viz_status_label.config(text="No motion detected yet")
|
|
|
|
# Set axis limits
|
|
self.ax.set_xlim(0, self.grid_size)
|
|
self.ax.set_ylim(0, self.grid_size)
|
|
self.ax.set_zlim(0, self.grid_size)
|
|
|
|
self.canvas.draw()
|
|
|
|
except Exception as e:
|
|
print(f"Visualization error: {e}")
|
|
|
|
def main():
|
|
"""Main function"""
|
|
print("🎥 AstraVoxel Simple Webcam Demo")
|
|
print("===================================")
|
|
print("This version focuses on basic USB webcam functionality.")
|
|
print("It avoids complex camera systems and provides simple motion detection.")
|
|
print()
|
|
print("Requirements:")
|
|
print("- Install opencv-python if needed: pip install opencv-python")
|
|
print("- Use with standard USB webcams only")
|
|
print()
|
|
|
|
root = tk.Tk()
|
|
app = AstraVoxelSimpleWebcam(root)
|
|
|
|
print("Starting AstraVoxel Simple Webcam Demo...")
|
|
root.mainloop()
|
|
|
|
if __name__ == "__main__":
|
|
main() |