Orlandinijunior commited on
Commit
e2f685b
·
verified ·
1 Parent(s): c802af6

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +385 -0
app.py ADDED
@@ -0,0 +1,385 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #===========================================================================#
2
+ #===========================================================================#
3
+ # SETUP INSTALLATIONS
4
+ #===========================================================================#
5
+ #===========================================================================#
6
+ import os
7
+ import sys
8
+
9
+ def install_packages():
10
+ # Atualizar pip
11
+ os.system(f"pip install --upgrade pip")
12
+
13
+ # Instalar pacotes necessários
14
+ packages = [
15
+ "opencv-python-headless==4.10.0.82",
16
+ "ultralytics==8.3",
17
+ "telethon==1.37.0",
18
+ "cryptography==43.0.3",
19
+ "nest_asyncio",
20
+ "torch==2.5.0 torchvision==0.20.0 torchaudio==2.5.0 --index-url https://download.pytorch.org/whl/cpu",
21
+ "paddlepaddle==2.6.2 -f https://paddlepaddle.org.cn/whl/mkl/avx/stable.html",
22
+ "paddleocr==2.9.1",
23
+ "prettytable==3.12",
24
+ "gradio==5.6",
25
+ ]
26
+
27
+ for package in packages:
28
+ print(f"Installing {package}...")
29
+ os.system(f"pip install {package}")
30
+
31
+ print("All packages installed successfully.")
32
+
33
+ install_packages()
34
+
35
+ #===========================================================================#
36
+ #===========================================================================#
37
+ # PLAY THE CLASS
38
+ #===========================================================================#
39
+ #===========================================================================#
40
+ import gradio as gr
41
+ import numpy as np
42
+ import cv2
43
+ from collections import deque, OrderedDict, defaultdict
44
+ from ultralytics import YOLO
45
+ from paddleocr import PaddleOCR
46
+ import asyncio
47
+ import threading
48
+ from telethon import TelegramClient
49
+ from cryptography.fernet import Fernet
50
+ import json
51
+ import nest_asyncio
52
+ from prettytable import PrettyTable
53
+
54
+ # Aplicar nest_asyncio para permitir loops de eventos aninhados
55
+ nest_asyncio.apply()
56
+
57
+ # Função para obter o caminho de recursos
58
+ def resource_path(relative_path):
59
+ return os.path.join(os.getcwd(), relative_path)
60
+
61
+ # Classe adaptada para processar frames individuais
62
+ class LicensePlateProcessor:
63
+ def __init__(self):
64
+ # Carregar modelo YOLO
65
+ model_path = resource_path('best.pt')
66
+ self.model = YOLO(model_path, task='detect')
67
+
68
+ # Carregar PaddleOCR
69
+ paddleocr_model_dir = resource_path('paddleocr_models')
70
+ self.ocr = PaddleOCR(
71
+ use_angle_cls=True,
72
+ use_gpu=False,
73
+ lang='en',
74
+ det_algorithm='DB',
75
+ rec_algorithm='CRNN',
76
+ show_log=False,
77
+ rec_model_dir=os.path.join(paddleocr_model_dir, 'en_PP-OCRv3_rec_infer'),
78
+ det_model_dir=os.path.join(paddleocr_model_dir, 'en_PP-OCRv3_det_infer'),
79
+ cls_model_dir=os.path.join(paddleocr_model_dir, 'ch_ppocr_mobile_v2.0_cls_infer')
80
+ )
81
+
82
+ # Carregar dados encriptados
83
+ self.load_encrypted_data()
84
+
85
+ # Inicializar TelegramClient
86
+ self.telegram_client = TelegramClient(self.session_name, self.api_id, self.api_hash)
87
+ self.telegram_client.start()
88
+
89
+ # Memória de placas
90
+ self.plates_memory = deque(maxlen=500)
91
+ self.last_sixteen_plates = OrderedDict()
92
+
93
+ # Filas para placas aguardando resposta
94
+ self.waiting_plates = {}
95
+
96
+ # Iniciar loop asyncio em uma thread separada
97
+ self.loop = asyncio.get_event_loop()
98
+ self.loop_thread = threading.Thread(target=self.start_loop, daemon=True)
99
+ self.loop_thread.start()
100
+
101
+ # Iniciar tarefa assíncrona para verificar respostas
102
+ asyncio.run_coroutine_threadsafe(self.check_responses(), self.loop)
103
+
104
+ def start_loop(self):
105
+ """Inicia o loop de eventos asyncio."""
106
+ asyncio.set_event_loop(self.loop)
107
+ self.loop.run_forever()
108
+
109
+ def load_encrypted_data(self):
110
+ """Carrega e decripta os dados sensíveis."""
111
+ encrypted_data_path = resource_path('SECRET_DATA.enc')
112
+ decrypt_key_path = resource_path('decrypt_key.txt')
113
+
114
+ with open(encrypted_data_path, "rb") as f:
115
+ data_encrypted = f.read()
116
+ with open(decrypt_key_path, "r") as key_file:
117
+ key_str = key_file.read().strip()
118
+ key = key_str.encode('utf-8')
119
+ cipher = Fernet(key)
120
+ data_decrypted = cipher.decrypt(data_encrypted)
121
+ config = json.loads(data_decrypted.decode())
122
+ self.api_id = config["api_id"]
123
+ self.api_hash = config["api_hash"]
124
+ self.phone_number = config["phone_number"]
125
+ self.session_name = resource_path(config["session_name"])
126
+
127
+ def has_seven(self, plate_text):
128
+ """Verifica o status de uma placa."""
129
+ # Verificar se a placa está na memória
130
+ for item in self.plates_memory:
131
+ if item['plate'] == plate_text:
132
+ return item['has_seven']
133
+ # Verificar se está aguardando resposta
134
+ if plate_text in self.waiting_plates:
135
+ return self.waiting_plates[plate_text]
136
+ else:
137
+ # Enviar placa para o bot do Telegram
138
+ self.waiting_plates[plate_text] = 'Waiting'
139
+ asyncio.run_coroutine_threadsafe(self.send_plate(plate_text), self.loop)
140
+ return 'Waiting'
141
+
142
+ async def send_plate(self, plate_text):
143
+ """Envia a placa para o bot do Telegram."""
144
+ chat_identifier = '@LT_BUSCABOT'
145
+ try:
146
+ await self.telegram_client.connect()
147
+ await self.telegram_client.send_message(chat_identifier, plate_text)
148
+ print(f"Enviado para Telegram: {plate_text}")
149
+ except Exception as e:
150
+ print(f"Erro ao enviar placa {plate_text}: {e}")
151
+
152
+ async def check_responses(self):
153
+ """Verifica respostas do bot do Telegram."""
154
+ while True:
155
+ if not self.waiting_plates:
156
+ await asyncio.sleep(1)
157
+ continue
158
+ chat_identifier = '@LT_BUSCABOT'
159
+ limit = 20
160
+ try:
161
+ await self.telegram_client.connect()
162
+ messages = await self.telegram_client.get_messages(chat_identifier, limit=limit)
163
+ # print(messages)
164
+ for message in messages:
165
+ text = message.text
166
+ # Check if message is a response to one of our plates
167
+ for plate in list(self.waiting_plates.keys()):
168
+ if plate.lower() in text.lower():
169
+ # Found response for this plate
170
+ if 'Placa Localizada' in text or 'não foi encontrada' in text:
171
+ self.waiting_plates.pop(plate)
172
+ self.plates_memory.append({'plate': plate, 'has_seven': True})
173
+ elif 'não é uma placa válida' in text:
174
+ self.waiting_plates.pop(plate)
175
+ self.plates_memory.append({'plate': plate, 'has_seven': 'Non Valid'})
176
+ # Update the plate status in the displayed grid
177
+ self.update_displayed_plate(plate)
178
+ except Exception as e:
179
+ print(f"Error checking responses: {e}")
180
+ await asyncio.sleep(2)
181
+
182
+ def update_displayed_plate(self, plate):
183
+ """Atualiza o status da placa exibida na tabela."""
184
+ for item in self.plates_memory:
185
+ if item['plate'] == plate:
186
+ if item['has_seven'] == 'Non Valid':
187
+ self.last_sixteen_plates.pop(plate, None)
188
+ else:
189
+ self.last_sixteen_plates[plate] = item['has_seven']
190
+ break
191
+ # Manter apenas as últimas 16 placas válidas
192
+ self.last_sixteen_plates = OrderedDict((p, s) for p, s in self.last_sixteen_plates.items() if s != 'Non Valid')
193
+ while len(self.last_sixteen_plates) > 16:
194
+ self.last_sixteen_plates.popitem(last=False)
195
+
196
+ def remove_non_alphanumeric(self, text):
197
+ """Remove caracteres não alfanuméricos."""
198
+ return ''.join(char for char in text if char.isalnum())
199
+
200
+ def has_number_and_letter(self, text):
201
+ """Verifica se o texto contém letras e números."""
202
+ return text.isalnum() and not text.isalpha() and not text.isdigit()
203
+
204
+ def process_license_plates(self, ocr_result):
205
+ """Processa o resultado do OCR para identificar placas."""
206
+ def is_overlapping(box1, box2):
207
+ x1_min = min(point[0] for point in box1)
208
+ x1_max = max(point[0] for point in box1)
209
+ y1_min = min(point[1] for point in box1)
210
+ y1_max = max(point[1] for point in box1)
211
+
212
+ x2_min = min(point[0] for point in box2)
213
+ x2_max = max(point[0] for point in box2)
214
+ y2_min = min(point[1] for point in box2)
215
+ y2_max = max(point[1] for point in box2)
216
+
217
+ # Verificar sobreposição em X
218
+ overlap_x = (x1_max + 2 >= x2_min - 2) and (x1_min - 2 <= x2_max + 2)
219
+ # Verificar sobreposição em Y
220
+ overlap_y = (y1_max + 2 >= y2_min - 2) and (y1_min - 2 <= y2_max + 2)
221
+
222
+ return overlap_x and overlap_y
223
+
224
+ # Extrair caixas e textos
225
+ boxes = [item[0] for item in ocr_result[0]]
226
+ strings = [item[1][0] for item in ocr_result[0]]
227
+
228
+ # Separar em placas completas e segmentos parciais
229
+ full_plates = []
230
+ partial_segments = []
231
+ for box, s in zip(boxes, strings):
232
+ if len(s) >= 7:
233
+ full_plates.append((box, s))
234
+ elif len(s) in [3, 4]:
235
+ partial_segments.append((box, s))
236
+
237
+ # Processar segmentos parciais
238
+ n = len(partial_segments)
239
+ parent = list(range(n))
240
+
241
+ def find(i):
242
+ while parent[i] != i:
243
+ parent[i] = parent[parent[i]]
244
+ i = parent[i]
245
+ return i
246
+
247
+ def union(i, j):
248
+ pi = find(i)
249
+ pj = find(j)
250
+ if pi != pj:
251
+ parent[pj] = pi
252
+
253
+ # Construir grupos com base na sobreposição
254
+ for i in range(n):
255
+ for j in range(i + 1, n):
256
+ if is_overlapping(partial_segments[i][0], partial_segments[j][0]):
257
+ union(i, j)
258
+
259
+ # Agrupar as caixas
260
+ groups = defaultdict(list)
261
+ for i in range(n):
262
+ groups[find(i)].append(partial_segments[i])
263
+
264
+ # Concatenar textos em cada grupo
265
+ concatenated_partials = []
266
+ for group in groups.values():
267
+ # Ordenar com base na coordenada Y mínima (de cima para baixo)
268
+ sorted_group = sorted(group, key=lambda x: min(point[1] for point in x[0]))
269
+ concatenated = ''.join([s for box, s in sorted_group])
270
+ concatenated_partials.append(concatenated)
271
+
272
+ # Adicionar placas completas
273
+ all_plates = [s for box, s in full_plates] + concatenated_partials
274
+ return all_plates
275
+
276
+ def perform_ocr(self, img_array):
277
+ """Realiza OCR na imagem e retorna os textos detectados."""
278
+ if img_array.shape[0] == 0 or img_array.shape[1] == 0:
279
+ return None
280
+
281
+ result = self.ocr.ocr(img_array, cls=True)
282
+ if not result[0]:
283
+ return None
284
+ return self.process_license_plates(result)
285
+
286
+ def save_plate(self, plate_text):
287
+ """Salva o status da placa."""
288
+ # Verificar se a placa está na memória
289
+ for item in self.plates_memory:
290
+ if item['plate'] == plate_text:
291
+ return item['has_seven']
292
+
293
+ # Obter o status a partir da função has_seven
294
+ has_seven = self.has_seven(plate_text)
295
+ if has_seven != 'Waiting':
296
+ self.plates_memory.append({'plate': plate_text, 'has_seven': has_seven})
297
+ return has_seven
298
+
299
+ def process_frame(self, frame):
300
+ """Processa um frame individual da webcam."""
301
+ img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
302
+
303
+ # Detectar placas usando YOLO
304
+ results = self.model.predict(img, imgsz=256, conf=0.5)
305
+ plates_list = []
306
+
307
+ for res in results[0].boxes.data:
308
+ x1, y1, x2, y2 = int(res[0]), int(res[1]), int(res[2]), int(res[3])
309
+
310
+ # Ajustar o recorte para incluir padding
311
+ if (y2 - y1) > 0 and (x2 - x1) > 0:
312
+ prod = img.shape[0] * img.shape[1]
313
+ adj = round(5 * prod / 315000)
314
+ y1_adj = max(y1 - adj, 0)
315
+ y2_adj = min(y2 + adj, img.shape[0])
316
+ x1_adj = max(x1 - adj, 0)
317
+ x2_adj = min(x2 + adj, img.shape[1])
318
+ crop = img[y1_adj:y2_adj, x1_adj:x2_adj]
319
+
320
+ # Redimensionar o recorte
321
+ scale_factor = 3
322
+ new_size = (int(crop.shape[1] * scale_factor), int(crop.shape[0] * scale_factor))
323
+ resized_crop = cv2.resize(crop, new_size, interpolation=cv2.INTER_LINEAR)
324
+
325
+ # Realizar OCR
326
+ text_list = self.perform_ocr(resized_crop)
327
+
328
+ if text_list is None:
329
+ continue
330
+ for text in text_list:
331
+ text = self.remove_non_alphanumeric(text)
332
+ if text and self.has_number_and_letter(text):
333
+ has_seven = self.save_plate(text)
334
+ plates_list.append((text, has_seven))
335
+ if text not in self.last_sixteen_plates and has_seven != 'Non Valid':
336
+ if len(self.last_sixteen_plates) >= 16:
337
+ self.last_sixteen_plates.popitem(last=False)
338
+ self.last_sixteen_plates[text] = has_seven
339
+
340
+ # Atualizar a exibição
341
+ return self.get_display_table()
342
+
343
+ def get_display_table(self):
344
+ """Retorna a tabela das últimas 16 placas detectadas."""
345
+ if not self.last_sixteen_plates:
346
+ return "Nenhuma placa detectada ainda."
347
+ else:
348
+ table = PrettyTable()
349
+ table.field_names = ["Placa", "Status"]
350
+ for plate, status in self.last_sixteen_plates.items():
351
+ if status != 'Waiting' and status != 'Non Valid':
352
+ status_text = 'Okay' if status == True else '!EITA!'
353
+ table.add_row([plate, status_text])
354
+ return table.get_string()
355
+
356
+ #===========================================================================#
357
+ #===========================================================================#
358
+ # PLAY GRADIO
359
+ #===========================================================================#
360
+ #===========================================================================#
361
+ # Instanciar o processador
362
+ processor = LicensePlateProcessor()
363
+
364
+ # Função para ser chamada pelo Gradio
365
+ def process_webcam_frame(frame):
366
+ return processor.process_frame(frame)
367
+
368
+
369
+ with gr.Blocks() as demo:
370
+ with gr.Row():
371
+ with gr.Column():
372
+ input_img = gr.Image(label="Webcam", sources="webcam", streaming=True, mirror_webcam=False)
373
+ with gr.Column():
374
+ output_text = gr.Textbox(label="Últimas 16 Placas Detectadas", lines=20)
375
+ input_img.stream(
376
+ process_webcam_frame,
377
+ inputs=input_img,
378
+ outputs=output_text,
379
+ time_limit=0.05,
380
+ stream_every=0.2,
381
+ concurrency_limit=10
382
+ )
383
+
384
+ demo.launch(share=True)
385
+ # demo.launch(debug=True, share=True)