Mostrando postagens com marcador Visão Computacional. Mostrar todas as postagens
Mostrando postagens com marcador Visão Computacional. Mostrar todas as postagens

sexta-feira, 19 de maio de 2017

Espaços de cores

Imagens digitais coloridas podem ser codificadas em uma série de formatos, chamados de espaços de cores. Dependendo da aplicação pode ser mais conveniente trabalhar com um espaço de cores em particular. Neste texto iremos explorar dois espaços de cores bastante usados em Processamento de Imagens: RGB e HSV/HSL. Também iremos comentar brevemente sobre outros espaços de cores existentes e suas características.

O espaço de cores RGB

No espaço RGB uma cor é representada por uma tripla \((r, g, b)\) que codifica a quantidade de vermelho (r), verde (g) e azul(b) presente na cor. Este é um espaço de cores conveniente para a criação de telas pois mapeia facilmente para o sistema visual humano, que contém células receptoras para estas três cores. A cor \((0, 0, 0)\) representa o preto, enquanto \((1, 1, 1)\) representa o branco. Imagens em RGB são comumente representadas em computadores usando 1 byte para cada cor. Isto resulta em imagens em que (255, 255, 255) representa a cor branca.

RGB color solid cube.png
Espaço de cores RGB representado como um cubo. Por SharkD [fonte]


Podemos fazer segmentação de cores no espaço RGB de duas maneiras:

  1. Selecionando cores em um intervalo \([(r_1, b_1, g_1), (r_2, b_2, g_2)]\);
  2. Selecionando cores usando uma esfera ou elipse em torno de uma cor modelo.

Um dos problemas de fazer detecção de cores usando o espaço RGB é que a cor, assim como sua intensidade, fica espalhada nos três canais. No caso da opção 1, identificar os intervalos \([(r_1, b_1, g_1), (r_2, b_2, g_2)]\) pode ser uma tarefa trabalhosa. Os cantos do cubo formado pelo intervalo podem ser cores bem diferentes da desejada se optarmos por incluir todas variações da cor de interesse. Por outro lado, se evitarmos falsos positivos perdemos também muitas cores que são bastante similares à cor de interesse.

No caso da opção 2 precisamos definir um raio \(r\) (ou três raios \(r_r, r_b, r_g\) para a elipse) que contenha as cores desejadas. Apesar desta solução ser mais robusta, continua sendo necessário definir valores para estes parâmetros.

O espaço de cores HSV

Os espaços de cores HSV separam uma cor em três componentes: Hue (Matiz, de \(0^o\) a \(360^o\)), representando a cor do pixel; Saturação (\([0,1]\)), representando quanto a cor é pura; e Valor (\([0,1]\)), representando o brilho da cor. O canal H contém a cor vermelha em \(0^o\), passando por roxo, azul, ciano, verde, amarelo, laranja e vermelho novamente (próximo a \(360^o\)). Saturação pequena indica que a cor é próxima de um tom de cinza (incluindo preto e branco), enquanto Saturação \(1\) indica uma cor "forte". Um Valor pequeno representa cores escuras, enquanto um Valor (próximo de \(1\)) representa cores brilhantes.


Espaço de cores HSV representado como um cone. Por SharkD [fonte]


Diferente de imagens em RGB, que são comumente armazenadas em um uint8, cada implementação/biblioteca pode usar intervalos diferentes para cada um dos canais. Não se esqueça de consultar a documentação!

Uma das maiores desvantagens do espaço RGB é a dificuldade de modificar cores. Por exemplo, como podemos "escurescer" um cor? Como podemos escolher um laranja mais avermelhado do que outro? Ambas tarefas são facilmente realizadas no espaço HSV.

A segmentação de cores também é mais simples no espaço HSV. Ela pode ser feita com um simples intervalo no canal H. Para evitar a detecção de variantes muito escuras ou clares também podem ser adotados valores mínimos para Saturação e Valor.

Uma imagem RGB pode ser convertida para HSV usando a seguintes fórmulas. Primeiro definimos as constantes \(M = \max(R, G, B)\), \(m = \min(R, G, B)\) e \(C = M - m\).

A Matiz (Hue) é definida abaixo. Adotamos \(H = 0\) para tons de cinza.

\begin{equation} H' = \begin{cases} 0, & \textrm{if } C = 0 \\ \frac{G-B}{C} \mod 6, & \textrm{if } M = R \\ \frac{B-R}{C} + 2, & \textrm{if } M = G \\ \frac{R-G}{C} + 4, & \textrm{if } M = B \\ \end{cases} \end{equation} \begin{equation} H = 60^o \times H' \end{equation}

