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.
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.
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.

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
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)(720, 960) uint8 [ 0 255] cloth: 79.3 % of pixels
| Chamada | Argumentos | O que faz |
|---|---|---|
cv2.inRange(hsv, lower, upper) | uma imagem, e o menor e o maior valor permitidos em cada canal | 255 onde todos os canais estão dentro da faixa, 0 no resto |
np.unique(cloth) | um array | os 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:
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)
| Chamada | O 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.
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:
# a máscara passada como segunda imagem, não como mask=
cut = cv2.bitwise_and(img, not_cloth)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.
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.
inRangenã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 arrayuint8de 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