Ajustes operação
This commit is contained in:
parent
053bd9994f
commit
778af21ebf
|
|
@ -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/
|
||||
|
|
|
|||
|
|
@ -797,11 +797,19 @@ namespace AgroBase.Models.Modules
|
|||
return BombaOk;
|
||||
}
|
||||
|
||||
public async Task RealizarCalibragemAsync()
|
||||
public List<Task> RealizarCalibragemAsync()
|
||||
{
|
||||
List<Task> tasks = new List<Task>();
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Task> RealizarCalibragemAsync()
|
||||
{
|
||||
List<Task> tasks = new List<Task>();
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -695,8 +695,8 @@ namespace AgroBase.Models
|
|||
var DispMvd = Variaveis.OperacaoEmAndamento.DispMvd;
|
||||
|
||||
var tasks = new List<Task>();
|
||||
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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -138,36 +138,43 @@ namespace AgroBase.Services.Operadores
|
|||
|
||||
public static void AtualizarAnalise()
|
||||
{
|
||||
DadosLeitura.Imu = JsonConvert.DeserializeObject<VisualWorkerMessageIMUModel>(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<VisualWorkerMessageIMUModel>(imuJson);
|
||||
}
|
||||
|
||||
var json = RedisService.Get(CtxKey.DadosVisualWorker);
|
||||
var dados = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
|
||||
|
||||
DateTime timestamp = FuncoesGlobais.UnixToDateTime(dados["ts_analise"].ToString());
|
||||
|
||||
//var matrizes = JsonConvert.DeserializeObject<Dictionary<string, object>>(dados["matrizes"].ToString());
|
||||
//var confianca = JsonConvert.DeserializeObject<List<List<double>>>(JsonConvert.SerializeObject(matrizes["confianca"]));
|
||||
//var custo = JsonConvert.DeserializeObject<List<List<double>>>(JsonConvert.SerializeObject(matrizes["custo"]));
|
||||
|
||||
var anomalias = JsonConvert.DeserializeObject<Dictionary<string, object>>(dados["anomalias"].ToString());
|
||||
var deteccoes = JsonConvert.DeserializeObject<List<VisualWorkerMessageAnaliseBBoxModel>>(JsonConvert.SerializeObject(anomalias["deteccoes"]));
|
||||
var sombras = JsonConvert.DeserializeObject<List<VisualWorkerMessageAnaliseBBoxModel>>(JsonConvert.SerializeObject(anomalias["sombras"]));
|
||||
|
||||
var corredor = JsonConvert.DeserializeObject<Dictionary<string, object>>(dados["perfil_corredor"].ToString());
|
||||
var segmentacao = JsonConvert.DeserializeObject<List<VisualWorkerMessageRadar2DPerfilModel>>(JsonConvert.SerializeObject(corredor["segmentacao"]));
|
||||
var profundidade = JsonConvert.DeserializeObject<List<VisualWorkerMessageRadar2DPerfilModel>>(JsonConvert.SerializeObject(corredor["profundidade"]));
|
||||
|
||||
var solo = JsonConvert.DeserializeObject<VisualWorkerMessageAnaliseSoloModel>(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<Dictionary<string, object>>(dados["matrizes"].ToString());
|
||||
//var confianca = JsonConvert.DeserializeObject<List<List<double>>>(JsonConvert.SerializeObject(matrizes["confianca"]));
|
||||
//var custo = JsonConvert.DeserializeObject<List<List<double>>>(JsonConvert.SerializeObject(matrizes["custo"]));
|
||||
|
||||
var anomalias = JsonConvert.DeserializeObject<Dictionary<string, object>>(dados["anomalias"].ToString());
|
||||
var deteccoes = JsonConvert.DeserializeObject<List<VisualWorkerMessageAnaliseBBoxModel>>(JsonConvert.SerializeObject(anomalias["deteccoes"]));
|
||||
var sombras = JsonConvert.DeserializeObject<List<VisualWorkerMessageAnaliseBBoxModel>>(JsonConvert.SerializeObject(anomalias["sombras"]));
|
||||
|
||||
var corredor = JsonConvert.DeserializeObject<Dictionary<string, object>>(dados["perfil_corredor"].ToString());
|
||||
var segmentacao = JsonConvert.DeserializeObject<List<VisualWorkerMessageRadar2DPerfilModel>>(JsonConvert.SerializeObject(corredor["segmentacao"]));
|
||||
var profundidade = JsonConvert.DeserializeObject<List<VisualWorkerMessageRadar2DPerfilModel>>(JsonConvert.SerializeObject(corredor["profundidade"]));
|
||||
|
||||
var solo = JsonConvert.DeserializeObject<VisualWorkerMessageAnaliseSoloModel>(JsonConvert.SerializeObject(dados["solo"]));
|
||||
|
||||
DadosLeitura.Analises = new VisualWorkerMessageAnaliseModel()
|
||||
{
|
||||
timestamp = timestamp,
|
||||
solo = solo,
|
||||
anomalias = deteccoes,
|
||||
sombras = sombras,
|
||||
corredor_prof = profundidade,
|
||||
corredor_seg = segmentacao,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
|
|
@ -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!")
|
||||
|
|
@ -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!")
|
||||
|
|
@ -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 ..
|
||||
|
|
@ -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!")
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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!")
|
||||
|
|
@ -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.")
|
||||
|
|
@ -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!")
|
||||
|
|
@ -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()
|
||||
|
|
@ -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.")
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
Loading…
Reference in New Issue