O Valor é definido como

\begin{equation} V = M. \end{equation}

A Saturação é definida como

\begin{equation} S = \begin{cases} 0 & \textrm{if } V = 0 \\ \frac{C}{V} & \textrm{otherwise} \end{cases} \end{equation}

Uma variante do HSV é chamada de HSL e troca o Valor pela Luminância (quantidade de luz recebida). Apesar da troca de um canal, a detecção de cores não é afetada significativamente por esta mudança.

Imagens em níveis de cinza

Apesar de não ser exatamente um espaço de cores (já que só temos tons de cinza), mostramos nesta seção como converter imagens em RGB para níveis de cinza (abreviado para GL, do inglês gray-level).

Cada pixel em uma imagem em GL representa a quantidade de luz observada naquela ponto da imagem. Se nenhuma ou muito pouca luz é observada o pixel toma o valor \(0\) (preto). Se muita luz é observada o pixel toma o valor \(1\) (branco).

Uma maneira popular de converter RGB para GL é usando a fórmula abaixo. Ela é inspirada no sistema visual humano e pesa mais fortemente o verde que o vermelho e azul.

\begin{equation} L = 0.587G + 0.299R + 0.114B \end{equation}

Outros espaços de cores (CMIK etc)

RGB é um sistema aditivo, ou seja, cores são criadas pela emissão de luz. O aumento de uma componente de uma cor implica em um aumento na emissão de luz nesta frequência. Em comparação, CMIK é um sistema subtrativo, ou seja, cores são criadas pela absorção da luz. Logo, CMIK é um formato muito presente na indústria de impressão, pois ele permite representar melhor cores em materiais impressos.

SubtractiveColor.svg
Mistura de cores por subtração - Por SharkD, Jacobolus e Dacium [fonte]


O espaço CMIK não é comumente usado em Processamento de Imagens e Visão Computacional. Ele está listado principalmente por curiosidade e completude. É importante saber que existem espaços de cores adequados para diversas aplicações.

Existem diversos outros espaços de cores (veja uma lista aqui) e cada um possui um caso de uso ideal. Apesar de RGB e HSV serem os mais usados em Processamento de Imagens, o conhecimento de outros espaços de cores pode ser um diferencial.

Bibliotecas para processamento de imagens em Python

Por fim, listo aqui algumas bibliotecas para Processamento de Imagens e Visão Computacional em Python.

  • OpenCV - usado principalmente para aplicações de Visão Computacional, com ênfase no processamento de imagens em tempo real. Feito em C++, mas com excelentes wrappers para Python.
  • Scikit-Image - Biblioteca parte do pacote Scipy para computação científica em Python. Integra muito bem com Numpy e tem diversos algoritmos de processamento de imagens já implementados. Mais completa que OpenCV em termos de quantidade de algoritmos implementados.
  • Pillow - Uma biblioteca um pouco mais antiga de processamento de imagens usando Python. Pode ler uma grande quantidade de formatos de imagens.

sexta-feira, 12 de maio de 2017

Detecção de faces usando OpenCV

A detecção de faces é uma tarefa muito comum em diversas programas de visão computacional. O Facebook, por exemplo, detecta faces para sugerir a identificação de pessoas nas fotos adicionadas por seus usuários. Várias câmeras digitais detectam rostos para melhorar a qualidade da fotografia. Esta câmera, por exemplo, tira fotos quando as pessoas sorriem.

O OpenCV é uma biblioteca muito usada em aplicações de visão computacional, especialmente aquelas que requerem interação em tempo real. Python é uma das linguagens com suporte oficial e possui uma interface bastante prática para trabalhar com detecção de faces.

Antes de tudo, vamos diferenciar duas tarefas muito parecidas a primeira vista: detecçãos e reconhecimento/identificação.

  • Detecção de faces consiste em dizer onde existe uma face em uma imagem. Ou seja, a localização da face na imagem é o resultado final. A entrada é uma imagem ``natural'' que pode ou não conter uma face.
  • Reconhecimento/Identificação consiste em dizer quem está retratado na imagem. Ou seja, o nome de uma pessoa específica é o resultafo final. Normalmente a entrada já é uma imagem contendo somente a face que desejamos identificar.

Detecção de faces em imagens

