Skip to content

Commit a1f30e3

Browse files
...
1 parent f0b7c1a commit a1f30e3

10 files changed

Lines changed: 368 additions & 0 deletions

freeze_requirements.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/bin/bash
2+
3+
echo "📦 Generating requirements.txt from current environment..."
4+
5+
# Activate venv if it exists
6+
if [ -d "venv" ]; then
7+
echo "🔧 Activating virtual environment..."
8+
source venv/bin/activate
9+
fi
10+
11+
# Generate requirements.txt
12+
pip freeze > requirements.txt
13+
14+
echo "✅ requirements.txt created successfully!"

make_executables.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/bin/bash
2+
3+
echo "🔧 Setting execute permissions..."
4+
5+
chmod +x install_requirements
6+
chmod +x freeze_requirements
7+
chmod +x setup
8+
chmod +x run_server
9+
10+
echo "✅ Permissions set!"
11+

obstacle_avoidance.py

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
from gpiozero import DistanceSensor
2+
import time
3+
4+
class ObstacleAvoidance:
5+
def __init__(self, trigger_pin=27, echo_pin=22, motors=None, threshold=0.5):
6+
# Faster response (no smoothing delay)
7+
self.sensor = DistanceSensor(
8+
echo=echo_pin,
9+
trigger=trigger_pin,
10+
max_distance=2.0,
11+
queue_len=1
12+
)
13+
self.motors = motors
14+
self.threshold = threshold
15+
self.turn_left_next = True
16+
17+
# More realistic speeds for a Pi 3 robot
18+
self.min_speed = 0.3
19+
self.max_speed = 0.6
20+
21+
# Stuck detection
22+
self.obstacle_count = 0
23+
self.last_obstacle_time = 0
24+
self.stuck_threshold = 3 # Number of obstacles in quick succession
25+
self.stuck_time_window = 2.0 # Within 2 seconds
26+
self.committed_direction = None # Will be 'left' or 'right'
27+
self.commitment_timeout = 5.0 # Stay committed for 5 seconds
28+
self.commitment_start_time = 0
29+
30+
def get_distance(self):
31+
try:
32+
return self.sensor.distance
33+
except Exception:
34+
return None
35+
36+
def _map_speed(self, distance):
37+
if distance >= self.threshold:
38+
return self.max_speed
39+
if distance <= 0:
40+
return self.min_speed
41+
ratio = distance / self.threshold
42+
speed = self.min_speed + (self.max_speed - self.min_speed) * ratio
43+
return max(self.min_speed, min(self.max_speed, speed))
44+
45+
def _detect_stuck(self):
46+
"""Check if robot is repeatedly hitting obstacles (stuck pattern)"""
47+
current_time = time.time()
48+
49+
# Check if we're still in the time window
50+
if current_time - self.last_obstacle_time < self.stuck_time_window:
51+
self.obstacle_count += 1
52+
else:
53+
# Reset counter if too much time has passed
54+
self.obstacle_count = 1
55+
56+
self.last_obstacle_time = current_time
57+
58+
# If we hit stuck threshold, commit to a direction
59+
if self.obstacle_count >= self.stuck_threshold:
60+
if self.committed_direction is None:
61+
# Choose direction based on current preference
62+
self.committed_direction = 'left' if self.turn_left_next else 'right'
63+
self.commitment_start_time = current_time
64+
print(f"🔄 STUCK DETECTED! Committing to turn {self.committed_direction.upper()}")
65+
return True
66+
return False
67+
68+
def _should_stay_committed(self):
69+
"""Check if we should still follow committed direction"""
70+
if self.committed_direction is None:
71+
return False
72+
73+
# Release commitment after timeout
74+
if time.time() - self.commitment_start_time > self.commitment_timeout:
75+
print(f"✅ Commitment timeout - resuming normal operation")
76+
self.committed_direction = None
77+
self.obstacle_count = 0
78+
return False
79+
80+
return True
81+
82+
def check_and_avoid(self):
83+
if self.motors is None:
84+
return False
85+
86+
distance = self.get_distance()
87+
if distance is None:
88+
return False
89+
90+
# 🚨 OBSTACLE DETECTED
91+
if distance < self.threshold:
92+
# Check if we're stuck
93+
is_stuck = self._detect_stuck()
94+
95+
self.motors.stop()
96+
time.sleep(0.05)
97+
98+
# Reverse briefly
99+
self.motors.backward(self.min_speed)
100+
start_time = time.time()
101+
while time.time() - start_time < 0.3:
102+
pass
103+
self.motors.stop()
104+
time.sleep(0.05)
105+
106+
# Decide turn direction
107+
if self._should_stay_committed():
108+
# Stuck mode: always turn the same way
109+
turn_direction = self.committed_direction
110+
# Longer turn when stuck
111+
turn_duration = 0.7
112+
else:
113+
# Normal mode: alternate
114+
turn_direction = 'left' if self.turn_left_next else 'right'
115+
turn_duration = 0.55
116+
self.turn_left_next = not self.turn_left_next
117+
118+
# Execute turn
119+
if turn_direction == 'left':
120+
self.motors.turn_left(self.min_speed)
121+
else:
122+
self.motors.turn_right(self.min_speed)
123+
124+
start_time = time.time()
125+
while time.time() - start_time < turn_duration:
126+
pass
127+
128+
self.motors.stop()
129+
return True
130+
131+
# ✅ PATH CLEAR
132+
else:
133+
# Reset stuck detection if we have clear path
134+
if distance > self.threshold * 1.5: # Good clearance
135+
if self.obstacle_count > 0:
136+
self.obstacle_count = max(0, self.obstacle_count - 1)
137+
138+
speed = self._map_speed(distance)
139+
self.motors.forward(speed)
140+
return False
141+
142+
def cleanup(self):
143+
if self.motors:
144+
self.motors.stop()
145+
self.sensor.close()

