Segmentação com Envoltória Convexa e OpenCV
Segmentando e interpretando imagens com OpenCV.
A Envoltória Convexa (Convex Hull) de um conjunto de pontos é definido como o menor polígono convexo, que inclui todos os pontos no conjunto. Convexo significa que o polígono não tem canto dobrado para dentro.
As bordas vermelhas no polígono direito incluem o canto onde a forma é côncava, o oposto de convexo.
Uma maneira útil de pensar sobre o envoltória convexa é a analogia do elástico:
Suponha que os pontos do conjunto fossem pregos, saindo de uma superfície plana. Imagine agora, o que aconteceria se você pegasse um elástico e o esticasse ao redor desses pregos. Tentando se contrair de volta ao seu comprimento original, o elástico envolvia os pregos, tocando àqueles que ficavam mais afastadas do centro.
Definindo a Envoltória Convexa
Em um espaço euclidiano, uma região convexa é uma região onde, para cada par de pontos dentro da região, cada ponto no segmento de reta que une o par também está dentro da região. De forma geral, em geometria convexa, um conjunto convexo é um subconjunto de um espaço afim que é fechado sob combinações convexas.
O limite de um conjunto convexo é sempre uma curva convexa. A interseção de todos os conjuntos convexos contendo um determinado subconjunto A do espaço euclidiano é chamada de invólucro convexo ou envoltória convexa de A. Assim, a Envoltória Convexa é o menor conjunto convexo contendo A.
Por exemplo, A figura a seguir apresenta duas formulações distintas P1 e P2 para o conjunto X = {(1,3), (2,1), (2,2), (2,3), (2,4), (3,1), (3,2), (3,3),(4,2)}
Um conjunto X ⊂ Rⁿ é chamado convexo se para qualquer x₁, x₂ ∈ X e todo número real 𝜶, 0 < 𝜶 < 1, o ponto 𝜶x₁ +(1-𝜶)x₂ ∈ X. A interpretação geométrica desta definição é que, se um conjunto é convexo, então, dados dois pontos no conjunto, todo ponto no segmento de reta que liga esses dois pontos também é um elemento do conjunto.
Assim, dado um conjunto X ⊂ Rⁿ, a envoltória convexa de X, representada por conv(X), é definida como a interseção de todos os conjuntos convexos que contêm X, isto é, o menor conjunto convexo que contém X.
A envoltória convexa corresponde à combinação convexa dos pontos x₁=(1,3), x₂ =(2,1), x₃ =(2,4), x₄ =(3,1), x₅ =(4,2), isto é:
Abordagem
De forma prática para a nossa bordagem vamos definir uma área de interesse calculando o valor médio de execução para uma quantidade de quadros. Depois que exibirmos a mão, podemos detectar uma alteração e identificar os limites.Quando a mão entrar no ROI, usaremos um envoltório convexo para desenhar um polígono ao redor da mão.
No nosso caso, esse conjunto de pontos é na verdade apenas a imagem do limiar de uma mão (e as informações externas de contorno). Usando um pouco de matemática, calcularemos o centro da mão em relação ao ângulo dos pontos externos para inferir a contagem de dedos.
Observe que precisaremos contabilizar as linhas do pulso (a). Em seguinda, calcularemos os pontos mais extremos (superior, inferior, esquerdo e direito)(b). Finalmente, podemos então calcular sua interseção e estimar isso como o centro da mão(c).
Em seguida, calcularemos a distância para o ponto mais distante do centro(a). Então, usando uma razão dessa distância, criamos um círculo(b). Finalmente, podemos supor que quaisquer pontos fora deste círculo e suficientemente distantes do fundo devem ser dedos abertos(c).
Implementação
Nesta vou destacar apenas os pontos mais relevantes em relação a implementação, dado que todo código encontra-se disponível no Github.
Inicialmente precisaremos definir algumas variáveis globais referentes ao nosso background e área de interesse.
background = None
accumulated_weight = .5
roi_top = 20
roi_bottom = 400
roi_right = 800
roi_left = 1200
O background será atualizado constantemente a partir de uma cópia do frame inicial.
cv2.accumulateWeighted(frame, background, accumulated_weight)
O propósito é calculamos a média ponderada dos pesos e atualizarmos o background. O processo de segmentação da nossa entrada, é realizado através de uma extração de background, além da definição um limiar para calcularmos a diferença absoluta entre o background e o frame anterior.
diff = cv2.absdiff(background.astype(“uint8”), frame)
_, thresholded = cv2.threshold(diff, threshold, 255, cv2.THRESH_BINARY)
Assim, os contornos da nossa entrada são definidos da seguinte forma:
contours, hierarchy = cv2.findContours(thresholded.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
A contagem dos dedos fica por conta da função convexHull
conv_hull = cv2.convexHull(hand_segment)
Vamos capturar os limites na nossa imagem segmentada tomando o cuidado de torna-los imutáveis, para isso definimos tuplas. Posteriormente precisaremos definir o ponto central da mão para o calculo da envoltória convexa. Em teoria o ponto central da mão é a metade entre a margem superior e margem inferior, e a metade entre a margem esquerda e a margem direita.
top = tuple(conv_hull[conv_hull[:, :, 1].argmin()][0])
bottom = tuple(conv_hull[conv_hull[:, :, 1].argmax()][0])
left = tuple(conv_hull[conv_hull[:, :, 0].argmin()][0])
right = tuple(conv_hull[conv_hull[:, :, 0].argmax()][0])cX = (left[0] + right[0]) // 2
cY = (top[1] + bottom[1]) // 2
Encontraremos a distancia entre o centro da mão e as extremidades através do cálculo da distancia euclidiana.
distance = pairwise.euclidean_distances([(cX, cY)], Y=[left, right, top, bottom])[0]
Vamos então utilizar a função bitwise_and para criar uma máscara e retornar o corte obtido.
circular_roi = cv2.bitwise_and(thresholded, thresholded, mask=circular_roi)
Por fim, contaremos o total de dedos que serão exibidos baseando-se em duas condições:
- A região contornada não é inferior a área da mão (pulso).
out_of_wrist = ((cY + (cY * 0.25)) > (y + h))
2. O número de pontos ao longo do contorno não excede 25% da circunferência(nossa máscara), caso contrário, estamos contando pontos fora da mão.
limit_points = ((circumference * 0.25) > cnt.shape[0])
Resultado
Conclusão
Finalmente, espero que esse material tenha sido útil e faça sentido pra você, principalmente aos iniciantes. De forma elementar apresentamos conceitos relacionados a envoltória convexa e um pouco de segmentação de imagens utilizando OpenCV. Além disso nas referências do artigo é possível encontrar um material muito útil utilizado para elaboração desse artigo que pode te ajudar a ampliar seus conhecimentos na área.
Lembrando que qualquer feedback, seja positivo ou negativo é só entrar em contato através do meu twitter, linkedin ou nos comentários aqui em baixo :)