outputPath = $outputPath; // Carrega a imagem $this->srcImage = cv\imread($imagePath); if ($this->srcImage === null) { throw new Exception('Failed to load image'); } // Pipeline de processamento $this->detectDocument() ->correctPerspective() ->enhanceImage() ->saveResult(); return $this->outputPath; } private function detectDocument() { // Converte para escala de cinza $gray = cv\cvtColor($this->srcImage, cv\COLOR_BGR2GRAY); // Aplica blur para reduzir ruído $blur = new cv\Mat(); cv\GaussianBlur($gray, $blur, new cv\Size(5, 5), 0); // Detecta bordas $edges = new cv\Mat(); cv\Canny($blur, $edges, 75, 200); // Encontra contornos $contours = []; $hierarchy = new cv\Mat(); cv\findContours($edges, $contours, $hierarchy, cv\RETR_LIST, cv\CHAIN_APPROX_SIMPLE); // Ordena contornos por área usort($contours, function($a, $b) { return cv\contourArea($b) - cv\contourArea($a); }); // Procura o contorno mais próximo de um documento foreach ($contours as $contour) { $peri = cv\arcLength($contour, true); $approx = cv\approxPolyDP($contour, 0.02 * $peri, true); if (count($approx) == 4) { $this->documentContour = $approx; break; } } return $this; } private function correctPerspective() { if (!isset($this->documentContour)) { return $this; } // Ordena os pontos para perspectiva $points = $this->orderPoints($this->documentContour); // Calcula dimensões do documento $width = max( sqrt(pow($points[1][0] - $points[0][0], 2) + pow($points[1][1] - $points[0][1], 2)), sqrt(pow($points[3][0] - $points[2][0], 2) + pow($points[3][1] - $points[2][1], 2)) ); $height = max( sqrt(pow($points[2][0] - $points[1][0], 2) + pow($points[2][1] - $points[1][1], 2)), sqrt(pow($points[3][0] - $points[0][0], 2) + pow($points[3][1] - $points[0][1], 2)) ); // Define pontos de destino $dst = cv\Mat::zeros([4, 2], CV_32F); $dst[0] = [0, 0]; $dst[1] = [$width - 1, 0]; $dst[2] = [$width - 1, $height - 1]; $dst[3] = [0, $height - 1]; // Aplica transformação de perspectiva $matrix = cv\getPerspectiveTransform($points, $dst); $this->processedImage = cv\warpPerspective($this->srcImage, $matrix, [$width, $height]); return $this; } private function enhanceImage() { if (!isset($this->processedImage)) { $this->processedImage = $this->srcImage; } // Converte para LAB para melhor processamento $lab = cv\cvtColor($this->processedImage, cv\COLOR_BGR2LAB); $l_channel = cv\split($lab)[0]; // Aplica CLAHE para melhorar contraste $clahe = cv\createCLAHE(3.0, [8, 8]); $cl = $clahe->apply($l_channel); // Reconstrói imagem $limg = cv\merge([$cl, cv\split($lab)[1], cv\split($lab)[2]]); $this->processedImage = cv\cvtColor($limg, cv\COLOR_LAB2BGR); return $this; } private function saveResult() { if (!isset($this->processedImage)) { throw new Exception('No processed image to save'); } cv\imwrite($this->outputPath, $this->processedImage); return $this; } private function orderPoints($pts) { // Ordena pontos para transformação de perspectiva $rect = array_fill(0, 4, [0, 0]); $s = array_map(function($p) { return $p[0] + $p[1]; }, $pts); $d = array_map(function($p) { return $p[0] - $p[1]; }, $pts); $rect[0] = $pts[array_search(min($s), $s)]; // top-left $rect[2] = $pts[array_search(max($s), $s)]; // bottom-right $rect[1] = $pts[array_search(min($d), $d)]; // top-right $rect[3] = $pts[array_search(max($d), $d)]; // bottom-left return $rect; } }