Por que funciona: Unidade 1.3: A imagem como dado

Máscara de cor com cv2.inRange no OpenCV

Transforme uma faixa HSV numa máscara preto e branco com cv2.inRange, inverta-a com bitwise_not e aplique-a com bitwise_and. Na mesa de sinuca uma chamada marca 79,3% da foto como pano.

Luis Condados · · 4 min de leitura Iniciante

Tradução assistida por máquina do original em inglês. Números, código e referências foram conferidos com ele; o texto não foi revisado por um falante nativo. Ler o original. Os labs interativos e o código estão em inglês.

A máscara do pano na foto da sinuca: branco onde o HSV do pixel cai dentro da faixa.
A máscara do pano na foto da sinuca: branco onde o HSV do pixel cai dentro da faixa.

Uma máscara é uma imagem em preto e branco que marca quais pixels passam num teste, e cv2.inRange cria uma a partir de uma faixa de cor numa única chamada.

A foto da sinuca ao lado da máscara do pano: o pano é branco; as bolas, o taco, as tabelas e a sala são pretos.
À esquerda: a foto. À direita: a máscara para matiz 75–100, saturação 80–255, valor 40–255. Os pixels brancos passaram. As bolas e o taco são os buracos pretos nela. Foto de MarkBuckawicki, CC0 [4].

Onde você já viu isso: o filtro de chroma key do OBS e dos editores de vídeo, que é uma máscara sobre o verde; Selecionar › Por cor no GIMP e Selecionar › Intervalo de cores no Photoshop.

TL;DR. Uma chamada transforma a faixa de cor da lição anterior num mapa de onde essa cor está, e inverter o mapa dá todo o resto. Na foto da sinuca 79,3% dos pixels caem na faixa do pano; o resto, o “não pano”, contém todas as bolas, mas também as tabelas, a sala e o taco.

O problema

A lição 1 achou a faixa do pano em HSV. No projeto as bolas são o que não é pano, então o próximo passo é marcar cada pixel como pano ou não e depois ficar com os outros.

Uma faixa, uma máscara

Python · OpenCV
import cv2
import numpy as np

img = cv2.imread("pool.webp")
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

lower = np.array([75, 80, 40])
upper = np.array([100, 255, 255])
cloth = cv2.inRange(hsv, lower, upper)

print(cloth.shape, cloth.dtype, np.unique(cloth))
print("cloth:", round(100 * np.mean(cloth > 0), 1), "% of pixels")
cv2.imshow("cloth", cloth)
Saída
(720, 960) uint8 [  0 255]
cloth: 79.3 % of pixels
cloth
cloth
ChamadaArgumentosO que faz
cv2.inRange(hsv, lower, upper)uma imagem, e o menor e o maior valor permitidos em cada canal255 onde todos os canais estão dentro da faixa, 0 no resto
np.unique(cloth)um arrayos valores distintos nele: aqui só 0 e 255

O resultado tem um canal, não três, e só dois valores. Um pixel só fica branco se os três testes passam: matiz entre 75 e 100, e saturação entre 80 e 255, e valor entre 40 e 255 [1].

Inverta e depois aplique

As bolas são os buracos no pano. bitwise_not troca preto por branco, e bitwise_and com mask= mantém os pixels da foto só onde a máscara é branca:

Python · OpenCV
not_cloth = cv2.bitwise_not(cloth)
# mantém as cores da foto onde a máscara é branca
cut = cv2.bitwise_and(img, img, mask=not_cloth)
cv2.imshow("not cloth", cut)
Saída
not cloth
not cloth
ChamadaO que faz
cv2.bitwise_not(mask)255 vira 0 e 0 vira 255
cv2.bitwise_and(img, img, mask=m)img onde m é branca, preto no resto