requirements.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,7 @@ Pillow
1010
qrcode
1111
spidev
1212
tensorflow-aarch64
13+
numpy==1.26.2
14+
opencv-python==4.8.1.78
15+
Pillow==8.3.2
16+

scripts/face-detection/__init__.py

Whitespace-only changes.

scripts/gender_age_predictor.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
from deepface import DeepFace
2+
from PIL import Image, ImageDraw, ImageFont
3+
import os
4+
import json
5+
6+
def extract_gender_and_age(input_image_path, output_directory):
7+
try:
8+
# Load the input image using Pillow
9+
input_img = Image.open(input_image_path)
10+
11+
# Extract gender and age predictions
12+
result = DeepFace.analyze(input_image_path, actions=['gender', 'age'], enforce_detection=False)
13+
gender_stats = result[0]['gender']
14+
age = int(result[0]['age'])
15+
print("Predicted Gender:", gender_stats)
16+
print("Predicted Age:", age)
17+
18+
# Select the dominant gender label
19+
gender = max(gender_stats, key=gender_stats.get)
20+
21+
# Create a drawing object
22+
draw = ImageDraw.Draw(input_img)
23+
font = ImageFont.load_default()
24+
position_gender = (10, 10) # Top-left corner for gender
25+
position_age = (10, 30) # Below gender information
26+
27+
# Write gender and age information on the image
28+
draw.text(position_gender, f"Gender: {gender} - {gender_stats[gender]:.2f}%", font=font, fill=(255, 255, 255))
29+
draw.text(position_age, f"Age: {age} years", font=font, fill=(255, 255, 255))
30+
31+
# Create the output directory if it doesn't exist
32+
os.makedirs(output_directory, exist_ok=True)
33+
34+
# Save the output image as JPEG with a filename based on predicted gender
35+
output_filename = f"{gender.lower()}_age_{age}.jpg"
36+
output_path = os.path.join(output_directory, output_filename)
37+
input_img.save(output_path, format='JPEG')
38+
39+
except Exception as e:
40+
print(f"Error: {e}")
41+
42+
43+
if __name__ == "__main__":
44+
# Get the directory of the current script
45+
script_directory = os.path.dirname(os.path.abspath(__file__))
46+
47+
# Load settings from the JSON file
48+
with open("settings.json", "r") as file:
49+
settings = json.load(file)
50+
51+
# Specify the paths for input and output directories based on the loaded settings
52+
input_directory = os.path.join(script_directory, settings["directories"]["input"])
53+
output_directory = os.path.join(script_directory, settings["directories"]["output"])
54+
55+
# Get a list of all image files in the input directory
56+
image_files = [f for f in os.listdir(input_directory) if os.path.isfile(os.path.join(input_directory, f))]
57+
58+
# Process each image file in the input directory
59+
for image_file in image_files:
60+
input_path = os.path.join(input_directory, image_file)
61+
62+
extract_gender_and_age(input_path, output_directory)
63+
64+
print("Gender and age extraction complete. Output saved as JPEG to", output_directory)
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import cv2
2+
from deepface import DeepFace
3+
import numpy as np
4+
import json
5+
6+
def extract_gender_and_age(frame, face_cascade):
7+
try:
8+
# Convert the frame to RGB (required by DeepFace)
9+
rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
10+
11+
# Use Haar Cascade to detect faces
12+
gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
13+
faces = face_cascade.detectMultiScale(gray_frame, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))
14+
15+
for (x, y, w, h) in faces:
16+
# Extract face region
17+
face_roi = frame[y:y+h, x:x+w]
18+
19+
# Use DeepFace to analyze gender and age
20+
result = DeepFace.analyze(face_roi, actions=['gender', 'age'])
21+
22+
gender_stats = result[0]['gender']
23+
age = int(result[0]['age'])
24+
print("Predicted Gender:", gender_stats)
25+
print("Predicted Age:", age)
26+
27+
# Select the dominant gender label
28+
gender = max(gender_stats, key=gender_stats.get)
29+
30+
# Display gender and age information on the frame
31+
cv2.putText(frame, f"Gender: {gender} - {gender_stats[gender]:.2f}%", (x, y - 10),
32+
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1, cv2.LINE_AA)
33+
cv2.putText(frame, f"Age: {age} years", (x, y + h + 20),
34+
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1, cv2.LINE_AA)
35+
36+
# Draw rectangle around the face
37+
cv2.rectangle(frame, (x, y), (x+w, y+h), (255, 0, 0), 2)
38+
39+
return frame
40+
41+
except Exception as e:
42+
print(f"Error: {e}")
43+
return frame
44+
45+
if __name__ == "__main__":
46+
# Open the webcam
47+
cap = cv2.VideoCapture(0)
48+
49+
# Load Haar Cascade for face detection
50+
face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
51+
52+
while True:
53+
# Capture video from webcam
54+
ret, frame = cap.read()
55+
56+
# Check if frame is valid
57+
if not ret:
58+
print("Error: Couldn't capture frame from the webcam.")
59+
break
60+
61+
# Apply face detection and gender/age analysis to the frame
62+
frame = extract_gender_and_age(frame, face_cascade)
63+
64+
# Display the processed frame
65+
cv2.imshow('Webcam Feed with Gender and Age Analysis', frame)
66+
67+
# Break the loop when 'q' key is pressed
68+
if cv2.waitKey(1) & 0xFF == ord('q'):
69+
break
70+
71+
# Release the webcam and close all windows
72+
cap.release()
73+
cv2.destroyAllWindows()

