|
| 1 | +# pc_detector_sender.py |
| 2 | +import time |
| 3 | +import threading |
| 4 | +import requests |
| 5 | +import cv2 |
| 6 | +import numpy as np |
| 7 | +from collections import deque |
| 8 | +from ultralytics import YOLO |
| 9 | +from datetime import datetime |
| 10 | + |
| 11 | +# ---------- CONFIGURAÇÃO ---------- |
| 12 | +VIDEO_SOURCE = 0 # 0 = webcam. Pode ser "carros.mp4" ou "rtsp://..." |
| 13 | +YAOLO_MODEL = "yolo11n.pt" # caminho para o modelo (já baixado) |
| 14 | +LIMIT_CONFIANCA = 0.45 |
| 15 | +VEICULOS_CLASSES = {2:"Carro", 3:"Moto", 5:"Onibus", 7:"Caminhao"} # COCO ids relevantes |
| 16 | + |
| 17 | +PI_URL = "http://10.186.150.9:5000/update" # <-- Troque pelo IP do seu Raspberry Pi |
| 18 | +SEND_INTERVAL = 1.0 # segundos entre POSTs ao Pi |
| 19 | + |
| 20 | +# Parâmetros de movimento / estacionamento |
| 21 | +MOVE_TOLERANCE = 15 # pixels — movimento mínimo para considerar "em movimento" |
| 22 | +PARK_SECONDS = 10.0 # se sem movimento por X secs, considera estacionado |
| 23 | + |
| 24 | +# ---------- RASTREADOR SIMPLES ---------- |
| 25 | +class SimpleTracker: |
| 26 | + def __init__(self, max_disappeared=50): |
| 27 | + self.next_id = 0 |
| 28 | + self.objects = {} # id -> centroid (x,y) |
| 29 | + self.disappeared = {} # id -> frames desaparecido |
| 30 | + # para lógica de estacionamento: |
| 31 | + self.last_moved_time = {} # id -> timestamp of last movement |
| 32 | + self.last_position = {} # id -> last centroid |
| 33 | + self.max_disappeared = max_disappeared |
| 34 | + |
| 35 | + def register(self, centroid): |
| 36 | + i = self.next_id |
| 37 | + self.objects[i] = centroid |
| 38 | + self.disappeared[i] = 0 |
| 39 | + self.last_position[i] = centroid |
| 40 | + self.last_moved_time[i] = time.time() |
| 41 | + self.next_id += 1 |
| 42 | + |
| 43 | + def deregister(self, oid): |
| 44 | + for d in (self.objects, self.disappeared, self.last_moved_time, self.last_position): |
| 45 | + if oid in d: |
| 46 | + del d[oid] |
| 47 | + |
| 48 | + def update(self, input_centroids): |
| 49 | + # input_centroids: list of (x,y) |
| 50 | + if len(input_centroids) == 0: |
| 51 | + # increment disappeared count |
| 52 | + for oid in list(self.disappeared.keys()): |
| 53 | + self.disappeared[oid] += 1 |
| 54 | + if self.disappeared[oid] > self.max_disappeared: |
| 55 | + self.deregister(oid) |
| 56 | + return self.objects |
| 57 | + |
| 58 | + if len(self.objects) == 0: |
| 59 | + for c in input_centroids: |
| 60 | + self.register(c) |
| 61 | + return self.objects |
| 62 | + |
| 63 | + # build arrays |
| 64 | + object_ids = list(self.objects.keys()) |
| 65 | + object_centroids = list(self.objects.values()) |
| 66 | + |
| 67 | + D = np.linalg.norm(np.array(object_centroids)[:, np.newaxis] - np.array(input_centroids), axis=2) |
| 68 | + rows = D.min(axis=1).argsort() |
| 69 | + cols = D.argmin(axis=1)[rows] |
| 70 | + |
| 71 | + used_rows, used_cols = set(), set() |
| 72 | + for row, col in zip(rows, cols): |
| 73 | + if row in used_rows or col in used_cols: |
| 74 | + continue |
| 75 | + oid = object_ids[row] |
| 76 | + new_centroid = input_centroids[col] |
| 77 | + # update disappeared |
| 78 | + self.disappeared[oid] = 0 |
| 79 | + # check movement distance |
| 80 | + old = self.last_position.get(oid, new_centroid) |
| 81 | + dist = np.linalg.norm(np.array(old) - np.array(new_centroid)) |
| 82 | + if dist >= MOVE_TOLERANCE: |
| 83 | + self.last_moved_time[oid] = time.time() |
| 84 | + self.last_position[oid] = new_centroid |
| 85 | + self.objects[oid] = new_centroid |
| 86 | + used_rows.add(row) |
| 87 | + used_cols.add(col) |
| 88 | + |
| 89 | + # rows not matched -> disappeared |
| 90 | + unused_rows = set(range(0, D.shape[0])) - used_rows |
| 91 | + for row in unused_rows: |
| 92 | + oid = object_ids[row] |
| 93 | + self.disappeared[oid] += 1 |
| 94 | + if self.disappeared[oid] > self.max_disappeared: |
| 95 | + self.deregister(oid) |
| 96 | + |
| 97 | + # cols not matched -> new objects |
| 98 | + unused_cols = set(range(0, D.shape[1])) - used_cols |
| 99 | + for col in unused_cols: |
| 100 | + self.register(input_centroids[col]) |
| 101 | + |
| 102 | + return self.objects |
| 103 | + |
| 104 | + def get_active_ids(self): |
| 105 | + """Retorna IDs que NÃO estão estacionados (moved within PARK_SECONDS)""" |
| 106 | + now = time.time() |
| 107 | + active = [] |
| 108 | + for oid, centroid in self.objects.items(): |
| 109 | + last_moved = self.last_moved_time.get(oid, 0) |
| 110 | + if (now - last_moved) <= PARK_SECONDS: |
| 111 | + active.append(oid) |
| 112 | + return active |
| 113 | + |
| 114 | +# ---------- ENVIADOR ASSÍNCRONO ---------- |
| 115 | +class SenderThread(threading.Thread): |
| 116 | + def __init__(self, url, interval=1.0): |
| 117 | + super().__init__(daemon=True) |
| 118 | + self.url = url |
| 119 | + self.interval = interval |
| 120 | + self.payload = {"vehicle_present": False, "moving_count": 0} |
| 121 | + self._stop = threading.Event() |
| 122 | + |
| 123 | + def run(self): |
| 124 | + while not self._stop.is_set(): |
| 125 | + try: |
| 126 | + requests.post(self.url, json=self.payload, timeout=1.5) |
| 127 | + except Exception as e: |
| 128 | + # opcional: print("Erro ao enviar para Pi:", e) |
| 129 | + pass |
| 130 | + time.sleep(self.interval) |
| 131 | + |
| 132 | + def update_payload(self, present, moving_count): |
| 133 | + self.payload = {"vehicle_present": bool(present), "moving_count": int(moving_count)} |
| 134 | + |
| 135 | + def stop(self): |
| 136 | + self._stop.set() |
| 137 | + |
| 138 | +# ---------- LÓGICA PRINCIPAL ---------- |
| 139 | +def main(): |
| 140 | + model = YOLO(YAOLO_MODEL) |
| 141 | + cap = cv2.VideoCapture(VIDEO_SOURCE) |
| 142 | + if not cap.isOpened(): |
| 143 | + print("Erro ao abrir fonte:", VIDEO_SOURCE) |
| 144 | + return |
| 145 | + |
| 146 | + tracker = SimpleTracker() |
| 147 | + sender = SenderThread(PI_URL, interval=SEND_INTERVAL) |
| 148 | + sender.start() |
| 149 | + |
| 150 | + try: |
| 151 | + while True: |
| 152 | + ret, frame = cap.read() |
| 153 | + if not ret: |
| 154 | + break |
| 155 | + |
| 156 | + # run detection (note: may be heavy; adjust skip frames if needed) |
| 157 | + results = model(frame) |
| 158 | + |
| 159 | + # collect centroids for vehicle classes |
| 160 | + centroids = [] |
| 161 | + for r in results: |
| 162 | + for box in r.boxes: |
| 163 | + cls_id = int(box.cls[0]) |
| 164 | + conf = float(box.conf[0]) |
| 165 | + if conf < LIMIT_CONFIANCA: |
| 166 | + continue |
| 167 | + if cls_id not in VEICULOS_CLASSES: |
| 168 | + continue |
| 169 | + x1, y1, x2, y2 = map(int, box.xyxy[0]) |
| 170 | + # optional: skip tiny boxes |
| 171 | + area = (x2 - x1) * (y2 - y1) |
| 172 | + if area < 500: # ajuste conforme vídeo |
| 173 | + continue |
| 174 | + cx, cy = (x1 + x2) // 2, (y1 + y2) // 2 |
| 175 | + centroids.append((cx, cy)) |
| 176 | + # draw box |
| 177 | + name = VEICULOS_CLASSES[cls_id] |
| 178 | + cv2.rectangle(frame, (x1, y1), (x2, y2), (0,255,0), 2) |
| 179 | + cv2.putText(frame, f"{name} {conf:.2f}", (x1, y1-8), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 1) |
| 180 | + |
| 181 | + # update tracker |
| 182 | + objs = tracker.update(centroids) |
| 183 | + active_ids = tracker.get_active_ids() # ids considered "moving" (not parked) |
| 184 | + |
| 185 | + # show tracker ids on frame |
| 186 | + for oid, centroid in objs.items(): |
| 187 | + cx, cy = map(int, centroid) |
| 188 | + text = f"ID{oid}" |
| 189 | + if oid in active_ids: |
| 190 | + color = (0,255,0) |
| 191 | + else: |
| 192 | + color = (0,0,255) # parked = red id |
| 193 | + cv2.putText(frame, text, (cx-10, cy-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1) |
| 194 | + cv2.circle(frame, (cx,cy), 4, color, -1) |
| 195 | + |
| 196 | + # update sender payload |
| 197 | + sender.update_payload(present=len(active_ids)>0, moving_count=len(active_ids)) |
| 198 | + |
| 199 | + cv2.imshow("PC - Detector", frame) |
| 200 | + if cv2.waitKey(1) & 0xFF == ord('q'): |
| 201 | + break |
| 202 | + finally: |
| 203 | + sender.stop() |
| 204 | + sender.join(timeout=2.0) |
| 205 | + cap.release() |
| 206 | + cv2.destroyAllWindows() |
| 207 | + |
| 208 | +if __name__ == "__main__": |
| 209 | + main() |
0 commit comments