Compare commits

...

4 Commits

Author SHA1 Message Date
AG_CV_GAAIM
990de6345d Committed Changes 2025-06-10 10:24:36 -07:00
M1MacMini
e3b8320ad2 added finished test track and botsort yaml 2025-03-26 12:41:13 -04:00
M1MacMini
ce6b1c6705 Merge branch 'main' of https://git.factory.uga.edu/GEORGIA-AIM/CV_AG 2025-03-13 15:27:29 -04:00
M1MacMini
8a4f684cc1 added MQTT 2025-02-18 10:46:30 -05:00
6 changed files with 309 additions and 22 deletions

View File

@@ -1,12 +1,35 @@
import cv2 import cv2
from ultralytics import YOLO from ultralytics import YOLO
from collections import deque from collections import deque
import paho.mqtt.client as mqtt
from influxdb import InfluxDBClient
from influxdb_client import InfluxDBClient, Point, WriteOptions
import time
from datetime import datetime
# InfluxDB Configuration
INFLUX_URL = "http://localhost:8086"
INFLUX_TOKEN = "--k98NX5UQ2qBCGAO80lLc_-teD-AUtKNj4uQfz0M8WyjHt04AT9d0dr6w8pup93ukw6YcJxWURmo2v6CAP_2g=="
INFLUX_ORG = "GAAIM"
INFLUX_BUCKET = "AGVIGNETTE"
# Connect to InfluxDB
client = InfluxDBClient(url=INFLUX_URL, token=INFLUX_TOKEN, org=INFLUX_ORG)
write_api = client.write_api(write_options=WriteOptions(batch_size=1))
# MQTT Setup
MQTT_BROKER = "172.20.29.125"
MQTT_TOPIC = "fruit/classification"
mqtt_client = mqtt.Client()
mqtt_client.connect(MQTT_BROKER, 1883, 6000)
# Camera index (default camera is 0) # Camera index (default camera is 0)
camera_index = 1 camera_index = 0
i = 0
# Load the YOLO model # Load the YOLO model
model = YOLO(r"D:\AIM\lemon\runs\detect\train4\weights\best.pt") # Load custom model model = YOLO(r"/Users/vel/Desktop/CvModel/CV_AG/runs/detect/train5/weights/best.pt") # Load custom model
# Initialize the camera # Initialize the camera
cap = cv2.VideoCapture(camera_index) cap = cv2.VideoCapture(camera_index)
@@ -32,8 +55,9 @@ class_labels = {
id_tracked_classes = ["DefectiveLemon", "GoodLemon", "NotRipeLemon"] id_tracked_classes = ["DefectiveLemon", "GoodLemon", "NotRipeLemon"]
# Parameters # Parameters
HISTORY_LENGTH = 5 # Number of frames to consider for majority voting HISTORY_LENGTH = 7 # Number of frames to consider for majority voting
CONFIRMATION_FRAMES = 5 # Frames needed to confirm a new label CONFIRMATION_FRAMES = 7 # Frames needed to confirm a new label
lemon_time = 0
# Dictionary to track detection history and confirmed states # Dictionary to track detection history and confirmed states
lemon_history = {} # Format: {ID: deque(maxlen=HISTORY_LENGTH)} lemon_history = {} # Format: {ID: deque(maxlen=HISTORY_LENGTH)}
@@ -76,7 +100,7 @@ while cap.isOpened():
break break
# Perform object tracking using BoT-SORT # Perform object tracking using BoT-SORT
results = model.track(source=frame, conf=0.5, tracker='botsort.yaml', show=False) results = model.track(source=frame, conf=0.5, tracker='botsort.yaml', show=False, device = 'mps')
for result in results: for result in results:
frame = result.orig_img # Current frame frame = result.orig_img # Current frame
@@ -109,6 +133,43 @@ while cap.isOpened():
cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
cv2.putText(frame, display_text, (x1, y1 - 10), cv2.putText(frame, display_text, (x1, y1 - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
# Create Decision Point at x = 600
if x1 > 100:
cv2.line(frame, (600, 0), (600, height), (255, 0, 0), 2)
# Create Decision Point at x = 670
if x1 > 100:
cv2.line(frame, (760, 0), (760, height), (255, 0, 0), 2)
cv2.putText(frame, "Decision Point", (630, height // 2),
cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 2)
# Lock in the label once it crosses the decision point
if x1 > 700 and obj_id in lemon_states:
cv2.putText(frame, f"Locked: {lemon_states[obj_id]}", (x1, y1 - 40),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
else:
cv2.putText(frame, "Waiting to Lock", (x1, y1 - 40),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 0), 2)
if x1 > 600 and x1 < 780:
if final_label == "DefectiveLemon":
mqtt_message = f"lemon_classification classification=\"{final_label}\" {int(time.time()*1e9)}"
if time.time() - lemon_time > .3:
mqtt_client.publish(MQTT_TOPIC, mqtt_message)
lemon_time = time.time()
i = i + 1
elif final_label == "NotRipeLemon":
mqtt_message = f"lemon_classification classification=\"{final_label}\" {int(time.time()*1e9)}"
if time.time() - lemon_time > .3:
mqtt_client.publish(MQTT_TOPIC, mqtt_message)
lemon_time = time.time()
i = i + 1
elif final_label == "GoodLemon":
mqtt_message = f"lemon_classification classification=\"{final_label}\" {int(time.time()*1e9)}"
if time.time() - lemon_time > .3:
mqtt_client.publish(MQTT_TOPIC, mqtt_message)
lemon_time = time.time()
i = i + 1
# Display the processed video stream # Display the processed video stream
cv2.imshow("Live Detection", frame) cv2.imshow("Live Detection", frame)

View File

@@ -1,7 +1,99 @@
import cv2 import cv2
from ultralytics import YOLO from ultralytics import YOLO
from collections import deque from collections import deque
import paho.mqtt.client as mqtt
from influxdb import InfluxDBClient
from influxdb_client import InfluxDBClient, Point, WriteOptions
import time
from datetime import datetime
import ssl
import os import os
import tkinter as tk
from tkinter import ttk
from PIL import Image, ImageTk
import threading
# InfluxDB Configuration
INFLUX_URL = "http://localhost:8086"
INFLUX_TOKEN = "export INFLUX_TOKEN=duVTQHPpHqr6WmdYfpSStqm-pxnvZHs-W0-3lXDnk8Tn6PGt59MlnTSR6egjMWdYvmL_ZI6xt3YUzGVBZHvc7w=="
INFLUX_ORG = "GAAIM"
INFLUX_BUCKET = "AGVIGNETTE"
# Connect to InfluxDB
client = InfluxDBClient(url=INFLUX_URL, token=INFLUX_TOKEN, org=INFLUX_ORG)
write_api = client.write_api(write_options=WriteOptions(batch_size=1))
# MQTT Setup
MQTT_BROKER = "192.168.8.172"
MQTT_TOPIC = "fruit/classification"
def start_loading():
for i in range(101): # 0 to 100%
time.sleep(0.38) # 0.4s * 100 = 40 seconds
progress_var.set(i)
progress_bar.update_idletasks()
root.destroy()
# Set up full-screen window
root = tk.Tk()
root.title("Starting Up")
root.attributes('-fullscreen', True)
# Get screen size
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
# Load and resize the background image
try:
bg_img = Image.open("comicrobodog.png") # Replace with your image
bg_img = bg_img.resize((screen_width, screen_height), Image.ANTIALIAS)
bg_photo = ImageTk.PhotoImage(bg_img)
# Set as background using a label
bg_label = tk.Label(root, image=bg_photo)
bg_label.place(x=0, y=0, relwidth=1, relheight=1)
except Exception as e:
print("Error loading background image:", e)
root.configure(bg='black') # Fallback
# Overlay content frame (transparent background)
overlay = tk.Frame(root, bg='', padx=20, pady=20)
overlay.place(relx=0.5, rely=0.5, anchor='center')
# Message label
label = tk.Label(
overlay,
text="Computer Vision Vignette is Starting Up",
font=("Helvetica", 32, "bold"),
fg="white"
)
label.pack(pady=10)
# Progress bar
progress_var = tk.IntVar()
progress_bar = ttk.Progressbar(
overlay,
maximum=100,
variable=progress_var,
length=800
)
progress_bar.pack(pady=20)
# Start the progress in a thread
threading.Thread(target=start_loading, daemon=True).start()
# Close on ESC
root.bind("<Escape>", lambda e: root.destroy())
root.mainloop()
mqtt_client = mqtt.Client()
# Set up TLS/SSL for MQTT connection
mqtt_client.connect(MQTT_BROKER, 1883, 60000)
# Allow duplicate loading of OpenMP runtime # Allow duplicate loading of OpenMP runtime
os.environ["KMP_DUPLICATE_LIB_OK"] = "True" os.environ["KMP_DUPLICATE_LIB_OK"] = "True"
@@ -10,10 +102,15 @@ os.environ["KMP_DUPLICATE_LIB_OK"] = "True"
yaml_path = "botsort.yaml" yaml_path = "botsort.yaml"
# Camera index (default camera index, 1 indicates an external camera) # Camera index (default camera index, 1 indicates an external camera)
camera_index = 1 camera_index = 0
cap = cv2.VideoCapture(camera_index)
cap.set(cv2.CAP_PROP_FPS, 30)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
# Load the YOLO model # Load the YOLO model
model = YOLO(r"D:\AIM\lemon\runs\detect\train4\weights\best.pt") # Load custom model model = YOLO(r"/Users/ag_cv_gaaim/Desktop/CV_AG/runs/detect/train4/weights/best.pt") # Load custom model
# Define class labels # Define class labels
class_labels = { class_labels = {
@@ -34,9 +131,11 @@ GOOD_THRESHOLD = 0.7 # Threshold for "GoodLemon" and "NotRipeLemon" proportio
# State history for each target (used for smoothing), format: {ID: deque([...], maxlen=HISTORY_LENGTH)} # State history for each target (used for smoothing), format: {ID: deque([...], maxlen=HISTORY_LENGTH)}
lemon_history = {} lemon_history = {}
lemon_send_history = []
# Set the display window to be resizable # Set the display window to be resizable
cv2.namedWindow("Live Detection", cv2.WINDOW_NORMAL) cv2.namedWindow("Live Detection", cv2.WINDOW_NORMAL)
cv2.setWindowProperty("Live Detection", cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)
# Smoothing function: # Smoothing function:
# If the current detected label is not in smoothing_labels, clear the target's history and return the current label; # If the current detected label is not in smoothing_labels, clear the target's history and return the current label;
@@ -71,20 +170,47 @@ def get_smoothed_label(obj_id, current_label):
# Use streaming tracking mode to maintain tracker state # Use streaming tracking mode to maintain tracker state
results = model.track( results = model.track(
source=camera_index, # Get video stream directly from the camera source=camera_index, # Get video stream directly from the camera
conf=0.5, conf=0.3,
tracker=yaml_path, # Use the YAML configuration file tracker=yaml_path, # Use the YAML configuration file
persist=True, # Persist tracking (do not reset) persist=True, # Persist tracking (do not reset)
stream=True, # Stream processing, not frame-by-frame calling stream=True, # Stream processing, not frame-by-frame calling
show=False show=False,
device = 'mps' #'cpu'
) )
# Create variables to store the tracking results
num_defective = 0
num_good = 0
num_notripe = 0
last_classification = None
# Iterate over streaming tracking results # Iterate over streaming tracking results
for result in results: for result in results:
frame = result.orig_img # Current frame frame = result.orig_img # Current frame
frame = cv2.flip(frame, 1)
detections = result.boxes # Detection box information detections = result.boxes # Detection box information
# Create bounding box for classification area
cv2.rectangle(frame, (0, 370), (1000, 700), (0, 0, 0), -1) # Black background for text
cv2.rectangle(frame, (0, 0), (1000, 200), (0, 0, 0), -1) # Black background for text
cv2.rectangle(frame, (600, 200), (660, 370), (255, 255, 255), 2)
cv2.putText(frame, "Classification Area", (560, 190), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
# Display the number of lemons in the top left corner
cv2.putText(frame, f"Defective: {num_defective}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
cv2.putText(frame, f"Good: {num_good}", (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
cv2.putText(frame, f"Not Ripe: {num_notripe}", (10, 90), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 100, 80), 2)
cv2.putText(frame, f"Last Classification: {last_classification}", (10, 120), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
cv2.putText(frame, f"Total Lemons: {num_defective + num_good + num_notripe}", (10, 150), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 255), 2)
for box in detections: for box in detections:
x1, y1, x2, y2 = map(int, box.xyxy[0]) # Detection box coordinates x1, y1, x2, y2 = map(int, box.xyxy[0]) # Detection box coordinates
# Adjust x-coordinates for the flipped frame
x1, x2 = width - x2, width - x1
obj_id = int(box.id) if box.id is not None else -1 # Tracking ID obj_id = int(box.id) if box.id is not None else -1 # Tracking ID
class_id = int(box.cls) # Class ID class_id = int(box.cls) # Class ID
score = box.conf # Confidence score = box.conf # Confidence
@@ -100,6 +226,41 @@ for result in results:
if final_label in smoothing_labels: if final_label in smoothing_labels:
position = f"({x1}, {y1}, {x2}, {y2})" position = f"({x1}, {y1}, {x2}, {y2})"
print(f"ID: {obj_id}, Position: {position}, Label: {display_text}") print(f"ID: {obj_id}, Position: {position}, Label: {display_text}")
# Draw detection box and label with color based on classification
if final_label == "DefectiveLemon":
box_color = (100, 100, 255) # Red for defective
elif final_label == "NotRipeLemon":
box_color = (255, 100, 80) # Blue for unripe
elif final_label == "GoodLemon":
box_color = (0, 255, 0) # Green for good
else:
box_color = (255, 255, 255) # White for unknown or other classes
# Add background rectangle for text
text_size = cv2.getTextSize(display_text, cv2.FONT_HERSHEY_TRIPLEX, 0.6, 2)[0]
text_x, text_y = x1, y1 - 10
text_w, text_h = text_size[0], text_size[1]
cv2.rectangle(frame, (text_x, text_y - text_h - 5), (text_x + text_w, text_y + 5), (0, 0, 0), -1)
# Draw detection box and text
cv2.rectangle(frame, (x1, y1), (x2, y2), box_color, 2)
cv2.putText(frame, display_text, (text_x, text_y),
cv2.FONT_HERSHEY_TRIPLEX, 0.6, box_color, 2)
cv2.rectangle(frame, (500, 0), (1000, 170), (0, 0, 0), -1) # Black background for text
if x1 > 600 and x1 < 660 and y2 < 410 and y1 > 190 and obj_id not in lemon_send_history:
if final_label in ["DefectiveLemon", "NotRipeLemon", "GoodLemon"]:
mqtt_message = f"lemon_classification classification=\"{final_label}\" {int(time.time()*1e9)}"
lemon_send_history.append(obj_id)
mqtt_client.publish(MQTT_TOPIC, mqtt_message)
# Update Tracking Variables
if final_label == "DefectiveLemon":
num_defective += 1
elif final_label == "GoodLemon":
num_good += 1
elif final_label == "NotRipeLemon":
num_notripe += 1
last_classification = final_label
else: else:
# For other classes, display the current detection result directly and clear history (if exists) # For other classes, display the current detection result directly and clear history (if exists)
if obj_id in lemon_history: if obj_id in lemon_history:
@@ -108,11 +269,6 @@ for result in results:
else: else:
display_text = label display_text = label
# Draw detection box and label
cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
cv2.putText(frame, display_text, (x1, y1 - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
# Display the processed frame # Display the processed frame
cv2.imshow("Live Detection", frame) cv2.imshow("Live Detection", frame)
@@ -123,3 +279,5 @@ for result in results:
cv2.destroyAllWindows() cv2.destroyAllWindows()
print("Camera video processing complete. Program terminated.") print("Camera video processing complete. Program terminated.")

3
autostart.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash
source /Users/ag_cv_gaaim/Desktop/CV_AG/cvag/bin/activate
python3 /Users/ag_cv_gaaim/Desktop/CV_AG/Test_Track_updated.py

View File

@@ -4,18 +4,18 @@
# For documentation and examples see https://docs.ultralytics.com/modes/track/ # For documentation and examples see https://docs.ultralytics.com/modes/track/
# For BoT-SORT source code see https://github.com/NirAharon/BoT-SORT # For BoT-SORT source code see https://github.com/NirAharon/BoT-SORT
tracker_type: botsort # tracker type, ['botsort', 'bytetrack'] tracker_type: bytetrack # tracker type, ['botsort', 'bytetrack']
track_high_thresh: 0.25 # threshold for the first association track_high_thresh: 0.2 # threshold for the first association
track_low_thresh: 0.1 # threshold for the second association track_low_thresh: 0.05 # threshold for the second association
new_track_thresh: 0.4 # threshold for init new track if the detection does not match any tracks new_track_thresh: 0.3 # threshold for init new track if the detection does not match any tracks
track_buffer: 30 # buffer to calculate the time when to remove tracks track_buffer: 50 # buffer to calculate the time when to remove tracks
match_thresh: 0.7 # threshold for matching tracks match_thresh: 0.8 # threshold for matching tracks
fuse_score: True # Whether to fuse confidence scores with the iou distances before matching fuse_score: True # Whether to fuse confidence scores with the iou distances before matching
# min_box_area: 10 # threshold for min box areas(for tracker evaluation, not used for now) # min_box_area: 10 # threshold for min box areas(for tracker evaluation, not used for now)
# BoT-SORT settings # BoT-SORT settings
gmc_method: sparseOptFlow # method of global motion compensation gmc_method: sparseOptFlow # method of global motion compensation
# ReID model related thresh (not supported yet) # ReID model related thresh (not supported yet)
proximity_thresh: 0.5 proximity_thresh: 0.6
appearance_thresh: 0.25 appearance_thresh: 0.2
with_reid: False with_reid: False

BIN
comicrobodog.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

65
loadingscreen2.py Normal file
View File

@@ -0,0 +1,65 @@
import tkinter as tk
from tkinter import ttk
from PIL import Image, ImageTk
import time
import threading
def start_loading():
for i in range(101): # 0 to 100%
time.sleep(0.4) # 0.4s * 100 = 40 seconds
progress_var.set(i)
progress_bar.update_idletasks()
root.destroy()
# Set up full-screen window
root = tk.Tk()
root.title("Starting Up")
root.attributes('-fullscreen', True)
# Get screen size
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
# Load and resize the background image
try:
bg_img = Image.open("comicrobodog.png") # Replace with your image
bg_img = bg_img.resize((screen_width, screen_height), Image.ANTIALIAS)
bg_photo = ImageTk.PhotoImage(bg_img)
# Set as background using a label
bg_label = tk.Label(root, image=bg_photo)
bg_label.place(x=0, y=0, relwidth=1, relheight=1)
except Exception as e:
print("Error loading background image:", e)
root.configure(bg='black') # Fallback
# Overlay content frame (transparent background)
overlay = tk.Frame(root, bg='', padx=20, pady=20)
overlay.place(relx=0.5, rely=0.5, anchor='center')
# Message label
label = tk.Label(
overlay,
text="Computer Vision Vignette is Starting Up",
font=("Helvetica", 32, "bold"),
fg="white"
)
label.pack(pady=10)
# Progress bar
progress_var = tk.IntVar()
progress_bar = ttk.Progressbar(
overlay,
maximum=100,
variable=progress_var,
length=800
)
progress_bar.pack(pady=20)
# Start the progress in a thread
threading.Thread(target=start_loading, daemon=True).start()
# Close on ESC
root.bind("<Escape>", lambda e: root.destroy())
root.mainloop()