Todas as bolas estão ali. Também está tudo o que não é a mesa: as tabelas, as caçapas, a sala escura e o taco. A máscara responde “este pixel é pano?”, e a sala não é pano. A lição 4 usa a máscara do pano uma segunda vez para achar o contorno da mesa, e assim a sala pode ser descartada. Antes disso, a lição 3 limpa os ciscos.

Mexa na faixa você mesmo

Os mesmos seis números, em sliders, aplicados à mesma foto num tamanho menor. Suba o H min de 75 em direção a 85 e a máscara começa a se desfazer ao longo da borda próxima, onde o matiz do pano é mais baixo; baixe o V min para 0 e pedaços da sala escura se juntam ao pano, a falha que a lição 5 mede.

image
mask
result

Quebre

bitwise_and espera duas imagens da mesma forma. A foto tem três canais e a máscara um, então passar a máscara como segunda imagem, em vez de como mask=, falha:

Quebre
# a máscara passada como segunda imagem, não como mask=
cut = cv2.bitwise_and(img, not_cloth)
Saída
Traceback (most recent call last):
  File "<exec>", line 2, in <module>
cv2.error: OpenCV(4.11.0) /home/runner/work/pyodide-recipes/pyodide-recipes/packages/opencv-python/build/opencv-python-4.11.0.86/opencv/modules/core/src/arithm.cpp:214: error: (-209:Sizes of input arguments do not match) The operation is neither 'array op array' (where arrays have the same size and type), nor 'array op scalar', nor 'scalar op array' in function 'binary_op'

O OpenCV avisa que os tamanhos das entradas não batem. Máscaras vão no argumento mask=.

Sua vez

O branco tem qualquer matiz, pouquíssima saturação e um valor alto. Crie uma máscara chamada white que pegue a bola branca em x = 791, y = 311 e deixe o pano preto.

Sua vez
white = cv2.inRange(hsv, (0, 0, 0), (179, 255, 255))  # estreite isto
cv2.imshow("white", white)

Por que funciona

Uma máscara é uma imagem binária: cada pixel tem um de dois valores, e os dois querem dizer “sim” e “não”. inRange faz a mesma pergunta de sim ou não a cada pixel, se os seus três números caem dentro de uma caixa no espaço HSV [1] [2]. Gonzalez e Woods tratam a segmentação por cor em HSI, um espaço próximo do HSV que também separa o matiz do brilho, no capítulo sobre cor [3]. A seção final de Colour spaces: choosing axes you can threshold (em inglês) faz o mesmo com o vermelho, cujo matiz dá a volta em 0.

Erros comuns

  • Inferior e superior trocados. inRange não os ordena. Um limite inferior acima do superior não casa com nada, sem erro.
  • Vermelho numa faixa só. O vermelho fica perto de 0 e também de 179. Faça duas máscaras e junte-as com cv2.bitwise_or.
  • A máscara como argumento de imagem. Passe-a como mask=, como a célula Quebre mostra. Ela tem de ser um array uint8 de um canal com a mesma altura e largura da imagem.
  • Esperar que “não pano” queira dizer “bolas”. Inverter uma máscara seleciona todo o resto, e numa foto isso inclui o fundo.

Referências

[1] OpenCV. Thresholding Operations using inRange (tutorial, doc/tutorials/imgproc/threshold_inRange/threshold_inRange.markdown). OpenCV 4.11.0, commit 31b0eee. github.com/opencv/opencv

[2] OpenCV. Changing Colorspaces (Python tutorial, doc/py_tutorials/py_imgproc/py_colorspaces/py_colorspaces.markdown). OpenCV 4.11.0, commit 31b0eee. github.com/opencv/opencv

[3] Gonzalez, R. C., & Woods, R. E. (2018). Digital Image Processing (4th ed.), section 7.7 “Using Color in Image Segmentation”, Segmentation in HSI Color Space, p. 576. Pearson.

[4] MarkBuckawicki. Billiards table 2.JPG (27 Feb 2014). Wikimedia Commons, CC0 1.0. commons.wikimedia.org