7.3. Olá BlazeFace¶
O BlazeFace é uma rede neuronal de deteção de faces da coleção MediaPipe da Google. Uma única chamada de inferência devolve um retângulo delimitador em torno de cada face detetada, juntamente com seis marcos faciais – olho direito, olho esquerdo, nariz, boca, orelha direita, orelha esquerda. Todas as OpenMV Cam que são entregues com suporte para redes neuronais incluem o modelo blazeface_front_128.tflite em flash, pelo que executar um detetor de faces de ponta a ponta requer apenas algumas linhas de Python.
7.3.1. O script completo¶
import csi
import ml
from ml.postprocessing.mediapipe import BlazeFace
csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.RGB565)
csi0.framesize(csi.VGA)
csi0.window((400, 400))
model = ml.Model("/rom/blazeface_front_128.tflite",
postprocess=BlazeFace(threshold=0.4))
while True:
img = csi0.snapshot()
for (x, y, w, h), score, keypoints in model.predict([img]):
img.draw_rectangle((x, y, w, h), color=(0, 255, 0))
ml.utils.draw_keypoints(img, keypoints, color=(255, 0, 0))
Esse é o detetor de faces completo. Não há mais nada; o script captura um fotograma, entrega-o ao modelo, percorre a lista devolvida de deteções e desenha o retângulo delimitador de cada face e os seus seis marcos de volta no fotograma. A pré-visualização na IDE mostra as caixas e os marcos em tempo real.
7.3.2. O que cada linha faz¶
As primeiras três linhas importam os módulos de que o script necessita. csi é a interface do sensor da câmara; ml é o módulo de aprendizagem automática sobre o qual se debruça o resto deste capítulo; BlazeFace é o pós-processador que converte os tensores de saída brutos do BlazeFace na lista de caixas delimitadoras e marcos que o script itera.
As cinco linhas seguintes configuram o sensor. A câmara é reiniciada para um estado conhecido, definida para cor RGB565, definida para resolução VGA, e depois janelada para um quadrado de 400 por 400. A janela é importante: o BlazeFace foi treinado em recortes quadrados, e fornecer-lhe uma entrada quadrada alinha o rácio de aspeto esperado pela rede com o que ela vê no fotograma capturado.
A linha de carregamento do modelo abre o ficheiro de modelo:
model = ml.Model("/rom/blazeface_front_128.tflite",
postprocess=BlazeFace(threshold=0.4))
ml.Model lê o ficheiro no caminho indicado – /rom/ é um sistema de ficheiros residente em flash abordado mais adiante – e devolve um objeto de modelo contra o qual o script executa inferências. O argumento postprocess= regista o pós-processador BlazeFace; sem ele, predict devolveria os tensores de saída brutos da rede e a aplicação teria de os descodificar manualmente. Com ele, predict devolve o resultado descodificado diretamente. O argumento threshold=0.4 no pós-processador define a confiança mínima que a rede deve reportar para que uma deteção seja mantida; valores mais baixos detetam faces mais ténues ao custo de mais falsos positivos.
As quatro linhas restantes constituem o ciclo principal. Cada passagem captura um fotograma e pergunta ao modelo o que vê:
img = csi0.snapshot()
for (x, y, w, h), score, keypoints in model.predict([img]):
img.draw_rectangle((x, y, w, h), color=(0, 255, 0))
ml.utils.draw_keypoints(img, keypoints, color=(255, 0, 0))
predict() recebe uma lista de entradas (aqui, uma imagem capturada) e devolve uma lista de tuplos de deteção. Cada tuplo contém o retângulo delimitador (x, y, w, h), uma pontuação de confiança score entre zero e um, e um ndarray (6, 2) de coordenadas de marcos – o olho direito, olho esquerdo, nariz, boca, orelha direita e orelha esquerda por essa ordem. A chamada de desenho utiliza draw_rectangle() – a mesma primitiva com que terminou cada detetor clássico no capítulo de imagem – para contornar a face. ml.utils.draw_keypoints() é um pequeno auxiliar dos utilitários ml que marca cada ponto-chave com uma cruz na sua posição (x, y).
7.3.3. O que o script não diz¶
O script tem sete linhas executáveis de trabalho de inferência a seguir às importações e à configuração do sensor, mas uma grande quantidade de aritmética acontece dentro dessas sete linhas. O fotograma RGB565 de 400 por 400 capturado torna-se um tensor quantizado de 8 bits de 128 por 128 antes de chegar à rede; a rede executa centenas de operações sobre dezenas de milhares de pesos; os tensores resultantes de pontuações de confiança e deslocamentos de caixas tornam-se uma lista ordenada de caixas delimitadoras não sobrepostas com marcos associados antes de predict devolver. Cada uma dessas transformações é algo que a aplicação pode controlar se necessário, e várias delas têm de ser ajustadas para qualquer modelo não predefinido.
As quatro subsecções seguintes percorrem essas transformações em detalhe. Por ordem:
O módulo ml – o que
ml.Modelexpõe depois de um modelo ser carregado, e onde o ficheiro de modelo reside efetivamente na câmara.O pipeline de inferência – as quatro etapas de cada chamada a
predict().Motores de inferência – os percursos de CPU e NPU que executam a aritmética da rede.
Descodificação da saída – os pós-processadores que convertem tensores de saída brutos nas deteções sobre as quais este script iterou.
No final do capítulo, o leitor consegue escrever o script equivalente para um modelo que não foi entregue com a câmara, descodificar um tensor cujo pós-processador ainda não existe, e perceber por que razão um determinado modelo corre a 30 FPS numa câmara e a 3 FPS noutra.