Visão Computacional com OpenCV e Python: Guia Prático
Aprenda visão computacional do básico ao avançado — instalação do OpenCV, manipulação de imagens, filtros, detecção de bordas, faces com Haar Cascades e DNN, e detecção de objetos com YOLO
Visão Computacional com OpenCV e Python: Guia Prático
Visão computacional é o campo da inteligência artificial que ensina computadores a "ver" — interpretar e extrair informação de imagens e vídeos. É a tecnologia por trás de carros autônomos, reconhecimento facial, diagnósticos médicos por imagem, sistemas de vigilância e muito mais.
O OpenCV (Open Source Computer Vision Library) é a biblioteca mais popular e completa para visão computacional, com mais de 2.500 algoritmos otimizados. Neste tutorial, vamos explorar desde o básico — leitura e manipulação de imagens — até técnicas avançadas como detecção de faces com redes neurais e detecção de objetos com YOLO.
Instalação e Configuração
Instalando o OpenCV
# Instalação básica
pip install opencv-python
# Instalação com módulos extras (recomendado)
pip install opencv-contrib-python
# Bibliotecas auxiliares
pip install numpy matplotlib pillow
Verificando a Instalação
import cv2
print(f"OpenCV versão: {cv2.__version__}")
print(f"Módulos disponíveis: {[m for m in dir(cv2) if not m.startswith('_')][:10]}...")
Estrutura do Projeto
visao-computacional/
├── src/
│ ├── basics.py # Operações básicas
│ ├── filters.py # Filtros e transformações
│ ├── edge_detection.py # Detecção de bordas
│ ├── face_detection.py # Detecção de faces
│ ├── object_detection.py # Detecção de objetos (YOLO)
│ └── utils.py # Utilitários
├── images/ # Imagens de teste
├── models/ # Modelos pré-treinados
├── output/ # Resultados
└── requirements.txt
Parte 1: Operações Básicas com Imagens
Leitura, Exibição e Salvamento
"""Operações fundamentais com imagens no OpenCV."""
import cv2
import numpy as np
import matplotlib.pyplot as plt
def load_and_display(image_path: str) -> np.ndarray:
"""Carrega e exibe uma imagem."""
# Carregar imagem (OpenCV usa BGR por padrão, não RGB!)
img = cv2.imread(image_path)
if img is None:
raise FileNotFoundError(f"Imagem não encontrada: {image_path}")
print(f"Dimensões: {img.shape}") # (altura, largura, canais)
print(f"Tipo: {img.dtype}") # uint8 (0-255)
print(f"Tamanho: {img.size} pixels")
# Exibir com matplotlib (converter BGR -> RGB)
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.figure(figsize=(10, 8))
plt.imshow(img_rgb)
plt.title("Imagem Original")
plt.axis("off")
plt.show()
return img
def save_image(img: np.ndarray, output_path: str) -> None:
"""Salva uma imagem em disco."""
cv2.imwrite(output_path, img)
print(f"Imagem salva em: {output_path}")
def create_blank_image(width: int, height: int, color: tuple = (0, 0, 0)) -> np.ndarray:
"""Cria uma imagem em branco (ou com cor sólida)."""
img = np.full((height, width, 3), color, dtype=np.uint8)
return img
Manipulação Básica
def basic_manipulations(img: np.ndarray) -> dict[str, np.ndarray]:
"""Demonstra manipulações básicas de imagem."""
results = {}
# 1. Redimensionar
height, width = img.shape[:2]
resized = cv2.resize(img, (width // 2, height // 2), interpolation=cv2.INTER_AREA)
results["resized"] = resized
# 2. Recortar (crop)
h, w = img.shape[:2]
cropped = img[h // 4 : 3 * h // 4, w // 4 : 3 * w // 4]
results["cropped"] = cropped
# 3. Rotacionar
center = (width // 2, height // 2)
matrix = cv2.getRotationMatrix2D(center, angle=45, scale=1.0)
rotated = cv2.warpAffine(img, matrix, (width, height))
results["rotated"] = rotated
# 4. Espelhar
flipped_h = cv2.flip(img, 1) # Horizontal
flipped_v = cv2.flip(img, 0) # Vertical
results["flipped_h"] = flipped_h
results["flipped_v"] = flipped_v
# 5. Converter para escala de cinza
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
results["gray"] = gray
# 6. Ajustar brilho e contraste
alpha = 1.3 # Contraste (1.0 = sem mudança)
beta = 30 # Brilho (0 = sem mudança)
adjusted = cv2.convertScaleAbs(img, alpha=alpha, beta=beta)
results["brightness_contrast"] = adjusted
return results
def draw_on_image(img: np.ndarray) -> np.ndarray:
"""Desenha formas e texto em uma imagem."""
canvas = img.copy()
# Retângulo
cv2.rectangle(canvas, (50, 50), (200, 200), (0, 255, 0), 2)
# Círculo
cv2.circle(canvas, (300, 150), 80, (0, 0, 255), 3)
# Linha
cv2.line(canvas, (0, 0), (400, 300), (255, 0, 0), 2)
# Texto
cv2.putText(
canvas,
"OpenCV Python",
(50, 350),
cv2.FONT_HERSHEY_SIMPLEX,
1.2,
(255, 255, 255),
2,
cv2.LINE_AA,
)
return canvas
Espaços de Cores
def color_spaces(img: np.ndarray) -> dict[str, np.ndarray]:
"""Demonstra diferentes espaços de cores."""
results = {}
# BGR (padrão OpenCV)
results["bgr"] = img
# RGB
results["rgb"] = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# HSV (Hue, Saturation, Value)
results["hsv"] = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# LAB
results["lab"] = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
# Separar canais
b, g, r = cv2.split(img)
results["blue_channel"] = b
results["green_channel"] = g
results["red_channel"] = r
return results
def color_segmentation(img: np.ndarray, color: str = "blue") -> np.ndarray:
"""Segmentação por cor usando espaço HSV."""
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# Ranges de cores em HSV
color_ranges = {
"blue": ((100, 50, 50), (130, 255, 255)),
"green": ((35, 50, 50), (85, 255, 255)),
"red_low": ((0, 50, 50), (10, 255, 255)),
"red_high": ((170, 50, 50), (180, 255, 255)),
"yellow": ((20, 50, 50), (35, 255, 255)),
}
if color == "red":
# Vermelho precisa de duas faixas (wraparound no H)
mask1 = cv2.inRange(hsv, np.array(color_ranges["red_low"][0]),
np.array(color_ranges["red_low"][1]))
mask2 = cv2.inRange(hsv, np.array(color_ranges["red_high"][0]),
np.array(color_ranges["red_high"][1]))
mask = cv2.bitwise_or(mask1, mask2)
else:
lower, upper = color_ranges.get(color, color_ranges["blue"])
mask = cv2.inRange(hsv, np.array(lower), np.array(upper))
# Aplicar máscara
result = cv2.bitwise_and(img, img, mask=mask)
return result
Parte 2: Filtros e Transformações
Filtros de Suavização
"""Filtros de suavização (blur) e nitidez."""
import cv2
import numpy as np
def apply_blur_filters(img: np.ndarray) -> dict[str, np.ndarray]:
"""Aplica diferentes filtros de suavização."""
results = {}
# 1. Blur médio (averaging)
results["average_blur"] = cv2.blur(img, (7, 7))
# 2. Gaussian Blur (mais natural)
results["gaussian_blur"] = cv2.GaussianBlur(img, (7, 7), 0)
# 3. Median Blur (excelente para remover ruído salt-and-pepper)
results["median_blur"] = cv2.medianBlur(img, 7)
# 4. Bilateral Filter (suaviza mantendo bordas nítidas)
results["bilateral"] = cv2.bilateralFilter(img, 9, 75, 75)
return results
def apply_morphology(img: np.ndarray) -> dict[str, np.ndarray]:
"""Aplica operações morfológicas."""
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
results = {}
# Erosão: diminui regiões brancas
results["erosion"] = cv2.erode(binary, kernel, iterations=1)
# Dilatação: aumenta regiões brancas
results["dilation"] = cv2.dilate(binary, kernel, iterations=1)
# Opening: erosão seguida de dilatação (remove ruído pequeno)
results["opening"] = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)
# Closing: dilatação seguida de erosão (fecha buracos pequenos)
results["closing"] = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)
# Gradient: diferença entre dilatação e erosão (detecta bordas)
results["gradient"] = cv2.morphologyEx(binary, cv2.MORPH_GRADIENT, kernel)
return results
def sharpen_image(img: np.ndarray) -> np.ndarray:
"""Aumenta a nitidez de uma imagem usando kernel de convolução."""
kernel = np.array([
[0, -1, 0],
[-1, 5, -1],
[0, -1, 0]
])
return cv2.filter2D(img, -1, kernel)
def apply_thresholds(img: np.ndarray) -> dict[str, np.ndarray]:
"""Aplica diferentes técnicas de threshold (limiarização)."""
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
results = {}
# Threshold global simples
_, results["binary"] = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# Threshold inverso
_, results["binary_inv"] = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV)
# Threshold de Otsu (automático)
_, results["otsu"] = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# Threshold adaptativo (melhor para iluminação desigual)
results["adaptive_mean"] = cv2.adaptiveThreshold(
gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 2
)
results["adaptive_gaussian"] = cv2.adaptiveThreshold(
gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2
)
return results
Parte 3: Detecção de Bordas e Contornos
Detectores de Bordas
"""Detecção de bordas com Sobel, Laplacian e Canny."""
import cv2
import numpy as np
def detect_edges(img: np.ndarray) -> dict[str, np.ndarray]:
"""Aplica diferentes detectores de bordas."""
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Suavizar para reduzir ruído
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
results = {}
# 1. Sobel (detecta bordas em X e Y separadamente)
sobel_x = cv2.Sobel(blurred, cv2.CV_64F, 1, 0, ksize=3)
sobel_y = cv2.Sobel(blurred, cv2.CV_64F, 0, 1, ksize=3)
sobel_combined = cv2.magnitude(sobel_x, sobel_y)
results["sobel"] = cv2.convertScaleAbs(sobel_combined)
# 2. Laplacian (detecta bordas em todas as direções)
laplacian = cv2.Laplacian(blurred, cv2.CV_64F)
results["laplacian"] = cv2.convertScaleAbs(laplacian)
# 3. Canny (o mais popular — multi-estágio, bordas finas)
results["canny"] = cv2.Canny(blurred, 50, 150)
# 4. Canny com auto-threshold
median = np.median(blurred)
lower = int(max(0, 0.7 * median))
upper = int(min(255, 1.3 * median))
results["canny_auto"] = cv2.Canny(blurred, lower, upper)
return results
def find_and_draw_contours(img: np.ndarray) -> tuple[np.ndarray, list]:
"""Encontra e desenha contornos na imagem."""
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
edges = cv2.Canny(blurred, 50, 150)
# Encontrar contornos
contours, hierarchy = cv2.findContours(
edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
)
# Ordenar por área (maior primeiro)
contours = sorted(contours, key=cv2.contourArea, reverse=True)
# Desenhar contornos
result = img.copy()
cv2.drawContours(result, contours, -1, (0, 255, 0), 2)
# Informações sobre cada contorno
contour_info = []
for i, contour in enumerate(contours[:10]): # Top 10
area = cv2.contourArea(contour)
perimeter = cv2.arcLength(contour, True)
x, y, w, h = cv2.boundingRect(contour)
# Aproximação poligonal
approx = cv2.approxPolyDP(contour, 0.02 * perimeter, True)
n_vertices = len(approx)
# Classificar forma
if n_vertices == 3:
shape = "triangulo"
elif n_vertices == 4:
aspect_ratio = w / float(h)
shape = "quadrado" if 0.9 <= aspect_ratio <= 1.1 else "retangulo"
elif n_vertices > 8:
shape = "circulo"
else:
shape = f"poligono ({n_vertices} lados)"
contour_info.append({
"index": i,
"area": area,
"perimeter": round(perimeter, 2),
"bounding_box": (x, y, w, h),
"vertices": n_vertices,
"shape": shape,
})
# Desenhar bounding box e label
cv2.rectangle(result, (x, y), (x + w, y + h), (255, 0, 0), 2)
cv2.putText(result, shape, (x, y - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 2)
print(f"Contornos encontrados: {len(contours)}")
for info in contour_info:
print(f" #{info['index']}: {info['shape']}, área={info['area']:.0f}")
return result, contour_info
Detecção de Linhas e Círculos (Hough Transform)
def detect_lines(img: np.ndarray) -> np.ndarray:
"""Detecta linhas usando Hough Transform."""
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 50, 150)
result = img.copy()
# Hough Lines Probabilístico (mais prático)
lines = cv2.HoughLinesP(
edges,
rho=1,
theta=np.pi / 180,
threshold=100,
minLineLength=50,
maxLineGap=10,
)
if lines is not None:
for line in lines:
x1, y1, x2, y2 = line[0]
cv2.line(result, (x1, y1), (x2, y2), (0, 0, 255), 2)
print(f"Linhas detectadas: {len(lines)}")
return result
def detect_circles(img: np.ndarray) -> np.ndarray:
"""Detecta círculos usando Hough Circle Transform."""
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blurred = cv2.medianBlur(gray, 5)
result = img.copy()
circles = cv2.HoughCircles(
blurred,
cv2.HOUGH_GRADIENT,
dp=1,
minDist=50,
param1=100,
param2=30,
minRadius=10,
maxRadius=200,
)
if circles is not None:
circles = np.uint16(np.around(circles))
for circle in circles[0, :]:
center = (circle[0], circle[1])
radius = circle[2]
cv2.circle(result, center, radius, (0, 255, 0), 2)
cv2.circle(result, center, 3, (0, 0, 255), -1)
print(f"Círculos detectados: {len(circles[0])}")
return result
Parte 4: Detecção de Faces
Haar Cascades (Método Clássico)
"""Detecção de faces com Haar Cascades e DNN."""
import cv2
import numpy as np
from pathlib import Path
def detect_faces_haar(img: np.ndarray) -> tuple[np.ndarray, list]:
"""
Detecta faces usando Haar Cascade Classifier.
Rápido mas menos preciso que métodos baseados em DNN.
"""
# Carregar classificador pré-treinado (incluso no OpenCV)
face_cascade = cv2.CascadeClassifier(
cv2.data.haarcascades + "haarcascade_frontalface_default.xml"
)
eye_cascade = cv2.CascadeClassifier(
cv2.data.haarcascades + "haarcascade_eye.xml"
)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray = cv2.equalizeHist(gray) # Melhorar contraste
# Detectar faces
faces = face_cascade.detectMultiScale(
gray,
scaleFactor=1.1,
minNeighbors=5,
minSize=(30, 30),
flags=cv2.CASCADE_SCALE_IMAGE,
)
result = img.copy()
detections = []
for (x, y, w, h) in faces:
# Desenhar retângulo na face
cv2.rectangle(result, (x, y), (x + w, y + h), (0, 255, 0), 2)
# Detectar olhos dentro da região da face
roi_gray = gray[y:y + h, x:x + w]
eyes = eye_cascade.detectMultiScale(roi_gray, scaleFactor=1.1, minNeighbors=3)
for (ex, ey, ew, eh) in eyes:
center = (x + ex + ew // 2, y + ey + eh // 2)
radius = min(ew, eh) // 2
cv2.circle(result, center, radius, (255, 0, 0), 2)
detections.append({
"bbox": (x, y, w, h),
"eyes_detected": len(eyes),
"confidence": None, # Haar não fornece confiança
})
print(f"Faces detectadas (Haar): {len(faces)}")
return result, detections
DNN Face Detection (Método Moderno)
def detect_faces_dnn(
img: np.ndarray,
confidence_threshold: float = 0.5,
model_path: str | None = None,
config_path: str | None = None,
) -> tuple[np.ndarray, list]:
"""
Detecta faces usando Deep Neural Network (DNN) do OpenCV.
Mais preciso e robusto que Haar Cascades.
"""
# Usar modelo Caffe pré-treinado
if model_path is None:
# Baixar se necessário
model_url = (
"https://raw.githubusercontent.com/opencv/opencv_3rdparty/"
"dnn_samples_face_detector_20170830/"
"res10_300x300_ssd_iter_140000.caffemodel"
)
config_url = (
"https://raw.githubusercontent.com/opencv/opencv/master/"
"samples/dnn/face_detector/deploy.prototxt"
)
models_dir = Path("models")
models_dir.mkdir(exist_ok=True)
model_path = str(models_dir / "face_detector.caffemodel")
config_path = str(models_dir / "face_detector.prototxt")
# Verificar se precisa baixar
if not Path(model_path).exists():
import urllib.request
print("Baixando modelo de detecção de faces...")
urllib.request.urlretrieve(model_url, model_path)
urllib.request.urlretrieve(config_url, config_path)
print("Download concluído!")
# Carregar rede neural
net = cv2.dnn.readNetFromCaffe(config_path, model_path)
h, w = img.shape[:2]
# Preparar input (blob)
blob = cv2.dnn.blobFromImage(
img, scalefactor=1.0, size=(300, 300),
mean=(104.0, 177.0, 123.0), swapRB=False, crop=False
)
# Inferência
net.setInput(blob)
detections_raw = net.forward()
result = img.copy()
detections = []
for i in range(detections_raw.shape[2]):
confidence = detections_raw[0, 0, i, 2]
if confidence > confidence_threshold:
box = detections_raw[0, 0, i, 3:7] * np.array([w, h, w, h])
x1, y1, x2, y2 = box.astype(int)
# Garantir que está dentro da imagem
x1 = max(0, x1)
y1 = max(0, y1)
x2 = min(w, x2)
y2 = min(h, y2)
# Desenhar detecção
cv2.rectangle(result, (x1, y1), (x2, y2), (0, 255, 0), 2)
label = f"Face: {confidence:.2%}"
cv2.putText(result, label, (x1, y1 - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
detections.append({
"bbox": (x1, y1, x2 - x1, y2 - y1),
"confidence": float(confidence),
})
print(f"Faces detectadas (DNN): {len(detections)}")
return result, detections
Parte 5: Detecção de Objetos com YOLO
Configuração do YOLOv8
# Instalar ultralytics (YOLOv8)
pip install ultralytics
Detecção de Objetos
"""Detecção de objetos com YOLOv8."""
import cv2
import numpy as np
from pathlib import Path
from ultralytics import YOLO
class ObjectDetector:
"""Detector de objetos usando YOLOv8."""
def __init__(self, model_name: str = "yolov8n.pt"):
"""
Inicializa o detector.
Modelos disponíveis (do mais leve ao mais preciso):
- yolov8n.pt (Nano - mais rápido)
- yolov8s.pt (Small)
- yolov8m.pt (Medium)
- yolov8l.pt (Large)
- yolov8x.pt (Extra Large - mais preciso)
"""
self.model = YOLO(model_name)
print(f"Modelo carregado: {model_name}")
def detect(
self,
img: np.ndarray,
confidence: float = 0.5,
classes: list[int] | None = None,
) -> tuple[np.ndarray, list[dict]]:
"""
Detecta objetos em uma imagem.
Args:
img: Imagem de entrada (BGR)
confidence: Limiar de confiança mínima
classes: Lista de classes para detectar (None = todas)
Returns:
Imagem anotada e lista de detecções
"""
results = self.model(img, conf=confidence, classes=classes, verbose=False)
result = results[0]
annotated = result.plot() # Imagem com anotações
detections = []
for box in result.boxes:
x1, y1, x2, y2 = box.xyxy[0].cpu().numpy().astype(int)
conf = float(box.conf[0])
cls_id = int(box.cls[0])
cls_name = result.names[cls_id]
detections.append({
"class": cls_name,
"class_id": cls_id,
"confidence": round(conf, 4),
"bbox": (int(x1), int(y1), int(x2 - x1), int(y2 - y1)),
})
print(f"Objetos detectados: {len(detections)}")
for det in detections:
print(f" - {det['class']}: {det['confidence']:.2%}")
return annotated, detections
def detect_video(
self,
video_path: str,
output_path: str,
confidence: float = 0.5,
) -> None:
"""Detecta objetos em um vídeo e salva o resultado."""
cap = cv2.VideoCapture(video_path)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = int(cap.get(cv2.CAP_PROP_FPS))
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
frame_count = 0
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
annotated, _ = self.detect(frame, confidence=confidence)
out.write(annotated)
frame_count += 1
if frame_count % 30 == 0:
print(f"Processado: {frame_count}/{total_frames} frames")
cap.release()
out.release()
print(f"Vídeo salvo em: {output_path}")
def detect_webcam(self, confidence: float = 0.5) -> None:
"""Detecção em tempo real usando webcam."""
cap = cv2.VideoCapture(0)
print("Pressione 'q' para sair")
while True:
ret, frame = cap.read()
if not ret:
break
annotated, detections = self.detect(frame, confidence=confidence)
# Mostrar FPS
fps_text = f"Objetos: {len(detections)}"
cv2.putText(annotated, fps_text, (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
cv2.imshow("YOLO Detection", annotated)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cap.release()
cv2.destroyAllWindows()
# Classes COCO (80 classes que YOLO detecta por padrão)
COCO_CLASSES = {
0: "person", 1: "bicycle", 2: "car", 3: "motorcycle", 4: "airplane",
5: "bus", 6: "train", 7: "truck", 8: "boat", 9: "traffic light",
10: "fire hydrant", 11: "stop sign", 12: "parking meter", 13: "bench",
14: "bird", 15: "cat", 16: "dog", 17: "horse", 18: "sheep", 19: "cow",
20: "elephant", 21: "bear", 22: "zebra", 23: "giraffe", 24: "backpack",
25: "umbrella", 26: "handbag", 27: "tie", 28: "suitcase", 29: "frisbee",
# ... mais 50 classes
}
Exemplo de Uso Completo
"""Exemplo completo: detecção de objetos em imagem."""
from pathlib import Path
def main():
# 1. Carregar imagem
img = cv2.imread("images/rua.jpg")
if img is None:
print("Crie uma pasta 'images' e coloque imagens de teste nela.")
return
# 2. Inicializar detector
detector = ObjectDetector("yolov8n.pt")
# 3. Detectar todos os objetos
annotated, detections = detector.detect(img, confidence=0.5)
cv2.imwrite("output/detections.jpg", annotated)
# 4. Detectar apenas pessoas e carros (classes 0 e 2)
annotated_filtered, filtered = detector.detect(
img, confidence=0.5, classes=[0, 2]
)
cv2.imwrite("output/people_cars.jpg", annotated_filtered)
# 5. Contar objetos por classe
class_counts = {}
for det in detections:
cls = det["class"]
class_counts[cls] = class_counts.get(cls, 0) + 1
print("\nContagem por classe:")
for cls, count in sorted(class_counts.items(), key=lambda x: -x[1]):
print(f" {cls}: {count}")
if __name__ == "__main__":
main()
Parte 6: Processamento de Vídeo
Captura e Processamento de Webcam
"""Processamento de vídeo em tempo real."""
import cv2
import numpy as np
import time
def process_webcam_with_effects(effect: str = "edges") -> None:
"""Processa vídeo da webcam com efeitos em tempo real."""
cap = cv2.VideoCapture(0)
if not cap.isOpened():
print("Erro: não foi possível abrir a webcam.")
return
effects = {
"edges": lambda f: cv2.Canny(cv2.cvtColor(f, cv2.COLOR_BGR2GRAY), 50, 150),
"blur": lambda f: cv2.GaussianBlur(f, (21, 21), 0),
"cartoon": lambda f: cartoon_effect(f),
"negative": lambda f: cv2.bitwise_not(f),
"gray": lambda f: cv2.cvtColor(f, cv2.COLOR_BGR2GRAY),
}
process_fn = effects.get(effect, effects["edges"])
prev_time = time.time()
print(f"Efeito: {effect} | Pressione 'q' para sair")
while True:
ret, frame = cap.read()
if not ret:
break
processed = process_fn(frame)
# Calcular FPS
curr_time = time.time()
fps = 1 / (curr_time - prev_time)
prev_time = curr_time
if len(processed.shape) == 2:
processed = cv2.cvtColor(processed, cv2.COLOR_GRAY2BGR)
cv2.putText(processed, f"FPS: {fps:.1f}", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
cv2.imshow("Webcam", processed)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cap.release()
cv2.destroyAllWindows()
def cartoon_effect(img: np.ndarray) -> np.ndarray:
"""Aplica efeito cartoon a uma imagem."""
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray = cv2.medianBlur(gray, 5)
edges = cv2.adaptiveThreshold(
gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C,
cv2.THRESH_BINARY, 9, 9
)
color = cv2.bilateralFilter(img, 9, 300, 300)
cartoon = cv2.bitwise_and(color, color, mask=edges)
return cartoon
def detect_motion(threshold: int = 25, min_area: int = 500) -> None:
"""Detecta movimento usando diferença de frames."""
cap = cv2.VideoCapture(0)
ret, prev_frame = cap.read()
prev_gray = cv2.cvtColor(prev_frame, cv2.COLOR_BGR2GRAY)
prev_gray = cv2.GaussianBlur(prev_gray, (21, 21), 0)
print("Detectando movimento... Pressione 'q' para sair")
while True:
ret, frame = cap.read()
if not ret:
break
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (21, 21), 0)
frame_delta = cv2.absdiff(prev_gray, gray)
_, thresh = cv2.threshold(frame_delta, threshold, 255, cv2.THRESH_BINARY)
thresh = cv2.dilate(thresh, None, iterations=2)
contours, _ = cv2.findContours(
thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
)
motion_detected = False
for contour in contours:
if cv2.contourArea(contour) > min_area:
motion_detected = True
x, y, w, h = cv2.boundingRect(contour)
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
status = "MOVIMENTO!" if motion_detected else "Quieto"
color = (0, 0, 255) if motion_detected else (0, 255, 0)
cv2.putText(frame, status, (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 1, color, 2)
cv2.imshow("Motion Detection", frame)
prev_gray = gray
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cap.release()
cv2.destroyAllWindows()
Parte 7: Projeto Prático — Sistema de Contagem de Pessoas
"""
Projeto prático: Sistema de contagem de pessoas em imagens.
Combina detecção de faces (DNN) com detecção de pessoas (YOLO).
"""
import cv2
import numpy as np
from pathlib import Path
from dataclasses import dataclass
@dataclass
class PersonCount:
"""Resultado da contagem de pessoas."""
total_people: int
total_faces: int
detections: list[dict]
image_path: str
def count_people(image_path: str) -> PersonCount:
"""Conta pessoas em uma imagem usando YOLO."""
from ultralytics import YOLO
img = cv2.imread(image_path)
if img is None:
raise FileNotFoundError(f"Imagem não encontrada: {image_path}")
# Detectar pessoas com YOLO
model = YOLO("yolov8n.pt")
results = model(img, classes=[0], conf=0.5, verbose=False) # classe 0 = person
people_count = len(results[0].boxes)
# Detectar faces
face_cascade = cv2.CascadeClassifier(
cv2.data.haarcascades + "haarcascade_frontalface_default.xml"
)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(gray, 1.1, 5, minSize=(30, 30))
# Anotar imagem
annotated = results[0].plot()
for (x, y, w, h) in faces:
cv2.rectangle(annotated, (x, y), (x + w, y + h), (255, 0, 0), 2)
# Adicionar contagem no topo
text = f"Pessoas: {people_count} | Faces: {len(faces)}"
cv2.putText(annotated, text, (10, 40),
cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0, 255, 255), 3)
output_path = f"output/counted_{Path(image_path).name}"
Path("output").mkdir(exist_ok=True)
cv2.imwrite(output_path, annotated)
return PersonCount(
total_people=people_count,
total_faces=len(faces),
detections=[],
image_path=output_path,
)
Conclusão
Neste tutorial, percorremos todo o espectro da visão computacional com OpenCV e Python:
- Operações básicas: leitura, manipulação, espaços de cores e desenho.
- Filtros: suavização, nitidez, morfologia e threshold.
- Detecção de bordas: Sobel, Laplacian, Canny e contornos.
- Detecção de faces: Haar Cascades (clássico) e DNN (moderno).
- Detecção de objetos: YOLOv8 para detecção multi-classe.
- Processamento de vídeo: webcam, efeitos em tempo real e detecção de movimento.
- Projeto prático: sistema de contagem de pessoas.
A visão computacional é um campo vasto e em constante evolução. Com as bases que construímos aqui, você está preparado para explorar aplicações mais avançadas.
Próximos Passos
- Explorar segmentação semântica com SAM (Segment Anything Model).
- Implementar rastreamento de objetos com DeepSORT.
- Treinar modelos YOLO customizados para domínios específicos.
- Explorar estimação de pose com MediaPipe.
- Construir aplicações de realidade aumentada com OpenCV.
- Integrar com frameworks de deep learning (PyTorch) para modelos customizados.
Acompanhe o Inteligência em Código para mais tutoriais sobre visão computacional e deep learning.