scripts/gender_predictor.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
from deepface import DeepFace
2+
from PIL import Image, ImageDraw, ImageFont
3+
import os
4+
import json
5+
6+
def extract_gender(input_image_path, output_directory):
7+
try:
8+
# Load the input image using Pillow
9+
input_img = Image.open(input_image_path)
10+
11+
# Extract gender prediction
12+
result = DeepFace.analyze(input_image_path, actions=['gender'], enforce_detection=False)
13+
gender_stats = result[0]['gender']
14+
print("Predicted Gender:", gender_stats)
15+
16+
# Select the dominant gender label
17+
gender = max(gender_stats, key=gender_stats.get)
18+
19+
# Create a drawing object
20+
draw = ImageDraw.Draw(input_img)
21+
font = ImageFont.load_default()
22+
position = (10, 10) # Top-left corner
23+
24+
# Create the output directory if it doesn't exist
25+
os.makedirs(output_directory, exist_ok=True)
26+
27+
# Save the output image as JPEG with a filename based on predicted gender
28+
output_filename = f"{gender.lower()}.jpg"
29+
output_path = os.path.join(output_directory, output_filename)
30+
input_img.save(output_path, format='JPEG')
31+
32+
except Exception as e:
33+
print(f"Error: {e}")
34+
35+
36+
if __name__ == "__main__":
37+
# Get the directory of the current script
38+
script_directory = os.path.dirname(os.path.abspath(__file__))
39+
40+
# Load settings from the JSON file
41+
with open("settings.json", "r") as file:
42+
settings = json.load(file)
43+
44+
# Specify the paths for input and output directories based on the loaded settings
45+
input_directory = os.path.join(script_directory, settings["directories"]["input"])
46+
output_directory = os.path.join(script_directory, settings["directories"]["output"])
47+
48+
# Get a list of all image files in the input directory
49+
image_files = [f for f in os.listdir(input_directory) if os.path.isfile(os.path.join(input_directory, f))]
50+
51+
# Process each image file in the input directory
52+
for image_file in image_files:
53+
input_path = os.path.join(input_directory, image_file)
54+
55+
extract_gender(input_path, output_directory)
56+
57+
print("Gender extraction complete. Output saved as JPEG to", output_directory)

0 commit comments

Comments
 (0)