Como descrito na última postagem, o HSV é um sistema de cores composto por três componentes. Neste post, será descrita a criação de um círculo de cores, gerado através de variações das combinações dessas componentes. Para tal, será utilizada a linguagem Python, juntamente com as bibliotecas Numpy e OpenCV.
A função para criação do círculo recebe dois parâmetros: tamanho da imagem a ser gerada e o raio do círculo.
import numpy as np
import cv2
def geraCirculoHSV(tam, raio):
Para construção da imagem será preciso montar cada banda separadamente e depois juntá-las em uma estrutura tridimensional.
Hue
A primeira banda, Hue ou matiz, contém a medida do comprimento de onda médio da luz que ele reflete ou emite. Define a cor ou tonalidade do objeto (vermelho, laranja, amarelo, etc). Os valores variam de 0º à 360º.
O círculo de cores irá variar todo o diagrama de cores, do vermelho ao vermelho (cor representada tanto pelo valor mínimo 0, quanto pelo valor máximo 360). É preciso representar cada ponto da imagem, portanto é definida uma matriz de duas dimensões para tal. O valor de cada ponto P pode ser encontrado através do cálculo ângulo entre o ponto P e a reta que uma reta que corte o círculo verticalmente, passando pelo centro.
Para substituir o processo de percorrer a matriz do modo tradicional, é utilizada a função "numpy.indices", que gera duas matrizes (aqui denominadas "ll" e "cc") representando índices de linha e coluna. Se executarmos, por exemplo, o código a seguir:
ll, cc = np.indices((5,5))
print "ll:"
print ll
print
print "cc:"
print cc
Teremos a seguinte saída:
ll:
[[0 0 0 0 0]
[1 1 1 1 1]
[2 2 2 2 2]
[3 3 3 3 3]
[4 4 4 4 4]]
cc:
[[0 1 2 3 4]
[0 1 2 3 4]
[0 1 2 3 4]
[0 1 2 3 4]
[0 1 2 3 4]
Para o cálculo da Matiz, ou do ângulo correspondente à cada ponto, é preciso as coordenadas do ponto. Com as matrizes de índices, já temos essa informação. O que precisa ser mudado é o centro da imagem. De acordo com as matrizes de índices, o centro (0,0) estaria na primeira posição, porém, precisamos que o centro corresponda com o centro da imagem (ou do círculo). Dessa forma, é feita uma translação. O valor dessa translação é obtido dividindo o tamanho da imagem por dois.
div = np.floor(tam/2)
ll,cc = np.indices((tam,tam))
ll = ll - div
cc = cc - div
Agora temos duas matrizes transladadas. No caso do exemplo numérico anterior, teríamos:
ll:
[[-2 -2 -2 -2 -2]
[-1 -1 -1 -1 -1]
[ 0 0 0 0 0]
[ 1 1 1 1 1]
[ 2 2 2 2 2]]
cc:
[[-2 -1 0 1 2]
[-2 -1 0 1 2]
[-2 -1 0 1 2]
[-2 -1 0 1 2]
[-2 -1 0 1 2]]
O ângulo entre um ponto e uma reta é obtido calculando arcotangente de X / Y, onde X e Y são as coordenadas do ponto. Utilizando a função "numpy,arctan", temos um resultado em radianos, que é convertido para graus através da multiplicação por 180 / Pi.
hh = np.arctan(cc/ll) * 180/np.pi
Apenas essas operações gerariam a seguinte imagem:
Portanto, é preciso fazer a correção da metade inferior. Isso é feito somando 180 à essa parte da imagem, através de mecanismos simples de amostragem de matrizes utilizados pelo Python.
hh[-1:div-1:-1,::] += 180
Agora temos na matriz "hh" todas as informações da matiz (não se preocupe ainda com a delimitação do círculo, isso acontecerá com a adição das outras bandas):
Saturation
A segunda banda, Saturation ou Saturação, é responsável pela profundidade ou "pureza" da cor (de esmaecida à intensa). Diferente do que intuitivamente pensaríamos, quanto mais saturada uma cor, mais "pura" ela será e, quanto menos saturada, mais próxima da cor branca. O valor da saturação varia entre 0 e 100%, ou entre 0 e 1.
Para calcular a saturação, utilizaremos o valor do raio do círculo. O ponto central da imagem possui valor 0 (não saturado) e os pontos ao redor dele, a medida que nos aproximamos da borda, vão aumentando de valor até atingir 1 (completamente saturado) na borda do círculo. Podemos perceber, então, que a saturação irá depender da distância entre o ponto e o centro do círculo, calculada através da fórmula da distância euclidiana. Como nesse caso um dos pontos é o (0,0), não é preciso fazer a subtração entre as coordenadas.
ll,cc = np.indices((tam,tam))
ll = ll - div
cc = cc - div
ll = np.square(ll)
cc = np.square(cc)
ss = np.sqrt(ll+cc)
ss = np.clip(ss,0,raio)
Para simplificar a operação, foi gerada as matrizes de índices, como no caso da matiz, e o resultado dos quadrados foi mantido nas próprias matrizes. A operação de "clip" realizada na matriz serve para realizar a delimitação do círculo. Essa operação, "numpy.clip(M,a,b)", substitui todos os valores menores que "a" contidos na matriz M pelo valor de "a", e todos os valores maiores que "b", pelo valor de "b". Como as distâncias entre o ponto e o centro devem variar entre 0 e o valor do raio, todos os valores acima disso serão substituídos pelo valor do raio.
Agora é preciso normalizar os valores. A matriz está variando de 0 ao raio, mas precisamos que varie de 0 à 1. Assim sendo, basta dividir o valor de cada ponto pelo valor do raio.
ss = ss/raio
Juntas as bandas de Hue e Saturation temos a imagem:
Value
A terceira banda, Value ou Valor, é responsável por definir o brilho da cor, ou a intensidade com que é percebida (mais clara ou mais escura). Os valores dessa componente variam entre 0 e 100%. A questão nesse exemplo é que estamos trabalhando com uma imagem bidimensional, não sendo possível descrever o que seria a altura do cilindro de cores que representa o sistema HSV. Consideremos então que esse círculo seja o topo do cilindro, onde o brilho tem valor 1 em seu interior e valor 0 no exterior.
Para delimitar o círculo desta maneira, basta lembrar que já temos as informações necessárias na matriz de saturação. Então é criada uma matriz do tamanho da imagem preenchida com o valor 1 e, em todas as posições da matriz onde a posição correspondente na matriz de saturação possuir valor 1, é colocado valor 0.
vv = np.ones((tam,tam))
vv[ss==1] = 0
Pronto!!!
Temos as três bandas que compõem a imagem e o seguinte resultado:
Para compor esse resultado, entretanto, é necessário juntar as bandas de maneira adequada, bem como converter as matrizes para o formato numérico apropriado para a exibição.
hh = np.float32(hh)
ss = np.float32(ss)
vv = np.float32(vv)
hsv = np.dstack((np.dstack((hh,ss)),vv))
bgr_img = cv2.cvtColor(hsv,cv2.COLOR_HSV2BGR)
cv2.imshow("Circulo",bgr_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
No código acima é feita (depois das conversões) a junção em profundidade da banda Hue com a a Saturação e, o resultado disto, com o Valor. A imagem então é convertida para o formato BGR e exibida através dos comandos OpenCV.
Dica: Não recrie sempre as matrizes, reutilize as operações já realizadas para otimizar o processamento.
Código completo (com os valores utilizados para gerar as imagens acima):
import numpy as np
import cv2
def geraCirculoHSV(tam, raio):
##HUE
#Criação das matrizes de índices
ll,cc = np.indices((tam,tam))
div = np.floor(tam/2)
#Translação
ll = ll - div
cc = cc - div
#Cálculo do ângulo
hh = np.arctan(cc/ll) * 180/np.pi
#Adição de 180 graus nos valores da metade inferior da imagem
hh[-1:div-1:-1,::] += 180
##Saturação
#Criação das matrizes de índices
ll,cc = np.indices((tam,tam))
#Translação
ll = ll - div
cc = cc - div
#Cálculo da distância entre o ponto e o centro
ll = np.square(ll)
cc = np.square(cc)
ss = np.sqrt(ll+cc)
#Delimitando os valores da matriz para a faixa entre 0 e raio
ss = np.clip(ss,0,raio)
#Normalização
ss = ss/raio
##VALOR
#Criação da matriz de uns
vv = np.ones((tam,tam))
#Seta para 0 as posições em vv onde as correspondentes em ss são 1
vv[ss==1] = 0
#Conversção de tipos
hh = np.float32(hh)
ss = np.float32(ss)
vv = np.float32(vv)
#Junção das bandas
hsv = np.dstack((np.dstack((hh,ss)),vv))
#Converção para o formato BGR
bgr_img = cv2.cvtColor(hsv,cv2.COLOR_HSV2BGR)
#Exibição da imagem
cv2.imshow("Circulo",bgr_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
geraCirculoHSV(700,300)




0 comentários:
Postar um comentário