From 486d05eaebf272cd202effb5f3d40efb56b14fc6 Mon Sep 17 00:00:00 2001 From: Diego Freitas Date: Thu, 17 Jul 2025 08:19:42 -0300 Subject: [PATCH] =?UTF-8?q?Ajustes=20opera=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 12 +- .../AgroBase/Models/Modules/AtuadorModel.cs | 12 +- .../Modules/MovimentacaoUnificadoModel.cs | 22 +- .../Models/Modules/SensoriamentoModel.cs | 11 +- .../Models/Operacoes/OperacaoModel.cs | 4 +- .../Operadores/HealthWorkerService.cs | 16 +- .../Operadores/VisualWorkerService.cs | 57 ++-- Python/OAK/datasets/capture_worker.py | 302 ++++++++++++++++++ Python/OAK/datasets/oak-1/_1_capture.py | 139 ++++++++ Python/OAK/datasets/oak-1/_2_augmentation.py | 70 ++++ Python/OAK/datasets/oak-1/_3_normalize.py | 71 ++++ Python/OAK/datasets/oak-1/_4_prepare.txt | 14 + Python/OAK/datasets/oak-1/_5_split.py | 55 ++++ Python/OAK/datasets/oak-1/_6_train.txt | 28 ++ Python/OAK/datasets/oak-d/_1_capture.py | 139 ++++++++ Python/OAK/datasets/oak-d/_2_augmentation.py | 110 +++++++ Python/OAK/datasets/oak-d/_3_normalize.py | 107 +++++++ Python/OAK/datasets/oak-d/_4_split.py | 60 ++++ Python/OAK/datasets/oak-d/_5_train.py | 234 ++++++++++++++ Python/OAK/datasets/oak-d/_6_test.py | 193 +++++++++++ Python/OAK/datasets/requirements.txt | 59 ++++ Python/OAK/oak-d-lite-dataset.py | 49 +++ 22 files changed, 1721 insertions(+), 43 deletions(-) create mode 100644 Python/OAK/datasets/capture_worker.py create mode 100644 Python/OAK/datasets/oak-1/_1_capture.py create mode 100644 Python/OAK/datasets/oak-1/_2_augmentation.py create mode 100644 Python/OAK/datasets/oak-1/_3_normalize.py create mode 100644 Python/OAK/datasets/oak-1/_4_prepare.txt create mode 100644 Python/OAK/datasets/oak-1/_5_split.py create mode 100644 Python/OAK/datasets/oak-1/_6_train.txt create mode 100644 Python/OAK/datasets/oak-d/_1_capture.py create mode 100644 Python/OAK/datasets/oak-d/_2_augmentation.py create mode 100644 Python/OAK/datasets/oak-d/_3_normalize.py create mode 100644 Python/OAK/datasets/oak-d/_4_split.py create mode 100644 Python/OAK/datasets/oak-d/_5_train.py create mode 100644 Python/OAK/datasets/oak-d/_6_test.py create mode 100644 Python/OAK/datasets/requirements.txt create mode 100644 Python/OAK/oak-d-lite-dataset.py diff --git a/.gitignore b/.gitignore index ba81f89ee..93bfd2da9 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,14 @@ Thumbs.db *.mov *.wmv -# Ignorar a pasta Operacoes dentro da hierarquia completa +# Ignorar arquivos de build AgroBase/AgroBase/obj/ -AgroBase/AgroBase/bin/Debug/ +AgroBase/AgroBase/bin/ + +# Ignorar a pasta Operacoes e outras dentro da pasta Debug +AgroBase/AgroBase/bin/x64/Debug/Operacoes/ +AgroBase/AgroBase/bin/x64/Debug/Logs/ +AgroBase/AgroBase/bin/x64/Debug/Python/venv/ +Python/OAK/datasets/venv/ +Python/OAK/datasets/oak-1/dataset/ +Python/OAK/datasets/oak-d/dataset/ diff --git a/AgroBase/AgroBase/Models/Modules/AtuadorModel.cs b/AgroBase/AgroBase/Models/Modules/AtuadorModel.cs index 6427a09a8..a7689cf24 100644 --- a/AgroBase/AgroBase/Models/Modules/AtuadorModel.cs +++ b/AgroBase/AgroBase/Models/Modules/AtuadorModel.cs @@ -797,11 +797,19 @@ namespace AgroBase.Models.Modules return BombaOk; } - public async Task RealizarCalibragemAsync() + public List RealizarCalibragemAsync() { + List tasks = new List(); var DispAtu = Variaveis.OperacaoEmAndamento.DispAtu; if (DispAtu != null) - DispAtu.Dados.AtuPronto = await DispAtu.Dados.RealizarTestesIniciais(); + tasks.Add(DispAtu.Dados.RealizarTestesIniciais().ContinueWith(t => { + if (t.IsFaulted) + Console.WriteLine($"Erro no referenciamento do módulo {Modulo_ID}: {t.Exception?.InnerException?.Message}"); + if (t.IsCompleted) + Console.WriteLine($"Referenciamento do módulo {Modulo_ID} concluído com sucesso!"); + + })); + return tasks; } } diff --git a/AgroBase/AgroBase/Models/Modules/MovimentacaoUnificadoModel.cs b/AgroBase/AgroBase/Models/Modules/MovimentacaoUnificadoModel.cs index 953e466a3..2ad17db6b 100644 --- a/AgroBase/AgroBase/Models/Modules/MovimentacaoUnificadoModel.cs +++ b/AgroBase/AgroBase/Models/Modules/MovimentacaoUnificadoModel.cs @@ -20,10 +20,6 @@ namespace AgroBase.Models.Modules { return Modulos.Any(x => x.Conectado); } - set - { - - } } public bool DirPronto { get; set; } = false; public bool MovPronto { get; set; } = false; @@ -398,16 +394,30 @@ namespace AgroBase.Models.Modules return Modulos.All(x => x.DirMotor.Referenciado); } - public async Task RealizarCalibragemAsync() + public List RealizarCalibragemAsync() { + List tasks = new List(); var DispMvd = Variaveis.OperacaoEmAndamento.DispMvd; if (DispMvd != null) { DispMvd.Dados.MovPronto = DispMvd.Dados.Modulos.All(x => x.MovMotor.Inicializado); DispMvd.Dados.DirPronto = DispMvd.Dados.Modulos.All(x => x.DirMotor.Inicializado); if (DispMvd.Dados.Modulos.Any(x => x.DirMotor.Inicializado)) - DispMvd.Dados.DirPronto &= await DispMvd.Dados.RealizarReferenciamentoDirecional(); + { + Variaveis.OperacaoEmAndamento.Controle.TipoMovimento = TipoMovimentoDirecional.Diagnostico; + var ModsRef = Modulos.Where(x => x.DirMotor.Comandar).ToList(); // && x.DirMotor.Inicializado + foreach (var Modulo in ModsRef) + { + tasks.Add(Modulo.DirMotor.ReferenciaMotor().ContinueWith(t => { + if (t.IsFaulted) + Console.WriteLine($"Erro no referenciamento do módulo {Modulo.Modulo_ID}: {t.Exception?.InnerException?.Message}"); + if (t.IsCompleted) + Console.WriteLine($"Referenciamento do módulo {Modulo.Modulo_ID} concluído com sucesso!"); + })); + } + } } + return tasks; } diff --git a/AgroBase/AgroBase/Models/Modules/SensoriamentoModel.cs b/AgroBase/AgroBase/Models/Modules/SensoriamentoModel.cs index 4dab67f86..a97d27755 100644 --- a/AgroBase/AgroBase/Models/Modules/SensoriamentoModel.cs +++ b/AgroBase/AgroBase/Models/Modules/SensoriamentoModel.cs @@ -20,6 +20,13 @@ namespace AgroBase.Models.Modules public byte _EnderecoCAN_Tx { get; set; } = 0x11; public byte _EnderecoCAN_Rx { get; set; } = 0x12; public int _TaxaAmostragem { get; set; } = 1000; + public double FrequenciaBase + { + get + { + return 1.0 / (_TaxaAmostragem / 1000.0); + } + } public bool RequisitarDados { get; set; } = true; public static int PingsConsiderarConexao { get; set; } = 5; public static int TempoLimiteConexao @@ -1997,8 +2004,8 @@ namespace AgroBase.Models.Modules private void AtualizaComponentesIniciados() { - AtualizarStatusComponentes(); - return; + //AtualizarStatusComponentes(); + //return; DateTime Agora = DateTime.Now; bool modConectado = DadosLeitura.UltimoComandoRespondido.AddMilliseconds(TempoLimiteConexao) > Agora; diff --git a/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs b/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs index 0858b3ce2..95f4e4658 100644 --- a/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs +++ b/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs @@ -695,8 +695,8 @@ namespace AgroBase.Models var DispMvd = Variaveis.OperacaoEmAndamento.DispMvd; var tasks = new List(); - if (DispAtu?.Dados != null) tasks.Add(DispAtu.Dados.RealizarCalibragemAsync()); - if (DispMvd?.Dados != null) tasks.Add(DispMvd.Dados.RealizarCalibragemAsync()); + if (DispAtu?.Dados != null) tasks.AddRange(DispAtu.Dados.RealizarCalibragemAsync()); + if (DispMvd?.Dados != null) tasks.AddRange(DispMvd.Dados.RealizarCalibragemAsync()); if (tasks.Count == 0) { diff --git a/AgroBase/AgroBase/Services/Operadores/HealthWorkerService.cs b/AgroBase/AgroBase/Services/Operadores/HealthWorkerService.cs index a5ccdb878..cdb67a592 100644 --- a/AgroBase/AgroBase/Services/Operadores/HealthWorkerService.cs +++ b/AgroBase/AgroBase/Services/Operadores/HealthWorkerService.cs @@ -321,8 +321,14 @@ namespace AgroBase.Services.Operadores ); } + var DadosSen = Variaveis.OperacaoEmAndamento.DispSen.Dados; + RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Sen), + ("timestamp", agora), + ("conectado", DadosSen.Conectado), + ("freq_base", DadosSen.FrequenciaBase) + ); var sensoresAtualizados = new List<(string caminho, object valor)>(); - foreach (var sensor in Variaveis.OperacaoEmAndamento.DispSen.Dados.Sensores) + foreach (var sensor in DadosSen.Sensores) { int tipo = (int)sensor.Componente; int id = sensor.ID_Num; @@ -361,7 +367,7 @@ namespace AgroBase.Services.Operadores RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Sen), sensoresAtualizados.ToArray()); var servosAtualizados = new List<(string caminho, object valor)>(); - foreach (var servo in Variaveis.OperacaoEmAndamento.DispSen.Dados.Servos) + foreach (var servo in DadosSen.Servos) { int id = servo.ID_Num; string basePath = $"servos.{id}"; @@ -394,10 +400,12 @@ namespace AgroBase.Services.Operadores servosAtualizados.Add(($"{basePath}", servoObj)); } RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Sen), servosAtualizados.ToArray()); + + } - - + + } } diff --git a/AgroBase/AgroBase/Services/Operadores/VisualWorkerService.cs b/AgroBase/AgroBase/Services/Operadores/VisualWorkerService.cs index 04fb9daa5..173ae30e9 100644 --- a/AgroBase/AgroBase/Services/Operadores/VisualWorkerService.cs +++ b/AgroBase/AgroBase/Services/Operadores/VisualWorkerService.cs @@ -138,36 +138,43 @@ namespace AgroBase.Services.Operadores public static void AtualizarAnalise() { - DadosLeitura.Imu = JsonConvert.DeserializeObject(RedisService.Get(RedisService.ModKey(Enums.T_Code.Imu))); + string imuJson = RedisService.Get(RedisService.ModKey(Enums.T_Code.Imu)); + if (!string.IsNullOrEmpty(imuJson)) + { + DadosLeitura.Imu = JsonConvert.DeserializeObject(imuJson); + } var json = RedisService.Get(CtxKey.DadosVisualWorker); var dados = JsonConvert.DeserializeObject>(json); - DateTime timestamp = FuncoesGlobais.UnixToDateTime(dados["ts_analise"].ToString()); - - //var matrizes = JsonConvert.DeserializeObject>(dados["matrizes"].ToString()); - //var confianca = JsonConvert.DeserializeObject>>(JsonConvert.SerializeObject(matrizes["confianca"])); - //var custo = JsonConvert.DeserializeObject>>(JsonConvert.SerializeObject(matrizes["custo"])); - - var anomalias = JsonConvert.DeserializeObject>(dados["anomalias"].ToString()); - var deteccoes = JsonConvert.DeserializeObject>(JsonConvert.SerializeObject(anomalias["deteccoes"])); - var sombras = JsonConvert.DeserializeObject>(JsonConvert.SerializeObject(anomalias["sombras"])); - - var corredor = JsonConvert.DeserializeObject>(dados["perfil_corredor"].ToString()); - var segmentacao = JsonConvert.DeserializeObject>(JsonConvert.SerializeObject(corredor["segmentacao"])); - var profundidade = JsonConvert.DeserializeObject>(JsonConvert.SerializeObject(corredor["profundidade"])); - - var solo = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(dados["solo"])); - - DadosLeitura.Analises = new VisualWorkerMessageAnaliseModel() + if (dados.ContainsKey("ts_analise")) { - timestamp = timestamp, - solo = solo, - anomalias = deteccoes, - sombras = sombras, - corredor_prof = profundidade, - corredor_seg = segmentacao, - }; + DateTime timestamp = FuncoesGlobais.UnixToDateTime(dados["ts_analise"].ToString()); + + //var matrizes = JsonConvert.DeserializeObject>(dados["matrizes"].ToString()); + //var confianca = JsonConvert.DeserializeObject>>(JsonConvert.SerializeObject(matrizes["confianca"])); + //var custo = JsonConvert.DeserializeObject>>(JsonConvert.SerializeObject(matrizes["custo"])); + + var anomalias = JsonConvert.DeserializeObject>(dados["anomalias"].ToString()); + var deteccoes = JsonConvert.DeserializeObject>(JsonConvert.SerializeObject(anomalias["deteccoes"])); + var sombras = JsonConvert.DeserializeObject>(JsonConvert.SerializeObject(anomalias["sombras"])); + + var corredor = JsonConvert.DeserializeObject>(dados["perfil_corredor"].ToString()); + var segmentacao = JsonConvert.DeserializeObject>(JsonConvert.SerializeObject(corredor["segmentacao"])); + var profundidade = JsonConvert.DeserializeObject>(JsonConvert.SerializeObject(corredor["profundidade"])); + + var solo = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(dados["solo"])); + + DadosLeitura.Analises = new VisualWorkerMessageAnaliseModel() + { + timestamp = timestamp, + solo = solo, + anomalias = deteccoes, + sombras = sombras, + corredor_prof = profundidade, + corredor_seg = segmentacao, + }; + } } } diff --git a/Python/OAK/datasets/capture_worker.py b/Python/OAK/datasets/capture_worker.py new file mode 100644 index 000000000..dc4f82318 --- /dev/null +++ b/Python/OAK/datasets/capture_worker.py @@ -0,0 +1,302 @@ +# capture_unificado.py com thread de status e IMU da OAK-D +import depthai as dai +import cv2 +import os +import numpy as np +from datetime import datetime +import time +import threading +from ahrs.filters import Madgwick +from scipy.spatial.transform import Rotation as R + +DIMENSAO_FINAL = (1280, 720) +QUALIDADE_JPEG = 95 +TECLA_SALVAR = ord('s') +ALTURA_JANELA = 1080 +LARGURA_JANELA = 1920 +RECONNECT_INTERVAL = 5 +AUTO_INTERVAL = 2.0 + +CAMERAS = { + "oak-d": None, + "oak-1": None +} + +CAMERAS_IDS = { + "oak-d": "14442C10C143E2D600", + "oak-1": "14442C1011AD1ED000" +} + +ULTIMA_TENTATIVA = {nome: 0 for nome in CAMERAS} +ULTIMA_CAPTURA = {nome: None for nome in CAMERAS} +STATUS_INFO = {nome: None for nome in CAMERAS} +IMU_INFO = {"oak-d": None} +AUTO_MODE = {nome: False for nome in CAMERAS} +ULTIMO_AUTO_SNAPSHOT = {nome: time.time() for nome in CAMERAS} + +madgwick = Madgwick() +q_imu = np.array([1.0, 0.0, 0.0, 0.0]) + +PASTAS_SAIDA = { + "oak-d": os.path.join("oak-d", "dataset", "original", "images"), + "oak-1": os.path.join("oak-1", "dataset", "original", "images") +} + +ISO_ATUAL = { + "oak-d": 400, + "oak-1": 400 +} + +EXPOSICAO_ATUAL = { + "oak-d": 6000, + "oak-1": 6000 +} + +ultimo_frame_salvo = { + "oak-d": np.zeros((360, 480, 3), dtype=np.uint8), + "oak-1": np.zeros((360, 480, 3), dtype=np.uint8) +} + +camera_ativa = "oak-d" + +for pasta in PASTAS_SAIDA.values(): + os.makedirs(pasta, exist_ok=True) + +def monitorar_status(nome): + while True: + time.sleep(2) + if CAMERAS[nome] is None: + STATUS_INFO[nome] = None + continue + try: + dev = CAMERAS[nome]["device"] + temp = dev.getChipTemperature().average + ddr = dev.getDdrMemoryUsage().used / 1024 / 1024 + running = dev.isPipelineRunning() + speed = dev.getUsbSpeed().name + STATUS_INFO[nome] = { + "temp": temp, + "ddr": ddr, + "pipeline": running, + "usb": speed + } + except: + STATUS_INFO[nome] = None + +def iniciar_dispositivo(nome): + try: + pipeline = dai.Pipeline() + + camRgb = pipeline.create(dai.node.ColorCamera) + camRgb.setPreviewSize(DIMENSAO_FINAL[0], DIMENSAO_FINAL[1]) + camRgb.setInterleaved(False) + camRgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1080_P) + camRgb.setColorOrder(dai.ColorCameraProperties.ColorOrder.BGR) + xoutRgb = pipeline.create(dai.node.XLinkOut) + xoutRgb.setStreamName("rgb") + camRgb.preview.link(xoutRgb.input) + + control_in = pipeline.create(dai.node.XLinkIn) + control_in.setStreamName("control") + control_in.out.link(camRgb.inputControl) + + if nome == "oak-d": + imu = pipeline.create(dai.node.IMU) + imu.enableIMUSensor(dai.IMUSensor.ACCELEROMETER_RAW, 500) + imu.enableIMUSensor(dai.IMUSensor.GYROSCOPE_RAW, 500) + imu.setBatchReportThreshold(1) + imu.setMaxBatchReports(20) + xoutImu = pipeline.create(dai.node.XLinkOut) + xoutImu.setStreamName("imu") + imu.out.link(xoutImu.input) + + mx_id = CAMERAS_IDS[nome] + dev_info = dai.DeviceInfo(mx_id) + device = dai.Device(pipeline, dev_info) + rgbQueue = device.getOutputQueue(name="rgb", maxSize=4, blocking=False) + controlQueue = device.getInputQueue("control") + + cam = { + "device": device, + "queue": rgbQueue, + "control": controlQueue + } + + if nome == "oak-d": + imuQueue = device.getOutputQueue(name="imu", maxSize=50, blocking=False) + cam["imu"] = imuQueue + + CAMERAS[nome] = cam + return True + except Exception as e: + print(f"[{nome.upper()} ❌] Erro ao iniciar: {e}") + return False + +def aplicar_controle_manual(nome): + if CAMERAS[nome] is None: + return + ctrl = dai.CameraControl() + ctrl.setManualExposure(EXPOSICAO_ATUAL[nome], ISO_ATUAL[nome]) + ctrl.setManualFocus(130) + CAMERAS[nome]["control"].send(ctrl) + +def monitorar_imu(): + global q_imu + while True: + time.sleep(0.01) + cam = CAMERAS["oak-d"] + if cam is None or "imu" not in cam: + continue + imuQueue = cam["imu"] + imuData = imuQueue.tryGet() + if imuData is not None: + for packet in imuData.packets: + accel = packet.acceleroMeter + gyro = packet.gyroscope + ax, ay, az = accel.x, accel.y, accel.z + gx = np.deg2rad(gyro.x) + gy = np.deg2rad(gyro.y) + gz = np.deg2rad(gyro.z) + q_imu = madgwick.updateIMU(q=q_imu, gyr=np.array([gx, gy, gz]), acc=np.array([ax, ay, az])) + r = R.from_quat([q_imu[1], q_imu[2], q_imu[3], q_imu[0]]) + roll, pitch, yaw = r.as_euler('xyz', degrees=True) + IMU_INFO["oak-d"] = { + "roll": roll, + "pitch": pitch, + "yaw": yaw + } + +for nome in CAMERAS: + threading.Thread(target=monitorar_status, args=(nome,), daemon=True).start() +th_IMU = threading.Thread(target=monitorar_imu, daemon=True) +th_IMU.start() + +while True: + canvas = np.zeros((ALTURA_JANELA, LARGURA_JANELA, 3), dtype=np.uint8) + agora = datetime.now() + timestamp_atual = agora.strftime("%d/%m/%Y %H:%M:%S") + agora_sec = time.time() + + for nome in CAMERAS: + if CAMERAS[nome] is None and agora_sec - ULTIMA_TENTATIVA[nome] >= RECONNECT_INTERVAL: + print(f"[{nome.upper()} 🔄] Tentando reconectar...") + sucesso = iniciar_dispositivo(nome) + if sucesso: + aplicar_controle_manual(nome) + ULTIMA_TENTATIVA[nome] = agora_sec + + for nome in CAMERAS: + if AUTO_MODE[nome] and (time.time() - ULTIMO_AUTO_SNAPSHOT[nome]) >= AUTO_INTERVAL: + if CAMERAS[nome] is not None: + try: + frame = CAMERAS[nome]["queue"].get().getCvFrame() + now = datetime.now() + timestamp = now.strftime("%Y%m%d_%H%M%S_%f")[:-3] + nome_base = f"img_{timestamp}.jpg" + caminho_final = os.path.join(PASTAS_SAIDA[nome], nome_base) + cv2.imwrite(caminho_final, frame, [cv2.IMWRITE_JPEG_QUALITY, QUALIDADE_JPEG]) + ultimo_frame_salvo[nome] = cv2.resize(frame, (480, 360)) + ULTIMA_CAPTURA[nome] = now + print(f"[{nome.upper()} 🕒] Auto-snapshot salvo: {caminho_final}") + ULTIMO_AUTO_SNAPSHOT[nome] = time.time() + except Exception as e: + print(f"[{nome.upper()} ❌] Erro no auto-snapshot: {e}") + + for idx, nome in enumerate(CAMERAS): + x_base = idx * 960 + + if CAMERAS[nome] is None: + cv2.putText(canvas, f"{nome.upper()} DESCONECTADA - Reconectando...", (x_base + 20, 50), + cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 2) + continue + + try: + in_rgb = CAMERAS[nome]["queue"].get() + frame = in_rgb.getCvFrame() + except Exception as e: + print(f"[{nome.upper()} ⚠️] Erro ao capturar frame: {e}") + CAMERAS[nome] = None + continue + + frame_vivo = cv2.resize(frame, (960, 720)) + captura_redimensionada = cv2.resize(ultimo_frame_salvo[nome], (480, 360)) + + canvas[0:720, x_base:x_base+960] = frame_vivo + canvas[720:1080, x_base:x_base+480] = captura_redimensionada + + cor = (0,255,0) if camera_ativa == nome else (255,255,255) + cv2.putText(canvas, f"{nome.upper()} (Ativa: {'SIM' if camera_ativa==nome else 'NAO'}) | Modo: {'AUTO' if AUTO_MODE[nome] else 'MANUAL'}", + (x_base + 10, 40), cv2.FONT_HERSHEY_SIMPLEX, 0.8, cor, 2) + cv2.putText(canvas, f"ISO: {ISO_ATUAL[nome]} | EXP: {EXPOSICAO_ATUAL[nome]}", (x_base + 10, 680), cv2.FONT_HERSHEY_SIMPLEX, 0.7, cor, 2) + cv2.putText(canvas, f"AO VIVO: {timestamp_atual}", (x_base + 10, 700), cv2.FONT_HERSHEY_SIMPLEX, 0.6, cor, 1) + + if ULTIMA_CAPTURA[nome] is not None: + stamp = ULTIMA_CAPTURA[nome].strftime("%d/%m/%Y %H:%M:%S") + cv2.putText(canvas, f"ULTIMA CAPTURA: {stamp}", (x_base + 10, 740), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255,255,255), 1) + + # Mostrar status cacheado se existir + info = STATUS_INFO.get(nome) + if info: + info_x = x_base + 500 + y_base = 740 + cv2.putText(canvas, f"Temp: {info['temp']:.2f} C", (info_x, y_base), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (200,200,255), 1) + cv2.putText(canvas, f"DDR: {info['ddr']:.2f} MB", (info_x, y_base+30), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (200,200,255), 1) + cv2.putText(canvas, f"Pipeline: {'ON' if info['pipeline'] else 'OFF'}", (info_x, y_base+60), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (200,200,255), 1) + cv2.putText(canvas, f"USB: {info['usb']}", (info_x, y_base+90), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (200,200,255), 1) + + if nome == "oak-d" and IMU_INFO["oak-d"]: + imu = IMU_INFO["oak-d"] + cv2.putText(canvas, f"Roll: {imu['roll']:.2f}", (info_x, y_base+130), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (180,255,180), 1) + cv2.putText(canvas, f"Pitch: {imu['pitch']:.2f}", (info_x, y_base+160), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (180,255,180), 1) + cv2.putText(canvas, f"Yaw: {imu['yaw']:.2f}", (info_x, y_base+190), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (180,255,180), 1) + + if AUTO_MODE: + cv2.putText(canvas, f"AUTO SNAP: ON ({AUTO_INTERVAL:.1f}s)", (30, 1035), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0,255,255), 2) + else: + cv2.putText(canvas, "AUTO SNAP: OFF", (30, 1035), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (100,100,100), 2) + + cv2.putText(canvas, "Teclas: [1] OAK-D | [2] OAK-1 | s/+/-/m/n = camera ativa | q para sair", + (30, 1070), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (200, 200, 255), 2) + + cv2.imshow("Captura Dual", canvas) + key = cv2.waitKey(1) & 0xFF + + if key == ord('1'): + camera_ativa = "oak-d" + elif key == ord('2'): + camera_ativa = "oak-1" + elif key == TECLA_SALVAR: + if CAMERAS[camera_ativa] is not None: + try: + frame = CAMERAS[camera_ativa]["queue"].get().getCvFrame() + now = datetime.now() + timestamp = now.strftime("%Y%m%d_%H%M%S_%f")[:-3] + nome_base = f"img_{timestamp}.jpg" + caminho_final = os.path.join(PASTAS_SAIDA[camera_ativa], nome_base) + cv2.imwrite(caminho_final, frame, [cv2.IMWRITE_JPEG_QUALITY, QUALIDADE_JPEG]) + ultimo_frame_salvo[camera_ativa] = cv2.resize(frame, (480, 360)) + ULTIMA_CAPTURA[camera_ativa] = now + print(f"[{camera_ativa.upper()} ✔] Imagem salva: {caminho_final}") + except Exception as e: + print(f"[{camera_ativa.upper()} ❌] Erro ao salvar imagem: {e}") + elif key == ord('+') or key == ord('='): + ISO_ATUAL[camera_ativa] = min(1600, ISO_ATUAL[camera_ativa] + 50) + aplicar_controle_manual(camera_ativa) + elif key == ord('-'): + ISO_ATUAL[camera_ativa] = max(100, ISO_ATUAL[camera_ativa] - 50) + aplicar_controle_manual(camera_ativa) + elif key == ord('m'): + EXPOSICAO_ATUAL[camera_ativa] = min(33000, EXPOSICAO_ATUAL[camera_ativa] + 1000) + aplicar_controle_manual(camera_ativa) + elif key == ord('n'): + EXPOSICAO_ATUAL[camera_ativa] = max(100, EXPOSICAO_ATUAL[camera_ativa] - 1000) + aplicar_controle_manual(camera_ativa) + elif key == ord('a'): + AUTO_MODE[camera_ativa] = not AUTO_MODE[camera_ativa] + print(f"[AUTO] {camera_ativa.upper()} modo automático {'ativado' if AUTO_MODE[camera_ativa] else 'desativado'}") + ULTIMO_AUTO_SNAPSHOT[camera_ativa] = time.time() + elif key == ord('q'): + print("[INFO] Encerrando...") + break + +cv2.destroyAllWindows() diff --git a/Python/OAK/datasets/oak-1/_1_capture.py b/Python/OAK/datasets/oak-1/_1_capture.py new file mode 100644 index 000000000..27bbef95c --- /dev/null +++ b/Python/OAK/datasets/oak-1/_1_capture.py @@ -0,0 +1,139 @@ +import depthai as dai +import cv2 +import os +import numpy as np +from datetime import datetime + +# === CONFIGURAÇÕES === +PASTA_SAIDA = "dataset" +DIMENSAO_FINAL = (1280, 720) +QUALIDADE_JPEG = 95 +TECLA_SALVAR = ord('s') + +# Área de exibição +ALTURA_JANELA = 1080 +LARGURA_JANELA = 1920 + +EXPOSICAO_ATUAL = [6000] +ISO_ATUAL = [400] + +os.makedirs(PASTA_SAIDA, exist_ok=True) + +# === PIPELINE === +pipeline = dai.Pipeline() + +cam_rgb = pipeline.createColorCamera() +cam_rgb.setPreviewSize(DIMENSAO_FINAL) +cam_rgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1080_P) +cam_rgb.setInterleaved(False) +cam_rgb.setColorOrder(dai.ColorCameraProperties.ColorOrder.BGR) + +xout = pipeline.createXLinkOut() +xout.setStreamName("rgb") +cam_rgb.preview.link(xout.input) + +control_in = pipeline.createXLinkIn() +control_in.setStreamName("control") +control_in.out.link(cam_rgb.inputControl) + +# === EXECUÇÃO === +with dai.Device(pipeline) as device: + print("[INFO] Pressione 's' para salvar | '+' e '-' para ISO | 'q' para sair.") + + controlQueue = device.getInputQueue("control") + rgbQueue = device.getOutputQueue("rgb", maxSize=4, blocking=False) + + def aplicar_controle_manual(exposicao, iso): + ctrl = dai.CameraControl() + ctrl.setManualExposure(exposicao, iso) + ctrl.setManualFocus(130) + controlQueue.send(ctrl) + print(f"[⚙️] Exposicao: {exposicao} µs | ISO: {iso}") + + aplicar_controle_manual(EXPOSICAO_ATUAL[0], ISO_ATUAL[0]) + ultimo_frame_salvo = np.zeros((DIMENSAO_FINAL[1], DIMENSAO_FINAL[0], 3), dtype=np.uint8) + + # Fullscreen + cv2.namedWindow("Coletor de Dataset", cv2.WINDOW_NORMAL) + cv2.setWindowProperty("Coletor de Dataset", cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN) + + while True: + in_rgb = rgbQueue.get() + frame = in_rgb.getCvFrame() + + # Redimensiona o frame ao vivo + frame_vivo = cv2.resize(frame, (1280, 960)) + + # Redimensiona a última captura + captura_redimensionada = cv2.resize(ultimo_frame_salvo, (512, 512)) + + # Cria o canvas vazio + canvas = np.zeros((ALTURA_JANELA, LARGURA_JANELA, 3), dtype=np.uint8) + + # Posiciona o ao vivo à esquerda + canvas[60:1020, 30:1310] = frame_vivo + + # Posiciona a última imagem salva à direita + canvas[60:572, 1400:1912] = captura_redimensionada + + # Adiciona textos no lado direito + cv2.putText(canvas, f"ISO: {ISO_ATUAL[0]} | Exposicao: {EXPOSICAO_ATUAL[0]}", (1350, 620), cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0, 255, 0), 2) + cv2.putText(canvas, "Pressione 's' para salvar", (1350, 680), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 1) + cv2.putText(canvas, "'+' ou '-' para ajustar ISO", (1350, 720), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 1) + cv2.putText(canvas, "'m' ou 'n' para ajustar a exposicao", (1350, 760), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 1) + cv2.putText(canvas, "'q' para sair", (1350, 800), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 1) + + cv2.imshow("Coletor de Dataset", canvas) + + key = cv2.waitKey(1) & 0xFF + + if key == TECLA_SALVAR: + now = datetime.now() + timestamp = now.strftime("%Y%m%d_%H%M%S_%f")[:-3] + caminho = os.path.join(PASTA_SAIDA, f"img_{timestamp}.jpg") + + # Gera timestamp e nome-base + now = datetime.now() + timestamp = now.strftime("%Y%m%d_%H%M%S_%f")[:-3] + nome_base = f"img_{timestamp}.jpg" + + # Original (sem resize) + os.makedirs(os.path.join("dataset", "original", "images"), exist_ok=True) + cv2.imwrite(os.path.join("dataset", "original", "images", nome_base), frame, [cv2.IMWRITE_JPEG_QUALITY, QUALIDADE_JPEG]) + + # Demais resoluções + #resolucoes = { + # "512x512": (512, 512), + # "768x768": (768, 768), + # "384x384": (384, 384), + #} + #for nome_pasta, dim in resolucoes.items(): + # path_pasta = os.path.join("dataset", nome_pasta) + # os.makedirs(path_pasta, exist_ok=True) + # imagem = cv2.resize(frame, dim, interpolation=cv2.INTER_AREA) + # cv2.imwrite(os.path.join(path_pasta, nome_base), imagem, [cv2.IMWRITE_JPEG_QUALITY, QUALIDADE_JPEG]) + + print(f"[✔] Imagem salva: {caminho}") + ultimo_frame_salvo = frame.copy() + + elif key == ord('+') or key == ord('='): + ISO_ATUAL[0] = min(1600, ISO_ATUAL[0] + 50) + aplicar_controle_manual(EXPOSICAO_ATUAL[0], ISO_ATUAL[0]) + + elif key == ord('-'): + ISO_ATUAL[0] = max(100, ISO_ATUAL[0] - 50) + aplicar_controle_manual(EXPOSICAO_ATUAL[0], ISO_ATUAL[0]) + + elif key == ord('m'): + EXPOSICAO_ATUAL[0] = min(10000, EXPOSICAO_ATUAL[0] + 500) + aplicar_controle_manual(EXPOSICAO_ATUAL[0], ISO_ATUAL[0]) + + elif key == ord('n'): + EXPOSICAO_ATUAL[0] = max(1000, EXPOSICAO_ATUAL[0] - 500) + aplicar_controle_manual(EXPOSICAO_ATUAL[0], ISO_ATUAL[0]) + + elif key == ord('q'): + print("[INFO] Encerrando...") + break + + cv2.destroyAllWindows() diff --git a/Python/OAK/datasets/oak-1/_2_augmentation.py b/Python/OAK/datasets/oak-1/_2_augmentation.py new file mode 100644 index 000000000..f485c020a --- /dev/null +++ b/Python/OAK/datasets/oak-1/_2_augmentation.py @@ -0,0 +1,70 @@ +import os +import cv2 +import albumentations as A +from albumentations.pytorch import ToTensorV2 +import numpy as np +import random + +# === CONFIG === +PASTA_ORIGINAL = "dataset/original" +PASTA_AUGMENTED = "dataset/augmented" +NUM_AUGMENTACOES = 3 # quantas imagens gerar por imagem original + +os.makedirs(os.path.join(PASTA_AUGMENTED, "images"), exist_ok=True) +os.makedirs(os.path.join(PASTA_AUGMENTED, "labels"), exist_ok=True) + +transform = A.Compose([ + A.HorizontalFlip(p=0.5), + A.VerticalFlip(p=0.1), + A.RandomBrightnessContrast(p=0.3), + A.Rotate(limit=10, p=0.4), + A.RandomScale(scale_limit=0.1, p=0.3), +], bbox_params=A.BboxParams(format='yolo', label_fields=['class_labels'])) + +imagens = [f for f in os.listdir(os.path.join(PASTA_ORIGINAL, "images")) if f.endswith(('.jpg', '.jpeg', '.png'))] + +for nome_img in imagens: + caminho_img = os.path.join(PASTA_ORIGINAL, "images", nome_img) + caminho_lbl = os.path.join(PASTA_ORIGINAL, "labels", nome_img.replace(".jpg", ".txt").replace(".jpeg", ".txt").replace(".png", ".txt")) + + if not os.path.exists(caminho_lbl): + print(f"[!] Label ausente: {nome_img}") + continue + + # Carrega imagem e label + image = cv2.imread(caminho_img) + height, width = image.shape[:2] + + with open(caminho_lbl, 'r') as f: + linhas = f.readlines() + + bboxes = [] + class_labels = [] + for linha in linhas: + parts = linha.strip().split() + if len(parts) != 5: + continue + cls, x, y, w, h = map(float, parts) + bboxes.append([x, y, w, h]) + class_labels.append(int(cls)) + + for i in range(NUM_AUGMENTACOES): + augmented = transform(image=image, bboxes=bboxes, class_labels=class_labels) + img_aug = augmented['image'] + bboxes_aug = augmented['bboxes'] + labels_aug = augmented['class_labels'] + + nome_base = os.path.splitext(nome_img)[0] + nome_img_out = f"{nome_base}_aug{i}.jpg" + nome_lbl_out = f"{nome_base}_aug{i}.txt" + + cv2.imwrite(os.path.join(PASTA_AUGMENTED, "images", nome_img_out), img_aug) + + with open(os.path.join(PASTA_AUGMENTED, "labels", nome_lbl_out), 'w') as f: + for cls, bbox in zip(labels_aug, bboxes_aug): + x, y, w, h = bbox + f.write(f"{cls} {x:.6f} {y:.6f} {w:.6f} {h:.6f}\n") + + print(f"[+] Augmentado: {nome_img_out}") + +print("\n✅ Augmentation finalizado!") \ No newline at end of file diff --git a/Python/OAK/datasets/oak-1/_3_normalize.py b/Python/OAK/datasets/oak-1/_3_normalize.py new file mode 100644 index 000000000..e1b718606 --- /dev/null +++ b/Python/OAK/datasets/oak-1/_3_normalize.py @@ -0,0 +1,71 @@ +import os +import cv2 +import glob + +# === CONFIGURACAO === +RESOLUCOES = { + "640x640": (640, 640), + "1280x720": (1280, 720), +} + +FONTES = ["original", "augmented"] +PASTA_BASE = "dataset" + +for fonte in FONTES: + for nome_res, (larg, alt) in RESOLUCOES.items(): + pasta_imgs_saida = os.path.join(PASTA_BASE, nome_res, "images") + pasta_lbls_saida = os.path.join(PASTA_BASE, nome_res, "labels") + os.makedirs(pasta_imgs_saida, exist_ok=True) + os.makedirs(pasta_lbls_saida, exist_ok=True) + + pasta_imgs_origem = os.path.join(PASTA_BASE, fonte, "images") + pasta_lbls_origem = os.path.join(PASTA_BASE, fonte, "labels") + + imagens = sorted(glob.glob(os.path.join(pasta_imgs_origem, "*.jpg"))) + + for i, caminho_img in enumerate(imagens, 1): + nome_base = os.path.splitext(os.path.basename(caminho_img))[0] + caminho_lbl = os.path.join(pasta_lbls_origem, f"{nome_base}.txt") + if not os.path.exists(caminho_lbl): + print(f"[!] Label ausente para {nome_base}, pulando...") + continue + + # Carrega imagem + img = cv2.imread(caminho_img) + if img is None: + print(f"[!] Erro ao ler imagem {caminho_img}") + continue + + h_orig, w_orig = img.shape[:2] + img_resized = cv2.resize(img, (larg, alt), interpolation=cv2.INTER_AREA) + + # Salva imagem redimensionada + caminho_saida_img = os.path.join(pasta_imgs_saida, f"{nome_base}.jpg") + cv2.imwrite(caminho_saida_img, img_resized) + + # Recalcula labels + with open(caminho_lbl, 'r') as f: + linhas = f.readlines() + + with open(os.path.join(pasta_lbls_saida, f"{nome_base}.txt"), 'w') as saida_lbl: + for linha in linhas: + partes = linha.strip().split() + if len(partes) != 5: + continue + cls, x, y, w, h = map(float, partes) + # Conversao relativa antiga -> relativa nova + x_pix = x * w_orig + y_pix = y * h_orig + w_pix = w * w_orig + h_pix = h * h_orig + + x_novo = x_pix / larg + y_novo = y_pix / alt + w_novo = w_pix / larg + h_novo = h_pix / alt + + saida_lbl.write(f"{int(cls)} {x_novo:.6f} {y_novo:.6f} {w_novo:.6f} {h_novo:.6f}\n") + + print(f"[{fonte}] [{nome_res}] {i}/{len(imagens)} normalizado: {nome_base}") + +print("\n✅ Normalizacao YOLO concluida!") \ No newline at end of file diff --git a/Python/OAK/datasets/oak-1/_4_prepare.txt b/Python/OAK/datasets/oak-1/_4_prepare.txt new file mode 100644 index 000000000..c28c806e5 --- /dev/null +++ b/Python/OAK/datasets/oak-1/_4_prepare.txt @@ -0,0 +1,14 @@ +Passo 1: Clonar repositório do YoloV7 +git clone https://github.com/WongKinYiu/yolov7.git + +Passo 2: Navegar para yolov7 +cd yolov7 + +Passo 3: Instalar requirements +pip install -r requirements.txt + +Passo 4: Ajustar arquivo em "models/experimental.py", linha 252, adicionando a flag weights_only=False +ckpt = torch.load(w, map_location=map_location, weights_only=False) # load + +Passo 6: Retornar a pasta anterior +cd .. diff --git a/Python/OAK/datasets/oak-1/_5_split.py b/Python/OAK/datasets/oak-1/_5_split.py new file mode 100644 index 000000000..a5655df55 --- /dev/null +++ b/Python/OAK/datasets/oak-1/_5_split.py @@ -0,0 +1,55 @@ +import os +import random +import shutil + +# === CONFIG === +PASTA_ORIGEM_IMG = "dataset/1280x720/images" +PASTA_ORIGEM_LBL = "dataset/1280x720/labels" +PASTA_SAIDA = "yolov7/train" + +SPLIT = { + "train": 0.7, + "val": 0.2, + "test": 0.1 +} + +# === PREPARA PASTAS === +for tipo in SPLIT.keys(): + os.makedirs(os.path.join(PASTA_SAIDA, tipo, "images"), exist_ok=True) + os.makedirs(os.path.join(PASTA_SAIDA, tipo, "labels"), exist_ok=True) + +# === LISTA E EMBARALHA === +arquivos = [f for f in os.listdir(PASTA_ORIGEM_IMG) if f.endswith(".jpg")] +random.shuffle(arquivos) + +total = len(arquivos) +qt_train = int(SPLIT["train"] * total) +qt_val = int(SPLIT["val"] * total) + +splits = { + "train": arquivos[:qt_train], + "val": arquivos[qt_train:qt_train+qt_val], + "test": arquivos[qt_train+qt_val:] +} + +# === COPIA === +for tipo, lista in splits.items(): + for nome_img in lista: + nome_lbl = nome_img.replace(".jpg", ".txt") + + origem_img = os.path.join(PASTA_ORIGEM_IMG, nome_img) + origem_lbl = os.path.join(PASTA_ORIGEM_LBL, nome_lbl) + + destino_img = os.path.join(PASTA_SAIDA, tipo, "images", nome_img) + destino_lbl = os.path.join(PASTA_SAIDA, tipo, "labels", nome_lbl) + + shutil.copy2(origem_img, destino_img) + + if os.path.exists(origem_lbl): + shutil.copy2(origem_lbl, destino_lbl) + else: + print(f"[!] Label ausente: {nome_lbl}") + + print(f"[✔] {tipo.upper()} - {len(lista)} imagens") + +print("\n✅ Split do dataset para YOLOv7 concluído!") diff --git a/Python/OAK/datasets/oak-1/_6_train.txt b/Python/OAK/datasets/oak-1/_6_train.txt new file mode 100644 index 000000000..dc9a65bd7 --- /dev/null +++ b/Python/OAK/datasets/oak-1/_6_train.txt @@ -0,0 +1,28 @@ +Passo 1: Navegar para yolov7 +cd yolov7 + +Passo 2: Criar arquivo de configuração do Treinamento, em "train/data.yaml", com o conteúdo + +train: train/train/images +val: train/val/images +test: train/test/images + +nc: 2 # número de classes +names: ["weed", "crop"] + +Passo 3: Baixar recursos e testar, onde --device 0 para cuda, e cpu para CPU +python test.py --data train/data.yaml --img 640 --batch 32 --conf 0.001 --iou 0.65 --device cpu --weights yolov7.pt --name ervas_v7 + +Passo 4: Ajustar arquivo "train.py" na linha 71, adicionando a flag weights_only=False +run_id = torch.load(weights, map_location=device, weights_only=False).get('wandb_id') if weights.endswith('.pt') and os.path.isfile(weights) else None + +E na linha 87 +ckpt = torch.load(weights, map_location=device, weights_only=False) # load checkpoint + +Passo 5: Remover cache dos labels antes do treinamento +del train\val\labels.cache +del train\train\labels.cache +del train\test\labels.cache + +Passo 6: Treinamento, onde --device 0 para cuda, e cpu para CPU +python train.py --data train/data.yaml --weights yolov7.pt --batch 16 --epochs 50 --img 412 --device cpu --workers 8 --name ervas_v7 diff --git a/Python/OAK/datasets/oak-d/_1_capture.py b/Python/OAK/datasets/oak-d/_1_capture.py new file mode 100644 index 000000000..02bc4353b --- /dev/null +++ b/Python/OAK/datasets/oak-d/_1_capture.py @@ -0,0 +1,139 @@ +import depthai as dai +import cv2 +import os +import numpy as np +from datetime import datetime + +# === CONFIGURAÇÕES === +PASTA_SAIDA = "dataset" +DIMENSAO_FINAL = (1920, 1080) +QUALIDADE_JPEG = 95 +TECLA_SALVAR = ord('s') + +# Área de exibição +ALTURA_JANELA = 1080 +LARGURA_JANELA = 1920 + +EXPOSICAO_ATUAL = [6000] +ISO_ATUAL = [400] + +os.makedirs(PASTA_SAIDA, exist_ok=True) + +# === PIPELINE === +pipeline = dai.Pipeline() + +cam_rgb = pipeline.createColorCamera() +cam_rgb.setPreviewSize(DIMENSAO_FINAL) +cam_rgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1080_P) +cam_rgb.setInterleaved(False) +cam_rgb.setColorOrder(dai.ColorCameraProperties.ColorOrder.BGR) + +xout = pipeline.createXLinkOut() +xout.setStreamName("rgb") +cam_rgb.preview.link(xout.input) + +control_in = pipeline.createXLinkIn() +control_in.setStreamName("control") +control_in.out.link(cam_rgb.inputControl) + +# === EXECUÇÃO === +with dai.Device(pipeline) as device: + print("[INFO] Pressione 's' para salvar | '+' e '-' para ISO | 'q' para sair.") + + controlQueue = device.getInputQueue("control") + rgbQueue = device.getOutputQueue("rgb", maxSize=4, blocking=False) + + def aplicar_controle_manual(exposicao, iso): + ctrl = dai.CameraControl() + ctrl.setManualExposure(exposicao, iso) + ctrl.setManualFocus(130) + controlQueue.send(ctrl) + print(f"[⚙️] Exposicao: {exposicao} µs | ISO: {iso}") + + aplicar_controle_manual(EXPOSICAO_ATUAL[0], ISO_ATUAL[0]) + ultimo_frame_salvo = np.zeros((DIMENSAO_FINAL[1], DIMENSAO_FINAL[0], 3), dtype=np.uint8) + + # Fullscreen + cv2.namedWindow("Coletor de Dataset", cv2.WINDOW_NORMAL) + cv2.setWindowProperty("Coletor de Dataset", cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN) + + while True: + in_rgb = rgbQueue.get() + frame = in_rgb.getCvFrame() + + # Redimensiona o frame ao vivo + frame_vivo = cv2.resize(frame, (1280, 960)) + + # Redimensiona a última captura + captura_redimensionada = cv2.resize(ultimo_frame_salvo, (512, 512)) + + # Cria o canvas vazio + canvas = np.zeros((ALTURA_JANELA, LARGURA_JANELA, 3), dtype=np.uint8) + + # Posiciona o ao vivo à esquerda + canvas[60:1020, 30:1310] = frame_vivo + + # Posiciona a última imagem salva à direita + canvas[60:572, 1400:1912] = captura_redimensionada + + # Adiciona textos no lado direito + cv2.putText(canvas, f"ISO: {ISO_ATUAL[0]} | Exposicao: {EXPOSICAO_ATUAL[0]}", (1350, 620), cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0, 255, 0), 2) + cv2.putText(canvas, "Pressione 's' para salvar", (1350, 680), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 1) + cv2.putText(canvas, "'+' ou '-' para ajustar ISO", (1350, 720), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 1) + cv2.putText(canvas, "'m' ou 'n' para ajustar a exposicao", (1350, 760), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 1) + cv2.putText(canvas, "'q' para sair", (1350, 800), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 1) + + cv2.imshow("Coletor de Dataset", canvas) + + key = cv2.waitKey(1) & 0xFF + + if key == TECLA_SALVAR: + now = datetime.now() + timestamp = now.strftime("%Y%m%d_%H%M%S_%f")[:-3] + caminho = os.path.join(PASTA_SAIDA, f"img_{timestamp}.jpg") + + # Gera timestamp e nome-base + now = datetime.now() + timestamp = now.strftime("%Y%m%d_%H%M%S_%f")[:-3] + nome_base = f"img_{timestamp}.jpg" + + # Original (sem resize) + os.makedirs(os.path.join("dataset", "original", "images"), exist_ok=True) + cv2.imwrite(os.path.join("dataset", "original", "images", nome_base), frame, [cv2.IMWRITE_JPEG_QUALITY, QUALIDADE_JPEG]) + + # Demais resoluções + #resolucoes = { + # "512x512": (512, 512), + # "768x768": (768, 768), + # "384x384": (384, 384), + #} + #for nome_pasta, dim in resolucoes.items(): + # path_pasta = os.path.join("dataset", nome_pasta) + # os.makedirs(path_pasta, exist_ok=True) + # imagem = cv2.resize(frame, dim, interpolation=cv2.INTER_AREA) + # cv2.imwrite(os.path.join(path_pasta, nome_base), imagem, [cv2.IMWRITE_JPEG_QUALITY, QUALIDADE_JPEG]) + + print(f"[✔] Imagem salva: {caminho}") + ultimo_frame_salvo = frame.copy() + + elif key == ord('+') or key == ord('='): + ISO_ATUAL[0] = min(1600, ISO_ATUAL[0] + 50) + aplicar_controle_manual(EXPOSICAO_ATUAL[0], ISO_ATUAL[0]) + + elif key == ord('-'): + ISO_ATUAL[0] = max(100, ISO_ATUAL[0] - 50) + aplicar_controle_manual(EXPOSICAO_ATUAL[0], ISO_ATUAL[0]) + + elif key == ord('m'): + EXPOSICAO_ATUAL[0] = min(10000, EXPOSICAO_ATUAL[0] + 500) + aplicar_controle_manual(EXPOSICAO_ATUAL[0], ISO_ATUAL[0]) + + elif key == ord('n'): + EXPOSICAO_ATUAL[0] = max(1000, EXPOSICAO_ATUAL[0] - 500) + aplicar_controle_manual(EXPOSICAO_ATUAL[0], ISO_ATUAL[0]) + + elif key == ord('q'): + print("[INFO] Encerrando...") + break + + cv2.destroyAllWindows() diff --git a/Python/OAK/datasets/oak-d/_2_augmentation.py b/Python/OAK/datasets/oak-d/_2_augmentation.py new file mode 100644 index 000000000..9f03cb2de --- /dev/null +++ b/Python/OAK/datasets/oak-d/_2_augmentation.py @@ -0,0 +1,110 @@ +import os +from torchvision import transforms +from PIL import ImageOps, Image +import torchvision.transforms.functional as TF +from torchvision.transforms.functional import to_pil_image +import torchvision.transforms as T +import cv2 +import numpy as np + +class ComposeWithSeed(object): + def __init__(self, transforms): + self.transforms = transforms + + def __call__(self, i, img, mask): + transforms.RandomHorizontalFlip(p=0.5) + t = self.transforms[i] + img = t(img) + mask = t(mask) + return img, mask + +# Função para aplicar flip horizontal e vertical +def flip_image(img): + return ImageOps.mirror(ImageOps.flip(img)) + +# Função para rotacionar a imagem em ângulos fixos +def rotate_image(img, angle): + return TF.rotate(img, angle) + +def perspective_image(img, magnitude=0.5): + width, height = img.size + + # Pontos de origem + points_orig = np.float32([ + [0, 0], + [width, 0], + [0, height], + [width, height] + ]) + + # Pontos de destino, deslocados com base na magnitude + points_dest = np.float32([ + [int(magnitude * width), int(magnitude * height)], + [int((1 - magnitude) * width), 0], + [0, int((1 - magnitude) * height)], + [width, height] + ]) + + # Calcula a matriz de transformação e aplica a transformação de perspectiva + matrix = cv2.getPerspectiveTransform(points_orig, points_dest) + img_transformed = cv2.warpPerspective(np.array(img), matrix, (width, height)) + + return Image.fromarray(img_transformed) + +# Definindo as transformações +transform_list = [ + T.Lambda(lambda img: flip_image(img)), # Aplica flip nos dois eixos + T.Lambda(lambda img: rotate_image(img, 90)), # Rotação de 90 graus + T.Lambda(lambda img: rotate_image(img, -90)), # Rotação de -90 graus + T.Lambda(lambda img: perspective_image(img, magnitude=0.2)), + T.Lambda(lambda img: perspective_image(img, magnitude=0.1)), + # Aqui, você pode adicionar outras transformações fixas conforme necessário + #T.Resize((256, 256)), # Redimensionamento para o tamanho desejado + T.ToTensor(), # Converte as imagens PIL para tensores PyTorch + # Normalização pode ser adicionada aqui, se necessário +] + +# Agora, definimos a transformação composta com a classe personalizada +transform = ComposeWithSeed(transform_list) + +# Caminhos para os diretórios onde suas imagens e máscaras originais estão armazenadas +dataset_path = 'dataset/original/images' +masks_path = 'dataset/original/masks' + +# Caminhos para os diretórios onde as imagens e máscaras aumentadas serão salvas +augmented_images_path = 'dataset/augmented/images' +augmented_masks_path = 'dataset/augmented/masks' + +# Verifica se os diretórios de destino existem, caso contrário, cria os diretórios +os.makedirs(augmented_images_path, exist_ok=True) +os.makedirs(augmented_masks_path, exist_ok=True) + +# Função para aplicar a transformação e salvar as imagens e máscaras transformadas +def augment_images_and_masks(dataset_path, masks_path, augmented_images_path, augmented_masks_path, transform, num_copies=5): + # Lista todos os arquivos nos diretórios do dataset de imagens e máscaras + image_files = [f for f in os.listdir(dataset_path) if os.path.isfile(os.path.join(dataset_path, f))] + mask_files = [f for f in os.listdir(masks_path) if os.path.isfile(os.path.join(masks_path, f))] + + for image_file, mask_file in zip(image_files, mask_files): + image_path = os.path.join(dataset_path, image_file) + mask_path = os.path.join(masks_path, mask_file) + image = Image.open(image_path).convert('RGB') + mask = Image.open(mask_path).convert('RGB') + + for i in range(num_copies): + # Aplica a transformação de maneira consistente em ambos, imagem e máscara + transformed_image, transformed_mask = transform(i, image, mask) + + # Salva a imagem e a máscara transformadas + image_save_path = os.path.join(augmented_images_path, f"{os.path.splitext(image_file)[0]}_aug_{i}{os.path.splitext(image_file)[1]}") + mask_save_path = os.path.join(augmented_masks_path, f"{os.path.splitext(mask_file)[0]}_aug_{i}{os.path.splitext(mask_file)[1]}") + + #transformed_image_pil = to_pil_image(transformed_image) + transformed_image.save(image_save_path) + #transformed_mask_pil = to_pil_image(transformed_mask) + transformed_mask.save(mask_save_path) + +# Chama a função para iniciar o processo de aumento de dados +augment_images_and_masks(dataset_path, masks_path, augmented_images_path, augmented_masks_path, transform, num_copies=5) + +print("Augmentation completed!") diff --git a/Python/OAK/datasets/oak-d/_3_normalize.py b/Python/OAK/datasets/oak-d/_3_normalize.py new file mode 100644 index 000000000..337d25366 --- /dev/null +++ b/Python/OAK/datasets/oak-d/_3_normalize.py @@ -0,0 +1,107 @@ +import os +import cv2 +import numpy as np + +# === CONFIGURAÇÕES === +PASTA_BASE = "dataset" +FONTE_DADOS = ["original", "augmented"] # <<-- novas fontes +LABELMAP_PATH = os.path.join(PASTA_BASE, "labelmap.txt") + +RESOLUCOES = { + "512x512": (512, 512), + "768x768": (768, 768), + "384x384": (384, 384) +} + +# === Função para ler o labelmap === +def carregar_labelmap_completo(caminho): + cor_para_id = {} + id_para_nome = {} + cores_bgr = [] + + with open(caminho, 'r') as arquivo: + for linha in arquivo: + if linha.startswith("#") or not linha.strip(): + continue + partes = linha.strip().split(':') + if len(partes) >= 2: + nome_classe, cor_rgb_str = partes[0], partes[1] + r, g, b = map(int, cor_rgb_str.split(',')) + cor_bgr = (b, g, r) # Corrige para BGR + + classe_id = len(cor_para_id) + cor_para_id[cor_bgr] = classe_id + cores_bgr.append(cor_bgr) + id_para_nome[classe_id] = nome_classe + + return cor_para_id, cores_bgr, id_para_nome + + +# === Converte uma máscara RGB para índice de classes === +def converter_mask_rgb_para_ids(img_rgb, mapa): + h, w, _ = img_rgb.shape + mask = np.zeros((h, w), dtype=np.uint8) + for cor, classe_id in mapa.items(): + r, g, b = cor + cond = (img_rgb[:,:,0] == b) & (img_rgb[:,:,1] == g) & (img_rgb[:,:,2] == r) + mask[cond] = classe_id + return mask + +# === Início do processamento === +cor_para_id, _, _ = carregar_labelmap_completo(LABELMAP_PATH) + +# Cria pastas de saída +for nome_res, dim in RESOLUCOES.items(): + os.makedirs(os.path.join(PASTA_BASE, nome_res, "images"), exist_ok=True) + os.makedirs(os.path.join(PASTA_BASE, nome_res, "masks"), exist_ok=True) + +# Processa cada fonte de dados (original + augmented) +for fonte in FONTE_DADOS: + if (not os.path.exists(os.path.join(PASTA_BASE, fonte))): + continue + pasta_rgb = os.path.join(PASTA_BASE, fonte, "images") + pasta_masks = os.path.join(PASTA_BASE, fonte, "masks") + + nomes_arquivos = sorted([f for f in os.listdir(pasta_rgb) if f.endswith(".jpg") or f.endswith(".jpeg")]) + total = len(nomes_arquivos) + + for i, nome in enumerate(nomes_arquivos, 1): + caminho_rgb = os.path.join(pasta_rgb, nome) + caminho_mask = os.path.join(pasta_masks, nome.replace(".jpg", ".png").replace(".jpeg", ".png")) + + img_rgb = cv2.imread(caminho_rgb) + if img_rgb is None: + print(f"[!] Erro ao ler imagem {nome}") + continue + + # Tenta carregar a máscara RGB (se existir) + if os.path.exists(caminho_mask): + img_mask_rgb = cv2.imread(caminho_mask) + img_mask_rgb = cv2.cvtColor(img_mask_rgb, cv2.COLOR_BGR2RGB) # ← CORRIGE isso! + if img_mask_rgb is not None: + mask_ids = converter_mask_rgb_para_ids(img_mask_rgb, cor_para_id) + else: + print(f"[!] Erro ao ler máscara {caminho_mask}, ignorando.") + mask_ids = None + else: + mask_ids = None + + for nome_res, dim in RESOLUCOES.items(): + # Cria nomes únicos baseados na fonte + nome_saida_img = f"{fonte}_{nome}" + nome_saida_mask = nome_saida_img.replace(".jpg", ".png").replace(".jpeg", ".png") + + # Redimensiona e salva imagem + img_resized = cv2.resize(img_rgb, dim, interpolation=cv2.INTER_AREA) + path_img_saida = os.path.join(PASTA_BASE, nome_res, "images", nome_saida_img) + cv2.imwrite(path_img_saida, img_resized) + + # Redimensiona e salva máscara (se existir) + if mask_ids is not None: + mask_resized = cv2.resize(mask_ids, dim, interpolation=cv2.INTER_NEAREST) + path_mask_saida = os.path.join(PASTA_BASE, nome_res, "masks", nome_saida_mask) + cv2.imwrite(path_mask_saida, mask_resized) + + print(f"[{fonte}] [{i}/{total}] Redimensionado: {nome}") + +print("\n✅ Concluído com sucesso! Todas as fontes foram processadas.") diff --git a/Python/OAK/datasets/oak-d/_4_split.py b/Python/OAK/datasets/oak-d/_4_split.py new file mode 100644 index 000000000..59e1db8a0 --- /dev/null +++ b/Python/OAK/datasets/oak-d/_4_split.py @@ -0,0 +1,60 @@ +import os +import shutil +import random + +# === CONFIGURAÇÕES === +RESOLUCAO_BASE = "512x512" +PASTA_ORIGEM = os.path.join("dataset", RESOLUCAO_BASE) +PASTA_DESTINO = os.path.join("dataset", "split") + +PERCENT_TRAIN = 0.7 +PERCENT_VAL = 0.2 +PERCENT_TEST = 0.1 + +SEED = 42 +random.seed(SEED) + +# === Coleta imagens === +pasta_rgb = os.path.join(PASTA_ORIGEM, "images") +pasta_masks = os.path.join(PASTA_ORIGEM, "masks") + +arquivos = sorted([f for f in os.listdir(pasta_rgb) if f.endswith(".jpg") or f.endswith(".jpeg")]) + +# Embaralha +random.shuffle(arquivos) + +# Divide +total = len(arquivos) +n_train = int(total * PERCENT_TRAIN) +n_val = int(total * PERCENT_VAL) + +arquivos_train = arquivos[:n_train] +arquivos_val = arquivos[n_train:n_train+n_val] +arquivos_test = arquivos[n_train+n_val:] + +conjuntos = { + "train": arquivos_train, + "val": arquivos_val, + "test": arquivos_test +} + +# === Função auxiliar === +def copiar(imagens, conjunto): + path_img_dest = os.path.join(PASTA_DESTINO, conjunto, "images") + path_mask_dest = os.path.join(PASTA_DESTINO, conjunto, "masks") + os.makedirs(path_img_dest, exist_ok=True) + os.makedirs(path_mask_dest, exist_ok=True) + + for nome in imagens: + nome_mask = nome.replace(".jpg", ".png").replace(".jpeg", ".png") + if not os.path.exists(nome_mask): + continue + shutil.copy2(os.path.join(pasta_rgb, nome), os.path.join(path_img_dest, nome)) + shutil.copy2(os.path.join(pasta_masks, nome_mask), os.path.join(path_mask_dest, nome_mask)) + +# === Executa cópia === +for conjunto, lista in conjuntos.items(): + print(f"[{conjunto}] {len(lista)} arquivos") + copiar(lista, conjunto) + +print("\n✅ Dataset dividido com sucesso!") diff --git a/Python/OAK/datasets/oak-d/_5_train.py b/Python/OAK/datasets/oak-d/_5_train.py new file mode 100644 index 000000000..46ef301cc --- /dev/null +++ b/Python/OAK/datasets/oak-d/_5_train.py @@ -0,0 +1,234 @@ +import torch +import torch.optim as optim +#import network.modeling as models +import numpy as np +from torch.utils.data import Dataset, DataLoader +from torchvision import transforms as T +from PIL import Image +import os +import argparse +import matplotlib.pyplot as plt + +# Configuração do parsing de argumentos +parser = argparse.ArgumentParser(description='Treinamento do modelo DeepLabV3+') +parser.add_argument('--checkpoint', type=str, help='Caminho para o checkpoint de onde continuar o treinamento', default=None) +parser.add_argument('--n_epoch', type=str, help='Número de épocas para o treinamento', default='10') +args = parser.parse_args() + +# Transformação personalizada para máscaras +def mask_transform(mask): + return torch.tensor(np.array(mask), dtype=torch.long) + +# Transformações para as imagens +image_transforms = T.Compose([ + T.Resize((512, 512)), + T.ToTensor(), + T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), +]) + +# Dataset personalizado +class CustomVOC(Dataset): + def __init__(self, image_dirs, mask_dirs, transform=None, mask_transform=None): + self.transform = transform + self.mask_transform = mask_transform + self.images = [] + self.masks = [] + + # Combine todos os arquivos de imagem dos diretórios fornecidos + for image_dir in image_dirs: + for img in os.listdir(image_dir): + if not img.endswith(".jpg") and not img.endswith(".jpeg"): + continue + self.images.append(os.path.join(image_dir, img)) + mask_name = img.replace(".jpg", ".png").replace(".jpeg", ".png") + self.masks.append(os.path.join(mask_dirs[0], mask_name)) + + def __len__(self): + return len(self.images) + + def __getitem__(self, idx): + img_path = self.images[idx] + mask_path = self.masks[idx] # Já ajustado na inicialização + image = Image.open(img_path).convert("RGB") + mask = Image.open(mask_path).convert("L") # 1 canal + + if self.transform: + image = self.transform(image) + if self.mask_transform: + mask = self.mask_transform(mask) + + return image, mask + +# Instanciando o Dataset +save_dir = "backup/" +model_name = "ruasModel" + +train_dataset = CustomVOC( + image_dirs=["dataset/split/train/images"], + mask_dirs=["dataset/split/train/masks"], + transform=image_transforms, + mask_transform=mask_transform +) +train_loader = DataLoader(train_dataset, batch_size=4, shuffle=True, num_workers=0) + +val_dataset = CustomVOC( + image_dirs=["dataset/split/val/images"], + mask_dirs=["dataset/split/val/masks"], + transform=image_transforms, + mask_transform=mask_transform +) +val_loader = DataLoader(val_dataset, batch_size=4, shuffle=False, num_workers=0) + +# Definindo o modelo DeepLabV3+ +def create_deeplabv3plus(_num_classes, _output_stride): + #model = models.deeplabv3plus_resnet50(num_classes=_num_classes, output_stride=_output_stride) + from torchvision.models.segmentation import deeplabv3_resnet50 + model = deeplabv3_resnet50(weights=None, num_classes=_num_classes, output_stride=_output_stride) + return model + + +# Configuração do modelo, perda e otimizador +num_classes = 4 +output_stride = 16 +model = create_deeplabv3plus(num_classes, output_stride) +criterion = torch.nn.CrossEntropyLoss() +optimizer = optim.Adam(model.parameters(), lr=0.001) + +# Preparando o modelo para treinamento +device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + +# Carregar checkpoint se fornecido +start_epoch = 0 # Iniciar do começo se nenhum checkpoint for fornecido +if args.checkpoint: + checkpoint = torch.load(args.checkpoint) + model.load_state_dict(checkpoint['model_state_dict']) + + # Carrega o estado do otimizador + optimizer.load_state_dict(checkpoint['optimizer_state_dict']) + + # Garante que todos os tensores no otimizador estejam no dispositivo correto + for state in optimizer.state.values(): + for k, v in state.items(): + if isinstance(v, torch.Tensor): + state[k] = v.to(device) + + start_epoch = checkpoint['epoch'] + print(f"Continuando o treinamento do checkpoint {args.checkpoint}, a partir da época {start_epoch+1}") + +model.to(device) +# Força os BatchNorms internos (como do ASPP) para modo eval +for m in model.modules(): + if isinstance(m, torch.nn.BatchNorm2d): + m.eval() + +epoch_losses = [] +epoch_accuracies = [] + +plt.ion() # Ativa o modo interativo +fig, ax1 = plt.subplots() + +color_loss = 'tab:red' +ax1.set_xlabel('Época') +ax1.set_ylabel('Loss', color=color_loss) +ax1.tick_params(axis='y', labelcolor=color_loss) + +ax2 = ax1.twinx() # Instancia um segundo eixo que compartilha o mesmo eixo x +color_accuracy = 'tab:blue' +ax2.set_ylabel('Accuracy', color=color_accuracy) # Definimos o label do eixo y +ax2.tick_params(axis='y', labelcolor=color_accuracy) + +fig.tight_layout() # Ajusta o layout para evitar sobreposições + +# Treinamento do modelo +num_epochs = int(args.n_epoch) +# Loop de treinamento, ajustado para continuar de onde parou +best_val_loss = float('inf') + +for epoch in range(start_epoch, num_epochs): + print(f"Epoca {epoch}") + # === TREINO === + model.train() + running_loss = 0.0 + correct = 0 + total = 0 + + for images, masks in train_loader: + if images.size(0) == 1: + print("[⚠️] Pulando batch com 1 imagem (evita erro no BatchNorm)") + continue + images = images.to(device) + masks = masks.to(device) + + optimizer.zero_grad() + print(f"Batch shape: {images.shape}") + outputs = model(images) + loss = criterion(outputs['out'], masks) + loss.backward() + optimizer.step() + + running_loss += loss.item() + _, predicted = torch.max(outputs['out'].data, 1) + correct += (predicted == masks).sum().item() + total += masks.numel() + + train_loss = running_loss / len(train_loader) + train_accuracy = 100 * correct / total + + # === VALIDAÇÃO === + model.eval() + val_loss = 0.0 + val_correct = 0 + val_total = 0 + + with torch.no_grad(): + for images, masks in val_loader: + images = images.to(device) + masks = masks.to(device) + + outputs = model(images) + loss = criterion(outputs['out'], masks) + val_loss += loss.item() + + _, predicted = torch.max(outputs['out'].data, 1) + val_correct += (predicted == masks).sum().item() + val_total += masks.numel() + + val_loss /= len(val_loader) + val_accuracy = 100 * val_correct / val_total + + # === Checkpoint da época === + checkpoint_path = os.path.join(save_dir, f"{model_name}_checkpoint_epoch_{epoch+1}.pth") + torch.save({ + 'epoch': epoch+1, + 'model_state_dict': model.state_dict(), + 'optimizer_state_dict': optimizer.state_dict(), + 'loss': train_loss, + 'val_loss': val_loss, + }, checkpoint_path) + print(f"[✔] Checkpoint salvo: {checkpoint_path}") + + # === Melhor modelo (baseado no val_loss) === + if val_loss < best_val_loss: + best_val_loss = val_loss + best_model_path = os.path.join(save_dir, f"{model_name}_best.pth") + torch.save(model.state_dict(), best_model_path) + print(f"[🌟] Novo melhor modelo salvo: {best_model_path}") + + # === Plot dados === + epoch_losses.append(train_loss) + epoch_accuracies.append(train_accuracy) + ax1.plot(epoch_losses, color='tab:red') + ax2.plot(epoch_accuracies, color='tab:blue') + fig.canvas.draw() + fig.canvas.flush_events() + + print(f"[📊] Época {epoch+1} | Train Loss: {train_loss:.4f} | Train Acc: {train_accuracy:.2f}% | Val Loss: {val_loss:.4f} | Val Acc: {val_accuracy:.2f}%") + + +# Salvar o modelo final +save_path = f'{save_dir}{model_name}_final.pth' +torch.save(model.state_dict(), save_path) +print(f"Modelo salvo em {save_path}") + +plt.ioff() # Desativa o modo interativo +plt.show() diff --git a/Python/OAK/datasets/oak-d/_6_test.py b/Python/OAK/datasets/oak-d/_6_test.py new file mode 100644 index 000000000..b374a2e89 --- /dev/null +++ b/Python/OAK/datasets/oak-d/_6_test.py @@ -0,0 +1,193 @@ +import glob +import os +import torch +import torchvision.transforms as T +import matplotlib.pyplot as plt +import numpy as np +from PIL import Image +import cv2 +import depthai as dai + +# === CONFIGURAÇÕES === +model_path = 'backup/ruasModel_best.pth' +num_classes = 4 +labelmap_path = 'dataset/labelmap.txt' +image_path = 'dataset/split/test/images/' +USE_CAMERA = False + +device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + +# === FUNÇÕES AUXILIARES === + +def carregar_labelmap_completo(caminho): + cor_para_id = {} + id_para_nome = {} + cores_bgr = [] + + with open(caminho, 'r') as arquivo: + for linha in arquivo: + if linha.startswith("#") or not linha.strip(): + continue + partes = linha.strip().split(':') + if len(partes) >= 2: + nome_classe, cor_rgb_str = partes[0], partes[1] + r, g, b = map(int, cor_rgb_str.split(',')) + cor_bgr = (b, g, r) # Corrige para BGR + + classe_id = len(cor_para_id) + cor_para_id[cor_bgr] = classe_id + cores_bgr.append(cor_bgr) + id_para_nome[classe_id] = nome_classe + + return cor_para_id, cores_bgr, id_para_nome + +def segment_image(image_path, model): + image = Image.open(image_path).convert("RGB") + transform = T.Compose([ + T.Resize((512, 512)), + T.ToTensor(), + T.Normalize(mean=[0.485, 0.456, 0.406], + std=[0.229, 0.224, 0.225]) + ]) + input_tensor = transform(image).unsqueeze(0).to(device) + + model.eval() + with torch.no_grad(): + output = model(input_tensor)['out'] + prediction = torch.argmax(output.squeeze(), dim=0).cpu().numpy() + return prediction + +def display_segmentation(original_path, prediction): + original = cv2.imread(original_path) + original = cv2.resize(original, (512, 512)) + + _, cores_bgr, id_para_nome = carregar_labelmap_completo(labelmap_path) + + seg_color = np.zeros_like(original) + for class_id, color in enumerate(cores_bgr): + seg_color[prediction == class_id] = color + + overlay = cv2.addWeighted(original, 0.5, seg_color, 0.5, 0) + + # Legenda + legenda_inicio_y = 20 + for i, cor in enumerate(cores_bgr): + nome = id_para_nome.get(i, f"Classe {i}") + pos_y = legenda_inicio_y + i * 30 + cv2.rectangle(overlay, (10, pos_y - 15), (30, pos_y + 5), cor, -1) + cv2.putText(overlay, nome, (40, pos_y + 2), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255,255,255), 1, cv2.LINE_AA) + + cv2.imshow("Segmentacao - Overlay", overlay) + # Remove: cv2.waitKey() e destroyAllWindows + + +# === CARREGA MODELO === +import torchvision.models.segmentation as models +model = models.deeplabv3_resnet50(weights=None, num_classes=num_classes) +model.load_state_dict(torch.load(model_path, map_location=device)) +model.to(device) + +# === EXECUÇÃO EM TEMPO REAL COM OAK-D === +if USE_CAMERA: + print("[📷] Iniciando segmentação em tempo real com a OAK-D Lite...") + + # Setup da câmera + pipeline = dai.Pipeline() + cam_rgb = pipeline.createColorCamera() + cam_rgb.setPreviewSize(640, 640) + cam_rgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1080_P) + cam_rgb.setInterleaved(False) + cam_rgb.setColorOrder(dai.ColorCameraProperties.ColorOrder.BGR) + + xout = pipeline.createXLinkOut() + xout.setStreamName("rgb") + cam_rgb.preview.link(xout.input) + + _, cores_bgr, id_para_nome = carregar_labelmap_completo(labelmap_path) + + with dai.Device(pipeline) as oak_device: + queue = oak_device.getOutputQueue(name="rgb", maxSize=1, blocking=False) + + while True: + in_rgb = queue.get() + frame = in_rgb.getCvFrame() + frame_resized = cv2.resize(frame, (512, 512)) + + # Pré-processa o frame + image_pil = Image.fromarray(cv2.cvtColor(frame_resized, cv2.COLOR_BGR2RGB)) + transform = T.Compose([ + T.ToTensor(), + T.Normalize(mean=[0.485, 0.456, 0.406], + std=[0.229, 0.224, 0.225]) + ]) + input_tensor = transform(image_pil).unsqueeze(0).to(device) + + # Segmentação + model.eval() + with torch.no_grad(): + output = model(input_tensor)['out'] + prediction = torch.argmax(output.squeeze(), dim=0).cpu().numpy() + + # Cria imagem colorida da segmentação + seg_color = np.zeros_like(frame_resized) + for class_id, color in enumerate(cores_bgr): + seg_color[prediction == class_id] = color + + overlay = cv2.addWeighted(frame_resized, 0.5, seg_color, 0.5, 0) + + # Legenda + legenda_inicio_y = 20 + for i, cor in enumerate(cores_bgr): + nome = id_para_nome.get(i, f"Classe {i}") + pos_y = legenda_inicio_y + i * 30 + cv2.rectangle(overlay, (10, pos_y - 15), (30, pos_y + 5), cor, -1) + cv2.putText(overlay, nome, (40, pos_y + 2), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255,255,255), 1, cv2.LINE_AA) + + # Mostra + cv2.imshow("Segmentacao em Tempo Real", overlay) + if cv2.waitKey(1) & 0xFF == ord('q'): + break + + cv2.destroyAllWindows() +else: + # === SEGMENTAÇÃO POR NAVEGAÇÃO ENTRE IMAGENS === + if image_path and os.path.isdir(image_path): + print(f"[📁] Navegando imagens em: {image_path}") + + # Lista de imagens + extensoes = ("*.jpg", "*.png", "*.jpeg") + arquivos = [] + for ext in extensoes: + arquivos.extend(glob.glob(os.path.join(image_path, ext))) + arquivos.sort() + + if not arquivos: + print("[!] Nenhuma imagem encontrada na pasta de teste.") + exit() + + indice = 0 + while True: + caminho_img = arquivos[indice] + output_predictions = segment_image(caminho_img, model) + display_segmentation(caminho_img, output_predictions) + + print(f"[{indice+1}/{len(arquivos)}] {os.path.basename(caminho_img)}") + + key = cv2.waitKey(0) & 0xFF + cv2.destroyAllWindows() # ← fecha imagem ao mudar + + if key == ord('d'): + if indice < len(arquivos) - 1: + indice += 1 + else: + indice = 0 + elif key == ord('a'): + if indice > 0: + indice -= 1 + else: + indice = len(arquivos) - 1 + elif key == ord('q'): + print("[👋] Saindo da visualização.") + break + else: + print(f"[!] Tecla inválida ({key}). Use A, D ou Q.") diff --git a/Python/OAK/datasets/requirements.txt b/Python/OAK/datasets/requirements.txt new file mode 100644 index 000000000..6a072adef --- /dev/null +++ b/Python/OAK/datasets/requirements.txt @@ -0,0 +1,59 @@ +absl-py==2.3.0 +AHRS==0.3.1 +albucore==0.0.24 +albumentations==2.0.8 +annotated-types==0.7.0 +certifi==2025.6.15 +charset-normalizer==3.4.2 +colorama==0.4.6 +contourpy==1.3.2 +cycler==0.12.1 +depthai==2.30.0.0 +filelock==3.13.1 +fonttools==4.58.4 +fsspec==2024.6.1 +grpcio==1.73.1 +idna==3.10 +Jinja2==3.1.4 +kiwisolver==1.4.8 +labelImg==1.8.6 +lxml==6.0.0 +Markdown==3.8.2 +MarkupSafe==2.1.5 +matplotlib==3.10.3 +mpmath==1.3.0 +networkx==3.3 +numpy==2.2.6 +opencv-python==4.11.0.86 +opencv-python-headless==4.11.0.86 +packaging==25.0 +pandas==2.3.0 +pillow==11.0.0 +protobuf==6.31.1 +pydantic==2.11.7 +pydantic_core==2.33.2 +pyparsing==3.2.3 +PyQt5==5.15.11 +PyQt5-Qt5==5.15.2 +PyQt5_sip==12.17.0 +python-dateutil==2.9.0.post0 +pytz==2025.2 +PyYAML==6.0.2 +requests==2.32.4 +scipy==1.15.3 +seaborn==0.13.2 +simsimd==6.4.9 +six==1.17.0 +stringzilla==3.12.5 +sympy==1.13.3 +tensorboard==2.19.0 +tensorboard-data-server==0.7.2 +torch==2.7.1+cu118 +torchaudio==2.7.1+cu118 +torchvision==0.22.1+cu118 +tqdm==4.67.1 +typing-inspection==0.4.1 +typing_extensions==4.14.0 +tzdata==2025.2 +urllib3==2.5.0 +Werkzeug==3.1.3 diff --git a/Python/OAK/oak-d-lite-dataset.py b/Python/OAK/oak-d-lite-dataset.py new file mode 100644 index 000000000..57d2b9d0e --- /dev/null +++ b/Python/OAK/oak-d-lite-dataset.py @@ -0,0 +1,49 @@ +import depthai as dai +import cv2 +import os +from datetime import datetime + +# === CONFIGURAÇÕES === +PASTA_SAIDA = os.path.join("dataset", "oak-d-lite") +DIMENSAO_FINAL = (512, 512) +QUALIDADE_JPEG = 95 # 0 a 100 +TECLA_SALVAR = ord('s') # pressione 's' para salvar + +# Cria pasta de saída se não existir +os.makedirs(PASTA_SAIDA, exist_ok=True) + +# === PIPELINE OAK === +pipeline = dai.Pipeline() +cam_rgb = pipeline.createColorCamera() +cam_rgb.setPreviewSize(640, 640) +cam_rgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1080_P) +cam_rgb.setInterleaved(False) +cam_rgb.setColorOrder(dai.ColorCameraProperties.ColorOrder.BGR) + +xout = pipeline.createXLinkOut() +xout.setStreamName("rgb") +cam_rgb.preview.link(xout.input) + +# === EXECUÇÃO === +with dai.Device(pipeline) as device: + print("[INFO] Pressione 's' para salvar imagem, 'q' para sair.") + queue = device.getOutputQueue(name="rgb", maxSize=4, blocking=False) + + while True: + frame = queue.get().getCvFrame() + frame_resized = cv2.resize(frame, DIMENSAO_FINAL, interpolation=cv2.INTER_AREA) + + cv2.imshow("OAK-D RGB", frame_resized) + key = cv2.waitKey(1) & 0xFF + + if key == TECLA_SALVAR: + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + caminho = os.path.join(PASTA_SAIDA, f"img_{timestamp}.jpg") + cv2.imwrite(caminho, frame_resized, [cv2.IMWRITE_JPEG_QUALITY, QUALIDADE_JPEG]) + print(f"[✔] Imagem salva: {caminho}") + + elif key == ord('q'): + print("[INFO] Encerrando...") + break + + cv2.destroyAllWindows()