Python

Artigo 39 — Visão Computacional com OpenCV e PIL Já leu

16 min de leitura

Artigo 39 — Visão Computacional com OpenCV e PIL
Visão computacional é a capacidade de máquinas interpretarem e processarem imagens e vídeos. Está presente em reconhecimento facial, veículos autônomos,

Artigo 39 — Visão Computacional com OpenCV e PIL

Prof. Ricardo Matos Módulo 7 · Data Science e Machine Learning · Artigo 39 de 52


Introdução

Visão computacional é a capacidade de máquinas interpretarem e processarem imagens e vídeos. Está presente em reconhecimento facial, veículos autônomos, diagnóstico médico por imagem, controle de qualidade industrial e leitura de documentos. Python tem duas bibliotecas fundamentais para isso: Pillow (PIL) para manipulação geral de imagens e OpenCV para processamento de imagens em tempo real e visão computacional avançada.


Instalação

pip install pillow opencv-python numpy matplotlib

Pillow: Manipulação de Imagens

from PIL import Image, ImageDraw, ImageFont, ImageFilter, ImageEnhance
from pathlib import Path
import numpy as np


# Abrindo e inspecionando
img = Image.open("foto.jpg")
print(f"Formato:   {img.format}")
print(f"Modo:      {img.mode}")    # RGB, RGBA, L (grayscale), etc.
print(f"Tamanho:   {img.size}")    # (largura, altura)
print(f"Dimensões: {img.width} × {img.height}")

# Criando imagem do zero
img_nova = Image.new("RGB", (400, 300), color=(30, 60, 120))

Operações Básicas

def operacoes_basicas(caminho: str, pasta_saida: str = "saida"):
    Path(pasta_saida).mkdir(exist_ok=True)
    img = Image.open(caminho)

    # Redimensionamento
    img_pequena = img.resize((200, 200))
    img_thumb   = img.copy()
    img_thumb.thumbnail((300, 300))   # mantém proporção

    # Recorte
    largura, altura = img.size
    centro_x, centro_y = largura // 2, altura // 2
    crop_box = (
        centro_x - 100, centro_y - 100,
        centro_x + 100, centro_y + 100
    )
    img_crop = img.crop(crop_box)

    # Rotação e espelhamento
    img_rot90  = img.rotate(90, expand=True)
    img_flip_h = img.transpose(Image.FLIP_LEFT_RIGHT)
    img_flip_v = img.transpose(Image.FLIP_TOP_BOTTOM)

    # Conversão de modo
    img_gray = img.convert("L")      # escala de cinza
    img_rgba = img.convert("RGBA")   # com canal alpha

    # Salvando em diferentes formatos
    img_pequena.save(f"{pasta_saida}/pequena.jpg", quality=85)
    img_gray.save(f"{pasta_saida}/cinza.png")
    img_crop.save(f"{pasta_saida}/recorte.jpg")

    print("Operações básicas concluídas.")
    return img


# Filtros
def aplicar_filtros(img: Image.Image) -> dict:
    return {
        "blur":         img.filter(ImageFilter.BLUR),
        "sharpen":      img.filter(ImageFilter.SHARPEN),
        "edge":         img.filter(ImageFilter.FIND_EDGES),
        "emboss":       img.filter(ImageFilter.EMBOSS),
        "smooth":       img.filter(ImageFilter.SMOOTH_MORE),
        "contour":      img.filter(ImageFilter.CONTOUR),
        "gaussian":     img.filter(ImageFilter.GaussianBlur(radius=3)),
        "unsharp":      img.filter(ImageFilter.UnsharpMask(radius=2, percent=150)),
    }


# Ajustes de imagem
def ajustar_imagem(img: Image.Image) -> Image.Image:
    img = ImageEnhance.Brightness(img).enhance(1.2)   # +20% brilho
    img = ImageEnhance.Contrast(img).enhance(1.3)     # +30% contraste
    img = ImageEnhance.Sharpness(img).enhance(1.5)    # +50% nitidez
    img = ImageEnhance.Color(img).enhance(1.1)        # +10% saturação
    return img

Geração de Imagens: Marca D'água e Certificados

from PIL import Image, ImageDraw, ImageFont
import textwrap