Nós humanos conseguimos reconhecer faces instintivamente. Podemos descrever uma face, listar quais são as partes (olhos, nariz, boca, ect) de uma face e mesmo reconhecer a mesma pessoa em presença de objetos externos (óculos, chapéu, etc). Apesar de existir, aparentemente, uma programação ``natural'' para que possamos identificar rostos, conseguimos fazer o mesmo tipo de reconhecimento com qualquer objeto que observamos. Ou seja, aprendemos a reconhecer objetos baseado em exemplos e conseguimos descrevê-los internamente de modo a reconhecer variações dos objetos.

A primeira etapa de um sistema de detecção de faces consiste em extrair um conjunto de características que possam diferenciar imagens com faces de imagens sem faces. Dois métodos muito populares estão presentes no OpenCV: Haar Cascades e LBP. Como as faces podem ter vários tamanho, é feita uma análise multi-escala da imagem, ou seja, executamos o mesmo algoritmo de detecção de faces em versões redimensionadas da imagem. Desta maneira, mesmo que um objeto seja muito pequeno ou muito grande, ele será detectado em alguma escala.

Para ``aprendermos'' um objeto precisamos observar alguns exemplos positivos (variações do objeto de interesse), e exemplos negativos (outros objetos diferentes, incluindo alguns que possam ser confundidos com o de interesse). No caso da detecção de faces, precisamos então de conjuntos de imagens que contenham faces e de imagens sem faces. Esta etapa, chamada de treinamento, é uma das mais importantes de um sistema de detecção de faces. O resultado final desta etapa é um classificador que diz se uma imagem possui ou não uma face. Este classificador é aplicado em todos os pontos da imagem para podermos dizer onde estão localizadas as faces.

Por fim, carregamos o classificador treinado na etapa anterior e usamos para encontrar faces. Esta etapa é a única que ocorre durante a execução do sistema de reconhecimento em si. O treinamento sempre é feito em um momento anterior, de preferência supervisionado por um especialista.

Resumindo, um detecção de faces ocorre em dois momentos. No primeiro, o treinamento, usamos exemplos de imagens para ``aprender'' o que é uma face. No segundo usamos o modelo aprendido para detectar faces em imagens. Ao invés de usarmos os próprios pixels para representar as faces extraímos um conjunto de características que facilitem decidir se uma imagem é ou não uma face.

Implementação no OpenCV

O treinamento é uma etapa muito custosa, tanto devido à construção do conjunto de imagens exemplo quanto à determinação do melhor classificador e extrator de características. Felizmente o OpenCV já possui alguns classificadores treinados e prontos para uso. A classe CascadeClassifier é usada para detecção de faces. Suas duas principais funções são load, que carrega um modelo treinado, e detectMultiScale, que faz a detecção e devolve uma lista de retângulos que contém faces.

Os modelos treinados podem ser baixados do repositório do OpenCV no Github. Existem dois tipos de modelos pré-treinados,haarcascades e lbpcascades, para diversas situações. Estamos interessados em faces vistas de frente, então usaremos o modelo lbpcascades/lbpcascades_frontalface_improved.xml. Poderíamos usar também o equivalente encontrado na pasta haarcascades, porém nos testes que fiz o detector baseado em LBP funcionou melhor.

O código abaixo carrega o modelo já treinado (salvo com o nome faces_lbp.xml), o aplica em uma imagem carregada do disco e desenha quadrados nas faces de todos os presentes.

import cv2

# Carrega imagem
img = cv2.imread('foto.jpg')
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# Carrega detector de faces usando LBP.
cls = cv2.CascadeClassifier('faces_lbp.xml')
faces = cls.detectMultiScale(img_gray)

for face in faces:
    x, y, w, h = face
    # Argumentos: imagem, topo_esquerdo, baixo_direito, cor(BGR),
espessura
    cv2.rectangle(img, (x, y), (x+w, y+h), (0, 0, 255), 2 )

cv2.imwrite('foto_face.png', img)
Funciona mesmo!

Esta foi uma pequena introdução sobre detecção de faces usando OpenCV. Possivelmente farei novos posts sobre processamento de imagens e visão computacional no futuro, então se gostou compartilhe ou deixe um comentário. Até a próxima.

Mais informações

Os seguintes artigos podem ser ótimas primeiras leituras para maiores detalhes em detecção de faces.

Viola, P., & Jones, M. (2001). "Rapid object detection using a boosted cascade of simple features". In Computer Vision and Pattern Recognition, 2001. CVPR 2001.

Puttemans Steven, Can Ergun and Toon Goedeme, "Improving Open Source Face Detection by Combining an Adapted Cascade Classification Pipeline and Active Learning", VISAPP 2017

Resumo sobre LBP (bom, este não é clássico... mas é uma explicação simples do que é LBP.)