def adicionar_marca_dagua(
    caminho_img: str,
    texto: str,
    saida: str,
    opacidade: int = 80
):
    """Adiciona marca d'água diagonal com texto transparente."""
    img  = Image.open(caminho_img).convert("RGBA")
    larg, alt = img.size

    # Camada transparente para a marca
    marca = Image.new("RGBA", img.size, (0, 0, 0, 0))
    draw  = ImageDraw.Draw(marca)

    try:
        fonte = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 40)
    except:
        fonte = ImageFont.load_default()

    # Repete o texto em grade diagonal
    for x in range(-larg, larg * 2, 300):
        for y in range(-alt, alt * 2, 200):
            draw.text(
                (x, y), texto,
                font=fonte,
                fill=(200, 200, 200, opacidade),
                anchor="mm"
            )
            # Rotacionar individualmente não é direto — usamos imagem rotacionada

    # Rotaciona a marca 45 graus
    marca = marca.rotate(45)
    resultado = Image.alpha_composite(img, marca)
    resultado.convert("RGB").save(saida)
    print(f"Marca d'água adicionada: {saida}")


def gerar_certificado(
    nome_aluno: str,
    curso: str,
    data: str,
    saida: str,
    largura: int = 800,
    altura: int = 560
):
    """Gera certificado de conclusão."""

    # Fundo com gradiente simulado
    img  = Image.new("RGB", (largura, altura), color=(245, 245, 250))
    draw = ImageDraw.Draw(img)

    # Borda decorativa
    for i in range(5):
        cor = (30 + i * 10, 60 + i * 5, 120 + i * 5)
        draw.rectangle([i, i, largura - i, altura - i], outline=cor, width=1)

    # Faixa superior
    draw.rectangle([0, 0, largura, 80], fill=(30, 60, 120))

    try:
        fonte_titulo  = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 32)
        fonte_nome    = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 36)
        fonte_normal  = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 20)
        fonte_pequena = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 16)
    except:
        fonte_titulo = fonte_nome = fonte_normal = fonte_pequena = ImageFont.load_default()

    # Textos
    draw.text((largura // 2, 40), "CERTIFICADO DE CONCLUSÃO",
              font=fonte_titulo, fill=(255, 255, 255), anchor="mm")

    draw.text((largura // 2, 160), "Certificamos que",
              font=fonte_normal, fill=(80, 80, 80), anchor="mm")

    draw.text((largura // 2, 220), nome_aluno.upper(),
              font=fonte_nome, fill=(30, 60, 120), anchor="mm")

    # Linha decorativa sob o nome
    draw.line([(largura//2 - 200, 250), (largura//2 + 200, 250)],
              fill=(30, 60, 120), width=2)

    draw.text((largura // 2, 290),
              "concluiu com êxito o curso de",
              font=fonte_normal, fill=(80, 80, 80), anchor="mm")

    draw.text((largura // 2, 340), curso,
              font=fonte_titulo, fill=(30, 60, 120), anchor="mm")

    draw.text((largura // 2, 420),
              f"Emitido em {data}",
              font=fonte_pequena, fill=(120, 120, 120), anchor="mm")

    # Linha de assinatura
    draw.line([(200, 480), (400, 480)], fill=(80, 80, 80), width=1)
    draw.text((300, 495), "Prof. Ricardo Matos",
              font=fonte_pequena, fill=(80, 80, 80), anchor="mm")

    img.save(saida, quality=95)
    print(f"Certificado gerado: {saida}")
    return img


# Gerando certificado
gerar_certificado(
    nome_aluno="Ana Silva",
    curso="Dominando o Python em 1 Ano",
    data="15/03/2024",
    saida="certificado_ana.jpg"
)

OpenCV: Processamento de Imagens

import cv2
import numpy as np
import matplotlib.pyplot as plt


def exibir_imagens(imagens: dict, titulo: str = ""):
    """Exibe múltiplas imagens lado a lado com matplotlib."""
    n    = len(imagens)
    fig, axes = plt.subplots(1, n, figsize=(5 * n, 5))
    if n == 1:
        axes = [axes]

    for ax, (nome, img) in zip(axes, imagens.items()):
        if len(img.shape) == 3:
            ax.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
        else:
            ax.imshow(img, cmap="gray")
        ax.set_title(nome)
        ax.axis("off")

    plt.suptitle(titulo)
    plt.tight_layout()
    plt.savefig(f"{titulo.lower().replace(' ', '_')}.png", dpi=120)
    plt.show()


# Leitura e operações básicas
img = cv2.imread("foto.jpg")

print(f"Shape: {img.shape}")   # (altura, largura, canais)
print(f"Dtype: {img.dtype}")

# Conversão de espaços de cor
img_gray   = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_rgb    = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img_hsv    = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

# Redimensionamento
img_metade = cv2.resize(img, (img.shape[1]//2, img.shape[0]//2))
img_200x200 = cv2.resize(img, (200, 200), interpolation=cv2.INTER_CUBIC)

# Recorte (numpy slicing)
img_crop = img[100:300, 150:400]

# Rotação
centro  = (img.shape[1]//2, img.shape[0]//2)
matriz  = cv2.getRotationMatrix2D(centro, 45, 1.0)
img_rot = cv2.warpAffine(img, matriz, (img.shape[1], img.shape[0]))

exibir_imagens({
    "Original":     img,
    "Cinza":        img_gray,
    "Recorte":      img_crop,
    "Rotacionado":  img_rot,
}, "Operações Básicas")

Detecção de Bordas e Contornos

def detectar_bordas(img_path: str):
    img      = cv2.imread(img_path)
    gray     = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # Suavização antes da detecção de bordas
    blur     = cv2.GaussianBlur(gray, (5, 5), 0)

    # Canny — algoritmo clássico de detecção de bordas
    canny    = cv2.Canny(blur, threshold1=50, threshold2=150)

    # Sobel — gradiente nas direções X e Y
    sobel_x  = cv2.Sobel(blur, cv2.CV_64F, 1, 0, ksize=3)
    sobel_y  = cv2.Sobel(blur, cv2.CV_64F, 0, 1, ksize=3)
    sobel    = cv2.magnitude(sobel_x, sobel_y).astype(np.uint8)

    # Laplaciano
    laplacian = cv2.Laplacian(blur, cv2.CV_64F)
    laplacian = np.absolute(laplacian).astype(np.uint8)

    exibir_imagens({
        "Original":   gray,
        "Canny":      canny,
        "Sobel":      sobel,
        "Laplaciano": laplacian,
    }, "Detecção de Bordas")

    return canny


def encontrar_contornos(img_path: str):
    img   = cv2.imread(img_path)
    gray  = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    blur  = cv2.GaussianBlur(gray, (5, 5), 0)
    _, thresh = cv2.threshold(blur, 127, 255, cv2.THRESH_BINARY)

    contornos, hierarquia = cv2.findContours(
        thresh,
        cv2.RETR_EXTERNAL,
        cv2.CHAIN_APPROX_SIMPLE
    )

    img_contornos = img.copy()
    cv2.drawContours(img_contornos, contornos, -1, (0, 255, 0), 2)

    print(f"Contornos encontrados: {len(contornos)}")

    # Filtrando por área
    contornos_grandes = [c for c in contornos if cv2.contourArea(c) > 500]
    for c in contornos_grandes:
        x, y, w, h = cv2.boundingRect(c)
        area        = cv2.contourArea(c)
        perimetro   = cv2.arcLength(c, closed=True)
        cv2.rectangle(img_contornos, (x, y), (x+w, y+h), (255, 0, 0), 1)
        print(f"  Contorno: área={area:.0f}, perímetro={perimetro:.0f}")

    return img_contornos, contornos

Detecção de Faces com Haar Cascades

def detectar_faces(img_path: str, saida: str = "faces_detectadas.jpg"):
    """Detecção de faces usando classificadores Haar."""

    # Carregando classificador pré-treinado
    detector_face  = cv2.CascadeClassifier(
        cv2.data.haarcascades + "haarcascade_frontalface_default.xml"
    )
    detector_olhos = cv2.CascadeClassifier(
        cv2.data.haarcascades + "haarcascade_eye.xml"
    )

    img  = cv2.imread(img_path)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # Detectando faces
    faces = detector_face.detectMultiScale(
        gray,
        scaleFactor=1.1,
        minNeighbors=5,
        minSize=(30, 30),
        flags=cv2.CASCADE_SCALE_IMAGE
    )

    print(f"Faces detectadas: {len(faces)}")

    for i, (x, y, w, h) in enumerate(faces):
        # Desenhando retângulo ao redor da face
        cv2.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), 2)
        cv2.putText(
            img, f"Face {i+1}",
            (x, y - 10),
            cv2.FONT_HERSHEY_SIMPLEX, 0.7,
            (0, 255, 0), 2
        )

        # Detectando olhos dentro de cada face
        roi_gray = gray[y:y+h, x:x+w]
        roi_img  = img[y:y+h, x:x+w]
        olhos    = detector_olhos.detectMultiScale(roi_gray)

        for (ox, oy, ow, oh) in olhos:
            cv2.circle(
                roi_img,
                (ox + ow//2, oy + oh//2),
                ow//2,
                (255, 0, 0), 2
            )

    cv2.imwrite(saida, img)
    print(f"Imagem salva: {saida}")
    return img, faces

Processamento de Vídeo

def processar_video(
    fonte: int | str = 0,
    operacao: str = "gray",
    saida: str = None
):
    """
    Processa vídeo em tempo real ou arquivo.
    fonte=0 usa webcam; fonte="video.mp4" usa arquivo.
    """
    cap = cv2.VideoCapture(fonte)

    if not cap.isOpened():
        print("Não foi possível abrir a fonte de vídeo.")
        return

    # Configurações do vídeo
    fps    = cap.get(cv2.CAP_PROP_FPS)
    largura = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    altura  = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))

    print(f"Resolução: {largura}×{altura} @ {fps:.1f}fps")

    # Writer para salvar
    writer = None
    if saida:
        fourcc = cv2.VideoWriter_fourcc(*"mp4v")
        writer = cv2.VideoWriter(saida, fourcc, fps, (largura, altura))

    detector_face = cv2.CascadeClassifier(
        cv2.data.haarcascades + "haarcascade_frontalface_default.xml"
    )

    frame_count = 0
    while True:
        ret, frame = cap.read()
        if not ret:
            break

        frame_count += 1

        # Aplicando operação escolhida
        if operacao == "gray":
            processado = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
            processado = cv2.cvtColor(processado, cv2.COLOR_GRAY2BGR)

        elif operacao == "blur":
            processado = cv2.GaussianBlur(frame, (15, 15), 0)

        elif operacao == "canny":
            gray       = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
            edges      = cv2.Canny(gray, 50, 150)
            processado = cv2.cvtColor(edges, cv2.COLOR_GRAY2BGR)

        elif operacao == "faces":
            processado = frame.copy()
            gray       = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
            faces      = detector_face.detectMultiScale(gray, 1.1, 5)
            for (x, y, w, h) in faces:
                cv2.rectangle(processado, (x, y), (x+w, y+h), (0, 255, 0), 2)

        else:
            processado = frame

        # Informações no frame
        cv2.putText(
            processado,
            f"Frame: {frame_count} | {operacao.upper()}",
            (10, 30),
            cv2.FONT_HERSHEY_SIMPLEX, 0.7,
            (0, 255, 0), 2
        )

        if writer:
            writer.write(processado)

        # Exibe ao vivo (comentar em ambientes sem display)
        # cv2.imshow("Video", processado)
        # if cv2.waitKey(1) & 0xFF == ord("q"):
        #     break

        # Para arquivos: limita a 100 frames no exemplo
        if isinstance(fonte, str) and frame_count >= 100:
            break

    cap.release()
    if writer:
        writer.release()
    cv2.destroyAllWindows()
    print(f"Processamento concluído: {frame_count} frames")

OCR: Lendo Texto de Imagens

pip install pytesseract
# Instalar Tesseract: https://github.com/tesseract-ocr/tesseract
import pytesseract
from PIL import Image
import cv2
import numpy as np


def preprocessar_para_ocr(img_path: str) -> np.ndarray:
    """Prepara imagem para melhor resultado no OCR."""
    img  = cv2.imread(img_path)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # Redimensiona para melhor reconhecimento
    scale = 2.0
    gray  = cv2.resize(gray, None, fx=scale, fy=scale,
                        interpolation=cv2.INTER_CUBIC)

    # Remove ruído
    denoised = cv2.fastNlMeansDenoising(gray, h=10)

    # Binarização adaptativa
    binaria = cv2.adaptiveThreshold(
        denoised, 255,
        cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
        cv2.THRESH_BINARY, 11, 2
    )

    return binaria


def extrair_texto(img_path: str, idioma: str = "por") -> dict:
    """Extrai texto de imagem usando Tesseract OCR."""

    img_proc = preprocessar_para_ocr(img_path)
    img_pil  = Image.fromarray(img_proc)

    # Configuração do Tesseract
    config = "--psm 6 --oem 3"  # PSM 6: bloco uniforme de texto

    # Texto simples
    texto = pytesseract.image_to_string(
        img_pil, lang=idioma, config=config
    )

    # Dados detalhados por palavra
    dados = pytesseract.image_to_data(
        img_pil, lang=idioma,
        output_type=pytesseract.Output.DICT
    )

    # Filtra palavras com confiança > 60%
    palavras_confiaveis = [
        {
            "texto":      dados["text"][i],
            "confianca":  dados["conf"][i],
            "x": dados["left"][i], "y": dados["top"][i],
            "w": dados["width"][i], "h": dados["height"][i]
        }
        for i in range(len(dados["text"]))
        if int(dados["conf"][i]) > 60 and dados["text"][i].strip()
    ]

    return {
        "texto":    texto.strip(),
        "palavras": palavras_confiaveis,
        "total":    len(palavras_confiaveis)
    }

Exemplo Completo: Pipeline de Análise de Documentos

from PIL import Image, ImageDraw, ImageFont
import cv2
import numpy as np
from pathlib import Path
from dataclasses import dataclass
from typing import List, Tuple
import json


@dataclass
class DocumentoAnalisado:
    caminho:        str
    largura:        int
    altura:         int
    faces_detectadas: int
    contornos:      int
    texto_extraido: str
    qualidade:      float


class AnalisadorDocumentos:
    def __init__(self):
        self.detector_face = cv2.CascadeClassifier(
            cv2.data.haarcascades + "haarcascade_frontalface_default.xml"
        )

    def avaliar_qualidade(self, img: np.ndarray) -> float:
        """Avalia qualidade da imagem pelo desvio padrão do Laplaciano."""
        gray      = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        laplacian = cv2.Laplacian(gray, cv2.CV_64F)
        return float(laplacian.var())

    def detectar_faces(self, img: np.ndarray) -> List[Tuple]:
        gray  = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        faces = self.detector_face.detectMultiScale(gray, 1.1, 5)
        return list(faces)

    def contar_contornos(self, img: np.ndarray) -> int:
        gray  = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        blur  = cv2.GaussianBlur(gray, (5, 5), 0)
        canny = cv2.Canny(blur, 50, 150)
        contornos, _ = cv2.findContours(
            canny, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
        )
        return len([c for c in contornos if cv2.contourArea(c) > 200])

    def anotar_imagem(
        self,
        img: np.ndarray,
        faces: list,
        qualidade: float
    ) -> np.ndarray:
        resultado = img.copy()

        # Anotando faces
        for i, (x, y, w, h) in enumerate(faces):
            cv2.rectangle(resultado, (x, y), (x+w, y+h), (0, 255, 0), 2)
            cv2.putText(resultado, f"Face {i+1}",
                       (x, y-5), cv2.FONT_HERSHEY_SIMPLEX,
                       0.5, (0, 255, 0), 1)

        # Informações da qualidade
        status_qual = "Alta" if qualidade > 500 else ("Media" if qualidade > 100 else "Baixa")
        cor_qual    = (0,255,0) if qualidade > 500 else ((0,165,255) if qualidade > 100 else (0,0,255))
        cv2.putText(resultado, f"Qualidade: {status_qual} ({qualidade:.0f})",
                   (10, 25), cv2.FONT_HERSHEY_SIMPLEX, 0.6, cor_qual, 2)

        return resultado

    def analisar(self, caminho: str, salvar_anotado: bool = True) -> DocumentoAnalisado:
        img = cv2.imread(caminho)
        if img is None:
            raise ValueError(f"Não foi possível carregar: {caminho}")

        altura, largura = img.shape[:2]
        qualidade       = self.avaliar_qualidade(img)
        faces           = self.detectar_faces(img)
        n_contornos     = self.contar_contornos(img)
        texto           = ""   # OCR opcional

        doc = DocumentoAnalisado(
            caminho=        caminho,
            largura=        largura,
            altura=         altura,
            faces_detectadas= len(faces),
            contornos=      n_contornos,
            texto_extraido= texto,
            qualidade=      round(qualidade, 2)
        )

        if salvar_anotado:
            anotado = self.anotar_imagem(img, faces, qualidade)
            saida   = caminho.replace(".", "_anotado.")
            cv2.imwrite(saida, anotado)
            print(f"Anotado salvo: {saida}")

        return doc

    def analisar_pasta(self, pasta: str) -> List[DocumentoAnalisado]:
        extensoes = {".jpg", ".jpeg", ".png", ".bmp", ".tiff"}
        arquivos  = [
            str(f) for f in Path(pasta).iterdir()
            if f.suffix.lower() in extensoes
        ]

        print(f"Analisando {len(arquivos)} imagens...")
        resultados = []

        for caminho in arquivos:
            try:
                doc = self.analisar(caminho)
                resultados.append(doc)
                print(f"  ✓ {Path(caminho).name}: "
                      f"faces={doc.faces_detectadas}, "
                      f"qualidade={doc.qualidade:.0f}")
            except Exception as e:
                print(f"  ✗ {Path(caminho).name}: {e}")

        # Relatório
        total_faces = sum(d.faces_detectadas for d in resultados)
        media_qual  = sum(d.qualidade for d in resultados) / len(resultados)
        print(f"\nResumo: {len(resultados)} imagens processadas")
        print(f"  Total de faces: {total_faces}")
        print(f"  Qualidade média: {media_qual:.0f}")

        return resultados


# Gerando imagem de teste
def criar_imagem_teste(saida: str = "teste.jpg"):
    """Cria imagem de teste com formas geométricas."""
    img  = np.ones((400, 600, 3), dtype=np.uint8) * 240

    cv2.rectangle(img, (50,  50),  (200, 200), (30, 60, 120), -1)
    cv2.circle(img,    (350, 150),  80,        (0, 128, 0),   -1)
    cv2.ellipse(img,   (500, 300),  (80, 50),
                45, 0, 360,         (128, 0, 128), -1)
    cv2.putText(img, "Documento Teste",
               (150, 350), cv2.FONT_HERSHEY_SIMPLEX,
               1.0, (0, 0, 0), 2)

    cv2.imwrite(saida, img)
    print(f"Imagem de teste criada: {saida}")
    return saida


# Executando o pipeline
caminho_teste = criar_imagem_teste()
analisador    = AnalisadorDocumentos()
resultado     = analisador.analisar(caminho_teste)

print(f"\n=== Resultado da Análise ===")
print(f"Arquivo:    {resultado.caminho}")
print(f"Dimensões:  {resultado.largura}×{resultado.altura}")
print(f"Faces:      {resultado.faces_detectadas}")
print(f"Contornos:  {resultado.contornos}")
print(f"Qualidade:  {resultado.qualidade}")

Resumo

  • Pillow manipula imagens com API Pythonica — redimensiona, recorta, filtra e desenha
  • OpenCV oferece processamento mais rápido e algoritmos avançados de visão computacional
  • Canny e Sobel detectam bordas; findContours extrai formas geométricas
  • Haar Cascades detectam faces e olhos em tempo real com alta performance
  • VideoCapture processa webcam e arquivos de vídeo frame a frame
  • Tesseract OCR extrai texto de imagens com precisão dependente do pré-processamento
  • Qualidade de imagem pode ser medida pelo desvio padrão do Laplaciano — maior = mais nítida
  • Pipelines de análise de documentos combinam detecção, OCR e anotação de forma automatizada

Referências e Leituras Complementares

  • Pillow — documentação oficial — https://pillow.readthedocs.io/en/stable/
  • OpenCV Python — documentação — https://docs.opencv.org/4.x/d6/d00/tutorial_py_root.html
  • Tesseract OCR — https://github.com/tesseract-ocr/tesseract
  • pytesseract — https://github.com/madmaze/pytesseract
  • Haar Cascades pré-treinados — https://github.com/opencv/opencv/tree/master/data/haarcascades
  • BRADSKI, Gary; KAEHLER, Adrian. Learning OpenCV 3. O'Reilly Media, 2016. — referência clássica e completa de OpenCV.
  • SZELISKI, Richard. Computer Vision: Algorithms and Applications. 2. ed. Springer, 2022. — fundamentos teóricos de visão computacional, disponível em https://szeliski.org/Book/

Prof. Ricardo Matos — Dominando o Python em 1 Ano · Artigo 39 de 52 Próximo: Artigo 40 — Análise de Dados com Pandas Avançado e Plotly


you asked

Sim


claude response

Dominando o Python em 1 Ano

Comentários

Mais em Python

Recursão e Estruturas de Dados Avançadas: pilhas, filas e árvores
Recursão e Estruturas de Dados Avançadas: pilhas, filas e árvores

Recursão e estruturas de dados avançadas são o ponto em...

Laços de Repetição: for e while
Laços de Repetição: for e while

Computadores são excepcionalmente bons em repetir tarefas — muit...

Web Scraping com BeautifulSoup e Selenium
Web Scraping com BeautifulSoup e Selenium

Nem toda informação que você precisa está disponível em uma API. Preços de pr...